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

Skip to content

Commit f88cc3c

Browse files
committed
Python: Use custom PathGraph
1 parent 8ce5f41 commit f88cc3c

2 files changed

Lines changed: 132 additions & 39 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* This defines a `PathGraph` where sinks from `TaintTracking::Configuration`s are identified with
3+
* sources from `TaintTracking2::Configuration`s if they represent the same `ControlFlowNode`.
4+
*
5+
* Paths are then connected appropriately.
6+
*/
7+
8+
import python
9+
import experimental.dataflow.DataFlow
10+
import experimental.dataflow.DataFlow2
11+
import experimental.dataflow.TaintTracking
12+
import experimental.dataflow.TaintTracking2
13+
14+
/**
15+
* A `ControlFlowNode` that appears as a sink in Config1 and a source in Config2.
16+
*/
17+
private predicate crossoverNode(ControlFlowNode n) {
18+
exists(DataFlow::Node n1, DataFlow2::Node n2 |
19+
any(TaintTracking::Configuration t1).isSink(n1) and
20+
any(TaintTracking2::Configuration t2).isSource(n2) and
21+
n = n1.asCfgNode() and
22+
n = n2.asCfgNode()
23+
)
24+
}
25+
26+
/**
27+
* A new type which represents the union of the two sets of nodes.
28+
*/
29+
private newtype TCustomPathNode =
30+
Config1Node(DataFlow::PathNode node1) { not crossoverNode(node1.getNode().asCfgNode()) } or
31+
Config2Node(DataFlow2::PathNode node1) { not crossoverNode(node1.getNode().asCfgNode()) } or
32+
CrossoverNode(ControlFlowNode e) { crossoverNode(e) }
33+
34+
/**
35+
* A class representing the set of all the path nodes in either config.
36+
*/
37+
class CustomPathNode extends TCustomPathNode {
38+
/** Gets the PathNode if it is in Config1. */
39+
DataFlow::PathNode asNode1() {
40+
this = Config1Node(result) or this = CrossoverNode(result.getNode().asCfgNode())
41+
}
42+
43+
/** Gets the PathNode if it is in Config2. */
44+
DataFlow2::PathNode asNode2() {
45+
this = Config2Node(result) or this = CrossoverNode(result.getNode().asCfgNode())
46+
}
47+
48+
predicate hasLocationInfo(
49+
string filepath, int startline, int startcolumn, int endline, int endcolumn
50+
) {
51+
asNode1().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
52+
or
53+
asNode2().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
54+
}
55+
56+
string toString() {
57+
result = asNode1().toString()
58+
or
59+
result = asNode2().toString()
60+
}
61+
}
62+
63+
/** Holds if `(a,b)` is an edge in the graph of data flow path explanations. */
64+
query predicate edges(CustomPathNode a, CustomPathNode b) {
65+
// Edge is in Config1 graph
66+
DataFlow::PathGraph::edges(a.asNode1(), b.asNode1())
67+
or
68+
// Edge is in Config2 graph
69+
DataFlow2::PathGraph::edges(a.asNode2(), b.asNode2())
70+
}
71+
72+
/** Holds if `n` is a node in the graph of data flow path explanations. */
73+
query predicate nodes(CustomPathNode n, string key, string val) {
74+
// Node is in Config1 graph
75+
DataFlow::PathGraph::nodes(n.asNode1(), key, val)
76+
or
77+
// Node is in Config2 graph
78+
DataFlow2::PathGraph::nodes(n.asNode2(), key, val)
79+
}
Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
/**
2+
* The query detects the case where a path is not both normalized and _afterwards_ checked.
3+
*
4+
* It does so by dividing the problematic situation into two cases:
5+
* 1. The path is never normalized.
6+
* This is easily detected by using normalization as a sanitizer.
7+
*
8+
* 2. The path is normalized at least once, but never checked afterwards.
9+
* This is detected by finding the first normalization and then ensure that
10+
* no checks happen after. Since we start from the first normalization,
11+
* we know that the absence of checks means that no normalization has a
12+
* chek after it. (No checks after a second normalization would be ok if
13+
* there was a check between the first and the second.)
14+
*
15+
* Note that one could make the dual split on whether the path is ever checked. This does
16+
* not work as nicely, however, since checking is modelled as a `BarrierGuard` rather than
17+
* as a `Sanitizer`. That means that only some paths out of a check will be removed, and so
18+
* identifying the last check is not possible simply by finding a path from it to a sink.
19+
*
220
* @name Uncontrolled data used in path expression
321
* @description Accessing paths influenced by users can allow an attacker to access unexpected resources.
422
* @kind path-problem
@@ -23,11 +41,14 @@ import experimental.dataflow.TaintTracking
2341
import experimental.dataflow.TaintTracking2
2442
import experimental.semmle.python.Concepts
2543
import experimental.dataflow.RemoteFlowSources
26-
import DataFlow::PathGraph
44+
import ChainedConfigs12
2745

46+
// ---------------------------------------------------------------------------
47+
// Case 1. The path is never normalized.
48+
// ---------------------------------------------------------------------------
2849
/** Configuration to find paths from sources to sinks that contain no normalization. */
29-
class UnNormalizedPathConfiguration extends TaintTracking::Configuration {
30-
UnNormalizedPathConfiguration() { this = "UnNormalizedPathConfiguration" }
50+
class PathNotNormalizedConfiguration extends TaintTracking::Configuration {
51+
PathNotNormalizedConfiguration() { this = "PathNotNormalizedConfiguration" }
3152

3253
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
3354

@@ -38,8 +59,15 @@ class UnNormalizedPathConfiguration extends TaintTracking::Configuration {
3859
override predicate isSanitizer(DataFlow::Node node) { node instanceof PathNormalization }
3960
}
4061

62+
predicate pathNotNormalized(CustomPathNode source, CustomPathNode sink) {
63+
any(PathNotNormalizedConfiguration config).hasFlowPath(source.asNode1(), sink.asNode1())
64+
}
65+
66+
// ---------------------------------------------------------------------------
67+
// Case 2. The path is normalized at least once, but never checked afterwards.
68+
// ---------------------------------------------------------------------------
4169
/** Configuration to find paths from sources to normalizations that contain no prior normalizations. */
42-
class FirstNormalizationConfiguration extends TaintTracking2::Configuration {
70+
class FirstNormalizationConfiguration extends TaintTracking::Configuration {
4371
FirstNormalizationConfiguration() { this = "FirstNormalizationConfiguration" }
4472

4573
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
@@ -49,26 +77,11 @@ class FirstNormalizationConfiguration extends TaintTracking2::Configuration {
4977
override predicate isSanitizerOut(DataFlow::Node node) { node instanceof PathNormalization }
5078
}
5179

52-
class FirstNormalization extends DataFlow2::PathNode {
53-
DataFlow::Node sourceNode;
54-
55-
FirstNormalization() {
56-
exists(FirstNormalizationConfiguration conf, DataFlow2::PathNode source |
57-
sourceNode = source.getNode() and
58-
conf.hasFlowPath(source, this)
59-
)
60-
}
61-
62-
DataFlow::Node getSourceNode() { result = sourceNode }
63-
}
64-
6580
/** Configuration to find paths from normalizations to sinks that do not go through a check. */
66-
class UncheckedNormalizedConfiguration extends TaintTracking::Configuration {
67-
UncheckedNormalizedConfiguration() { this = "UncheckedNormalizedConfiguration" }
81+
class NormalizedPathNotCheckedConfiguration extends TaintTracking2::Configuration {
82+
NormalizedPathNotCheckedConfiguration() { this = "NormalizedPathNotCheckedConfiguration" }
6883

69-
override predicate isSource(DataFlow::Node source) {
70-
source = any(FirstNormalization n).getNode()
71-
}
84+
override predicate isSource(DataFlow::Node source) { source instanceof PathNormalization }
7285

7386
override predicate isSink(DataFlow::Node sink) {
7487
sink = any(FileSystemAccess e).getAPathArgument()
@@ -77,22 +90,23 @@ class UncheckedNormalizedConfiguration extends TaintTracking::Configuration {
7790
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { guard instanceof PathCheck }
7891
}
7992

80-
from TaintTracking::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
93+
predicate pathNotCheckedAfterNormalization(CustomPathNode source, CustomPathNode sink) {
94+
exists(
95+
FirstNormalizationConfiguration config, DataFlow::PathNode mid1, DataFlow2::PathNode mid2,
96+
NormalizedPathNotCheckedConfiguration config2
97+
|
98+
config.hasFlowPath(source.asNode1(), mid1) and
99+
config2.hasFlowPath(mid2, sink.asNode2()) and
100+
mid1.getNode().asCfgNode() = mid2.getNode().asCfgNode()
101+
)
102+
}
103+
104+
// ---------------------------------------------------------------------------
105+
// Query: Either case 1 or case 2.
106+
// ---------------------------------------------------------------------------
107+
from CustomPathNode source, CustomPathNode sink
81108
where
82-
// Path has no normalization on it.
83-
config instanceof UnNormalizedPathConfiguration and
84-
config.hasFlowPath(source, sink)
85-
or
86-
// Path has a normalization on it, but no subsequent check.
87-
config instanceof UncheckedNormalizedConfiguration and
88-
config.hasFlowPath(source, sink)
109+
pathNotNormalized(source, sink)
89110
or
90-
// This should report a better source, but does not quite work.
91-
// Path has a normalization on it, but no subsequent check.
92-
config instanceof UncheckedNormalizedConfiguration and
93-
exists(DataFlow::PathNode c, FirstNormalization n | n.getNode() = c.getNode() |
94-
config.hasFlowPath(c, sink) and
95-
source.getNode() = n.getSourceNode()
96-
)
97-
select sink.getNode(), source, sink, "This path depends on $@.", source.getNode(),
98-
"a user-provided value"
111+
pathNotCheckedAfterNormalization(source, sink)
112+
select sink, source, sink, "This path depends on $@.", source, "a user-provided value"

0 commit comments

Comments
 (0)