From 40c1f2482dbc54b8846f22aae4082ebec6af5683 Mon Sep 17 00:00:00 2001 From: wchresta <34962284+wchresta@users.noreply.github.com> Date: 2019年3月22日 17:01:05 -0400 Subject: [PATCH 1/4] Resolve aliases for black box and built-in function calls. * Allow trigger words to be fully qualified to reduce false positives --- .../command_injection_with_aliases.py | 12 +++++++++ pyt/cfg/alias_helper.py | 19 ++++++++++++++ pyt/cfg/stmt_visitor.py | 26 ++++++++++++++++--- .../all_trigger_words.pyt | 6 ++--- tests/main_test.py | 4 +-- tests/vulnerabilities/vulnerabilities_test.py | 15 +++++++++++ 6 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 examples/vulnerable_code/command_injection_with_aliases.py diff --git a/examples/vulnerable_code/command_injection_with_aliases.py b/examples/vulnerable_code/command_injection_with_aliases.py new file mode 100644 index 00000000..309e5268 --- /dev/null +++ b/examples/vulnerable_code/command_injection_with_aliases.py @@ -0,0 +1,12 @@ +import os +import os as myos +from os import system +from os import system as mysystem +from subprocess import call as mycall, Popen as mypopen + +os.system("ls") +myos.system("ls") +system("ls") +mysystem("ls") +mycall("ls") +mypopen("ls") diff --git a/pyt/cfg/alias_helper.py b/pyt/cfg/alias_helper.py index a1c83ab0..9de6d058 100644 --- a/pyt/cfg/alias_helper.py +++ b/pyt/cfg/alias_helper.py @@ -74,3 +74,22 @@ def retrieve_import_alias_mapping(names_list): if alias.asname: import_alias_names[alias.asname] = alias.name return import_alias_names + + +def fully_qualify_alias_labels(label, aliases): + """Replace any aliases in label with the fully qualified name. + + Args: + label -- A label : str representing a name (e.g. myos.system) + aliases -- A dict of {alias: real_name} (e.g. {'myos': 'os'}) + +>>> fully_qualify_alias_labels('myos.mycall', {'myos':'os'}) + 'os.mycall' + """ + for alias, full_name in aliases.items(): + if label == alias: + return full_name + if label.startswith(alias+'.'): + return full_name + label[len(alias):] + return label + diff --git a/pyt/cfg/stmt_visitor.py b/pyt/cfg/stmt_visitor.py index 3b9d5f48..e008b096 100644 --- a/pyt/cfg/stmt_visitor.py +++ b/pyt/cfg/stmt_visitor.py @@ -9,7 +9,8 @@ handle_aliases_in_init_files, handle_fdid_aliases, not_as_alias_handler, - retrieve_import_alias_mapping + retrieve_import_alias_mapping, + fully_qualify_alias_labels ) from ..core.ast_helper import ( generate_ast, @@ -61,6 +62,7 @@ class StmtVisitor(ast.NodeVisitor): def __init__(self, allow_local_directory_imports=True): self._allow_local_modules = allow_local_directory_imports + self.bb_or_bi_aliases = {} super().__init__() def visit_Module(self, node): @@ -624,6 +626,10 @@ def add_blackbox_or_builtin_call(self, node, blackbox): # noqa: C901 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 + call_function_label = fully_qualify_alias_labels(call_function_label, self.bb_or_bi_aliases) + # Create e.g. ~call_1 = ret_func_foo LHS = CALL_IDENTIFIER + 'call_' + str(saved_function_call_index) RHS = 'ret_' + call_function_label + '(' @@ -810,7 +816,6 @@ def add_module( # noqa: C901 module_path = module[1] parent_definitions = self.module_definitions_stack[-1] - # The only place the import_alias_mapping is updated parent_definitions.import_alias_mapping.update(import_alias_mapping) parent_definitions.import_names = local_names @@ -1052,7 +1057,13 @@ def visit_Import(self, node): retrieve_import_alias_mapping(node.names) ) for alias in node.names: - if alias.name not in uninspectable_modules: + if alias.name in uninspectable_modules: + # The module is uninspectable (so blackbox or built-in). If it has an alias, we remember + # the alias so we can do fully qualified name resolution for blackbox- and built-in trigger words + # e.g. we want a call to "os.system" be recognised, even if we do "import os as myos" + if alias.asname is not None and alias.asname != alias.name: + self.bb_or_bi_aliases[alias.asname] = alias.name + else: log.warn("Cannot inspect module %s", alias.name) uninspectable_modules.add(alias.name) # Don't repeatedly warn about this return IgnoredNode() @@ -1094,7 +1105,14 @@ def visit_ImportFrom(self, node): retrieve_import_alias_mapping(node.names), from_from=True ) - if node.module not in uninspectable_modules: + + if node.module in uninspectable_modules: + # Remember aliases for blackboxed and built-in imports such that we can label them fully qualified + # e.g. we want a call to "os.system" be recognised, even if we do "from os import system" + # from os import system as mysystem -> module=os, name=system, asname=mysystem + for name in node.names: + self.bb_or_bi_aliases[name.asname or name.name] = "{}.{}".format(node.module, name.name) + else: log.warn("Cannot inspect module %s", node.module) uninspectable_modules.add(node.module) return IgnoredNode() diff --git a/pyt/vulnerability_definitions/all_trigger_words.pyt b/pyt/vulnerability_definitions/all_trigger_words.pyt index 5642db5c..615e86b6 100644 --- a/pyt/vulnerability_definitions/all_trigger_words.pyt +++ b/pyt/vulnerability_definitions/all_trigger_words.pyt @@ -31,9 +31,10 @@ ] }, "execute(": {}, - "system(": {}, + "os.system(": {}, "filter(": {}, "subprocess.call(": {}, + "subprocess.Popen(": {}, "render_template(": {}, "set_cookie(": {}, "redirect(": {}, @@ -41,7 +42,6 @@ "flash(": {}, "jsonify(": {}, "render(": {}, - "render_to_response(": {}, - "Popen(": {} + "render_to_response(": {} } } diff --git a/tests/main_test.py b/tests/main_test.py index 561d8bd1..fef1e124 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -108,11 +108,11 @@ def test_targets_with_recursive(self): excluded_files = "" included_files = discover_files(targets, excluded_files, True) - self.assertEqual(len(included_files), 33) + self.assertEqual(len(included_files), 34) def test_targets_with_recursive_and_excluded(self): targets = ["examples/vulnerable_code/"] excluded_files = "inter_command_injection.py" included_files = discover_files(targets, excluded_files, True) - self.assertEqual(len(included_files), 32) + self.assertEqual(len(included_files), 33) diff --git a/tests/vulnerabilities/vulnerabilities_test.py b/tests/vulnerabilities/vulnerabilities_test.py index 5a28aa03..fe2fa64c 100644 --- a/tests/vulnerabilities/vulnerabilities_test.py +++ b/tests/vulnerabilities/vulnerabilities_test.py @@ -482,6 +482,21 @@ def test_list_append_taints_list(self): ) self.assert_length(vulnerabilities, expected_length=1) + def test_import_bb_or_bi_with_alias(self): + self.cfg_create_from_file('examples/vulnerable_code/command_injection_with_aliases.py') + + EXPECTED = ['Entry module', + "~call_1 = ret_os.system('ls')", + "~call_2 = ret_os.system('ls')", + "~call_3 = ret_os.system('ls')", + "~call_4 = ret_os.system('ls')", + "~call_5 = ret_subprocess.call('ls')", + "~call_6 = ret_subprocess.Popen('ls')", + 'Exit module' + ] + for node, expected_label in zip(self.cfg.nodes, EXPECTED): + self.assertEqual(node.label, expected_label) + class EngineDjangoTest(VulnerabilitiesBaseTestCase): def run_analysis(self, path): From 36bd520cf3e52e218aa79d23cb3113a934551bc6 Mon Sep 17 00:00:00 2001 From: wchresta <34962284+wchresta@users.noreply.github.com> Date: 2019年3月23日 16:30:01 -0400 Subject: [PATCH 2/4] Use import_alias_mapping for blackbox and built-in aliases, as well * This will give fully qualified names for blackboxes like flask * Improve readability by using keyword arguments --- .../command_injection_with_aliases.py | 31 +++- pyt/cfg/stmt_visitor.py | 149 +++++++++--------- .../vulnerabilities_across_files_test.py | 2 +- .../vulnerabilities_base_test_case.py | 4 +- tests/vulnerabilities/vulnerabilities_test.py | 58 +++---- 5 files changed, 132 insertions(+), 112 deletions(-) diff --git a/examples/vulnerable_code/command_injection_with_aliases.py b/examples/vulnerable_code/command_injection_with_aliases.py index 309e5268..4e409c52 100644 --- a/examples/vulnerable_code/command_injection_with_aliases.py +++ b/examples/vulnerable_code/command_injection_with_aliases.py @@ -4,9 +4,28 @@ from os import system as mysystem from subprocess import call as mycall, Popen as mypopen -os.system("ls") -myos.system("ls") -system("ls") -mysystem("ls") -mycall("ls") -mypopen("ls") +from flask import Flask, render_template, request + +app = Flask(__name__) + + +@app.route('/menu', methods=['POST']) +def menu(): + param = request.form['suggestion'] + command = 'echo ' + param + '>> ' + 'menu.txt' + + os.system(command) + myos.system(command) + system(command) + mysystem(command) + mycall(command) + mypopen(command) + + with open('menu.txt', 'r') as f: + menu_ctx = f.read() + + return render_template('command_injection.html', menu=menu_ctx) + + +if __name__ == '__main__': + app.run(debug=True) diff --git a/pyt/cfg/stmt_visitor.py b/pyt/cfg/stmt_visitor.py index e008b096..0a35c566 100644 --- a/pyt/cfg/stmt_visitor.py +++ b/pyt/cfg/stmt_visitor.py @@ -62,7 +62,6 @@ class StmtVisitor(ast.NodeVisitor): def __init__(self, allow_local_directory_imports=True): self._allow_local_modules = allow_local_directory_imports - self.bb_or_bi_aliases = {} super().__init__() def visit_Module(self, node): @@ -628,7 +627,8 @@ def add_blackbox_or_builtin_call(self, node, blackbox): # noqa: C901 # 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 - call_function_label = fully_qualify_alias_labels(call_function_label, self.bb_or_bi_aliases) + 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) @@ -924,10 +924,10 @@ def from_directory_import( if init_exists and not skip_init: package_name = os.path.split(module_path)[1] return self.add_module( - (module[0], init_file_location), - package_name, - local_names, - import_alias_mapping, + module=(module[0], init_file_location), + module_or_package_name=package_name, + local_names=local_names, + import_alias_mapping=import_alias_mapping, is_init=True, from_from=True ) @@ -937,10 +937,10 @@ def from_directory_import( new_init_file_location = os.path.join(full_name, '__init__.py') if os.path.isfile(new_init_file_location): self.add_module( - (real_name, new_init_file_location), - real_name, - local_names, - import_alias_mapping, + module=(real_name, new_init_file_location), + module_or_package_name=real_name, + local_names=local_names, + import_alias_mapping=import_alias_mapping, is_init=True, from_from=True, from_fdid=True @@ -950,10 +950,10 @@ def from_directory_import( else: file_module = (real_name, full_name + '.py') self.add_module( - file_module, - real_name, - local_names, - import_alias_mapping, + module=file_module, + module_or_package_name=real_name, + local_names=local_names, + import_alias_mapping=import_alias_mapping, from_from=True ) return IgnoredNode() @@ -964,10 +964,10 @@ def import_package(self, module, module_name, local_name, import_alias_mapping): init_exists = os.path.isfile(init_file_location) if init_exists: return self.add_module( - (module[0], init_file_location), - module_name, - local_name, - import_alias_mapping, + module=(module[0], init_file_location), + module_or_package_name=module_name, + local_names=local_name, + import_alias_mapping=import_alias_mapping, is_init=True ) else: @@ -1010,10 +1010,10 @@ def handle_relative_import(self, node): # Is it a file? if name_with_dir.endswith('.py'): return self.add_module( - (node.module, name_with_dir), - None, - as_alias_handler(node.names), - retrieve_import_alias_mapping(node.names), + module=(node.module, name_with_dir), + module_or_package_name=None, + local_names=as_alias_handler(node.names), + import_alias_mapping=retrieve_import_alias_mapping(node.names), from_from=True ) return self.from_directory_import( @@ -1036,10 +1036,10 @@ def visit_Import(self, node): retrieve_import_alias_mapping(node.names) ) return self.add_module( - module, - name.name, - name.asname, - retrieve_import_alias_mapping(node.names) + module=module, + module_or_package_name=name.name, + local_names=name.asname, + import_alias_mapping=retrieve_import_alias_mapping(node.names) ) for module in self.project_modules: if name.name == module[0]: @@ -1051,20 +1051,20 @@ def visit_Import(self, node): retrieve_import_alias_mapping(node.names) ) return self.add_module( - module, - name.name, - name.asname, - retrieve_import_alias_mapping(node.names) + module=module, + module_or_package_name=name.name, + local_names=name.asname, + import_alias_mapping=retrieve_import_alias_mapping(node.names) ) for alias in node.names: - if alias.name in uninspectable_modules: - # The module is uninspectable (so blackbox or built-in). If it has an alias, we remember - # the alias so we can do fully qualified name resolution for blackbox- and built-in trigger words - # e.g. we want a call to "os.system" be recognised, even if we do "import os as myos" - if alias.asname is not None and alias.asname != alias.name: - self.bb_or_bi_aliases[alias.asname] = alias.name - else: - log.warn("Cannot inspect module %s", alias.name) + # The module is uninspectable (so blackbox or built-in). If it has an alias, we remember + # the alias so we can do fully qualified name resolution for blackbox- and built-in trigger words + # e.g. we want a call to "os.system" be recognised, even if we do "import os as myos" + if alias.asname is not None and alias.asname != alias.name: + local_definitions = self.module_definitions_stack[-1] + local_definitions.import_alias_mapping[name.asname] = alias.name + if alias.name not in uninspectable_modules: + log.warning("Cannot inspect module %s", alias.name) uninspectable_modules.add(alias.name) # Don't repeatedly warn about this return IgnoredNode() @@ -1072,47 +1072,48 @@ def visit_ImportFrom(self, node): # Is it relative? if node.level> 0: return self.handle_relative_import(node) - else: - for module in self.local_modules: - if node.module == module[0]: - if os.path.isdir(module[1]): - return self.from_directory_import( - module, - not_as_alias_handler(node.names), - as_alias_handler(node.names) - ) - return self.add_module( + # not relative + for module in self.local_modules: + if node.module == module[0]: + if os.path.isdir(module[1]): + return self.from_directory_import( module, - None, - as_alias_handler(node.names), - retrieve_import_alias_mapping(node.names), - from_from=True + not_as_alias_handler(node.names), + as_alias_handler(node.names) ) - for module in self.project_modules: - name = module[0] - if node.module == name: - if os.path.isdir(module[1]): - return self.from_directory_import( - module, - not_as_alias_handler(node.names), - as_alias_handler(node.names), - retrieve_import_alias_mapping(node.names) - ) - return self.add_module( + return self.add_module( + module=module, + module_or_package_name=None, + local_names=as_alias_handler(node.names), + import_alias_mapping=retrieve_import_alias_mapping(node.names), + from_from=True + ) + for module in self.project_modules: + name = module[0] + if node.module == name: + if os.path.isdir(module[1]): + return self.from_directory_import( module, - None, + not_as_alias_handler(node.names), as_alias_handler(node.names), - retrieve_import_alias_mapping(node.names), - from_from=True + retrieve_import_alias_mapping(node.names) ) + return self.add_module( + module=module, + module_or_package_name=None, + local_names=as_alias_handler(node.names), + import_alias_mapping=retrieve_import_alias_mapping(node.names), + from_from=True + ) - if node.module in uninspectable_modules: - # Remember aliases for blackboxed and built-in imports such that we can label them fully qualified - # e.g. we want a call to "os.system" be recognised, even if we do "from os import system" - # from os import system as mysystem -> module=os, name=system, asname=mysystem - for name in node.names: - self.bb_or_bi_aliases[name.asname or name.name] = "{}.{}".format(node.module, name.name) - else: - log.warn("Cannot inspect module %s", node.module) + # Remember aliases for uninspecatble modules such that we can label them fully qualified + # e.g. we want a call to "os.system" be recognised, even if we do "from os import system" + # from os import system as mysystem -> module=os, name=system, asname=mysystem + for name in node.names: + local_definitions = self.module_definitions_stack[-1] + local_definitions.import_alias_mapping[name.asname or name.name] = "{}.{}".format(node.module, name.name) + + if node.module not in uninspectable_modules: + log.warning("Cannot inspect module %s", node.module) uninspectable_modules.add(node.module) return IgnoredNode() diff --git a/tests/vulnerabilities/vulnerabilities_across_files_test.py b/tests/vulnerabilities/vulnerabilities_across_files_test.py index bd63b190..d7b8f0d1 100644 --- a/tests/vulnerabilities/vulnerabilities_across_files_test.py +++ b/tests/vulnerabilities/vulnerabilities_across_files_test.py @@ -62,7 +62,7 @@ def test_blackbox_library_call(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code_across_files/blackbox_library_call.py > User input at line 12, source "request.args.get(": - ~call_1 = ret_request.args.get('suggestion') + ~call_1 = ret_flask.request.args.get('suggestion') Reassigned in: File: examples/vulnerable_code_across_files/blackbox_library_call.py > Line 12: param = ~call_1 diff --git a/tests/vulnerabilities/vulnerabilities_base_test_case.py b/tests/vulnerabilities/vulnerabilities_base_test_case.py index c21f81ed..a7e86121 100644 --- a/tests/vulnerabilities/vulnerabilities_base_test_case.py +++ b/tests/vulnerabilities/vulnerabilities_base_test_case.py @@ -11,7 +11,7 @@ def string_compare_alpha(self, output, expected_string): def assertAlphaEqual(self, output, expected_string): self.assertEqual( - [char for char in output if char.isalpha()], - [char for char in expected_string if char.isalpha()] + ''.join(char for char in output if char.isalpha()), + ''.join(char for char in expected_string if char.isalpha()) ) return True diff --git a/tests/vulnerabilities/vulnerabilities_test.py b/tests/vulnerabilities/vulnerabilities_test.py index fe2fa64c..893ec70a 100644 --- a/tests/vulnerabilities/vulnerabilities_test.py +++ b/tests/vulnerabilities/vulnerabilities_test.py @@ -150,7 +150,7 @@ def test_XSS_result(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code/XSS.py > User input at line 6, source "request.args.get(": - ~call_1 = ret_request.args.get('param', 'not set') + ~call_1 = ret_flask.request.args.get('param', 'not set') Reassigned in: File: examples/vulnerable_code/XSS.py > Line 6: param = ~call_1 @@ -186,7 +186,7 @@ def test_path_traversal_result(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code/path_traversal.py > User input at line 15, source "request.args.get(": - ~call_1 = ret_request.args.get('image_name') + ~call_1 = ret_flask.request.args.get('image_name') Reassigned in: File: examples/vulnerable_code/path_traversal.py > Line 15: image_name = ~call_1 @@ -210,7 +210,7 @@ def test_path_traversal_result(self): > Line 19: foo = ~call_2 File: examples/vulnerable_code/path_traversal.py > reaches line 20, sink "send_file(": - ~call_4 = ret_send_file(foo) + ~call_4 = ret_flask.send_file(foo) """ self.assertAlphaEqual(vulnerability_description, EXPECTED_VULNERABILITY_DESCRIPTION) @@ -222,7 +222,7 @@ def test_ensure_saved_scope(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code/ensure_saved_scope.py > User input at line 15, source "request.args.get(": - ~call_1 = ret_request.args.get('image_name') + ~call_1 = ret_flask.request.args.get('image_name') Reassigned in: File: examples/vulnerable_code/ensure_saved_scope.py > Line 15: image_name = ~call_1 @@ -232,7 +232,7 @@ def test_ensure_saved_scope(self): > Line 10: save_3_image_name = image_name File: examples/vulnerable_code/ensure_saved_scope.py > reaches line 20, sink "send_file(": - ~call_4 = ret_send_file(image_name) + ~call_4 = ret_flask.send_file(image_name) """ self.assertAlphaEqual( vulnerability_description, @@ -246,7 +246,7 @@ def test_path_traversal_sanitised_result(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code/path_traversal_sanitised.py > User input at line 8, source "request.args.get(": - ~call_1 = ret_request.args.get('image_name') + ~call_1 = ret_flask.request.args.get('image_name') Reassigned in: File: examples/vulnerable_code/path_traversal_sanitised.py > Line 8: image_name = ~call_1 @@ -258,7 +258,7 @@ def test_path_traversal_sanitised_result(self): > Line 12: ~call_4 = ret_os.path.join(~call_5, image_name) File: examples/vulnerable_code/path_traversal_sanitised.py > reaches line 12, sink "send_file(": - ~call_3 = ret_send_file(~call_4) + ~call_3 = ret_flask.send_file(~call_4) This vulnerability is sanitised by: Label: ~call_2 = ret_image_name.replace('..', '') """ @@ -271,7 +271,7 @@ def test_path_traversal_sanitised_2_result(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code/path_traversal_sanitised_2.py > User input at line 8, source "request.args.get(": - ~call_1 = ret_request.args.get('image_name') + ~call_1 = ret_flask.request.args.get('image_name') Reassigned in: File: examples/vulnerable_code/path_traversal_sanitised_2.py > Line 8: image_name = ~call_1 @@ -279,7 +279,7 @@ def test_path_traversal_sanitised_2_result(self): > Line 12: ~call_3 = ret_os.path.join(~call_4, image_name) File: examples/vulnerable_code/path_traversal_sanitised_2.py > reaches line 12, sink "send_file(": - ~call_2 = ret_send_file(~call_3) + ~call_2 = ret_flask.send_file(~call_3) This vulnerability is potentially sanitised by: Label: if '..' in image_name: """ @@ -292,7 +292,7 @@ def test_sql_result(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code/sql/sqli.py > User input at line 26, source "request.args.get(": - ~call_1 = ret_request.args.get('param', 'not set') + ~call_1 = ret_flask.request.args.get('param', 'not set') Reassigned in: File: examples/vulnerable_code/sql/sqli.py > Line 26: param = ~call_1 @@ -347,7 +347,7 @@ def test_XSS_reassign_result(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code/XSS_reassign.py > User input at line 6, source "request.args.get(": - ~call_1 = ret_request.args.get('param', 'not set') + ~call_1 = ret_flask.request.args.get('param', 'not set') Reassigned in: File: examples/vulnerable_code/XSS_reassign.py > Line 6: param = ~call_1 @@ -367,18 +367,18 @@ def test_XSS_sanitised_result(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code/XSS_sanitised.py > User input at line 7, source "request.args.get(": - ~call_1 = ret_request.args.get('param', 'not set') + ~call_1 = ret_flask.request.args.get('param', 'not set') Reassigned in: File: examples/vulnerable_code/XSS_sanitised.py > Line 7: param = ~call_1 File: examples/vulnerable_code/XSS_sanitised.py -> Line 9: ~call_2 = ret_Markup.escape(param) +> Line 9: ~call_2 = ret_flask.Markup.escape(param) File: examples/vulnerable_code/XSS_sanitised.py > Line 9: param = ~call_2 File: examples/vulnerable_code/XSS_sanitised.py > reaches line 12, sink "replace(": ~call_5 = ret_html.replace('{{ param }}', param) - This vulnerability is sanitised by: Label: ~call_2 = ret_Markup.escape(param) + This vulnerability is sanitised by: Label: ~call_2 = ret_flask.Markup.escape(param) """ self.assertAlphaEqual(vulnerability_description, EXPECTED_VULNERABILITY_DESCRIPTION) @@ -394,7 +394,7 @@ def test_XSS_variable_assign_result(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code/XSS_variable_assign.py > User input at line 6, source "request.args.get(": - ~call_1 = ret_request.args.get('param', 'not set') + ~call_1 = ret_flask.request.args.get('param', 'not set') Reassigned in: File: examples/vulnerable_code/XSS_variable_assign.py > Line 6: param = ~call_1 @@ -414,7 +414,7 @@ def test_XSS_variable_multiple_assign_result(self): EXPECTED_VULNERABILITY_DESCRIPTION = """ File: examples/vulnerable_code/XSS_variable_multiple_assign.py > User input at line 6, source "request.args.get(": - ~call_1 = ret_request.args.get('param', 'not set') + ~call_1 = ret_flask.request.args.get('param', 'not set') Reassigned in: File: examples/vulnerable_code/XSS_variable_multiple_assign.py > Line 6: param = ~call_1 @@ -483,19 +483,19 @@ def test_list_append_taints_list(self): self.assert_length(vulnerabilities, expected_length=1) def test_import_bb_or_bi_with_alias(self): - self.cfg_create_from_file('examples/vulnerable_code/command_injection_with_aliases.py') - - EXPECTED = ['Entry module', - "~call_1 = ret_os.system('ls')", - "~call_2 = ret_os.system('ls')", - "~call_3 = ret_os.system('ls')", - "~call_4 = ret_os.system('ls')", - "~call_5 = ret_subprocess.call('ls')", - "~call_6 = ret_subprocess.Popen('ls')", - 'Exit module' + vulnerabilities = self.run_analysis('examples/vulnerable_code/command_injection_with_aliases.py') + + EXPECTED_SINK_TRIGGER_WORDS = [ + 'os.system(', + 'os.system(', + 'os.system(', + 'os.system(', + 'subprocess.call(', + 'subprocess.Popen(' ] - for node, expected_label in zip(self.cfg.nodes, EXPECTED): - self.assertEqual(node.label, expected_label) + + for vuln, expected_sink_trigger_word in zip(vulnerabilities, EXPECTED_SINK_TRIGGER_WORDS): + self.assertEqual(vuln.sink_trigger_word, expected_sink_trigger_word) class EngineDjangoTest(VulnerabilitiesBaseTestCase): @@ -531,7 +531,7 @@ def test_django_view_param(self): param File: examples/vulnerable_code/django_XSS.py > reaches line 5, sink "render(": - ~call_1 = ret_render(request, 'templates/xss.html', 'param'param) + ~call_1 = ret_django.shortcuts.render(request, 'templates/xss.html', 'param'param) """ self.assertAlphaEqual(vulnerability_description, EXPECTED_VULNERABILITY_DESCRIPTION) From 61f0408574685f6fb2a1ffd4c39a49964056aa54 Mon Sep 17 00:00:00 2001 From: wchresta <34962284+wchresta@users.noreply.github.com> Date: 2019年3月23日 16:45:29 -0400 Subject: [PATCH 3/4] Remove blank line at end of file. --- pyt/cfg/alias_helper.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyt/cfg/alias_helper.py b/pyt/cfg/alias_helper.py index 9de6d058..9d780992 100644 --- a/pyt/cfg/alias_helper.py +++ b/pyt/cfg/alias_helper.py @@ -92,4 +92,3 @@ def fully_qualify_alias_labels(label, aliases): if label.startswith(alias+'.'): return full_name + label[len(alias):] return label - From a7bb0b275ffc78c4cc168ae529e5adb7467979cd Mon Sep 17 00:00:00 2001 From: wchresta <34962284+wchresta@users.noreply.github.com> Date: 2019年3月23日 18:13:50 -0400 Subject: [PATCH 4/4] Implement suggestions from code-review. --- pyt/cfg/alias_helper.py | 2 +- pyt/cfg/stmt_visitor.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyt/cfg/alias_helper.py b/pyt/cfg/alias_helper.py index 9d780992..920af3f5 100644 --- a/pyt/cfg/alias_helper.py +++ b/pyt/cfg/alias_helper.py @@ -89,6 +89,6 @@ def fully_qualify_alias_labels(label, aliases): for alias, full_name in aliases.items(): if label == alias: return full_name - if label.startswith(alias+'.'): + elif label.startswith(alias+'.'): return full_name + label[len(alias):] return label diff --git a/pyt/cfg/stmt_visitor.py b/pyt/cfg/stmt_visitor.py index 0a35c566..16da1bb0 100644 --- a/pyt/cfg/stmt_visitor.py +++ b/pyt/cfg/stmt_visitor.py @@ -6,11 +6,11 @@ from .alias_helper import ( as_alias_handler, + fully_qualify_alias_labels, handle_aliases_in_init_files, handle_fdid_aliases, not_as_alias_handler, - retrieve_import_alias_mapping, - fully_qualify_alias_labels + retrieve_import_alias_mapping ) from ..core.ast_helper import ( generate_ast, @@ -816,6 +816,7 @@ def add_module( # noqa: C901 module_path = module[1] parent_definitions = self.module_definitions_stack[-1] + # Here, in `visit_Import` and in `visit_ImportFrom` are the only places the `import_alias_mapping` is updated parent_definitions.import_alias_mapping.update(import_alias_mapping) parent_definitions.import_names = local_names @@ -1106,7 +1107,7 @@ def visit_ImportFrom(self, node): from_from=True ) - # Remember aliases for uninspecatble modules such that we can label them fully qualified + # Remember aliases for uninspectable modules such that we can label them fully qualified # e.g. we want a call to "os.system" be recognised, even if we do "from os import system" # from os import system as mysystem -> module=os, name=system, asname=mysystem for name in node.names:

AltStyle によって変換されたページ (->オリジナル) /