Skip to content

Commit a2d7634

Browse files
authored
Merge pull request #167 from bcaller/method-propagation
Taint propagates from methods of tainted objects
2 parents d2a8189 + 8811343 commit a2d7634

File tree

2 files changed

+29
-17
lines changed

2 files changed

+29
-17
lines changed

pyt/cfg/stmt_visitor.py

+12-15
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
)
1212
from ..core.ast_helper import (
1313
generate_ast,
14+
get_call_names,
1415
get_call_names_as_string
1516
)
1617
from ..core.module_definitions import (
@@ -472,14 +473,6 @@ def assignment_call_node(self, left_hand_label, ast_node):
472473
call = self.visit(ast_node.value)
473474
call_label = call.left_hand_side
474475

475-
if isinstance(call, BBorBInode):
476-
# Necessary to know e.g.
477-
# `image_name = image_name.replace('..', '')`
478-
# is a reassignment.
479-
vars_visitor = VarsVisitor()
480-
vars_visitor.visit(ast_node.value)
481-
call.right_hand_side_variables.extend(vars_visitor.result)
482-
483476
call_assignment = AssignmentCallNode(
484477
left_hand_label + ' = ' + call_label,
485478
left_hand_label,
@@ -572,7 +565,7 @@ def visit_While(self, node):
572565

573566
return self.loop_node_skeleton(test, node)
574567

575-
def add_blackbox_or_builtin_call(self, node, blackbox):
568+
def add_blackbox_or_builtin_call(self, node, blackbox): # noqa: C901
576569
"""Processes a blackbox or builtin function when it is called.
577570
Nothing gets assigned to ret_func_foo in the builtin/blackbox case.
578571
@@ -597,14 +590,14 @@ def add_blackbox_or_builtin_call(self, node, blackbox):
597590
saved_function_call_index = self.function_call_index
598591
self.undecided = False
599592

600-
call_label = LabelVisitor()
601-
call_label.visit(node)
593+
call_label_visitor = LabelVisitor()
594+
call_label_visitor.visit(node)
602595

603-
index = call_label.result.find('(')
596+
call_function_label = call_label_visitor.result[:call_label_visitor.result.find('(')]
604597

605598
# Create e.g. ~call_1 = ret_func_foo
606599
LHS = CALL_IDENTIFIER + 'call_' + str(saved_function_call_index)
607-
RHS = 'ret_' + call_label.result[:index] + '('
600+
RHS = 'ret_' + call_function_label + '('
608601

609602
call_node = BBorBInode(
610603
label='',
@@ -613,7 +606,7 @@ def add_blackbox_or_builtin_call(self, node, blackbox):
613606
right_hand_side_variables=[],
614607
line_number=node.lineno,
615608
path=self.filenames[-1],
616-
func_name=call_label.result[:index]
609+
func_name=call_function_label
617610
)
618611
visual_args = list()
619612
rhs_vars = list()
@@ -657,6 +650,11 @@ def add_blackbox_or_builtin_call(self, node, blackbox):
657650
# `scrypt.outer(scrypt.inner(image_name), scrypt.other_inner(image_name))`
658651
last_return_value_of_nested_call.connect(call_node)
659652

653+
call_names = list(get_call_names(node.func))
654+
if len(call_names) > 1:
655+
# taint is a RHS variable (self) of taint.lower()
656+
rhs_vars.append(call_names[0])
657+
660658
if len(visual_args) > 0:
661659
for arg in visual_args:
662660
RHS = RHS + arg + ", "
@@ -667,7 +665,6 @@ def add_blackbox_or_builtin_call(self, node, blackbox):
667665
call_node.label = LHS + " = " + RHS
668666

669667
call_node.right_hand_side_variables = rhs_vars
670-
# Used in get_sink_args, not using right_hand_side_variables because it is extended in assignment_call_node
671668
rhs_visitor = RHSVisitor()
672669
rhs_visitor.visit(node)
673670
call_node.args = rhs_visitor.result

tests/vulnerabilities/vulnerabilities_test.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,9 @@ def test_build_sanitiser_node_dict(self):
111111

112112
self.assertEqual(sanitiser_dict['escape'][0], cfg.nodes[3])
113113

114-
def run_analysis(self, path):
115-
self.cfg_create_from_file(path)
114+
def run_analysis(self, path=None):
115+
if path:
116+
self.cfg_create_from_file(path)
116117
cfg_list = [self.cfg]
117118

118119
FrameworkAdaptor(cfg_list, [], [], is_flask_route_function)
@@ -468,6 +469,20 @@ def test_yield(self):
468469

469470
self.assertAlphaEqual(str(vuln), EXPECTED_VULNERABILITY_DESCRIPTION)
470471

472+
def test_method_of_taint(self):
473+
def assert_vulnerable(fixture):
474+
tree = ast.parse('TAINT = request.args.get("")\n' + fixture + '\nexecute(result)')
475+
self.cfg_create_from_ast(tree)
476+
vulnerabilities = self.run_analysis()
477+
self.assert_length(vulnerabilities, expected_length=1, msg=fixture)
478+
479+
assert_vulnerable('result = TAINT')
480+
assert_vulnerable('result = TAINT.lower()')
481+
assert_vulnerable('result = str(TAINT)')
482+
assert_vulnerable('result = str(TAINT.lower())')
483+
assert_vulnerable('result = repr(str("%s" % TAINT.lower().upper()))')
484+
assert_vulnerable('result = repr(str("{}".format(TAINT.lower())))')
485+
471486

472487
class EngineDjangoTest(VulnerabilitiesBaseTestCase):
473488
def run_analysis(self, path):

0 commit comments

Comments
 (0)