Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 98a7b6b

Browse files
↪️ Merge pull request #198 from wchresta/177_support_from_import_sinks
177 support from import sinks
2 parents 022476a + a7bb0b2 commit 98a7b6b

File tree

8 files changed

+168
-84
lines changed

8 files changed

+168
-84
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import os
2+
import os as myos
3+
from os import system
4+
from os import system as mysystem
5+
from subprocess import call as mycall, Popen as mypopen
6+
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/alias_helper.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,21 @@ def retrieve_import_alias_mapping(names_list):
7474
if alias.asname:
7575
import_alias_names[alias.asname] = alias.name
7676
return import_alias_names
77+
78+
79+
def fully_qualify_alias_labels(label, aliases):
80+
"""Replace any aliases in label with the fully qualified name.
81+
82+
Args:
83+
label -- A label : str representing a name (e.g. myos.system)
84+
aliases -- A dict of {alias: real_name} (e.g. {'myos': 'os'})
85+
86+
>>> fully_qualify_alias_labels('myos.mycall', {'myos':'os'})
87+
'os.mycall'
88+
"""
89+
for alias, full_name in aliases.items():
90+
if label == alias:
91+
return full_name
92+
elif label.startswith(alias+'.'):
93+
return full_name + label[len(alias):]
94+
return label

‎pyt/cfg/stmt_visitor.py

