import python import semmle.python.dataflow.TaintTracking import semmle.python.security.strings.Untrusted class SimpleSource extends TaintSource { SimpleSource() { this.(NameNode).getId() = "TAINTED_STRING" } override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringKind } override string toString() { result = "taint source" } } class MySimpleSanitizer extends Sanitizer { MySimpleSanitizer() { this = "MySimpleSanitizer" } /** * The test `if is_safe(arg):` sanitizes `arg` on its `true` edge. * * Can't handle `if not is_safe(arg):` :\ that's why it's called MySimpleSanitizer */ override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { taint instanceof ExternalStringKind and exists(CallNode call | test.getTest() = call and test.getSense() = true | call = Value::named("test.is_safe").getACall() and test.getInput().getAUse() = call.getAnArg() ) } } class MySanitizerHandlingNot extends Sanitizer { MySanitizerHandlingNot() { this = "MySanitizerHandlingNot" } /** The test `if is_safe(arg):` sanitizes `arg` on its `true` edge. */ override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { taint instanceof ExternalStringKind and clears_taint_on_true(test.getTest(), test.getSense(), test) } } /** * Helper predicate that recurses into any nesting of `not` * * To reduce the number of tuples this predicate holds for, we include the `PyEdgeRefinement` and * ensure that `test` is a part of this `PyEdgeRefinement` (instead of just taking the * `edge_refinement.getInput().getAUse()` part as a part of the predicate). Without including * `PyEdgeRefinement` as an argument *any* `CallNode c` to `test.is_safe` would be a result of * this predicate, since the tuple where `test = c` and `sense = true` would hold. */ private predicate clears_taint_on_true( ControlFlowNode test, boolean sense, PyEdgeRefinement edge_refinement ) { edge_refinement.getTest().getNode().(Expr).getASubExpression*() = test.getNode() and ( test = Value::named("test.is_safe").getACall() and edge_refinement.getInput().getAUse() = test.(CallNode).getAnArg() and sense = true or test.(UnaryExprNode).getNode().getOp() instanceof Not and exists(ControlFlowNode nested_test | nested_test = test.(UnaryExprNode).getOperand() and clears_taint_on_true(nested_test, sense.booleanNot(), edge_refinement) ) ) } class TestConfig extends TaintTracking::Configuration { TestConfig() { this = "TestConfig" } override predicate isSanitizer(Sanitizer sanitizer) { sanitizer instanceof MySanitizerHandlingNot } override predicate isSource(TaintTracking::Source source) { source instanceof SimpleSource } override predicate isSink(TaintTracking::Sink sink) { none() } }