Skip to content

Commit cea2f82

Browse files
committed
Python: Port py/path-injection to use proper source/sink customization
1 parent bf214ac commit cea2f82

File tree

2 files changed

+74
-40
lines changed

2 files changed

+74
-40
lines changed

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

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,19 @@
11
/**
2-
* Provides a taint-tracking configuration for detecting path injection
3-
* vulnerabilities.
2+
* Provides taint-tracking configurations for detecting "path injection" vulnerabilities.
43
*
5-
* We detect cases where a user-controlled path is used in an unsafe manner,
6-
* meaning it is not both normalized and _afterwards_ checked.
7-
*
8-
* It does so by dividing the problematic situation into two cases:
9-
* 1. The file path is never normalized.
10-
* This is easily detected by using normalization as a sanitizer.
11-
*
12-
* 2. The file path is normalized at least once, but never checked afterwards.
13-
* This is detected by finding the earliest normalization and then ensuring that
14-
* no checks happen later. Since we start from the earliest normalization,
15-
* we know that the absence of checks means that no normalization has a
16-
* check after it. (No checks after a second normalization would be ok if
17-
* there was a check between the first and the second.)
18-
*
19-
* Note that one could make the dual split on whether the file path is ever checked. This does
20-
* not work as nicely, however, since checking is modelled as a `BarrierGuard` rather than
21-
* as a `Sanitizer`. That means that only some dataflow paths out of a check will be removed,
22-
* and so identifying the last check is not possible simply by finding a dataflow path from it
23-
* to a sink.
4+
* Note, for performance reasons: only import this file if
5+
* the Configurations or the `pathInjection` predicate are needed, otherwise
6+
* `PathInjectionCustomizations` should be imported instead.
247
*/
258

26-
import python
27-
import semmle.python.dataflow.new.DataFlow
28-
import semmle.python.dataflow.new.DataFlow2
29-
import semmle.python.dataflow.new.TaintTracking
30-
import semmle.python.dataflow.new.TaintTracking2
31-
import semmle.python.Concepts
32-
import semmle.python.dataflow.new.RemoteFlowSources
9+
private import python
10+
private import semmle.python.Concepts
11+
private import semmle.python.dataflow.new.DataFlow
12+
private import semmle.python.dataflow.new.DataFlow2
13+
private import semmle.python.dataflow.new.TaintTracking
14+
private import semmle.python.dataflow.new.TaintTracking2
3315
import ChainedConfigs12
34-
import semmle.python.dataflow.new.BarrierGuards
16+
import PathInjectionCustomizations::PathInjection
3517

3618
// ---------------------------------------------------------------------------
3719
// Case 1. The path is never normalized.
@@ -40,16 +22,14 @@ import semmle.python.dataflow.new.BarrierGuards
4022
class PathNotNormalizedConfiguration extends TaintTracking::Configuration {
4123
PathNotNormalizedConfiguration() { this = "PathNotNormalizedConfiguration" }
4224

43-
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
25+
override predicate isSource(DataFlow::Node source) { source instanceof Source }
4426

45-
override predicate isSink(DataFlow::Node sink) {
46-
sink = any(FileSystemAccess e).getAPathArgument()
47-
}
27+
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
4828

4929
override predicate isSanitizer(DataFlow::Node node) { node instanceof Path::PathNormalization }
5030

5131
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
52-
guard instanceof StringConstCompare
32+
guard instanceof SanitizerGuard
5333
}
5434
}
5535

@@ -68,14 +48,14 @@ predicate pathNotNormalized(CustomPathNode source, CustomPathNode sink) {
6848
class FirstNormalizationConfiguration extends TaintTracking::Configuration {
6949
FirstNormalizationConfiguration() { this = "FirstNormalizationConfiguration" }
7050

71-
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
51+
override predicate isSource(DataFlow::Node source) { source instanceof Source }
7252

7353
override predicate isSink(DataFlow::Node sink) { sink instanceof Path::PathNormalization }
7454

7555
override predicate isSanitizerOut(DataFlow::Node node) { node instanceof Path::PathNormalization }
7656

7757
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
78-
guard instanceof StringConstCompare
58+
guard instanceof SanitizerGuard
7959
}
8060
}
8161

@@ -85,14 +65,12 @@ class NormalizedPathNotCheckedConfiguration extends TaintTracking2::Configuratio
8565

8666
override predicate isSource(DataFlow::Node source) { source instanceof Path::PathNormalization }
8767

88-
override predicate isSink(DataFlow::Node sink) {
89-
sink = any(FileSystemAccess e).getAPathArgument()
90-
}
68+
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
9169

9270
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
9371
guard instanceof Path::SafeAccessCheck
9472
or
95-
guard instanceof StringConstCompare
73+
guard instanceof SanitizerGuard
9674
}
9775
}
9876

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Provides default sources, sinks and sanitizers for detecting
3+
* "path injection"
4+
* vulnerabilities, as well as extension points for adding your own.
5+
*/
6+
7+
private import python
8+
private import semmle.python.dataflow.new.DataFlow
9+
private import semmle.python.Concepts
10+
private import semmle.python.dataflow.new.RemoteFlowSources
11+
private import semmle.python.dataflow.new.BarrierGuards
12+
13+
/**
14+
* Provides default sources, and sinks for detecting
15+
* "path injection"
16+
* vulnerabilities, as well as extension points for adding your own.
17+
*
18+
* Since the path-injection configuration setup is rather complicated, we do not
19+
* expose a `Sanitizer` class, and instead you should extend
20+
* `Path::PathNormalization::Range` and `Path::SafeAccessCheck::Range` from
21+
* `semmle.python.Concepts` instead.
22+
*/
23+
module PathInjection {
24+
/**
25+
* A data flow source for "path injection" vulnerabilities.
26+
*/
27+
abstract class Source extends DataFlow::Node { }
28+
29+
/**
30+
* A data flow sink for "path injection" vulnerabilities.
31+
* Such as a file system access.
32+
*/
33+
abstract class Sink extends DataFlow::Node { }
34+
35+
/**
36+
* A sanitizer guard for "path injection" vulnerabilities.
37+
*/
38+
abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
39+
40+
/**
41+
* A source of remote user input, considered as a flow source.
42+
*/
43+
class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
44+
45+
/**
46+
* A file system access, considered as a flow sink.
47+
*/
48+
class FileSystemAccessAsSink extends Sink {
49+
FileSystemAccessAsSink() { this = any(FileSystemAccess e).getAPathArgument() }
50+
}
51+
52+
/**
53+
* A comparison with a constant string, considered as a sanitizer-guard.
54+
*/
55+
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
56+
}

0 commit comments

Comments
 (0)