def is_taintable_function(ast_node): """Returns only functions without a sanitization decorator""" for decorator in ast_node.decorator_list: if isinstance(decorator, ast.Call): if _get_last_of_iterable(get_call_names( decorator.func)) in safe_decorators: return False # Flask route and Django tag if _get_last_of_iterable(get_call_names(decorator.func)) in [ "route", "simple_tag", "inclusion_tag", "to_end_tag", "expose", "view_config", "template", "get", "post", "put", "delete", "middleware", "api_view", "action", "csrf_exempt", "deserialise_with", "marshal_with", "before", "csrf_protect", "requires_csrf_token", "xframe_options_exempt", "xframe_options_deny", "xframe_options_sameorigin", "before_first_request", ]: return True # Ignore database functions if len(ast_node.args.args): first_arg_name = ast_node.args.args[0].arg if first_arg_name == "self" and len(ast_node.args.args) > 1: first_arg_name = ast_node.args.args[1].arg # Common view functions such as django, starlette, falcon if first_arg_name in ["req", "request", "context", "scope"]: return True # Ignore dao classes due to potential FP if first_arg_name in [ "conn", "connection", "cls", "session", "session_cls" ]: return False # Ignore internal functions prefixed with _ if is_function_with_leading_(ast_node): return False # Ignore known validation and sanitization functions for n in ["valid", "sanitize", "sanitise", "is_", "set_", "assert"]: if n in ast_node.name: return False # Should we limit the scan only to web routes? web_route_only = os.environ.get("WEB_ROUTE_ONLY", False) if web_route_only: return False return True
def is_taintable_function(ast_node): """Returns only functions without a sanitization decorator""" for decorator in ast_node.decorator_list: if isinstance(decorator, ast.Call): if _get_last_of_iterable(get_call_names(decorator.func)) in safe_decorators: return False # Flask route and Django tag if _get_last_of_iterable(get_call_names(decorator.func)) in [ "route", "simple_tag", "inclusion_tag", "to_end_tag", "expose", ]: return True # Ignore database functions if len(ast_node.args.args): first_arg_name = ast_node.args.args[0].arg # Common view functions such as django, starlette if first_arg_name in ["request", "context", "scope"]: return True # Ignore internal functions prefixed with _ if is_function_with_leading_(ast_node): return False # Ignore known validation and sanitization functions for n in ["valid", "sanitize", "sanitise", "is_", "set_"]: if ast_node.name.startswith(n): return False return True
def is_flask_route_function(ast_node): """Check whether function uses a route decorator.""" for decorator in ast_node.decorator_list: if isinstance(decorator, ast.Call): if _get_last_of_iterable(get_call_names(decorator.func)) == "route": return True return False
def is_taintable_function(ast_node): """Returns only functions without a sanitization decorator""" for decorator in ast_node.decorator_list: if isinstance(decorator, ast.Call): if _get_last_of_iterable(get_call_names( decorator.func)) in safe_decorators: return False # Flask route and Django tag if _get_last_of_iterable(get_call_names(decorator.func)) in [ "route", "simple_tag", "inclusion_tag", "to_end_tag", "expose", "view_config", "template", "get", "post", "put", "delete", "middleware", "api_view", "action", ]: return True # Ignore database functions if len(ast_node.args.args): first_arg_name = ast_node.args.args[0].arg # Common view functions such as django, starlette if first_arg_name in ["request", "context", "scope"]: return True # Ignore internal functions prefixed with _ if is_function_with_leading_(ast_node): return False # Ignore known validation and sanitization functions for n in ["valid", "sanitize", "sanitise", "is_", "set_"]: if ast_node.name.startswith(n): return False # Should we limit the scan only to web routes? web_route_only = os.environ.get("WEB_ROUTE_ONLY", False) if web_route_only: return False return True
def add_blackbox_or_builtin_call(self, node, blackbox): # noqa: C901 """Processes a blackbox or builtin function when it is called. Nothing gets assigned to ret_func_foo in the builtin/blackbox case. Increments self.function_call_index each time it is called, we can refer to it as N in the comments. Create e.g. ~call_1 = ret_func_foo RestoreNode. Create e.g. temp_N_def_arg1 = call_arg1_label_visitor.result for each argument. Visit the arguments if they're calls. (save_def_args_in_temp) I do not think I care about this one actually -- Create e.g. def_arg1 = temp_N_def_arg1 for each argument. (create_local_scope_from_def_args) Add RestoreNode to the end of the Nodes. Args: node(ast.Call) : The node that calls the definition. blackbox(bool): Whether or not it is a builtin or blackbox call. Returns: call_node(BBorBInode): The call node. """ self.function_call_index += 1 saved_function_call_index = self.function_call_index self.undecided = False call_label_visitor = LabelVisitor() call_label_visitor.visit(node) call_function_label = call_label_visitor.result[ : call_label_visitor.result.find("(") ] # Check if function call matches a blackbox/built-in alias and if so, resolve it # This resolves aliases like "from os import system as mysys" as: mysys -> os.system local_definitions = self.module_definitions_stack[-1] call_function_label = fully_qualify_alias_labels( call_function_label, local_definitions.import_alias_mapping ) # Create e.g. ~call_1 = ret_func_foo LHS = CALL_IDENTIFIER + "call_" + str(saved_function_call_index) RHS = "ret_" + call_function_label + "(" call_node = BBorBInode( label="", left_hand_side=LHS, ast_node=node, right_hand_side_variables=[], line_number=node.lineno, path=self.filenames[-1], func_name=call_function_label, ) visual_args = list() rhs_vars = list() last_return_value_of_nested_call = None for arg_node in itertools.chain(node.args, node.keywords): arg = arg_node.value if isinstance(arg_node, ast.keyword) else arg_node if isinstance(arg, ast.Call): return_value_of_nested_call = self.visit(arg) if last_return_value_of_nested_call: # connect inner to other_inner in e.g. # `scrypt.outer(scrypt.inner(image_name), scrypt.other_inner(image_name))` # I should probably loop to the inner most call of other_inner here. try: last_return_value_of_nested_call.connect( return_value_of_nested_call.first_node ) except AttributeError: last_return_value_of_nested_call.connect( return_value_of_nested_call ) else: # I should only set this once per loop, inner in e.g. # `scrypt.outer(scrypt.inner(image_name), scrypt.other_inner(image_name))` # (inner_most_call is used when predecessor is a ControlFlowNode in connect_control_flow_node) call_node.inner_most_call = return_value_of_nested_call last_return_value_of_nested_call = return_value_of_nested_call if isinstance(arg_node, ast.keyword) and arg_node.arg is not None: visual_args.append( arg_node.arg + "=" + return_value_of_nested_call.left_hand_side ) else: if hasattr(return_value_of_nested_call, "left_hand_side"): visual_args.append(return_value_of_nested_call.left_hand_side) if hasattr(return_value_of_nested_call, "left_hand_side"): rhs_vars.append(return_value_of_nested_call.left_hand_side) else: label = LabelVisitor() label.visit(arg_node) visual_args.append(label.result) vv = VarsVisitor() vv.visit(arg_node) rhs_vars.extend(vv.result) if last_return_value_of_nested_call: # connect other_inner to outer in e.g. # `scrypt.outer(scrypt.inner(image_name), scrypt.other_inner(image_name))` last_return_value_of_nested_call.connect(call_node) call_names = list(get_call_names(node.func)) if len(call_names) > 1: # taint is a RHS variable (self) of taint.lower() rhs_vars.append(call_names[0]) if len(visual_args) > 0: for arg in visual_args: RHS = RHS + arg + ", " # Replace the last ", " with a ) RHS = RHS[: len(RHS) - 2] + ")" else: RHS = RHS + ")" call_node.label = LHS + " = " + RHS call_node.right_hand_side_variables = rhs_vars # Used in get_sink_args rhs_visitor = RHSVisitor() rhs_visitor.visit(node) call_node.args = rhs_visitor.result if blackbox: self.blackbox_assignments.add(call_node) self.connect_if_allowed(self.nodes[-1], call_node) self.nodes.append(call_node) return call_node
def visit_Subscript(self, node): if isinstance(node.value, ast.Attribute): # foo.bar[1] self.result.append(list(get_call_names(node.value))[0]) self.visit(node.value) self.slicev(node.slice)