def _get_ancestor_vars(self, node, vars = None, level=0): ''' Return the ancestor Variables for this var. For next example php code: <? $a = 'ls' . $_GET['bar'] . $_POST['foo']; $b = somefunc($a); ?> we got that $_GET and $_POST are both $a's ancestor as well as $a is for $b. Also determines if this var is safe for vulns ''' if vars is None: vars = [] for n in NodeRep.parse(node): if type(node) is phpast.BinaryOp: # only parse direct nodes for item in NodeRep.parse(node, 0, 0, 1): self._get_ancestor_vars(item, vars, level + 1) break if type(n) is phpast.Variable: vars.append(n) if level == 0: # Securing functions safe_for = {} for n in vars: # todo look at all vars for fc in self._get_parent_nodes(n, [phpast.FunctionCall]): # Don't set custom function calls params as parent, this is done by # looking at the return vars if fc in self.funccall_nodes and hasattr(fc, '_obj') and fc._obj.get_called_obj(): vars.remove(n) continue vulnty = get_vulnty_for_sec(fc.name) if vulnty: if vulnty not in safe_for: safe_for[vulnty] = 1 else: safe_for[vulnty] = safe_for[vulnty] + 1 for vulnty, count in safe_for.iteritems(): if count == len(vars): self._safe_for.append(vulnty) return vars
def _get_ancestor_funccalls(self, node, funcs = None, level=0): if funcs is None: funcs = [] for n in NodeRep.parse(node): if type(node) is phpast.BinaryOp: # only parse direct nodes for item in NodeRep.parse(node, 0, 0, 1): self._get_ancestor_funccalls(item, funcs, level + 1) break if type(n) is phpast.FunctionCall: funcs.append(n) return funcs
def _parse_me(self, node, scope): ''' TODO: add method call Traverse this AST subtree until either a Variable or FunctionCall node is found... ''' for node in NodeRep.parse(node): if type(node) is phpast.BinaryOp: # only parse direct nodes for item in NodeRep.parse(node, 0, 0, 1): self._parse_me(item, scope) break if type(node) is phpast.Variable: # object properties are stored as $this->property varname = node.name if type(node._parent_node) is phpast.ObjectProperty: varname = node._parent_node.node.name + '->' + node._parent_node.name vardef = VariableDef(varname + '__$temp_anon_var$_', node.lineno, scope) vardef.var_nodes = [node] # anon var is not stored in scope vardef._anon_var = True # get and set parent scopevar = scope.get_var(varname) vardef.add_parent(scopevar) # add Param to VarDef # TODO: not really necessary? vardef._parent_obj = self # add var to current scope scope.add_var(vardef) self.vars.append(vardef) break elif type(node) is phpast.FunctionCall: # TEST call functioncal visitor from core.visitors.base_visitor import BaseVisitor from core.visitors.function_call_visitor import FunctionCallVisitor visitor = FunctionCallVisitor(BaseVisitor); fc, stoponthis = visitor.visit(node, scope.get_state()) vardef = VariableDef(node.name + '_funvar', node.lineno, scope) # from core.nodes.function_call import FuncCall # from core.nodes.function_call import FuncCall # fc = FuncCall(node.name, node.lineno, node, scope, self) #TODO: Can we do this in a more extensible way? Why different ways for handling FILE_DISC / XSS? # Add vulntrace vulntype = fc.is_vulnerable_for() if vulntype and 'FILE_DISCLOSURE' in vulntype and \ self._parent_obj.name in SENSITIVE_FUNCTIONS['XSS']: # Add vulntrace to parent call with pending trace fc.add_vulntrace(vulntype) self._parent_obj._pending_trace = fc.get_vulntraces()[-1] vardef._safe_for.append('XSS') vardef.set_clean() # elif vulntype: # fc.add_vulntrace(vulntype) # # Keep track of thin funccal # self.get_root_obj()._functions.append(fc) # vardef.set_clean() # return values (custom function)? called_obj = fc.get_called_obj(); if called_obj: # Set function scope as active code called_obj._scope._dead_code = False for var in called_obj._return_vars: vardef.add_parent(var) # else: for param in fc.params: for var in param.vars: vardef.add_parent(var) # Securing function? vulnty = get_vulnty_for_sec(fc.name) if vulnty: vardef._safe_for.append(vulnty) self.vars.append(vardef) break