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
2341import experimental.dataflow.TaintTracking2
2442import experimental.semmle.python.Concepts
2543import 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
81108where
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