Skip to content

Commit e266ced

Browse files
authored
Merge pull request #4700 from RasmusWL/python-add-code-injection-FP
Approved by tausbn
2 parents 6017f25 + e6319e5 commit e266ced

File tree

19 files changed

+349
-98
lines changed

19 files changed

+349
-98
lines changed

python/ql/src/semmle/python/Flow.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,7 @@ class ListNode extends SequenceNode {
738738
}
739739
}
740740

741+
/** A control flow node corresponding to a set expression, such as `{ 1, 3, 5, 7, 9 }` */
741742
class SetNode extends ControlFlowNode {
742743
SetNode() { toAst(this) instanceof Set }
743744

@@ -771,6 +772,25 @@ class DictNode extends ControlFlowNode {
771772
}
772773
}
773774

775+
/**
776+
* A control flow node corresponding to an iterable literal. Currently does not include
777+
* dictionaries, use `DictNode` directly instead.
778+
*/
779+
class IterableNode extends ControlFlowNode {
780+
IterableNode() {
781+
this instanceof SequenceNode
782+
or
783+
this instanceof SetNode
784+
}
785+
786+
/** Gets the control flow node for an element of this iterable. */
787+
ControlFlowNode getAnElement() {
788+
result = this.(SequenceNode).getAnElement()
789+
or
790+
result = this.(SetNode).getAnElement()
791+
}
792+
}
793+
774794
private AstNode assigned_value(Expr lhs) {
775795
/* lhs = result */
776796
exists(Assign a | a.getATarget() = lhs and result = a.getValue())
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/** Provides commonly used BarrierGuards. */
2+
3+
private import python
4+
private import semmle.python.dataflow.new.DataFlow
5+
6+
/** A validation of unknown node by comparing with a constant string value. */
7+
class StringConstCompare extends DataFlow::BarrierGuard, CompareNode {
8+
ControlFlowNode checked_node;
9+
boolean safe_branch;
10+
11+
StringConstCompare() {
12+
exists(StrConst str_const, Cmpop op |
13+
op = any(Eq eq) and safe_branch = true
14+
or
15+
op = any(NotEq ne) and safe_branch = false
16+
|
17+
this.operands(str_const.getAFlowNode(), op, checked_node)
18+
or
19+
this.operands(checked_node, op, str_const.getAFlowNode())
20+
)
21+
or
22+
exists(IterableNode str_const_iterable, Cmpop op |
23+
op = any(In in_) and safe_branch = true
24+
or
25+
op = any(NotIn ni) and safe_branch = false
26+
|
27+
forall(ControlFlowNode elem | elem = str_const_iterable.getAnElement() |
28+
elem.getNode() instanceof StrConst
29+
) and
30+
this.operands(checked_node, op, str_const_iterable)
31+
)
32+
}
33+
34+
override predicate checks(ControlFlowNode node, boolean branch) {
35+
node = checked_node and branch = safe_branch
36+
}
37+
}

python/ql/src/semmle/python/security/dataflow/CodeInjection.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import semmle.python.dataflow.new.DataFlow
88
import semmle.python.dataflow.new.TaintTracking
99
import semmle.python.Concepts
1010
import semmle.python.dataflow.new.RemoteFlowSources
11+
import semmle.python.dataflow.new.BarrierGuards
1112

1213
/**
1314
* A taint-tracking configuration for detecting code injection vulnerabilities.
@@ -18,4 +19,8 @@ class CodeInjectionConfiguration extends TaintTracking::Configuration {
1819
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
1920

2021
override predicate isSink(DataFlow::Node sink) { sink = any(CodeExecution e).getCode() }
22+
23+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
24+
guard instanceof StringConstCompare
25+
}
2126
}

python/ql/src/semmle/python/security/dataflow/CommandInjection.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import semmle.python.dataflow.new.DataFlow
88
import semmle.python.dataflow.new.TaintTracking
99
import semmle.python.Concepts
1010
import semmle.python.dataflow.new.RemoteFlowSources
11+
import semmle.python.dataflow.new.BarrierGuards
1112

1213
/**
1314
* A taint-tracking configuration for detecting command injection vulnerabilities.
@@ -48,4 +49,8 @@ class CommandInjectionConfiguration extends TaintTracking::Configuration {
4849
// https://github.com/python/cpython/blob/fa7ce080175f65d678a7d5756c94f82887fc9803/Lib/subprocess.py#L341
4950
not sink.getScope().getEnclosingModule().getName() in ["os", "subprocess", "platform", "popen2"]
5051
}
52+
53+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
54+
guard instanceof StringConstCompare
55+
}
5156
}

python/ql/src/semmle/python/security/dataflow/PathInjection.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import semmle.python.dataflow.new.TaintTracking2
3131
import semmle.python.Concepts
3232
import semmle.python.dataflow.new.RemoteFlowSources
3333
import ChainedConfigs12
34+
import semmle.python.dataflow.new.BarrierGuards
3435

3536
// ---------------------------------------------------------------------------
3637
// Case 1. The path is never normalized.
@@ -46,6 +47,10 @@ class PathNotNormalizedConfiguration extends TaintTracking::Configuration {
4647
}
4748

4849
override predicate isSanitizer(DataFlow::Node node) { node instanceof Path::PathNormalization }
50+
51+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
52+
guard instanceof StringConstCompare
53+
}
4954
}
5055

5156
/**
@@ -68,6 +73,10 @@ class FirstNormalizationConfiguration extends TaintTracking::Configuration {
6873
override predicate isSink(DataFlow::Node sink) { sink instanceof Path::PathNormalization }
6974

7075
override predicate isSanitizerOut(DataFlow::Node node) { node instanceof Path::PathNormalization }
76+
77+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
78+
guard instanceof StringConstCompare
79+
}
7180
}
7281

7382
/** Configuration to find paths from normalizations to sinks that do not go through a check. */
@@ -82,6 +91,8 @@ class NormalizedPathNotCheckedConfiguration extends TaintTracking2::Configuratio
8291

8392
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
8493
guard instanceof Path::SafeAccessCheck
94+
or
95+
guard instanceof StringConstCompare
8596
}
8697
}
8798

