From 74f1dd3ec01f6e6cc66a70c9bd13703039480527 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Tue, 30 Jul 2019 16:18:50 +0100 Subject: [PATCH] Python taint-tracking. Add some tests and fix up various parts of the implementation. --- .../semmle/python/dataflow/Implementation.qll | 98 +++--- .../taint/config/RockPaperScissors.expected | 37 ++ .../taint/config/RockPaperScissors.ql | 13 + .../taint/config/Simple.expected | 76 ++++ .../test/library-tests/taint/config/Simple.ql | 13 + .../library-tests/taint/config/TaintLib.qll | 331 ++++++++++++++++++ .../taint/config/TaintedArgument.expected | 1 + .../taint/config/TaintedArgument.ql | 13 + .../taint/config/TestNode.expected | 0 .../library-tests/taint/config/TestNode.ql | 9 + .../taint/config/TestSink.expected | 78 +++++ .../library-tests/taint/config/TestSink.ql | 8 + .../taint/config/TestSource.expected | 37 ++ .../library-tests/taint/config/TestSource.ql | 8 + .../taint/config/TestStep.expected | 0 .../library-tests/taint/config/TestStep.ql | 13 + .../library-tests/taint/config/carrier.py | 35 ++ .../test/library-tests/taint/config/deep.py | 23 ++ .../test/library-tests/taint/config/module.py | 11 + .../taint/config/rockpaperscissors.py | 32 ++ .../library-tests/taint/config/sanitizer.py | 36 ++ .../test/library-tests/taint/config/test.py | 215 ++++++++++++ 22 files changed, 1045 insertions(+), 42 deletions(-) create mode 100644 python/ql/test/library-tests/taint/config/RockPaperScissors.expected create mode 100644 python/ql/test/library-tests/taint/config/RockPaperScissors.ql create mode 100644 python/ql/test/library-tests/taint/config/Simple.expected create mode 100644 python/ql/test/library-tests/taint/config/Simple.ql create mode 100644 python/ql/test/library-tests/taint/config/TaintLib.qll create mode 100644 python/ql/test/library-tests/taint/config/TaintedArgument.expected create mode 100644 python/ql/test/library-tests/taint/config/TaintedArgument.ql create mode 100644 python/ql/test/library-tests/taint/config/TestNode.expected create mode 100644 python/ql/test/library-tests/taint/config/TestNode.ql create mode 100644 python/ql/test/library-tests/taint/config/TestSink.expected create mode 100644 python/ql/test/library-tests/taint/config/TestSink.ql create mode 100644 python/ql/test/library-tests/taint/config/TestSource.expected create mode 100644 python/ql/test/library-tests/taint/config/TestSource.ql create mode 100644 python/ql/test/library-tests/taint/config/TestStep.expected create mode 100644 python/ql/test/library-tests/taint/config/TestStep.ql create mode 100644 python/ql/test/library-tests/taint/config/carrier.py create mode 100644 python/ql/test/library-tests/taint/config/deep.py create mode 100644 python/ql/test/library-tests/taint/config/module.py create mode 100644 python/ql/test/library-tests/taint/config/rockpaperscissors.py create mode 100644 python/ql/test/library-tests/taint/config/sanitizer.py create mode 100644 python/ql/test/library-tests/taint/config/test.py diff --git a/python/ql/src/semmle/python/dataflow/Implementation.qll b/python/ql/src/semmle/python/dataflow/Implementation.qll index 390dcaed7ac..52d30fe0fa8 100644 --- a/python/ql/src/semmle/python/dataflow/Implementation.qll +++ b/python/ql/src/semmle/python/dataflow/Implementation.qll @@ -5,10 +5,8 @@ private import semmle.python.objects.ObjectInternal newtype TTaintTrackingContext = TNoParam() or - TParamContext(TaintKind param, int n) { - exists(CallNode call | - param.taints(call.getArg(n)) - ) + TParamContext(TaintKind param, AttributePath path, int n) { + any(TaintTrackingImplementation impl).callWithTaintedArgument(_, _, _, _, n, path, param) } class TaintTrackingContext extends TTaintTrackingContext { @@ -16,21 +14,25 @@ class TaintTrackingContext extends TTaintTrackingContext { string toString() { this = TNoParam() and result = "No context" or - exists(TaintKind param, int n | - this = TParamContext(param, n) and - result = "Parameter " + n.toString() + " is " + param + exists(TaintKind param, AttributePath path, int n | + this = TParamContext(param, path, n) and + result = "Parameter " + n.toString() + "(" + path.toString() + ") is " + param ) } TaintKind getParameterTaint(int n) { - this = TParamContext(result, n) + this = TParamContext(result, _, n) + } + + AttributePath getAttributePath() { + this = TParamContext(_, result, _) } TaintTrackingContext getCaller() { - exists(TaintKind param, int n | - this = TParamContext(param, n) and + exists(TaintKind param, AttributePath path, int n | + this = TParamContext(param, path, n) and exists(TaintTrackingImplementation impl | - impl.callWithTaintedArgument(_, _, result, _, n, TNoAttribute(), param) + impl.callWithTaintedArgument(_, _, result, _, n, path, param) ) ) } @@ -158,7 +160,7 @@ class TaintTrackingImplementation extends string { predicate isPathSink(TaintTrackingNode sink) { exists(DataFlow::Node sinknode, TaintKind kind | - sink = TTaintTrackingNode_(sinknode, TNoParam(), TNoAttribute(), kind, this) and + sink = TTaintTrackingNode_(sinknode, _, TNoAttribute(), kind, this) and this.(TaintTracking::Configuration).isSink(sinknode, kind) ) } @@ -181,20 +183,26 @@ class TaintTrackingImplementation extends string { this.importStep(src, node, context, path, kind) or this.fromImportStep(src, node, context, path, kind) - or - this.attributeLoadStep(src, node, context, path, kind) - or - this.getattrStep(src, node, context, path, kind) + //or + //this.attributeLoadStep(src, node, context, path, kind) + //or + //this.getattrStep(src, node, context, path, kind) or this.useStep(src, node, context, path, kind) or this.callTaintStep(src, node, context, path, kind) or - this.callFlowStep(src, node, context, path, kind) + this.returnFlowStep(src, node, context, path, kind) + //or + //this.iterationStep(src, node, context, path, kind) + //or + //this.yieldStep(src, node, context, path, kind) + //or + //this.subscriptStep(src, node, context, path, kind) + //or + //this.ifExprStep(src, node, context, path, kind) or - this.iterationStep(src, node, context, path, kind) - or - this.yieldStep(src, node, context, path, kind) + this.essaFlowStep(src, node, context, path, kind) or exists(DataFlow::Node srcnode, TaintKind srckind | this.(TaintTracking::Configuration).isAdditionalFlowStep(srcnode, node, srckind, kind) and @@ -259,35 +267,40 @@ class TaintTrackingImplementation extends string { ) } + //pragma [noinline] + //predicate argumentFlowStep(TaintTrackingNode src, DataFlow::Node node, TaintTrackingContext context, AttributePath path, TaintKind kind) { + // exists(CallNode call, PythonFunctionObjectInternal pyfunc, int arg | + // this.callWithTaintedArgument(src, call, _, pyfunc, arg, path, kind) and + // node.asCfgNode() = pyfunc.getParameter(arg) and + // context = TParamContext(kind, arg) + // ) + // // TO DO... named parameters + //} + pragma [noinline] - predicate callFlowStep(TaintTrackingNode src, DataFlow::Node node, TaintTrackingContext context, AttributePath path, TaintKind kind) { - exists(CallNode call, PythonFunctionObjectInternal pyfunc, int arg | - this.callWithTaintedArgument(src, call, _, pyfunc, arg, path, kind) and - node.asCfgNode() = pyfunc.getParameter(arg) and - context = TParamContext(kind, arg) + predicate returnFlowStep(TaintTrackingNode src, DataFlow::Node node, TaintTrackingContext context, AttributePath path, TaintKind kind) { + exists(CallNode call, PythonFunctionObjectInternal pyfunc, int arg, TaintKind callerKind, DataFlow::Node srcNode, AttributePath callerPath, TaintTrackingContext srcContext | + src = TTaintTrackingNode_(srcNode, srcContext, path, kind, this) and + this.callWithTaintedArgument(_, call, context, pyfunc, arg, callerPath, callerKind) and + srcContext = TParamContext(callerKind, callerPath, arg) and + node.asCfgNode() = call and + srcNode.asCfgNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode() ) - or - exists(CallNode call, PythonFunctionObjectInternal pyfunc, int arg | - this.callWithTaintedArgument(src, call, context, pyfunc, arg, path, kind) and - src.getContext() = TParamContext(kind, arg) - ) - // TO DO... named parameters } - predicate callWithTaintedArgument(TaintTrackingNode src, CallNode call, TaintTrackingContext caller, PythonFunctionObjectInternal pyfunc, int arg, AttributePath path, TaintKind kind) { + predicate callWithTaintedArgument(TaintTrackingNode src, CallNode call, TaintTrackingContext caller, CallableValue pyfunc, int arg, AttributePath path, TaintKind kind) { exists(DataFlow::Node srcnode | src = TTaintTrackingNode_(srcnode, caller, path, kind, this) and - srcnode.asCfgNode() = call.getArg(arg) and - pyfunc.getACall() = call + srcnode.asCfgNode() = pyfunc.getArgumentForCall(call, arg) ) } pragma [noinline] predicate callTaintStep(TaintTrackingNode src, DataFlow::Node node, TaintTrackingContext context, AttributePath path, TaintKind kind) { - exists(DataFlow::Node srcnode, CallNode call, string name | - src = TTaintTrackingNode_(srcnode, context, path, kind, this) and + exists(DataFlow::Node srcnode, CallNode call, TaintKind srckind, string name | + src = TTaintTrackingNode_(srcnode, context, path, srckind, this) and call.getFunction().(AttrNode).getObject(name) = src.getNode().asCfgNode() and - kind = src.getTaintKind().getTaintOfMethodResult(name) and + kind = srckind.getTaintOfMethodResult(name) and node.asCfgNode() = call ) } @@ -366,7 +379,8 @@ class TaintTrackingImplementation extends string { src = TTaintTrackingNode_(srcnode, context, path, kind, this) and predvar = defn.getInput(pred) and not pred.unlikelySuccessor(defn.getBasicBlock()) and - not predvar.(DataFlowExtension::DataFlowVariable).prunedSuccessor(defn.getVariable()) + not predvar.(DataFlowExtension::DataFlowVariable).prunedSuccessor(defn.getVariable()) and + srcnode.asVariable() = predvar ) } @@ -390,11 +404,11 @@ class TaintTrackingImplementation extends string { pragma [noinline] predicate taintedParameterDefinition(TaintTrackingNode src, ParameterDefinition defn, TaintTrackingContext context, AttributePath path, TaintKind kind) { - exists(DataFlow::Node srcnode | - src = TTaintTrackingNode_(srcnode, context, path, kind, this) and - defn.getDefiningNode() = srcnode.asCfgNode() + exists(CallNode call, PythonFunctionObjectInternal pyfunc, int arg | + this.callWithTaintedArgument(src, call, _, pyfunc, arg, path, kind) and + defn.getDefiningNode() = pyfunc.getParameter(arg) and + context = TParamContext(kind, path, arg) ) - // TO DO... class intializers } pragma [noinline] diff --git a/python/ql/test/library-tests/taint/config/RockPaperScissors.expected b/python/ql/test/library-tests/taint/config/RockPaperScissors.expected new file mode 100644 index 00000000000..440e7aedf25 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/RockPaperScissors.expected @@ -0,0 +1,37 @@ +edges +| carrier.py:21:5:21:5 | explicit.carrier at carrier.py:21 | carrier.py:22:10:22:10 | explicit.carrier at carrier.py:22 | +| carrier.py:21:9:21:28 | explicit.carrier at carrier.py:21 | carrier.py:21:5:21:5 | explicit.carrier at carrier.py:21 | +| carrier.py:22:10:22:10 | explicit.carrier at carrier.py:22 | carrier.py:22:10:22:22 | simple.test at carrier.py:22 | +| rockpaperscissors.py:24:5:24:5 | rock at rockpaperscissors.py:24 | rockpaperscissors.py:25:9:25:9 | rock at rockpaperscissors.py:25 | +| rockpaperscissors.py:24:9:24:12 | rock at rockpaperscissors.py:24 | rockpaperscissors.py:24:5:24:5 | rock at rockpaperscissors.py:24 | +| rockpaperscissors.py:25:5:25:5 | paper at rockpaperscissors.py:25 | rockpaperscissors.py:26:14:26:14 | paper at rockpaperscissors.py:26 | +| rockpaperscissors.py:25:9:25:9 | rock at rockpaperscissors.py:25 | rockpaperscissors.py:25:9:25:16 | scissors at rockpaperscissors.py:25 | +| rockpaperscissors.py:25:9:25:16 | scissors at rockpaperscissors.py:25 | rockpaperscissors.py:25:9:25:23 | paper at rockpaperscissors.py:25 | +| rockpaperscissors.py:25:9:25:23 | paper at rockpaperscissors.py:25 | rockpaperscissors.py:25:5:25:5 | paper at rockpaperscissors.py:25 | +| test.py:6:5:6:5 | simple.test at test.py:6 | test.py:7:10:7:10 | simple.test at test.py:7 | +| test.py:6:9:6:14 | simple.test at test.py:6 | test.py:6:5:6:5 | simple.test at test.py:6 | +| test.py:12:10:12:12 | simple.test at test.py:12 | test.py:13:10:13:12 | simple.test at test.py:13 | +| test.py:20:5:20:5 | simple.test at test.py:20 | test.py:21:10:21:10 | simple.test at test.py:21 | +| test.py:20:9:20:14 | simple.test at test.py:20 | test.py:20:5:20:5 | simple.test at test.py:20 | +| test.py:21:10:21:10 | simple.test at test.py:21 | test.py:12:10:12:12 | simple.test at test.py:12 | +| test.py:37:9:37:9 | simple.test at test.py:37 | test.py:41:14:41:14 | simple.test at test.py:41 | +| test.py:37:13:37:18 | simple.test at test.py:37 | test.py:37:9:37:9 | simple.test at test.py:37 | +| test.py:49:17:49:19 | simple.test at test.py:49 | test.py:51:14:51:16 | simple.test at test.py:51 | +| test.py:51:14:51:16 | simple.test at test.py:51 | test.py:12:10:12:12 | simple.test at test.py:12 | +| test.py:62:9:62:9 | simple.test at test.py:62 | test.py:63:5:63:9 | simple.test at test.py:63 | +| test.py:62:13:62:18 | simple.test at test.py:62 | test.py:62:9:62:9 | simple.test at test.py:62 | +| test.py:63:5:63:9 | simple.test at test.py:63 | test.py:63:17:63:17 | simple.test at test.py:63 | +| test.py:63:17:63:17 | simple.test at test.py:63 | test.py:49:17:49:19 | simple.test at test.py:49 | +| test.py:67:9:67:9 | simple.test at test.py:67 | test.py:70:5:70:9 | simple.test at test.py:70 | +| test.py:67:13:67:18 | simple.test at test.py:67 | test.py:67:9:67:9 | simple.test at test.py:67 | +| test.py:70:5:70:9 | simple.test at test.py:70 | test.py:70:17:70:17 | simple.test at test.py:70 | +| test.py:70:17:70:17 | simple.test at test.py:70 | test.py:49:17:49:19 | simple.test at test.py:49 | +| test.py:126:9:126:9 | simple.test at test.py:126 | test.py:130:21:130:21 | simple.test at test.py:130 | +| test.py:126:13:126:25 | simple.test at test.py:126 | test.py:126:9:126:9 | simple.test at test.py:126 | +| test.py:128:9:128:9 | simple.test at test.py:128 | test.py:132:14:132:14 | simple.test at test.py:132 | +| test.py:128:13:128:18 | simple.test at test.py:128 | test.py:128:9:128:9 | simple.test at test.py:128 | +parents +#select +| rockpaperscissors.py:13:10:13:17 | ControlFlowNode for SCISSORS | rockpaperscissors.py:13:10:13:17 | scissors at rockpaperscissors.py:13 | rockpaperscissors.py:13:10:13:17 | scissors at rockpaperscissors.py:13 | $@ looses to $@. | rockpaperscissors.py:13:10:13:17 | ControlFlowNode for SCISSORS | scissors | rockpaperscissors.py:13:10:13:17 | ControlFlowNode for SCISSORS | scissors | +| rockpaperscissors.py:16:11:16:14 | ControlFlowNode for ROCK | rockpaperscissors.py:16:11:16:14 | rock at rockpaperscissors.py:16 | rockpaperscissors.py:16:11:16:14 | rock at rockpaperscissors.py:16 | $@ looses to $@. | rockpaperscissors.py:16:11:16:14 | ControlFlowNode for ROCK | rock | rockpaperscissors.py:16:11:16:14 | ControlFlowNode for ROCK | rock | +| rockpaperscissors.py:26:14:26:14 | ControlFlowNode for y | rockpaperscissors.py:24:9:24:12 | rock at rockpaperscissors.py:24 | rockpaperscissors.py:26:14:26:14 | paper at rockpaperscissors.py:26 | $@ looses to $@. | rockpaperscissors.py:24:9:24:12 | ControlFlowNode for ROCK | rock | rockpaperscissors.py:26:14:26:14 | ControlFlowNode for y | paper | diff --git a/python/ql/test/library-tests/taint/config/RockPaperScissors.ql b/python/ql/test/library-tests/taint/config/RockPaperScissors.ql new file mode 100644 index 00000000000..8c5fc75f0a0 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/RockPaperScissors.ql @@ -0,0 +1,13 @@ + +/** + * @kind path-problem + */ + +import python +import semmle.python.security.TaintTracking +import TaintLib +import semmle.python.security.Paths + +from RockPaperScissorConfig config, TaintedPathSource src, TaintedPathSink sink +where config.hasFlowPath(src, sink) +select sink.getSink(), src, sink, "$@ looses to $@.", src.getNode(), src.getTaintKind().toString(), sink.getNode(), sink.getTaintKind().toString() \ No newline at end of file diff --git a/python/ql/test/library-tests/taint/config/Simple.expected b/python/ql/test/library-tests/taint/config/Simple.expected new file mode 100644 index 00000000000..b9d870c06cc --- /dev/null +++ b/python/ql/test/library-tests/taint/config/Simple.expected @@ -0,0 +1,76 @@ +edges +| carrier.py:13:9:13:11 | explicit.carrier at carrier.py:13 | carrier.py:14:12:14:14 | explicit.carrier at carrier.py:14 | +| carrier.py:14:12:14:14 | explicit.carrier at carrier.py:14 | carrier.py:29:9:29:33 | explicit.carrier at carrier.py:29 | +| carrier.py:21:5:21:5 | explicit.carrier at carrier.py:21 | carrier.py:22:10:22:10 | explicit.carrier at carrier.py:22 | +| carrier.py:21:9:21:28 | explicit.carrier at carrier.py:21 | carrier.py:21:5:21:5 | explicit.carrier at carrier.py:21 | +| carrier.py:22:10:22:10 | explicit.carrier at carrier.py:22 | carrier.py:22:10:22:22 | simple.test at carrier.py:22 | +| carrier.py:29:5:29:5 | explicit.carrier at carrier.py:29 | carrier.py:30:10:30:10 | explicit.carrier at carrier.py:30 | +| carrier.py:29:9:29:33 | explicit.carrier at carrier.py:29 | carrier.py:29:5:29:5 | explicit.carrier at carrier.py:29 | +| carrier.py:29:13:29:32 | explicit.carrier at carrier.py:29 | carrier.py:13:9:13:11 | explicit.carrier at carrier.py:13 | +| carrier.py:30:10:30:10 | explicit.carrier at carrier.py:30 | carrier.py:30:10:30:22 | simple.test at carrier.py:30 | +| deep.py:2:8:2:10 | simple.test at deep.py:2 | deep.py:3:12:3:14 | simple.test at deep.py:3 | +| deep.py:3:12:3:14 | simple.test at deep.py:3 | deep.py:6:12:6:18 | simple.test at deep.py:6 | +| deep.py:5:8:5:10 | simple.test at deep.py:5 | deep.py:6:15:6:17 | simple.test at deep.py:6 | +| deep.py:6:12:6:18 | simple.test at deep.py:6 | deep.py:9:12:9:18 | simple.test at deep.py:9 | +| deep.py:6:15:6:17 | simple.test at deep.py:6 | deep.py:2:8:2:10 | simple.test at deep.py:2 | +| deep.py:8:8:8:10 | simple.test at deep.py:8 | deep.py:9:15:9:17 | simple.test at deep.py:9 | +| deep.py:9:12:9:18 | simple.test at deep.py:9 | deep.py:12:12:12:18 | simple.test at deep.py:12 | +| deep.py:9:15:9:17 | simple.test at deep.py:9 | deep.py:5:8:5:10 | simple.test at deep.py:5 | +| deep.py:11:8:11:10 | simple.test at deep.py:11 | deep.py:12:15:12:17 | simple.test at deep.py:12 | +| deep.py:12:12:12:18 | simple.test at deep.py:12 | deep.py:15:12:15:18 | simple.test at deep.py:15 | +| deep.py:12:15:12:17 | simple.test at deep.py:12 | deep.py:8:8:8:10 | simple.test at deep.py:8 | +| deep.py:14:8:14:10 | simple.test at deep.py:14 | deep.py:15:15:15:17 | simple.test at deep.py:15 | +| deep.py:15:12:15:18 | simple.test at deep.py:15 | deep.py:18:12:18:18 | simple.test at deep.py:18 | +| deep.py:15:15:15:17 | simple.test at deep.py:15 | deep.py:11:8:11:10 | simple.test at deep.py:11 | +| deep.py:17:8:17:10 | simple.test at deep.py:17 | deep.py:18:15:18:17 | simple.test at deep.py:18 | +| deep.py:18:12:18:18 | simple.test at deep.py:18 | deep.py:20:5:20:14 | simple.test at deep.py:20 | +| deep.py:18:15:18:17 | simple.test at deep.py:18 | deep.py:14:8:14:10 | simple.test at deep.py:14 | +| deep.py:20:1:20:1 | simple.test at deep.py:20 | deep.py:22:6:22:6 | simple.test at deep.py:22 | +| deep.py:20:5:20:14 | simple.test at deep.py:20 | deep.py:20:1:20:1 | simple.test at deep.py:20 | +| deep.py:20:8:20:13 | simple.test at deep.py:20 | deep.py:17:8:17:10 | simple.test at deep.py:17 | +| rockpaperscissors.py:24:5:24:5 | rock at rockpaperscissors.py:24 | rockpaperscissors.py:25:9:25:9 | rock at rockpaperscissors.py:25 | +| rockpaperscissors.py:24:9:24:12 | rock at rockpaperscissors.py:24 | rockpaperscissors.py:24:5:24:5 | rock at rockpaperscissors.py:24 | +| rockpaperscissors.py:25:5:25:5 | paper at rockpaperscissors.py:25 | rockpaperscissors.py:26:14:26:14 | paper at rockpaperscissors.py:26 | +| rockpaperscissors.py:25:9:25:9 | rock at rockpaperscissors.py:25 | rockpaperscissors.py:25:9:25:16 | scissors at rockpaperscissors.py:25 | +| rockpaperscissors.py:25:9:25:16 | scissors at rockpaperscissors.py:25 | rockpaperscissors.py:25:9:25:23 | paper at rockpaperscissors.py:25 | +| rockpaperscissors.py:25:9:25:23 | paper at rockpaperscissors.py:25 | rockpaperscissors.py:25:5:25:5 | paper at rockpaperscissors.py:25 | +| test.py:6:5:6:5 | simple.test at test.py:6 | test.py:7:10:7:10 | simple.test at test.py:7 | +| test.py:6:9:6:14 | simple.test at test.py:6 | test.py:6:5:6:5 | simple.test at test.py:6 | +| test.py:12:10:12:12 | simple.test at test.py:12 | test.py:13:10:13:12 | simple.test at test.py:13 | +| test.py:20:5:20:5 | simple.test at test.py:20 | test.py:21:10:21:10 | simple.test at test.py:21 | +| test.py:20:9:20:14 | simple.test at test.py:20 | test.py:20:5:20:5 | simple.test at test.py:20 | +| test.py:21:10:21:10 | simple.test at test.py:21 | test.py:12:10:12:12 | simple.test at test.py:12 | +| test.py:37:9:37:9 | simple.test at test.py:37 | test.py:41:14:41:14 | simple.test at test.py:41 | +| test.py:37:13:37:18 | simple.test at test.py:37 | test.py:37:9:37:9 | simple.test at test.py:37 | +| test.py:49:17:49:19 | simple.test at test.py:49 | test.py:51:14:51:16 | simple.test at test.py:51 | +| test.py:51:14:51:16 | simple.test at test.py:51 | test.py:12:10:12:12 | simple.test at test.py:12 | +| test.py:62:9:62:9 | simple.test at test.py:62 | test.py:63:5:63:9 | simple.test at test.py:63 | +| test.py:62:13:62:18 | simple.test at test.py:62 | test.py:62:9:62:9 | simple.test at test.py:62 | +| test.py:63:5:63:9 | simple.test at test.py:63 | test.py:63:17:63:17 | simple.test at test.py:63 | +| test.py:63:17:63:17 | simple.test at test.py:63 | test.py:49:17:49:19 | simple.test at test.py:49 | +| test.py:67:9:67:9 | simple.test at test.py:67 | test.py:70:5:70:9 | simple.test at test.py:70 | +| test.py:67:13:67:18 | simple.test at test.py:67 | test.py:67:9:67:9 | simple.test at test.py:67 | +| test.py:70:5:70:9 | simple.test at test.py:70 | test.py:70:17:70:17 | simple.test at test.py:70 | +| test.py:70:17:70:17 | simple.test at test.py:70 | test.py:49:17:49:19 | simple.test at test.py:49 | +| test.py:72:9:72:11 | simple.test at test.py:72 | test.py:73:12:73:14 | simple.test at test.py:73 | +| test.py:73:12:73:14 | simple.test at test.py:73 | test.py:77:9:77:14 | simple.test at test.py:77 | +| test.py:76:5:76:5 | simple.test at test.py:76 | test.py:77:13:77:13 | simple.test at test.py:77 | +| test.py:76:9:76:14 | simple.test at test.py:76 | test.py:76:5:76:5 | simple.test at test.py:76 | +| test.py:77:5:77:5 | simple.test at test.py:77 | test.py:78:10:78:10 | simple.test at test.py:78 | +| test.py:77:9:77:14 | simple.test at test.py:77 | test.py:77:5:77:5 | simple.test at test.py:77 | +| test.py:77:13:77:13 | simple.test at test.py:77 | test.py:72:9:72:11 | simple.test at test.py:72 | +| test.py:126:9:126:9 | simple.test at test.py:126 | test.py:130:21:130:21 | simple.test at test.py:130 | +| test.py:126:13:126:25 | simple.test at test.py:126 | test.py:126:9:126:9 | simple.test at test.py:126 | +| test.py:128:9:128:9 | simple.test at test.py:128 | test.py:132:14:132:14 | simple.test at test.py:132 | +| test.py:128:13:128:18 | simple.test at test.py:128 | test.py:128:9:128:9 | simple.test at test.py:128 | +parents +#select +| deep.py:22:6:22:6 | ControlFlowNode for x | deep.py:20:8:20:13 | simple.test at deep.py:20 | deep.py:22:6:22:6 | simple.test at deep.py:22 | $@ flows to $@. | deep.py:20:8:20:13 | ControlFlowNode for SOURCE | simple.test | deep.py:22:6:22:6 | ControlFlowNode for x | simple.test | +| test.py:3:10:3:15 | ControlFlowNode for SOURCE | test.py:3:10:3:15 | simple.test at test.py:3 | test.py:3:10:3:15 | simple.test at test.py:3 | $@ flows to $@. | test.py:3:10:3:15 | ControlFlowNode for SOURCE | simple.test | test.py:3:10:3:15 | ControlFlowNode for SOURCE | simple.test | +| test.py:7:10:7:10 | ControlFlowNode for s | test.py:6:9:6:14 | simple.test at test.py:6 | test.py:7:10:7:10 | simple.test at test.py:7 | $@ flows to $@. | test.py:6:9:6:14 | ControlFlowNode for SOURCE | simple.test | test.py:7:10:7:10 | ControlFlowNode for s | simple.test | +| test.py:13:10:13:12 | ControlFlowNode for arg | test.py:20:9:20:14 | simple.test at test.py:20 | test.py:13:10:13:12 | simple.test at test.py:13 | $@ flows to $@. | test.py:20:9:20:14 | ControlFlowNode for SOURCE | simple.test | test.py:13:10:13:12 | ControlFlowNode for arg | simple.test | +| test.py:13:10:13:12 | ControlFlowNode for arg | test.py:62:13:62:18 | simple.test at test.py:62 | test.py:13:10:13:12 | simple.test at test.py:13 | $@ flows to $@. | test.py:62:13:62:18 | ControlFlowNode for SOURCE | simple.test | test.py:13:10:13:12 | ControlFlowNode for arg | simple.test | +| test.py:13:10:13:12 | ControlFlowNode for arg | test.py:67:13:67:18 | simple.test at test.py:67 | test.py:13:10:13:12 | simple.test at test.py:13 | $@ flows to $@. | test.py:67:13:67:18 | ControlFlowNode for SOURCE | simple.test | test.py:13:10:13:12 | ControlFlowNode for arg | simple.test | +| test.py:41:14:41:14 | ControlFlowNode for t | test.py:37:13:37:18 | simple.test at test.py:37 | test.py:41:14:41:14 | simple.test at test.py:41 | $@ flows to $@. | test.py:37:13:37:18 | ControlFlowNode for SOURCE | simple.test | test.py:41:14:41:14 | ControlFlowNode for t | simple.test | +| test.py:78:10:78:10 | ControlFlowNode for t | test.py:76:9:76:14 | simple.test at test.py:76 | test.py:78:10:78:10 | simple.test at test.py:78 | $@ flows to $@. | test.py:76:9:76:14 | ControlFlowNode for SOURCE | simple.test | test.py:78:10:78:10 | ControlFlowNode for t | simple.test | +| test.py:132:14:132:14 | ControlFlowNode for t | test.py:128:13:128:18 | simple.test at test.py:128 | test.py:132:14:132:14 | simple.test at test.py:132 | $@ flows to $@. | test.py:128:13:128:18 | ControlFlowNode for SOURCE | simple.test | test.py:132:14:132:14 | ControlFlowNode for t | simple.test | diff --git a/python/ql/test/library-tests/taint/config/Simple.ql b/python/ql/test/library-tests/taint/config/Simple.ql new file mode 100644 index 00000000000..a3acf7272f6 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/Simple.ql @@ -0,0 +1,13 @@ + +/** + * @kind path-problem + */ + +import python +import semmle.python.security.TaintTracking +import TaintLib +import semmle.python.security.Paths + +from SimpleConfig config, TaintedPathSource src, TaintedPathSink sink +where config.hasFlowPath(src, sink) +select sink.getSink(), src, sink, "$@ flows to $@.", src.getNode(), src.getTaintKind().toString(), sink.getNode(), sink.getTaintKind().toString() diff --git a/python/ql/test/library-tests/taint/config/TaintLib.qll b/python/ql/test/library-tests/taint/config/TaintLib.qll new file mode 100644 index 00000000000..a5ec18b4fb9 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/TaintLib.qll @@ -0,0 +1,331 @@ +import python +import semmle.python.security.TaintTracking + + +class SimpleTest extends TaintKind { + + SimpleTest() { + this = "simple.test" + } + +} + +class SimpleConfig extends TaintTracking::Configuration { + + SimpleConfig() { this = "Simple config" } + + override predicate isSource(DataFlow::Node node, TaintKind kind) { + node.asCfgNode().(NameNode).getId() = "SOURCE" and + kind instanceof SimpleTest + } + + override predicate isSink(DataFlow::Node node, TaintKind kind) { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "SINK" and + node.asCfgNode() = call.getAnArg() + ) and + kind instanceof SimpleTest + } + + override predicate isBarrier(DataFlow::Node node, TaintKind kind) { + node.asCfgNode().(CallNode).getFunction().(NameNode).getId() = "SANITIZE" and + kind instanceof SimpleTest + } + +} + +class BasicCustomTaint extends TaintKind { + + BasicCustomTaint() { + this = "basic.custom" + } + + override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { + tonode.(CallNode).getAnArg() = fromnode and + tonode.(CallNode).getFunction().(NameNode).getId() = "TAINT_FROM_ARG" and + result = this + } + +} + + +class BasicCustomConfig extends TaintTracking::Configuration { + + BasicCustomConfig() { this = "Basic custom config" } + + override predicate isSource(DataFlow::Node node, TaintKind kind) { + node.asCfgNode().(NameNode).getId() = "CUSTOM_SOURCE" and + kind instanceof SimpleTest + } + + override predicate isSink(DataFlow::Node node, TaintKind kind) { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "CUSTOM_SINK" and + node.asCfgNode() = call.getAnArg() + ) and + kind instanceof SimpleTest + } + +} + + +class Rock extends TaintKind { + + Rock() { this = "rock" } + + override TaintKind getTaintOfMethodResult(string name) { + name = "prev" and result instanceof Scissors + } + +} + +class Paper extends TaintKind { + + Paper() { this = "paper" } + + override TaintKind getTaintOfMethodResult(string name) { + name = "prev" and result instanceof Rock + } + +} + +class Scissors extends TaintKind { + + Scissors() { this = "scissors" } + + override TaintKind getTaintOfMethodResult(string name) { + name = "prev" and result instanceof Paper + } + +} + +class RockPaperScissorConfig extends TaintTracking::Configuration { + + RockPaperScissorConfig() { this = "Rock-paper-scissors config" } + + + override predicate isSource(DataFlow::Node node, TaintKind kind) { + exists(string name | + node.asCfgNode().(NameNode).getId() = name and + kind = name.toLowerCase() + | + name = "ROCK" or name = "PAPER" or name = "SCISSORS" + ) + } + + override predicate isSink(DataFlow::Node node, TaintKind kind) { + exists(string name | + function_param(name, node) | + name = "paper" and kind = "rock" + or + name = "rock" and kind = "scissors" + or + name = "scissors" and kind = "paper" + ) + } + +} + +private predicate function_param(string funcname, DataFlow::Node arg) { + exists(FunctionObject f | + f.getName() = funcname and + arg.asCfgNode() = f.getArgumentForCall(_, _) + ) +} + + +class TaintCarrier extends TaintKind { + + TaintCarrier() { this = "explicit.carrier" } + + override TaintKind getTaintOfMethodResult(string name) { + name = "get_taint" and result instanceof SimpleTest + } + + +} + +class TaintCarrierConfig extends TaintTracking::Configuration { + + TaintCarrierConfig() { this = "Taint carrier config" } + + override predicate isSource(DataFlow::Node node, TaintKind kind) { + node.asCfgNode().(NameNode).getId() = "TAINT_CARRIER_SOURCE" and + kind instanceof TaintCarrier + } + + override predicate isSink(DataFlow::Node node, TaintKind kind) { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "SINK" and + node.asCfgNode() = call.getAnArg() + ) and + kind instanceof SimpleTest + } + + override predicate isBarrier(DataFlow::Node node, TaintKind kind) { + node.asCfgNode().(CallNode).getFunction().(NameNode).getId() = "SANITIZE" and + kind instanceof SimpleTest + } + +} + + +/* Some more realistic examples */ + +abstract class UserInput extends TaintKind { + + bindingset[this] + UserInput() { any() } + +} + +class UserInputSource extends TaintSource { + + UserInputSource() { + this.(CallNode).getFunction().(NameNode).getId() = "user_input" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof UserInput + } + + override string toString() { + result = "user.input.source" + } + +} + +class SqlInjectionTaint extends UserInput { + + SqlInjectionTaint() { this = "SQL injection" } + +} + +class CommandInjectionTaint extends UserInput { + + CommandInjectionTaint() { this = "Command injection" } + +} + +class SqlSanitizer extends Sanitizer { + + SqlSanitizer() { this = "SQL sanitizer" } + + /** Holds if `test` shows value to be untainted with `taint` */ + override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { + exists(FunctionObject f, CallNode call | + f.getName() = "isEscapedSql" and + test.getTest() = call and + call.getAnArg() = test.getSourceVariable().getAUse() and + f.getACall() = call and + test.getSense() = true + ) and + taint instanceof SqlInjectionTaint + } + +} + +class CommandSanitizer extends Sanitizer { + + CommandSanitizer() { this = "Command sanitizer" } + + /** Holds if `test` shows value to be untainted with `taint` */ + override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { + exists(FunctionObject f | + f.getName() = "isValidCommand" and + f.getACall().(CallNode).getAnArg() = test.getSourceVariable().getAUse() and + test.getSense() = true + ) and + taint instanceof CommandInjectionTaint + } + +} + +class SqlQuery extends TaintSink { + + SqlQuery() { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "sql_query" and + call.getAnArg() = this + ) + } + + override string toString() { result = "SQL query" } + + override predicate sinks(TaintKind taint) { + taint instanceof SqlInjectionTaint + } + +} + + +class OsCommand extends TaintSink { + + OsCommand() { + exists(CallNode call | + call.getFunction().(NameNode).getId() = "os_command" and + call.getAnArg() = this + ) + } + + override string toString() { result = "OS command" } + + override predicate sinks(TaintKind taint) { + taint instanceof CommandInjectionTaint + } + +} + + +class Falsey extends TaintKind { + + Falsey() { this = "falsey" } + + override boolean booleanValue() { + result = false + } + +} + +class FalseySource extends TaintSource { + + FalseySource() { + this.(NameNode).getId() = "FALSEY" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof Falsey + } + + override string toString() { + result = "falsey.source" + } + +} + +class TaintIterable extends TaintKind { + + TaintIterable() { + this = "iterable.simple" + } + + override TaintKind getTaintForIteration() { + result instanceof SimpleTest + } + +} + +class TaintIterableSource extends TaintSource { + + TaintIterableSource() { + this.(NameNode).getId() = "ITERABLE_SOURCE" + } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof TaintIterable + } + +} + + + diff --git a/python/ql/test/library-tests/taint/config/TaintedArgument.expected b/python/ql/test/library-tests/taint/config/TaintedArgument.expected new file mode 100644 index 00000000000..9dae856c349 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/TaintedArgument.expected @@ -0,0 +1 @@ +fail diff --git a/python/ql/test/library-tests/taint/config/TaintedArgument.ql b/python/ql/test/library-tests/taint/config/TaintedArgument.ql new file mode 100644 index 00000000000..7e1c71becc9 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/TaintedArgument.ql @@ -0,0 +1,13 @@ + +import python + + + +import semmle.python.security.TaintTracking +import TaintLib +import semmle.python.dataflow.Implementation + +from TaintTrackingImplementation config, TaintTrackingNode src, CallNode call, + TaintTrackingContext caller, CallableValue pyfunc, int arg, AttributePath path, TaintKind kind +where config.callWithTaintedArgument(src, call, caller, pyfunc, arg, path, kind) +select config, src, call, caller, pyfunc, arg, path, kind diff --git a/python/ql/test/library-tests/taint/config/TestNode.expected b/python/ql/test/library-tests/taint/config/TestNode.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/library-tests/taint/config/TestNode.ql b/python/ql/test/library-tests/taint/config/TestNode.ql new file mode 100644 index 00000000000..4db7bb2636c --- /dev/null +++ b/python/ql/test/library-tests/taint/config/TestNode.ql @@ -0,0 +1,9 @@ +import python +import semmle.python.security.TaintTracking +import semmle.python.dataflow.Implementation +import TaintLib + + +from TaintTrackingNode n +select n.getTaintKind(), n.getLocation().toString(), n.getNode().toString(), n.getPath().toString(), n.getContext().toString() + diff --git a/python/ql/test/library-tests/taint/config/TestSink.expected b/python/ql/test/library-tests/taint/config/TestSink.expected new file mode 100644 index 00000000000..7bece824242 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/TestSink.expected @@ -0,0 +1,78 @@ +| Rock-paper-scissors config | rockpaperscissors.py:13 | 13 | ControlFlowNode for SCISSORS | scissors | +| Rock-paper-scissors config | rockpaperscissors.py:16 | 16 | ControlFlowNode for ROCK | rock | +| Rock-paper-scissors config | rockpaperscissors.py:21 | 21 | ControlFlowNode for y | paper | +| Rock-paper-scissors config | rockpaperscissors.py:26 | 26 | ControlFlowNode for y | paper | +| Rock-paper-scissors config | rockpaperscissors.py:31 | 31 | ControlFlowNode for x | rock | +| Rock-paper-scissors config | rockpaperscissors.py:32 | 32 | ControlFlowNode for y | rock | +| Simple config | carrier.py:18 | 18 | ControlFlowNode for Attribute | simple.test | +| Simple config | carrier.py:22 | 22 | ControlFlowNode for Attribute() | simple.test | +| Simple config | carrier.py:26 | 26 | ControlFlowNode for Attribute() | simple.test | +| Simple config | carrier.py:30 | 30 | ControlFlowNode for Attribute() | simple.test | +| Simple config | carrier.py:35 | 35 | ControlFlowNode for Attribute() | simple.test | +| Simple config | test.py:3 | 3 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:7 | 7 | ControlFlowNode for s | simple.test | +| Simple config | test.py:13 | 13 | ControlFlowNode for arg | simple.test | +| Simple config | test.py:17 | 17 | ControlFlowNode for t | simple.test | +| Simple config | test.py:33 | 33 | ControlFlowNode for t | simple.test | +| Simple config | test.py:41 | 41 | ControlFlowNode for t | simple.test | +| Simple config | test.py:78 | 78 | ControlFlowNode for t | simple.test | +| Simple config | test.py:83 | 83 | ControlFlowNode for t | simple.test | +| Simple config | test.py:89 | 89 | ControlFlowNode for t | simple.test | +| Simple config | test.py:93 | 93 | ControlFlowNode for t | simple.test | +| Simple config | test.py:97 | 97 | ControlFlowNode for t | simple.test | +| Simple config | test.py:101 | 101 | ControlFlowNode for t | simple.test | +| Simple config | test.py:106 | 106 | ControlFlowNode for Attribute | simple.test | +| Simple config | test.py:111 | 111 | ControlFlowNode for Attribute | simple.test | +| Simple config | test.py:132 | 132 | ControlFlowNode for t | simple.test | +| Simple config | test.py:142 | 142 | ControlFlowNode for t | simple.test | +| Simple config | test.py:153 | 153 | ControlFlowNode for t | simple.test | +| Simple config | test.py:156 | 156 | ControlFlowNode for unsafe | simple.test | +| Simple config | test.py:160 | 160 | ControlFlowNode for t | simple.test | +| Simple config | test.py:165 | 165 | ControlFlowNode for s | simple.test | +| Simple config | test.py:172 | 172 | ControlFlowNode for Subscript | simple.test | +| Simple config | test.py:173 | 173 | ControlFlowNode for Subscript | simple.test | +| Simple config | test.py:180 | 180 | ControlFlowNode for t | simple.test | +| Simple config | test.py:182 | 182 | ControlFlowNode for t | simple.test | +| Simple config | test.py:184 | 184 | ControlFlowNode for t | simple.test | +| Simple config | test.py:186 | 186 | ControlFlowNode for t | simple.test | +| Simple config | test.py:197 | 197 | ControlFlowNode for t | simple.test | +| Simple config | test.py:199 | 199 | ControlFlowNode for t | simple.test | +| Simple config | test.py:214 | 214 | ControlFlowNode for x | simple.test | +| Taint carrier config | carrier.py:18 | 18 | ControlFlowNode for Attribute | simple.test | +| Taint carrier config | carrier.py:22 | 22 | ControlFlowNode for Attribute() | simple.test | +| Taint carrier config | carrier.py:26 | 26 | ControlFlowNode for Attribute() | simple.test | +| Taint carrier config | carrier.py:30 | 30 | ControlFlowNode for Attribute() | simple.test | +| Taint carrier config | carrier.py:35 | 35 | ControlFlowNode for Attribute() | simple.test | +| Taint carrier config | test.py:3 | 3 | ControlFlowNode for SOURCE | simple.test | +| Taint carrier config | test.py:7 | 7 | ControlFlowNode for s | simple.test | +| Taint carrier config | test.py:13 | 13 | ControlFlowNode for arg | simple.test | +| Taint carrier config | test.py:17 | 17 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:33 | 33 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:41 | 41 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:78 | 78 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:83 | 83 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:89 | 89 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:93 | 93 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:97 | 97 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:101 | 101 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:106 | 106 | ControlFlowNode for Attribute | simple.test | +| Taint carrier config | test.py:111 | 111 | ControlFlowNode for Attribute | simple.test | +| Taint carrier config | test.py:132 | 132 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:142 | 142 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:153 | 153 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:156 | 156 | ControlFlowNode for unsafe | simple.test | +| Taint carrier config | test.py:160 | 160 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:165 | 165 | ControlFlowNode for s | simple.test | +| Taint carrier config | test.py:172 | 172 | ControlFlowNode for Subscript | simple.test | +| Taint carrier config | test.py:173 | 173 | ControlFlowNode for Subscript | simple.test | +| Taint carrier config | test.py:180 | 180 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:182 | 182 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:184 | 184 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:186 | 186 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:197 | 197 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:199 | 199 | ControlFlowNode for t | simple.test | +| Taint carrier config | test.py:214 | 214 | ControlFlowNode for x | simple.test | +| Basic custom config | test.py:122 | 122 | ControlFlowNode for t | simple.test | +| Basic custom config | test.py:130 | 130 | ControlFlowNode for t | simple.test | +| Basic custom config | test.py:140 | 140 | ControlFlowNode for t | simple.test | +| Basic custom config | test.py:151 | 151 | ControlFlowNode for t | simple.test | diff --git a/python/ql/test/library-tests/taint/config/TestSink.ql b/python/ql/test/library-tests/taint/config/TestSink.ql new file mode 100644 index 00000000000..e749294fdf2 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/TestSink.ql @@ -0,0 +1,8 @@ +import python +import semmle.python.security.TaintTracking +import TaintLib + +from TaintTracking::Configuration config, DataFlow::Node sink, TaintKind kind + +where config.isSink(sink, kind) +select config, sink.getLocation().toString(), sink.getLocation().getStartLine(), sink.toString(), kind diff --git a/python/ql/test/library-tests/taint/config/TestSource.expected b/python/ql/test/library-tests/taint/config/TestSource.expected new file mode 100644 index 00000000000..3ce5442539a --- /dev/null +++ b/python/ql/test/library-tests/taint/config/TestSource.expected @@ -0,0 +1,37 @@ +| Rock-paper-scissors config | rockpaperscissors.py:13 | 13 | ControlFlowNode for SCISSORS | scissors | +| Rock-paper-scissors config | rockpaperscissors.py:16 | 16 | ControlFlowNode for ROCK | rock | +| Rock-paper-scissors config | rockpaperscissors.py:19 | 19 | ControlFlowNode for ROCK | rock | +| Rock-paper-scissors config | rockpaperscissors.py:24 | 24 | ControlFlowNode for ROCK | rock | +| Rock-paper-scissors config | rockpaperscissors.py:29 | 29 | ControlFlowNode for SCISSORS | scissors | +| Simple config | carrier.py:17 | 17 | ControlFlowNode for SOURCE | simple.test | +| Simple config | carrier.py:25 | 25 | ControlFlowNode for SOURCE | simple.test | +| Simple config | deep.py:20 | 20 | ControlFlowNode for SOURCE | simple.test | +| Simple config | module.py:3 | 3 | ControlFlowNode for SOURCE | simple.test | +| Simple config | module.py:7 | 7 | ControlFlowNode for SOURCE | simple.test | +| Simple config | module.py:10 | 10 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:3 | 3 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:6 | 6 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:10 | 10 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:20 | 20 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:31 | 31 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:37 | 37 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:62 | 62 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:67 | 67 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:76 | 76 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:128 | 128 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:138 | 138 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:148 | 148 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:159 | 159 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:163 | 163 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:168 | 168 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:169 | 169 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:178 | 178 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:195 | 195 | ControlFlowNode for SOURCE | simple.test | +| Simple config | test.py:208 | 208 | ControlFlowNode for SOURCE | simple.test | +| Taint carrier config | carrier.py:21 | 21 | ControlFlowNode for TAINT_CARRIER_SOURCE | explicit.carrier | +| Taint carrier config | carrier.py:29 | 29 | ControlFlowNode for TAINT_CARRIER_SOURCE | explicit.carrier | +| Taint carrier config | carrier.py:33 | 33 | ControlFlowNode for TAINT_CARRIER_SOURCE | explicit.carrier | +| Basic custom config | test.py:120 | 120 | ControlFlowNode for CUSTOM_SOURCE | simple.test | +| Basic custom config | test.py:126 | 126 | ControlFlowNode for CUSTOM_SOURCE | simple.test | +| Basic custom config | test.py:136 | 136 | ControlFlowNode for CUSTOM_SOURCE | simple.test | +| Basic custom config | test.py:146 | 146 | ControlFlowNode for CUSTOM_SOURCE | simple.test | diff --git a/python/ql/test/library-tests/taint/config/TestSource.ql b/python/ql/test/library-tests/taint/config/TestSource.ql new file mode 100644 index 00000000000..37292aee96f --- /dev/null +++ b/python/ql/test/library-tests/taint/config/TestSource.ql @@ -0,0 +1,8 @@ +import python +import semmle.python.security.TaintTracking +import TaintLib + +from TaintTracking::Configuration config, DataFlow::Node source, TaintKind kind + +where config.isSource(source, kind) +select config, source.getLocation().toString(), source.getLocation().getStartLine(), source.toString(), kind diff --git a/python/ql/test/library-tests/taint/config/TestStep.expected b/python/ql/test/library-tests/taint/config/TestStep.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/library-tests/taint/config/TestStep.ql b/python/ql/test/library-tests/taint/config/TestStep.ql new file mode 100644 index 00000000000..45a97793eb0 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/TestStep.ql @@ -0,0 +1,13 @@ +import python +import semmle.python.security.TaintTracking +import TaintLib +import semmle.python.dataflow.Implementation + + +from TaintTrackingNode n, TaintTrackingNode s, TaintTracking::Configuration config +where s = n.getASuccessor() and config = n.getConfiguration() +select + config + ":", + n.getTaintKind(), n.getLocation().toString(), n.getNode().toString(), n.getContext(), + " --> ", + s.getTaintKind(), s.getLocation().toString(), s.getNode().toString(), s.getContext() diff --git a/python/ql/test/library-tests/taint/config/carrier.py b/python/ql/test/library-tests/taint/config/carrier.py new file mode 100644 index 00000000000..10e70e41a93 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/carrier.py @@ -0,0 +1,35 @@ + +class ImplicitCarrier(object): + + def __init__(self, arg): + self.attr = arg + + def set_attr(self, arg): + self.attr = arg + + def get_attr(self): + return self.attr + +def hub(arg): + return arg + +def test1(): + c = ImplicitCarrier(SOURCE) + SINK(c.attr) + +def test2(): + c = TAINT_CARRIER_SOURCE + SINK(c.get_taint()) + +def test3(): + c = hub(ImplicitCarrier(SOURCE)) + SINK(c.get_attr()) + +def test4(): + c = hub(TAINT_CARRIER_SOURCE) + SINK(c.get_taint()) + +def test5(): + c = ImplicitCarrier(TAINT_CARRIER_SOURCE) + x = c.attr + SINK(x.get_taint()) diff --git a/python/ql/test/library-tests/taint/config/deep.py b/python/ql/test/library-tests/taint/config/deep.py new file mode 100644 index 00000000000..1151733a788 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/deep.py @@ -0,0 +1,23 @@ + +def f1(arg): + return arg + +def f2(arg): + return f1(arg) + +def f3(arg): + return f2(arg) + +def f4(arg): + return f3(arg) + +def f5(arg): + return f4(arg) + +def f6(arg): + return f5(arg) + +x = f6(SOURCE) + +SINK(x) + diff --git a/python/ql/test/library-tests/taint/config/module.py b/python/ql/test/library-tests/taint/config/module.py new file mode 100644 index 00000000000..437ef3bb239 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/module.py @@ -0,0 +1,11 @@ + + +dangerous = SOURCE +safe = "safe" + +def dangerous_func(): + return SOURCE + + +safe2 = SOURCE +safe2 = "safe" diff --git a/python/ql/test/library-tests/taint/config/rockpaperscissors.py b/python/ql/test/library-tests/taint/config/rockpaperscissors.py new file mode 100644 index 00000000000..8d73e090e33 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/rockpaperscissors.py @@ -0,0 +1,32 @@ + + +def rock(arg): + "SCISSORS are vulnerable" + +def paper(arg): + "ROCK is vulnerable" + +def scissors(arg): + "PAPER is vulnerable" + +def test1(): + rock(SCISSORS) + +def test2(): + paper(ROCK) + +def test3(): + x = ROCK + y = x.prev() #scissors + scissors(y) + +def test4(): + x = ROCK + y = x.prev().prev() # paper + scissors(y) + +def test5(): + x = SCISSORS + y = x.prev() # paper + paper(x) + paper(y) diff --git a/python/ql/test/library-tests/taint/config/sanitizer.py b/python/ql/test/library-tests/taint/config/sanitizer.py new file mode 100644 index 00000000000..9238f66fb32 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/sanitizer.py @@ -0,0 +1,36 @@ + +#Sanitizer functions +def isEscapedSql(arg): pass + +def isValidCommand(arg): pass + + +def sql_inject1(): + x = user_input() + if isEscapedSql(x): + sql_query(x) # Safe + else: + sql_query(x) # DANGEROUS + +def command_inject1(): + x = user_input() + if isValidCommand(x): + os_command(x) # Safe + else: + os_command(x) # DANGEROUS + + +def sql_inject2(): + x = user_input() + if notASanitizer(x): + sql_query(x) # DANGEROUS + else: + sql_query(x) # DANGEROUS + +def command_inject2(): + x = user_input() + if notASanitizer(x): + os_command(x) # DANGEROUS + else: + os_command(x) # DANGEROUS + diff --git a/python/ql/test/library-tests/taint/config/test.py b/python/ql/test/library-tests/taint/config/test.py new file mode 100644 index 00000000000..8ca23aff606 --- /dev/null +++ b/python/ql/test/library-tests/taint/config/test.py @@ -0,0 +1,215 @@ + +def test1(): + SINK(SOURCE) + +def test2(): + s = SOURCE + SINK(s) + +def source(): + return SOURCE + +def sink(arg): + SINK(arg) + +def test3(): + t = source() + SINK(t) + +def test4(): + t = SOURCE + sink(t) + +def test5(): + t = source() + sink(t) + +def test6(cond): + if cond: + t = "Safe" + else: + t = SOURCE + if cond: + SINK(t) + +def test7(cond): + if cond: + t = SOURCE + else: + t = "Safe" + if cond: + SINK(t) + +def source2(arg): + return source(arg) + +def sink2(arg): + sink(arg) + +def sink3(cond, arg): + if cond: + sink(arg) + +def test8(cond): + t = source2() + sink2(t) + +#False positive +def test9(cond): + if cond: + t = "Safe" + else: + t = SOURCE + sink3(cond, t) + +def test10(cond): + if cond: + t = SOURCE + else: + t = "Safe" + sink3(cond, t) + +def hub(arg): + return arg + +def test11(): + t = SOURCE + t = hub(t) + SINK(t) + +def test12(): + t = "safe" + t = hub(t) + SINK(t) + +import module + +def test13(): + t = module.dangerous + SINK(t) + +def test14(): + t = module.safe + SINK(t) + +def test15(): + t = module.safe2 + SINK(t) + +def test16(): + t = module.dangerous_func() + SINK(t) + +class C(object): pass + +def x_sink(arg): + SINK(arg.x) + +def test17(): + t = C() + t.x = module.dangerous + SINK(t.x) + +def test18(): + t = C() + t.x = module.dangerous + t = hub(t) + x_sink(t) + +def test19(): + t = CUSTOM_SOURCE + t = hub(TAINT_FROM_ARG(t)) + CUSTOM_SINK(t) + +def test20(cond): + if cond: + t = CUSTOM_SOURCE + else: + t = SOURCE + if cond: + CUSTOM_SINK(t) + else: + SINK(t) + +def test21(cond): + if cond: + t = CUSTOM_SOURCE + else: + t = SOURCE + if not cond: + CUSTOM_SINK(t) + else: + SINK(t) + +def test22(cond): + if cond: + t = CUSTOM_SOURCE + else: + t = SOURCE + t = TAINT_FROM_ARG(t) + if cond: + CUSTOM_SINK(t) + else: + SINK(t) + +from module import dangerous as unsafe +SINK(unsafe) + +def test23(): + with SOURCE as t: + SINK(t) + +def test24(): + s = SOURCE + SANITIZE(s) + SINK(s) + +def test_update_extend(x, y): + l = [SOURCE] + d = {"key" : SOURCE} + x.extend(l) + y.update(d) + SINK(x[0]) + SINK(y["key"]) + l2 = list(l) + d2 = dict(d) + +def test_truth(): + t = SOURCE + if t: + SINK(t) + else: + SINK(t) + if not t: + SINK(t) + else: + SINK(t) + +def test_early_exit(): + t = FALSEY + if not t: + return + t + +def flow_through_type_test_if_no_class(): + t = SOURCE + if isinstance(t, str): + SINK(t) + else: + SINK(t) + +def flow_in_iteration(): + t = ITERABLE_SOURCE + for i in t: + i + return i + +def flow_in_generator(): + seq = [SOURCE] + for i in seq: + yield i + +def flow_from_generator(): + for x in flow_in_generator(): + SINK(x) +