Skip to content

Commit 36bd520

Browse files
committed
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
1 parent 40c1f24 commit 36bd520

File tree

5 files changed

+132
-112
lines changed

5 files changed

+132
-112
lines changed

examples/vulnerable_code/command_injection_with_aliases.py

+25-6
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,28 @@
44
from os import system as mysystem
55
from subprocess import call as mycall, Popen as mypopen
66

7-
os.system("ls")
8-
myos.system("ls")
9-
system("ls")
10-
mysystem("ls")
11-
mycall("ls")
12-
mypopen("ls")
7+
from flask import Flask, render_template, request
8+
9+
app = Flask(__name__)
10+
11+
12+
@app.route('/menu', methods=['POST'])
13+
def menu():
14+
param = request.form['suggestion']
15+
command = 'echo ' + param + ' >> ' + 'menu.txt'
16+
17+
os.system(command)
18+
myos.system(command)
19+
system(command)
20+
mysystem(command)
21+
mycall(command)
22+
mypopen(command)
23+
24+
with open('menu.txt', 'r') as f:
25+
menu_ctx = f.read()
26+
27+
return render_template('command_injection.html', menu=menu_ctx)
28+
29+
30+
if __name__ == '__main__':
31+
app.run(debug=True)

pyt/cfg/stmt_visitor.py

+75-74
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262
class StmtVisitor(ast.NodeVisitor):
6363
def __init__(self, allow_local_directory_imports=True):
6464
self._allow_local_modules = allow_local_directory_imports
65-
self.bb_or_bi_aliases = {}
6665
super().__init__()
6766

6867
def visit_Module(self, node):
@@ -628,7 +627,8 @@ def add_blackbox_or_builtin_call(self, node, blackbox): # noqa: C901
628627

629628
# Check if function call matches a blackbox/built-in alias and if so, resolve it
630629
# This resolves aliases like "from os import system as mysys" as: mysys -> os.system
631-
call_function_label = fully_qualify_alias_labels(call_function_label, self.bb_or_bi_aliases)
630+
local_definitions = self.module_definitions_stack[-1]
631+
call_function_label = fully_qualify_alias_labels(call_function_label, local_definitions.import_alias_mapping)
632632