python/ql/src/semmle/python/security/dataflow/ReflectedXSS.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import semmle.python.dataflow.new.DataFlow
88
import semmle.python.dataflow.new.TaintTracking
99
import semmle.python.Concepts
1010
import semmle.python.dataflow.new.RemoteFlowSources
11+
import semmle.python.dataflow.new.BarrierGuards
1112

1213
/**
1314
* A taint-tracking configuration for detecting reflected server-side cross-site
@@ -24,4 +25,8 @@ class ReflectedXssConfiguration extends TaintTracking::Configuration {
2425
sink = response.getBody()
2526
)
2627
}
28+
29+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
30+
guard instanceof StringConstCompare
31+
}
2732
}

python/ql/src/semmle/python/security/dataflow/SqlInjection.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import semmle.python.dataflow.new.DataFlow
88
import semmle.python.dataflow.new.TaintTracking
99
import semmle.python.Concepts
1010
import semmle.python.dataflow.new.RemoteFlowSources
11+
import semmle.python.dataflow.new.BarrierGuards
1112

1213
/**
1314
* A taint-tracking configuration for detecting SQL injection vulnerabilities.
@@ -18,4 +19,8 @@ class SQLInjectionConfiguration extends TaintTracking::Configuration {
1819
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
1920

2021
override predicate isSink(DataFlow::Node sink) { sink = any(SqlExecution e).getSql() }
22+
23+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
24+
guard instanceof StringConstCompare
25+
}
2126
}

python/ql/src/semmle/python/security/dataflow/UnsafeDeserialization.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import semmle.python.dataflow.new.DataFlow
88
import semmle.python.dataflow.new.TaintTracking
99
import semmle.python.Concepts
1010
import semmle.python.dataflow.new.RemoteFlowSources
11+
import semmle.python.dataflow.new.BarrierGuards
1112

1213
/**
1314
* A taint-tracking configuration for detecting arbitrary code execution
@@ -24,4 +25,8 @@ class UnsafeDeserializationConfiguration extends TaintTracking::Configuration {
2425
sink = d.getAnInput()
2526
)
2627
}
28+
29+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
30+
guard instanceof StringConstCompare
31+
}
2732
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
| test_string_const_compare.py:16 | ok | test_eq | ts |
2+
| test_string_const_compare.py:18 | ok | test_eq | ts |
3+
| test_string_const_compare.py:20 | ok | test_eq | ts |
4+
| test_string_const_compare.py:27 | ok | test_eq_unsafe | ts |
5+
| test_string_const_compare.py:29 | ok | test_eq_unsafe | ts |
6+
| test_string_const_compare.py:35 | fail | test_eq_with_or | ts |
7+
| test_string_const_compare.py:37 | ok | test_eq_with_or | ts |
8+
| test_string_const_compare.py:43 | ok | test_non_eq1 | ts |
9+
| test_string_const_compare.py:45 | ok | test_non_eq1 | ts |
10+
| test_string_const_compare.py:51 | ok | test_non_eq2 | ts |
11+
| test_string_const_compare.py:53 | fail | test_non_eq2 | ts |
12+
| test_string_const_compare.py:59 | ok | test_in_list | ts |
13+
| test_string_const_compare.py:61 | ok | test_in_list | ts |
14+
| test_string_const_compare.py:67 | ok | test_in_tuple | ts |
15+
| test_string_const_compare.py:69 | ok | test_in_tuple | ts |
16+
| test_string_const_compare.py:75 | ok | test_in_set | ts |
17+
| test_string_const_compare.py:77 | ok | test_in_set | ts |
18+
| test_string_const_compare.py:83 | ok | test_in_unsafe1 | ts |
19+
| test_string_const_compare.py:85 | ok | test_in_unsafe1 | ts |
20+
| test_string_const_compare.py:91 | ok | test_in_unsafe2 | ts |
21+
| test_string_const_compare.py:93 | ok | test_in_unsafe2 | ts |
22+
| test_string_const_compare.py:99 | ok | test_not_in1 | ts |
23+
| test_string_const_compare.py:101 | ok | test_not_in1 | ts |
24+
| test_string_const_compare.py:107 | ok | test_not_in2 | ts |
25+
| test_string_const_compare.py:109 | fail | test_not_in2 | ts |
26+
| test_string_const_compare.py:119 | fail | test_eq_thorugh_func | ts |
27+
| test_string_const_compare.py:121 | ok | test_eq_thorugh_func | ts |
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import experimental.dataflow.tainttracking.TestTaintLib
2+
import semmle.python.dataflow.new.BarrierGuards
3+
4+
class CustomSanitizerOverrides extends TestTaintTrackingConfiguration {
5+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
6+
guard instanceof StringConstCompare
7+
}
8+
}

0 commit comments

Comments
 (0)