Lines changed: 79 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .alias_helper import (
88
as_alias_handler,
9+
fully_qualify_alias_labels,
910
handle_aliases_in_init_files,
1011
handle_fdid_aliases,
1112
not_as_alias_handler,
@@ -624,6 +625,11 @@ def add_blackbox_or_builtin_call(self, node, blackbox): # noqa: C901
624625

625626
call_function_label = call_label_visitor.result[:call_label_visitor.result.find('(')]
626627

628+
# Check if function call matches a blackbox/built-in alias and if so, resolve it
629+
# This resolves aliases like "from os import system as mysys" as: mysys -> os.system
630+
local_definitions = self.module_definitions_stack[-1]
631+
call_function_label = fully_qualify_alias_labels(call_function_label, local_definitions.import_alias_mapping)
632+
627633
# Create e.g. ~call_1 = ret_func_foo
628634
LHS = CALL_IDENTIFIER + 'call_' + str(saved_function_call_index)
629635
RHS = 'ret_' + call_function_label + '('
@@ -810,7 +816,7 @@ def add_module( # noqa: C901
810816
module_path = module[1]
811817

812818
parent_definitions = self.module_definitions_stack[-1]
813-
# The only place the import_alias_mapping is updated
819+
# Here, in `visit_Import` and in `visit_ImportFrom` are the only places the `import_alias_mapping` is updated
814820
parent_definitions.import_alias_mapping.update(import_alias_mapping)
815821
parent_definitions.import_names = local_names
816822

@@ -919,10 +925,10 @@ def from_directory_import(
919925
if init_exists and not skip_init:
920926
package_name = os.path.split(module_path)[1]
921927
return self.add_module(
922-
(module[0], init_file_location),
923-
package_name,
924-
local_names,
925-
import_alias_mapping,
928+
module=(module[0], init_file_location),
929+
module_or_package_name=package_name,
930+
local_names=local_names,
931+
import_alias_mapping=import_alias_mapping,
926932
is_init=True,
927933
from_from=True
928934
)
@@ -932,10 +938,10 @@ def from_directory_import(
932938
new_init_file_location = os.path.join(full_name, '__init__.py')
933939
if os.path.isfile(new_init_file_location):
934940
self.add_module(
935-
(real_name, new_init_file_location),
936-
real_name,
937-
local_names,
938-
import_alias_mapping,
941+
module=(real_name, new_init_file_location),
942+
module_or_package_name=real_name,
943+
local_names=local_names,
944+
import_alias_mapping=import_alias_mapping,
939945
is_init=True,
940946
from_from=True,
941947
from_fdid=True
@@ -945,10 +951,10 @@ def from_directory_import(
945951
else:
946952
file_module = (real_name, full_name + '.py')
947953
self.add_module(
948-
file_module,
949-
real_name,
950-
local_names,
951-
import_alias_mapping,
954+
module=file_module,
955+
module_or_package_name=real_name,
956+
local_names=local_names,
957+
import_alias_mapping=import_alias_mapping,
952958
from_from=True
953959
)
954960
return IgnoredNode()
@@ -959,10 +965,10 @@ def import_package(self, module, module_name, local_name, import_alias_mapping):
959965
init_exists = os.path.isfile(init_file_location)
960966
if init_exists:
961967
return self.add_module(
962-
(module[0], init_file_location),
963-
module_name,
964-
local_name,
965-
import_alias_mapping,
968+
module=(module[0], init_file_location),
969+
module_or_package_name=module_name,
970+
local_names=local_name,
971+
import_alias_mapping=import_alias_mapping,
966972
is_init=True
967973
)
968974
else:
@@ -1005,10 +1011,10 @@ def handle_relative_import(self, node):
10051011
# Is it a file?
10061012
if name_with_dir.endswith('.py'):
10071013
return self.add_module(
1008-
(node.module, name_with_dir),
1009-
None,
1010-
as_alias_handler(node.names),
1011-
retrieve_import_alias_mapping(node.names),
1014+
module=(node.module, name_with_dir),
1015+
module_or_package_name=None,
1016+
local_names=as_alias_handler(node.names),
1017+
import_alias_mapping=retrieve_import_alias_mapping(node.names),
10121018
from_from=True
10131019
)
10141020
return self.from_directory_import(
@@ -1031,10 +1037,10 @@ def visit_Import(self, node):
10311037
retrieve_import_alias_mapping(node.names)
10321038
)
10331039
return self.add_module(
1034-
module,
1035-
name.name,
1036-
name.asname,
1037-
retrieve_import_alias_mapping(node.names)
1040+
module=module,
1041+
module_or_package_name=name.name,
1042+
local_names=name.asname,
1043+
import_alias_mapping=retrieve_import_alias_mapping(node.names)
10381044
)
10391045
for module in self.project_modules:
10401046
if name.name == module[0]:
@@ -1046,55 +1052,69 @@ def visit_Import(self, node):
10461052
retrieve_import_alias_mapping(node.names)
10471053
)
10481054
return self.add_module(
1049-
module,
1050-
name.name,
1051-
name.asname,
1052-
retrieve_import_alias_mapping(node.names)
1055+
module=module,
1056+
module_or_package_name=name.name,
1057+
local_names=name.asname,
1058+
import_alias_mapping=retrieve_import_alias_mapping(node.names)
10531059
)
10541060
for alias in node.names:
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+
local_definitions = self.module_definitions_stack[-1]
1066+
local_definitions.import_alias_mapping[name.asname] = alias.name
10551067
if alias.name not in uninspectable_modules:
1056-
log.warn("Cannot inspect module %s", alias.name)
1068+
log.warning("Cannot inspect module %s", alias.name)
10571069
uninspectable_modules.add(alias.name) # Don't repeatedly warn about this
10581070
return IgnoredNode()
10591071

10601072
def visit_ImportFrom(self, node):
10611073
# Is it relative?
10621074
if node.level > 0:
10631075
return self.handle_relative_import(node)
1064-
else:
1065-
for module in self.local_modules:
1066-
if node.module == module[0]:
1067-
if os.path.isdir(module[1]):
1068-
return self.from_directory_import(
1069-
module,
1070-
not_as_alias_handler(node.names),
1071-
as_alias_handler(node.names)
1072-
)
1073-
return self.add_module(
1076+
# not relative
1077+
for module in self.local_modules:
1078+
if node.module == module[0]:
1079+
if os.path.isdir(module[1]):
1080+
return self.from_directory_import(
10741081
module,
1075-
None,
1076-
as_alias_handler(node.names),
1077-
retrieve_import_alias_mapping(node.names),
1078-
from_from=True
1082+
not_as_alias_handler(node.names),
1083+
as_alias_handler(node.names)
10791084
)
1080-
for module in self.project_modules:
1081-
name = module[0]
1082-
if node.module == name:
1083-
if os.path.isdir(module[1]):
1084-
return self.from_directory_import(
1085-
module,
1086-
not_as_alias_handler(node.names),
1087-
as_alias_handler(node.names),
1088-
retrieve_import_alias_mapping(node.names)
1089-
)
1090-
return self.add_module(
1085+
return self.add_module(
1086+
module=module,
1087+
module_or_package_name=None,
1088+
local_names=as_alias_handler(node.names),
1089+
import_alias_mapping=retrieve_import_alias_mapping(node.names),
1090+
from_from=True
1091+
)
1092+
for module in self.project_modules:
1093+
name = module[0]
1094+
if node.module == name:
1095+
if os.path.isdir(module[1]):
1096+
return self.from_directory_import(
10911097
module,
1092-
None,
1098+
not_as_alias_handler(node.names),
10931099
as_alias_handler(node.names),
1094-
retrieve_import_alias_mapping(node.names),
1095-
from_from=True
1100+
retrieve_import_alias_mapping(node.names)
10961101
)
1102+
return self.add_module(
1103+
module=module,
1104+
module_or_package_name=None,
1105+
local_names=as_alias_handler(node.names),
1106+
import_alias_mapping=retrieve_import_alias_mapping(node.names),
1107+
from_from=True
1108+
)
1109+
1110+
# Remember aliases for uninspectable modules 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+
local_definitions = self.module_definitions_stack[-1]
1115+
local_definitions.import_alias_mapping[name.asname or name.name] = "{}.{}".format(node.module, name.name)
1116+
10971117
if node.module not in uninspectable_modules:
1098-
log.warn("Cannot inspect module %s", node.module)
1118+
log.warning("Cannot inspect module %s", node.module)
10991119
uninspectable_modules.add(node.module)
11001120
return IgnoredNode()

‎pyt/vulnerability_definitions/all_trigger_words.pyt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,17 @@
3131
]
3232
},
3333
"execute(": {},
34-
"system(": {},
34+
"os.system(": {},
3535
"filter(": {},
3636
"subprocess.call(": {},
37+
"subprocess.Popen(": {},
3738
"render_template(": {},
3839
"set_cookie(": {},
3940
"redirect(": {},
4041
"url_for(": {},
4142
"flash(": {},
4243
"jsonify(": {},
4344
"render(": {},
44-
"render_to_response(": {},
45-
"Popen(": {}
45+
"render_to_response(": {}
4646
}
4747
}

‎tests/main_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,11 @@ def test_targets_with_recursive(self):
108108
excluded_files = ""
109109

110110
included_files = discover_files(targets, excluded_files, True)
111-
self.assertEqual(len(included_files), 33)
111+
self.assertEqual(len(included_files), 34)
112112

113113
def test_targets_with_recursive_and_excluded(self):
114114
targets = ["examples/vulnerable_code/"]
115115
excluded_files = "inter_command_injection.py"
116116

117117
included_files = discover_files(targets, excluded_files, True)
118-
self.assertEqual(len(included_files), 32)
118+
self.assertEqual(len(included_files), 33)

‎tests/vulnerabilities/vulnerabilities_across_files_test.py

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 2 deletions
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)

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