Skip to content

Commit 087961d

Browse files
committed
Python: Refactor to allow customizations
Also use new DataFlow API
1 parent db04597 commit 087961d

3 files changed

Lines changed: 88 additions & 43 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import python
2+
import semmle.python.dataflow.new.DataFlow
3+
import semmle.python.dataflow.new.RemoteFlowSources
4+
import semmle.python.Concepts
5+
6+
module NoSqlInjection {
7+
private newtype TFlowState =
8+
TStringInput() or
9+
TDictInput()
10+
11+
abstract class FlowState extends TFlowState {
12+
abstract string toString();
13+
}
14+
15+
class StringInput extends FlowState, TStringInput {
16+
override string toString() { result = "StringInput" }
17+
}
18+
19+
class DictInput extends FlowState, TDictInput {
20+
override string toString() { result = "DictInput" }
21+
}
22+
23+
abstract class StringSource extends DataFlow::Node { }
24+
25+
abstract class DictSource extends DataFlow::Node { }
26+
27+
abstract class StringSink extends DataFlow::Node { }
28+
29+
abstract class DictSink extends DataFlow::Node { }
30+
31+
abstract class StringToDictConversion extends DataFlow::Node {
32+
abstract DataFlow::Node getAnInput();
33+
34+
abstract DataFlow::Node getOutput();
35+
}
36+
37+
class RemoteFlowSourceAsStringSource extends RemoteFlowSource, StringSource { }
38+
39+
class NoSqlQueryAsDictSink extends DictSink {
40+
NoSqlQueryAsDictSink() { this = any(NoSqlQuery noSqlQuery).getQuery() }
41+
}
42+
43+
class JsonDecoding extends Decoding, StringToDictConversion {
44+
JsonDecoding() { this.getFormat() = "JSON" }
45+
46+
override DataFlow::Node getAnInput() { result = Decoding.super.getAnInput() }
47+
48+
override DataFlow::Node getOutput() { result = Decoding.super.getOutput() }
49+
}
50+
}
Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,48 @@
11
import python
22
import semmle.python.dataflow.new.DataFlow
33
import semmle.python.dataflow.new.TaintTracking
4-
import semmle.python.dataflow.new.RemoteFlowSources
54
import semmle.python.Concepts
5+
private import NoSQLInjectionCustomizations::NoSqlInjection as C
66

7-
module NoSqlInjection {
8-
class Configuration extends TaintTracking::Configuration {
9-
Configuration() { this = "NoSQLInjection" }
7+
module Config implements DataFlow::StateConfigSig {
8+
class FlowState = C::FlowState;
109

11-
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
12-
source instanceof RemoteFlowSource and
13-
state instanceof RemoteInput
14-
}
15-
16-
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
17-
sink = any(NoSqlQuery noSqlQuery).getQuery() and
18-
state instanceof ConvertedToDict
19-
}
20-
21-
override predicate isSanitizer(DataFlow::Node node, DataFlow::FlowState state) {
22-
// Block `RemoteInput` paths here, since they change state to `ConvertedToDict`
23-
exists(Decoding decoding | decoding.getFormat() = "JSON" and node = decoding.getOutput()) and
24-
state instanceof RemoteInput
25-
}
10+
predicate isSource(DataFlow::Node source, FlowState state) {
11+
source instanceof C::StringSource and
12+
state instanceof C::StringInput
13+
or
14+
source instanceof C::DictSource and
15+
state instanceof C::DictInput
16+
}
2617

27-
override predicate isAdditionalTaintStep(
28-
DataFlow::Node nodeFrom, DataFlow::FlowState stateFrom, DataFlow::Node nodeTo,
29-
DataFlow::FlowState stateTo
30-
) {
31-
exists(Decoding decoding | decoding.getFormat() = "JSON" |
32-
nodeFrom = decoding.getAnInput() and
33-
nodeTo = decoding.getOutput()
34-
) and
35-
stateFrom instanceof RemoteInput and
36-
stateTo instanceof ConvertedToDict
37-
}
18+
predicate isSink(DataFlow::Node source, FlowState state) {
19+
source instanceof C::StringSink and
20+
state instanceof C::StringInput
21+
or
22+
source instanceof C::DictSink and
23+
state instanceof C::DictInput
24+
}
3825

39-
override predicate isSanitizer(DataFlow::Node sanitizer) {
40-
sanitizer = any(NoSqlSanitizer noSqlSanitizer).getAnInput()
41-
}
26+
predicate isBarrier(DataFlow::Node node, FlowState state) {
27+
// Block `StringInput` paths here, since they change state to `DictInput`
28+
exists(C::StringToDictConversion c | node = c.getOutput()) and
29+
state instanceof C::StringInput
4230
}
4331

44-
/** A flow state signifying remote input. */
45-
class RemoteInput extends DataFlow::FlowState {
46-
RemoteInput() { this = "RemoteInput" }
32+
predicate isAdditionalFlowStep(
33+
DataFlow::Node nodeFrom, FlowState stateFrom, DataFlow::Node nodeTo, FlowState stateTo
34+
) {
35+
exists(C::StringToDictConversion c |
36+
nodeFrom = c.getAnInput() and
37+
nodeTo = c.getOutput()
38+
) and
39+
stateFrom instanceof C::StringInput and
40+
stateTo instanceof C::DictInput
4741
}
4842

49-
/** A flow state signifying remote input converted to a dictionary. */
50-
class ConvertedToDict extends DataFlow::FlowState {
51-
ConvertedToDict() { this = "ConvertedToDict" }
43+
predicate isBarrier(DataFlow::Node node) {
44+
node = any(NoSqlSanitizer noSqlSanitizer).getAnInput()
5245
}
5346
}
47+
48+
module Flow = TaintTracking::GlobalWithState<Config>;

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212

1313
import python
1414
import semmle.python.security.dataflow.NoSQLInjectionQuery
15-
import DataFlow::PathGraph
15+
import Flow::PathGraph
1616

17-
from NoSqlInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
18-
where config.hasFlowPath(source, sink)
19-
select sink, source, sink, "This NoSQL query contains an unsanitized $@.", source,
17+
from Flow::PathNode source, Flow::PathNode sink
18+
where Flow::flowPath(source, sink)
19+
select sink.getNode(), source, sink, "This NoSQL query contains an unsanitized $@.", source,
2020
"user-provided value"

0 commit comments

Comments
 (0)