633633
# Create e.g. ~call_1 = ret_func_foo
634634
LHS = CALL_IDENTIFIER + 'call_' + str(saved_function_call_index)
@@ -924,10 +924,10 @@ def from_directory_import(
924924
if init_exists and not skip_init:
925925
package_name = os.path.split(module_path)[1]
926926
return self.add_module(
927-
(module[0], init_file_location),
928-
package_name,
929-
local_names,
930-
import_alias_mapping,
927+
module=(module[0], init_file_location),
928+
module_or_package_name=package_name,
929+
local_names=local_names,
930+
import_alias_mapping=import_alias_mapping,
931931
is_init=True,
932932
from_from=True
933933
)
@@ -937,10 +937,10 @@ def from_directory_import(
937937
new_init_file_location = os.path.join(full_name, '__init__.py')
938938
if os.path.isfile(new_init_file_location):
939939
self.add_module(
940-
(real_name, new_init_file_location),
941-
real_name,
942-
local_names,
943-
import_alias_mapping,
940+
module=(real_name, new_init_file_location),
941+
module_or_package_name=real_name,
942+
local_names=local_names,
943+
import_alias_mapping=import_alias_mapping,
944944
is_init=True,
945945
from_from=True,
946946
from_fdid=True
@@ -950,10 +950,10 @@ def from_directory_import(
950950
else:
951951
file_module = (real_name, full_name + '.py')
952952
self.add_module(
953-
file_module,
954-
real_name,
955-
local_names,
956-
import_alias_mapping,
953+
module=file_module,
954+
module_or_package_name=real_name,
955+
local_names=local_names,
956+
import_alias_mapping=import_alias_mapping,
957957
from_from=True
958958
)
959959
return IgnoredNode()
@@ -964,10 +964,10 @@ def import_package(self, module, module_name, local_name, import_alias_mapping):
964964
init_exists = os.path.isfile(init_file_location)
965965
if init_exists:
966966
return self.add_module(
967-
(module[0], init_file_location),
968-
module_name,
969-
local_name,
970-
import_alias_mapping,
967+
module=(module[0], init_file_location),
968+
module_or_package_name=module_name,
969+
local_names=local_name,
970+
import_alias_mapping=import_alias_mapping,
971971
is_init=True
972972
)
973973
else:
@@ -1010,10 +1010,10 @@ def handle_relative_import(self, node):
10101010
# Is it a file?
10111011
if name_with_dir.endswith('.py'):
10121012
return self.add_module(
1013-
(node.module, name_with_dir),
1014-
None,
1015-
as_alias_handler(node.names),
1016-
retrieve_import_alias_mapping(node.names),
1013+
module=(node.module, name_with_dir),
1014+
module_or_package_name=None,
1015+
local_names=as_alias_handler(node.names),
1016+
import_alias_mapping=retrieve_import_alias_mapping(node.names),
10171017
from_from=True
10181018
)
10191019
return self.from_directory_import(
@@ -1036,10 +1036,10 @@ def visit_Import(self, node):
10361036
retrieve_import_alias_mapping(node.names)
10371037
)
10381038
return self.add_module(
1039-
module,
1040-
name.name,
1041-
name.asname,
1042-
retrieve_import_alias_mapping(node.names)
1039+
module=module,
1040+
module_or_package_name=name.name,
1041+
local_names=name.asname,
1042+
import_alias_mapping=retrieve_import_alias_mapping(node.names)
10431043
)
10441044
for module in self.project_modules:
10451045
if name.name == module[0]:
@@ -1051,68 +1051,69 @@ def visit_Import(self, node):
10511051
retrieve_import_alias_mapping(node.names)
10521052
)
10531053
return self.add_module(
1054-
module,
1055-
name.name,
1056-
name.asname,
1057-
retrieve_import_alias_mapping(node.names)
1054+
module=module,
1055+
module_or_package_name=name.name,
1056+
local_names=name.asname,
1057+
import_alias_mapping=retrieve_import_alias_mapping(node.names)
10581058
)
10591059
for alias in node.names:
1060-
if alias.name in uninspectable_modules:
1061-
# The module is uninspectable (so blackbox or built-in). If it has an alias, we remember
1062-
# the alias so we can do fully qualified name resolution for blackbox- and built-in trigger words
1063-
# e.g. we want a call to "os.system" be recognised, even if we do "import os as myos"
1064-
if alias.asname is not None and alias.asname != alias.name:
1065-
self.bb_or_bi_aliases[alias.asname] = alias.name
1066-
else:
1067-
log.warn("Cannot inspect module %s", alias.name)
1060+
# The module is uninspectable (so blackbox or built-in). If it has an alias, we remember
1061+
# the alias so we can do fully qualified name resolution for blackbox- and built-in trigger words
1062+
# e.g. we want a call to "os.system" be recognised, even if we do "import os as myos"
1063+
if alias.asname is not None and alias.asname != alias.name:
1064+
local_definitions = self.module_definitions_stack[-1]
1065+
local_definitions.import_alias_mapping[name.asname] = alias.name
1066+
if alias.name not in uninspectable_modules:
1067+
log.warning("Cannot inspect module %s", alias.name)
10681068
uninspectable_modules.add(alias.name) # Don't repeatedly warn about this
10691069
return IgnoredNode()
10701070

10711071
def visit_ImportFrom(self, node):
10721072
# Is it relative?
10731073
if node.level > 0:
10741074
return self.handle_relative_import(node)
1075-
else:
1076-
for module in self.local_modules:
1077-
if node.module == module[0]:
1078-
if os.path.isdir(module[1]):
1079-
return self.from_directory_import(
1080-
module,
1081-
not_as_alias_handler(node.names),
1082-
as_alias_handler(node.names)
1083-
)
1084-
return self.add_module(
1075+
# not relative
1076+
for module in self.local_modules:
1077+
if node.module == module[0]:
1078+
if os.path.isdir(module[1]):
1079+
return self.from_directory_import(
10851080
module,
1086-
None,
1087-
as_alias_handler(node.names),
1088-
retrieve_import_alias_mapping(node.names),
1089-
from_from=True
1081+
not_as_alias_handler(node.names),
1082+
as_alias_handler(node.names)
10901083
)
1091-
for module in self.project_modules:
1092-
name = module[0]
1093-
if node.module == name:
1094-
if os.path.isdir(module[1]):
1095-
return self.from_directory_import(
1096-
module,
1097-
not_as_alias_handler(node.names),
1098-
as_alias_handler(node.names),
1099-
retrieve_import_alias_mapping(node.names)
1100-
)
1101-
return self.add_module(
1084+
return self.add_module(
1085+
module=module,
1086+
module_or_package_name=None,
1087+
local_names=as_alias_handler(node.names),
1088+
import_alias_mapping=retrieve_import_alias_mapping(node.names),
1089+
from_from=True
1090+
)
1091+
for module in self.project_modules:
1092+
name = module[0]
1093+
if node.module == name:
1094+
if os.path.isdir(module[1]):
1095+
return self.from_directory_import(
11021096
module,
1103-
None,
1097+
not_as_alias_handler(node.names),
11041098
as_alias_handler(node.names),
1105-
retrieve_import_alias_mapping(node.names),
1106-
from_from=True
1099+
retrieve_import_alias_mapping(node.names)
11071100
)
1101+
return self.add_module(
1102+
module=module,
1103+
module_or_package_name=None,
1104+
local_names=as_alias_handler(node.names),
1105+
import_alias_mapping=retrieve_import_alias_mapping(node.names),
1106+
from_from=True
1107+
)
11081108

1109-
if node.module in uninspectable_modules:
1110-
# Remember aliases for blackboxed and built-in imports such that we can label them fully qualified
1111-
# e.g. we want a call to "os.system" be recognised, even if we do "from os import system"
1112-
# from os import system as mysystem -> module=os, name=system, asname=mysystem
1113-
for name in node.names:
1114-
self.bb_or_bi_aliases[name.asname or name.name] = "{}.{}".format(node.module, name.name)
1115-
else:
1116-
log.warn("Cannot inspect module %s", node.module)
1109+
# Remember aliases for uninspecatble modules such that we can label them fully qualified
1110+
# e.g. we want a call to "os.system" be recognised, even if we do "from os import system"
1111+
# from os import system as mysystem -> module=os, name=system, asname=mysystem
1112+
for name in node.names:
1113+
local_definitions = self.module_definitions_stack[-1]
1114+
local_definitions.import_alias_mapping[name.asname or name.name] = "{}.{}".format(node.module, name.name)
1115+
1116+
if node.module not in uninspectable_modules:
1117+
log.warning("Cannot inspect module %s", node.module)
11171118
uninspectable_modules.add(node.module)
11181119
return IgnoredNode()

tests/vulnerabilities/vulnerabilities_across_files_test.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_blackbox_library_call(self):
6262
EXPECTED_VULNERABILITY_DESCRIPTION = """
6363
File: examples/vulnerable_code_across_files/blackbox_library_call.py
6464
> User input at line 12, source "request.args.get(":
65-
~call_1 = ret_request.args.get('suggestion')
65+
~call_1 = ret_flask.request.args.get('suggestion')
6666
Reassigned in:
6767
File: examples/vulnerable_code_across_files/blackbox_library_call.py
6868
> Line 12: param = ~call_1

tests/vulnerabilities/vulnerabilities_base_test_case.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def string_compare_alpha(self, output, expected_string):
1111

1212
def assertAlphaEqual(self, output, expected_string):
1313
self.assertEqual(
14-
[char for char in output if char.isalpha()],
15-
[char for char in expected_string if char.isalpha()]
14+
''.join(char for char in output if char.isalpha()),
15+
''.join(char for char in expected_string if char.isalpha())
1616
)
1717
return True

0 commit comments

Comments
 (0)