Skip to content

Commit 7e6032f

Browse files
committed
Port to Decoding
1 parent 8e3d5ff commit 7e6032f

File tree

2 files changed

+62
-30
lines changed

2 files changed

+62
-30
lines changed

python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll

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

1314
/**
@@ -98,3 +99,47 @@ private module Re {
9899
override DataFlow::Node getRegexNode() { result = regexNode }
99100
}
100101
}
102+
103+
// ---------------------------------------------------------------------------
104+
// xmltodict
105+
// ---------------------------------------------------------------------------
106+
/** Gets a reference to the `xmltodict` module. */
107+
API::Node xmltodict() { result = API::moduleImport("xmltodict") }
108+
109+
/**
110+
* A call to `xmltodict.parse`
111+
* See https://github.com/martinblech/xmltodict/blob/ae19c452ca000bf243bfc16274c060bf3bf7cf51/xmltodict.py#L198
112+
*/
113+
private class XmlToDictParseCall extends Decoding::Range, DataFlow::CallCfgNode {
114+
XmlToDictParseCall() { this = xmltodict().getMember("parse").getACall() }
115+
116+
override predicate mayExecuteInput() { none() }
117+
118+
override DataFlow::Node getAnInput() { result = this.getArg(0) }
119+
120+
override DataFlow::Node getOutput() { result = this }
121+
122+
override string getFormat() { result = "JSON" }
123+
}
124+
125+
// ---------------------------------------------------------------------------
126+
// ujson
127+
// ---------------------------------------------------------------------------
128+
/** Gets a reference to the `ujson` module. */
129+
API::Node ujson() { result = API::moduleImport("ujson") }
130+
131+
/**
132+
* A call to `ujson.loads`
133+
* See https://pypi.org/project/ujson/#usage
134+
*/
135+
private class UltraJsonLoadsCall extends Decoding::Range, DataFlow::CallCfgNode {
136+
UltraJsonLoadsCall() { this = ujson().getMember("loads").getACall() }
137+
138+
override predicate mayExecuteInput() { none() }
139+
140+
override DataFlow::Node getAnInput() { result = this.getArg(0) }
141+
142+
override DataFlow::Node getOutput() { result = this }
143+
144+
override string getFormat() { result = "JSON" }
145+
}

python/ql/src/experimental/semmle/python/security/injection/NoSQLInjection.qll

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,37 @@ import semmle.python.dataflow.new.DataFlow
33
import semmle.python.dataflow.new.DataFlow2
44
import semmle.python.dataflow.new.TaintTracking
55
import semmle.python.dataflow.new.TaintTracking2
6-
import experimental.semmle.python.Concepts
76
import semmle.python.dataflow.new.RemoteFlowSources
8-
import semmle.python.ApiGraphs
97
import semmle.python.security.dataflow.ChainedConfigs12
8+
import experimental.semmle.python.Concepts
9+
import semmle.python.Concepts
1010

11-
class JsonLoadsCall extends DataFlow::CallCfgNode {
12-
JsonLoadsCall() { this = API::moduleImport("json").getMember("loads").getACall() }
13-
14-
DataFlow::Node getLoadNode() { result = this.getArg(0) }
15-
}
16-
17-
class XmlToDictParseCall extends DataFlow::CallCfgNode {
18-
XmlToDictParseCall() { this = API::moduleImport("xmltodict").getMember("parse").getACall() }
19-
20-
DataFlow::Node getParseNode() { result = this.getArg(0) }
21-
}
22-
23-
class UltraJsonLoadsCall extends DataFlow::CallCfgNode {
24-
UltraJsonLoadsCall() { this = API::moduleImport("ujson").getMember("loads").getACall() }
25-
26-
DataFlow::Node getLoadNode() { result = this.getArg(0) }
27-
}
28-
29-
class DataToDictSink extends DataFlow::Node {
30-
DataToDictSink() {
31-
this = any(JsonLoadsCall jsonLoads).getLoadNode() or
32-
this = any(XmlToDictParseCall jsonLoads).getParseNode() or
33-
this = any(UltraJsonLoadsCall jsonLoads).getLoadNode()
34-
}
35-
}
36-
11+
/**
12+
* A taint-tracking configuration for detecting string-to-dict conversions.
13+
*/
3714
class RFSToDictConfig extends TaintTracking::Configuration {
3815
RFSToDictConfig() { this = "RFSToDictConfig" }
3916

4017
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
4118

42-
override predicate isSink(DataFlow::Node sink) { sink instanceof DataToDictSink }
19+
override predicate isSink(DataFlow::Node sink) {
20+
exists(Decoding decoding | decoding.getFormat() = "JSON" and sink = decoding)
21+
}
4322

4423
override predicate isSanitizer(DataFlow::Node sanitizer) {
4524
sanitizer = any(NoSQLSanitizer noSQLSanitizer).getAnInput()
4625
}
4726
}
4827

28+
/**
29+
* A taint-tracking configuration for detecting NoSQL injections (previously converted to a dict).
30+
*/
4931
class FromDataDictToSink extends TaintTracking2::Configuration {
5032
FromDataDictToSink() { this = "FromDataDictToSink" }
5133

52-
override predicate isSource(DataFlow::Node source) { source instanceof DataToDictSink }
34+
override predicate isSource(DataFlow::Node source) {
35+
exists(Decoding decoding | decoding.getFormat() = "JSON" and source = decoding)
36+
}
5337

5438
override predicate isSink(DataFlow::Node sink) { sink = any(NoSQLQuery noSQLQuery).getQuery() }
5539

@@ -58,6 +42,9 @@ class FromDataDictToSink extends TaintTracking2::Configuration {
5842
}
5943
}
6044

45+
/**
46+
* A predicate checking string-to-dict conversion and its arrival to a NoSQL injection sink.
47+
*/
6148
predicate noSQLInjectionFlow(CustomPathNode source, CustomPathNode sink) {
6249
exists(
6350
RFSToDictConfig config, DataFlow::PathNode mid1, DataFlow2::PathNode mid2,

0 commit comments

Comments
 (0)