Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d31e55f

Browse files
committed
Python taint-tracking: Avoid ambiguous flows through calls. Fix up tests.
1 parent 78ce196 commit d31e55f

8 files changed

Lines changed: 88 additions & 52 deletions

File tree

python/ql/src/Security/CWE-209/StackTraceExposure.ql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,6 @@ class StackTraceExposureConfiguration extends TaintTracking::Configuration {
3333
from StackTraceExposureConfiguration config, TaintedPathSource src, TaintedPathSink sink
3434
where config.hasFlowPath(src, sink)
3535
select sink.getSink(), src, sink, "$@ may be exposed to an external user", src.getSource(), "Error information"
36+
37+
38+

python/ql/src/Security/CWE-327/BrokenCryptoAlgorithm.ql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ class BrokenCryptoConfiguration extends TaintTracking::Configuration {
1616

1717
BrokenCryptoConfiguration() { this = "Broken crypto configuration" }
1818

19-
override predicate isSource(TaintTracking::Source source) { source instanceof SensitiveDataSource }
19+
override predicate isSource(TaintTracking::Source source) {
20+
source instanceof SensitiveDataSource
21+
}
2022

2123
override predicate isSink(TaintTracking::Sink sink) {
2224
sink instanceof WeakCryptoSink

python/ql/src/semmle/python/dataflow/Implementation.qll

Lines changed: 65 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import python
22
import semmle.python.security.TaintTracking
33
private import semmle.python.objects.ObjectInternal
44
private import semmle.python.pointsto.Filters as Filters
5+
private import semmle.python.dataflow.Presentation
56

67
newtype TTaintTrackingContext =
78
TNoParam()
@@ -175,24 +176,6 @@ class TaintTrackingNode extends TTaintTrackingNode {
175176
result = this.getNode().getLocation()
176177
}
177178

178-
TaintTrackingNode getASuccessor() {
179-
result = this.getASuccessor(_)
180-
}
181-
182-
TaintTrackingNode getASuccessor(string edgeLabel) {
183-
this.isVisible() and
184-
result = this.getAnUnlabeledSuccessor*().getALabeledSuccessor(edgeLabel)
185-
}
186-
187-
private TaintTrackingNode getAnUnlabeledSuccessor() {
188-
this.getConfiguration().(TaintTrackingImplementation).flowStep(this, result, "")
189-
}
190-
191-
private TaintTrackingNode getALabeledSuccessor(string label) {
192-
not label = "" and
193-
this.getConfiguration().(TaintTrackingImplementation).flowStep(this, result, label)
194-
}
195-
196179
predicate isSource() {
197180
this.getConfiguration().(TaintTrackingImplementation).isPathSource(this)
198181
}
@@ -210,13 +193,21 @@ class TaintTrackingNode extends TTaintTrackingNode {
210193
result = this.getCfgNode().getNode()
211194
}
212195

213-
/** Holds if this node should be presented to the user as part of a path */
214-
predicate isVisible() {
215-
this.isSource() or
216-
exists(string label |
217-
not label = "" |
218-
this.getConfiguration().(TaintTrackingImplementation).flowStep(_, this, label)
219-
)
196+
TaintTrackingNode getASuccessor(string edgeLabel) {
197+
result = this.unlabeledSuccessor*().labeledSuccessor(edgeLabel)
198+
}
199+
200+
TaintTrackingNode getASuccessor() {
201+
result = this.getASuccessor(_)
202+
}
203+
204+
private TaintTrackingNode unlabeledSuccessor() {
205+
this.getConfiguration().(TaintTrackingImplementation).flowStep(this, result, "")
206+
}
207+
208+
private TaintTrackingNode labeledSuccessor(string label) {
209+
not label = "" and
210+
this.getConfiguration().(TaintTrackingImplementation).flowStep(this, result, label)
220211
}
221212

222213
}
@@ -231,7 +222,7 @@ class TaintTrackingImplementation extends string {
231222
predicate hasFlowPath(TaintTrackingNode source, TaintTrackingNode sink) {
232223
this.isPathSource(source) and
233224
this.isPathSink(sink) and
234-
sink = source.getASuccessor*()
225+
this.flowReaches(source, sink)
235226
}
236227

237228
predicate flowSource(DataFlow::Node node, TaintTrackingContext context, AttributePath path, TaintKind kind) {
@@ -282,6 +273,15 @@ class TaintTrackingImplementation extends string {
282273
)
283274
}
284275

276+
predicate flowReaches(TaintTrackingNode src, TaintTrackingNode dest) {
277+
this = src.getConfiguration() and dest = src
278+
or
279+
exists(TaintTrackingNode mid |
280+
this.flowReaches(src, mid) and
281+
this.flowStep(mid, dest, _)
282+
)
283+
}
284+
285285
predicate flowBarrier(DataFlow::Node node, TaintKind kind) {
286286
this.(TaintTracking::Configuration).isBarrier(node, kind)
287287
or
@@ -397,6 +397,8 @@ class TaintTrackingImplementation extends string {
397397
or
398398
this.returnFlowStep(src, node, context, path, kind, edgeLabel)
399399
or
400+
this.callFlowStep(src, node, context, path, kind, edgeLabel)
401+
or
400402
this.iterationStep(src, node, context, path, kind, edgeLabel)
401403
or
402404
this.yieldStep(src, node, context, path, kind, edgeLabel)
@@ -508,27 +510,39 @@ class TaintTrackingImplementation extends string {
508510
// // TO DO... named parameters
509511
//}
510512

513+
/* If the return value is tainted without context, then it always flows back to the caller */
511514
pragma [noinline]
512515
predicate returnFlowStep(TaintTrackingNode src, DataFlow::Node node, TaintTrackingContext context, AttributePath path, TaintKind kind, string edgeLabel) {
513-
exists(CallNode call, PythonFunctionObjectInternal pyfunc, TaintTrackingContext callee, DataFlow::Node retval |
514-
this.callContexts(call, pyfunc, context, callee) and
515-
src = TTaintTrackingNode_(retval, callee, path, kind, this) and
516+
exists(CallNode call, PythonFunctionObjectInternal pyfunc, DataFlow::Node retval |
517+
pyfunc.getACall() = call and
518+
context = TNoParam() and
519+
src = TTaintTrackingNode_(retval, TNoParam(), path, kind, this) and
516520
node.asCfgNode() = call and
517521
retval.asCfgNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode()
518522
) and
519523
edgeLabel = "return"
520524
}
521525

526+
/* Avoid taint flow from return value to caller as it can produce imprecise flow graphs
527+
* Step directly from tainted argument to call result.
528+
*/
522529
pragma [noinline]
523-
predicate callContexts(CallNode call, PythonFunctionObjectInternal pyfunc, TaintTrackingContext caller, TaintTrackingContext callee) {
530+
predicate callFlowStep(TaintTrackingNode src, DataFlow::Node node, TaintTrackingContext context, AttributePath path, TaintKind kind, string edgeLabel) {
531+
exists(CallNode call, PythonFunctionObjectInternal pyfunc, TaintTrackingContext callee, DataFlow::Node retval, TaintTrackingNode retnode |
532+
this.callContexts(call, src, pyfunc, context, callee) and
533+
retnode = TTaintTrackingNode_(retval, callee, path, kind, this) and
534+
node.asCfgNode() = call and
535+
retval.asCfgNode() = any(Return ret | ret.getScope() = pyfunc.getScope()).getValue().getAFlowNode()
536+
) and
537+
edgeLabel = "call"
538+
}
539+
540+
pragma [noinline]
541+
predicate callContexts(CallNode call, TaintTrackingNode argnode, PythonFunctionObjectInternal pyfunc, TaintTrackingContext caller, TaintTrackingContext callee) {
524542
exists(int arg, TaintKind callerKind, AttributePath callerPath |
525-
this.callWithTaintedArgument(_, call, caller, pyfunc, arg, callerPath, callerKind) and
543+
this.callWithTaintedArgument(argnode, call, caller, pyfunc, arg, callerPath, callerKind) and
526544
callee = TParamContext(callerKind, callerPath, arg)
527545
)
528-
or
529-
pyfunc.getACall() = call and
530-
callee = TNoParam() and
531-
caller = TNoParam()
532546
}
533547

534548
predicate callWithTaintedArgument(TaintTrackingNode src, CallNode call, TaintTrackingContext caller, CallableValue pyfunc, int arg, AttributePath path, TaintKind kind) {
@@ -633,6 +647,8 @@ class TaintTrackingImplementation extends string {
633647
this.taintedArgument(src, defn, context, path, kind)
634648
or
635649
this.taintedExceptionCapture(src, defn, context, path, kind)
650+
or
651+
this.taintedScopeEntryDefinition(src, defn, context, path, kind)
636652
}
637653

638654
pragma [noinline]
@@ -729,6 +745,14 @@ class TaintTrackingImplementation extends string {
729745
)
730746
}
731747

748+
pragma [noinline]
749+
predicate taintedScopeEntryDefinition(TaintTrackingNode src, ScopeEntryDefinition defn, TaintTrackingContext context, AttributePath path, TaintKind kind) {
750+
exists(EssaVariable var |
751+
BaseFlow::scope_entry_value_transfer_from_earlier(var, _, defn, _) and
752+
this.taintedDefinition(src, var.getDefinition(), context, path, kind)
753+
)
754+
}
755+
732756
predicate moduleAttributeTainted(ModuleValue m, string name, TaintTrackingNode taint) {
733757
exists(DataFlow::Node srcnode, EssaVariable var |
734758
taint = TTaintTrackingNode_(srcnode, TNoParam(), _, _, this) and
@@ -769,6 +793,12 @@ class TaintTrackingImplementation extends string {
769793
)
770794
}
771795

796+
predicate crossCallFlow(TaintTrackingNode taintedArg, TaintTrackingNode call) {
797+
this.parameterStep(taintedArg, _, _, _, _, _) and
798+
this.flowReaches(taintedArg, call) and
799+
call.getNode().asCfgNode().(CallNode).getArg(_) = taintedArg.getNode().asCfgNode()
800+
}
801+
772802
}
773803

774804
/* Backwards compatibility with config-less taint-tracking */

python/ql/src/semmle/python/security/Crypto.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,4 +192,16 @@ module Cryptography {
192192

193193
}
194194

195+
private class CipherConfig extends TaintTracking::Configuration {
196+
197+
CipherConfig() { this = "Crypto cipher config" }
198+
199+
override predicate isSource(TaintTracking::Source source) {
200+
source instanceof Pycrypto::CipherInstanceSource
201+
or
202+
source instanceof Cryptography::CipherSource
203+
}
204+
205+
}
206+
195207

python/ql/src/semmle/python/security/TaintTracking.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ abstract class TaintSource extends @py_flow_node {
431431
exists(TaintedNode src, TaintedNode tsink |
432432
src = this.getATaintNode() and
433433
src.getTaintKind() = srckind and
434-
src.getASuccessor*() = tsink and
434+
src.getConfiguration().(TaintTrackingImplementation).flowReaches(src, tsink) and
435435
this.isSourceOf(srckind, _) and
436436
sink = tsink.getCfgNode() and
437437
sink.sinks(tsink.getTaintKind()) and
@@ -624,7 +624,7 @@ class TaintedPathSource extends TaintTrackingNode {
624624

625625
/** Holds if taint can flow from this source to sink `sink` */
626626
final predicate flowsTo(TaintedPathSink sink) {
627-
this.getASuccessor*() = sink
627+
this.getConfiguration().(TaintTrackingImplementation).flowReaches(this, sink)
628628
}
629629

630630
DataFlow::Node getSource() {
@@ -676,7 +676,7 @@ module DataFlow {
676676
private predicate hasFlowPath(TaintedNode source, TaintedNode sink) {
677677
this.isSource(source.getCfgNode()) and
678678
this.isSink(sink.getCfgNode()) and
679-
source.getASuccessor*() = sink
679+
source.getConfiguration().(TaintTrackingImplementation).flowReaches(source, sink)
680680
}
681681

682682
predicate hasFlow(ControlFlowNode source, ControlFlowNode sink) {
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
edges
22
| test.py:33:15:33:36 | exception info | test.py:34:29:34:31 | exception info |
3-
| test.py:34:29:34:31 | exception info | test.py:36:18:36:20 | exception info |
4-
| test.py:36:18:36:20 | exception info | test.py:37:25:37:27 | exception info |
5-
| test.py:37:12:37:27 | exception info | test.py:34:16:34:32 | exception info |
6-
| test.py:37:25:37:27 | exception info | test.py:37:12:37:27 | exception info |
3+
| test.py:34:29:34:31 | exception info | test.py:34:16:34:32 | exception info |
74
#select
85
| test.py:16:16:16:37 | Attribute() | test.py:16:16:16:37 | exception info | test.py:16:16:16:37 | exception info | $@ may be exposed to an external user | test.py:16:16:16:37 | Attribute() | Error information |
96
| test.py:34:16:34:32 | format_error() | test.py:33:15:33:36 | exception info | test.py:34:16:34:32 | exception info | $@ may be exposed to an external user | test.py:33:15:33:36 | Attribute() | Error information |

python/ql/test/query-tests/Security/CWE-327/TestNode.expected

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,3 @@ WARNING: Predicate getNode has been deprecated and may be removed in future (Tes
66
| Taint cryptography.encryptor.RC4 | test_cryptography.py:6:17:6:34 | test_cryptography.py:6 | test_cryptography.py:6:17:6:34 | Attribute() | |
77
| Taint cryptography.encryptor.RC4 | test_cryptography.py:7:12:7:20 | test_cryptography.py:7 | test_cryptography.py:7:12:7:20 | encryptor | |
88
| Taint cryptography.encryptor.RC4 | test_cryptography.py:7:42:7:50 | test_cryptography.py:7 | test_cryptography.py:7:42:7:50 | encryptor | |
9-
| Taint sensitive.data | test_cryptography.py:4:17:4:28 | test_cryptography.py:4 | test_cryptography.py:4:17:4:28 | get_password | |
10-
| Taint sensitive.data | test_cryptography.py:4:17:4:30 | test_cryptography.py:4 | test_cryptography.py:4:17:4:30 | get_password() | |
11-
| Taint sensitive.data | test_cryptography.py:7:29:7:37 | test_cryptography.py:7 | test_cryptography.py:7:29:7:37 | dangerous | |
12-
| Taint sensitive.data | test_pycrypto.py:4:17:4:28 | test_pycrypto.py:4 | test_pycrypto.py:4:17:4:28 | get_password | |
13-
| Taint sensitive.data | test_pycrypto.py:4:17:4:30 | test_pycrypto.py:4 | test_pycrypto.py:4:17:4:30 | get_password() | |
14-
| Taint sensitive.data | test_pycrypto.py:6:27:6:35 | test_pycrypto.py:6 | test_pycrypto.py:6:27:6:35 | dangerous | |
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
edges
22
| test.py:7:22:7:33 | dict of externally controlled string | test.py:7:22:7:51 | externally controlled string |
33
| test.py:7:22:7:51 | externally controlled string | test.py:8:21:8:26 | externally controlled string |
4-
| test.py:15:17:15:28 | dict of externally controlled string | test.py:15:17:15:42 | externally controlled string |
5-
| test.py:15:17:15:42 | externally controlled string | test.py:17:13:17:21 | externally controlled string |
64
#select
7-
| test.py:8:21:8:26 | flask.redirect | test.py:7:22:7:33 | dict of externally controlled string | test.py:8:21:8:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:7:22:7:33 | flask.request.args | a user-provided value |
5+
| test.py:8:21:8:26 | target | test.py:7:22:7:33 | dict of externally controlled string | test.py:8:21:8:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:7:22:7:33 | Attribute | a user-provided value |

0 commit comments

Comments
 (0)