Skip to content

Commit 35c9307

Browse files
committed
python: rewrite NoSQLInjection to use flow state
This allows a bit more precision. Specifically, we could require the sanitizer to only affect `ConvertedToDict`. In practice, most sanitizers woudl probably fail on raw input also, though.
1 parent a5bc537 commit 35c9307

File tree

2 files changed

+43
-46
lines changed

2 files changed

+43
-46
lines changed

python/ql/src/experimental/Security/CWE-943/NoSQLInjection.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import python
1313
import experimental.semmle.python.security.injection.NoSQLInjection
1414

15-
from CustomPathNode source, CustomPathNode sink
16-
where noSQLInjectionFlow(source, sink)
15+
from NoSQLInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
16+
where config.hasFlowPath(source, sink)
1717
select sink, source, sink, "$@ NoSQL query contains an unsanitized $@", sink, "This", source,
1818
"user-provided value"
Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,54 @@
11
import python
22
import semmle.python.dataflow.new.DataFlow
3-
import semmle.python.dataflow.new.DataFlow2
43
import semmle.python.dataflow.new.TaintTracking
5-
import semmle.python.dataflow.new.TaintTracking2
64
import semmle.python.dataflow.new.RemoteFlowSources
7-
import semmle.python.security.dataflow.ChainedConfigs12
85
import experimental.semmle.python.Concepts
96
import semmle.python.Concepts
107

11-
/**
12-
* A taint-tracking configuration for detecting string-to-dict conversions.
13-
*/
14-
class RFSToDictConfig extends TaintTracking::Configuration {
15-
RFSToDictConfig() { this = "RFSToDictConfig" }
16-
17-
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
18-
19-
override predicate isSink(DataFlow::Node sink) {
20-
exists(Decoding decoding | decoding.getFormat() = "JSON" and sink = decoding.getOutput())
8+
module NoSQLInjection {
9+
class Configuration extends TaintTracking::Configuration {
10+
Configuration() { this = "NoSQLInjection" }
11+
12+
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
13+
source instanceof RemoteFlowSource and
14+
state instanceof RemoteInput
15+
}
16+
17+
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
18+
sink = any(NoSQLQuery noSQLQuery).getQuery() and
19+
state instanceof ConvertedToDict
20+
}
21+
22+
override predicate isBarrier(DataFlow::Node node, DataFlow::FlowState state) {
23+
// Block `RemoteInput` paths here, since they change state to `ConvertedToDict`
24+
exists(Decoding decoding | decoding.getFormat() = "JSON" and node = decoding.getOutput()) and
25+
state instanceof RemoteInput
26+
}
27+
28+
override predicate isAdditionalFlowStep(
29+
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
30+
DataFlow::FlowState stateTo
31+
) {
32+
exists(Decoding decoding | decoding.getFormat() = "JSON" |
33+
nodeFrom = decoding.getAnInput() and
34+
nodeTo = decoding.getOutput()
35+
) and
36+
stateFrom instanceof RemoteInput and
37+
stateTo instanceof ConvertedToDict
38+
}
39+
40+
override predicate isSanitizer(DataFlow::Node sanitizer) {
41+
sanitizer = any(NoSQLSanitizer noSQLSanitizer).getAnInput()
42+
}
2143
}
2244

23-
override predicate isSanitizer(DataFlow::Node sanitizer) {
24-
sanitizer = any(NoSQLSanitizer noSQLSanitizer).getAnInput()
45+
/** A flow state signifying remote input. */
46+
class RemoteInput extends DataFlow::FlowState {
47+
RemoteInput() { this = "RemoteInput" }
2548
}
26-
}
2749

28-
/**
29-
* A taint-tracking configuration for detecting NoSQL injections (previously converted to a dict).
30-
*/
31-
class FromDataDictToSink extends TaintTracking2::Configuration {
32-
FromDataDictToSink() { this = "FromDataDictToSink" }
33-
34-
override predicate isSource(DataFlow::Node source) {
35-
exists(Decoding decoding | decoding.getFormat() = "JSON" and source = decoding.getOutput())
50+
/** A flow state signifying remote input converted to a dictionary. */
51+
class ConvertedToDict extends DataFlow::FlowState {
52+
ConvertedToDict() { this = "ConvertedToDict" }
3653
}
37-
38-
override predicate isSink(DataFlow::Node sink) { sink = any(NoSQLQuery noSQLQuery).getQuery() }
39-
40-
override predicate isSanitizer(DataFlow::Node sanitizer) {
41-
sanitizer = any(NoSQLSanitizer noSQLSanitizer).getAnInput()
42-
}
43-
}
44-
45-
/**
46-
* A predicate checking string-to-dict conversion and its arrival to a NoSQL injection sink.
47-
*/
48-
predicate noSQLInjectionFlow(CustomPathNode source, CustomPathNode sink) {
49-
exists(
50-
RFSToDictConfig config, DataFlow::PathNode mid1, DataFlow2::PathNode mid2,
51-
FromDataDictToSink config2
52-
|
53-
config.hasFlowPath(source.asNode1(), mid1) and
54-
config2.hasFlowPath(mid2, sink.asNode2()) and
55-
mid1.getNode().asCfgNode() = mid2.getNode().asCfgNode()
56-
)
5754
}

0 commit comments

Comments
 (0)