diff --git a/python/ql/src/semmle/python/security/strings/External.qll b/python/ql/src/semmle/python/security/strings/External.qll index dc2a64ec2a3..a8b17b1f686 100644 --- a/python/ql/src/semmle/python/security/strings/External.qll +++ b/python/ql/src/semmle/python/security/strings/External.qll @@ -230,6 +230,8 @@ class UrlsplitUrlparseTempSanitizer extends Sanitizer { private predicate clears_taint(ControlFlowNode final_test, ControlFlowNode tainted, ControlFlowNode test, boolean sense) { test_equality_with_const(final_test, tainted, sense) or + test_in_const_seq(final_test, tainted, sense) + or test.(UnaryExprNode).getNode().getOp() instanceof Not and exists(ControlFlowNode nested_test | nested_test = test.(UnaryExprNode).getOperand() and @@ -238,19 +240,34 @@ class UrlsplitUrlparseTempSanitizer extends Sanitizer { } /** holds for `== "KNOWN_VALUE"` on `true` edge, and `!= "KNOWN_VALUE"` on `false` edge */ - private predicate test_equality_with_const(CompareNode cmp, ControlFlowNode operand, boolean sense) { + private predicate test_equality_with_const(CompareNode cmp, ControlFlowNode tainted, boolean sense) { exists(ControlFlowNode const, Cmpop op | const.getNode() instanceof StrConst | ( - cmp.operands(const, op, operand) + cmp.operands(const, op, tainted) or - cmp.operands(operand, op, const) - ) and ( + cmp.operands(tainted, op, const) + ) and + ( op instanceof Eq and sense = true or op instanceof NotEq and sense = false ) ) } + + /** holds for `in ["KNOWN_VALUE", ...]` on `true` edge, and `not in ["KNOWN_VALUE", ...]` on `false` edge */ + private predicate test_in_const_seq(CompareNode cmp, ControlFlowNode tainted, boolean sense) { + exists(SequenceNode const_seq, Cmpop op | + forall(ControlFlowNode elem | elem = const_seq.getAnElement() | elem.getNode() instanceof StrConst) + | + cmp.operands(tainted, op, const_seq) and + ( + op instanceof In and sense = true + or + op instanceof NotIn and sense = false + ) + ) + } } diff --git a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected index 471801f775d..11c8b1268a7 100644 --- a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected +++ b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected @@ -6,7 +6,7 @@ | test.py:20 | test_sanitizer | Attribute | NO TAINT | | test.py:23 | test_sanitizer | Subscript | NO TAINT | | test.py:26 | test_sanitizer | Attribute | NO TAINT | -| test.py:29 | test_sanitizer | Attribute | externally controlled string | +| test.py:29 | test_sanitizer | Attribute | NO TAINT | | test.py:32 | test_sanitizer | Attribute | externally controlled string | | test.py:42 | test_namedtuple | a | NO TAINT | | test.py:42 | test_namedtuple | b | NO TAINT | diff --git a/python/ql/test/library-tests/taint/namedtuple/test.py b/python/ql/test/library-tests/taint/namedtuple/test.py index d0b9c1e9212..f3d0b778e1b 100644 --- a/python/ql/test/library-tests/taint/namedtuple/test.py +++ b/python/ql/test/library-tests/taint/namedtuple/test.py @@ -26,7 +26,7 @@ def test_sanitizer(): test(urlsplit_res.path) # FN if urlsplit_res.netloc in ["OK"]: - test(urlsplit_res.netloc) # FP + test(urlsplit_res.netloc) if urlsplit_res.netloc in ["OK", non_constant()]: test(urlsplit_res.netloc) # should be tainted