Skip to content

Commit 3f2e314

Browse files
committed
python: add machinery for MaD barriers
and reinstate previously removed barrier now as a MaD row
1 parent 086dd7a commit 3f2e314

File tree

6 files changed

+120
-5
lines changed

6 files changed

+120
-5
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/python-all
4+
extensible: barrierGuardModel
5+
data:
6+
- ['django', 'Member[utils].Member[http].Member[url_has_allowed_host_and_scheme].Argument[0,url:]', "true", 'url-redirection']

python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModels.qll

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,26 @@ private predicate sinkModel(string type, string path, string kind, string model)
344344
)
345345
}
346346

347+
/** Holds if a barrier model exists for the given parameters. */
348+
private predicate barrierModel(string type, string path, string kind, string model) {
349+
// No deprecation adapter for barrier models, they were not around back then.
350+
exists(QlBuiltins::ExtensionId madId |
351+
Extensions::barrierModel(type, path, kind, madId) and
352+
model = "MaD:" + madId.toString()
353+
)
354+
}
355+
356+
/** Holds if a barrier guard model exists for the given parameters. */
357+
private predicate barrierGuardModel(
358+
string type, string path, string branch, string kind, string model
359+
) {
360+
// No deprecation adapter for barrier models, they were not around back then.
361+
exists(QlBuiltins::ExtensionId madId |
362+
Extensions::barrierGuardModel(type, path, branch, kind, madId) and
363+
model = "MaD:" + madId.toString()
364+
)
365+
}
366+
347367
/** Holds if a summary model `row` exists for the given parameters. */
348368
private predicate summaryModel(
349369
string type, string path, string input, string output, string kind, string model
@@ -400,6 +420,8 @@ predicate isRelevantType(string type) {
400420
(
401421
sourceModel(type, _, _, _) or
402422
sinkModel(type, _, _, _) or
423+
barrierModel(type, _, _, _) or
424+
barrierGuardModel(type, _, _, _, _) or
403425
summaryModel(type, _, _, _, _, _) or
404426
typeModel(_, type, _)
405427
) and
@@ -427,6 +449,8 @@ predicate isRelevantFullPath(string type, string path) {
427449
(
428450
sourceModel(type, path, _, _) or
429451
sinkModel(type, path, _, _) or
452+
barrierModel(type, path, _, _) or
453+
barrierGuardModel(type, path, _, _, _) or
430454
summaryModel(type, path, _, _, _, _) or
431455
typeModel(_, type, path)
432456
)
@@ -745,6 +769,32 @@ module ModelOutput {
745769
)
746770
}
747771

772+
/**
773+
* Holds if a barrier model contributed `barrier` with the given `kind`.
774+
*/
775+
cached
776+
API::Node getABarrierNode(string kind, string model) {
777+
exists(string type, string path |
778+
barrierModel(type, path, kind, model) and
779+
result = getNodeFromPath(type, path)
780+
)
781+
}
782+
783+
/**
784+
* Holds if a barrier model contributed `barrier` with the given `kind`.
785+
*/
786+
cached
787+
API::Node getABarrierGuardNode(string kind, boolean branch, string model) {
788+
exists(string type, string path, string branch_str |
789+
branch = true and branch_str = "true"
790+
or
791+
branch = false and branch_str = "false"
792+
|
793+
barrierGuardModel(type, path, branch_str, kind, model) and
794+
result = getNodeFromPath(type, path)
795+
)
796+
}
797+
748798
/**
749799
* Holds if a relevant summary exists for these parameters.
750800
*/
@@ -787,15 +837,27 @@ module ModelOutput {
787837
private import codeql.mad.ModelValidation as SharedModelVal
788838

789839
/**
790-
* Holds if a CSV source model contributed `source` with the given `kind`.
840+
* Holds if an external model contributed `source` with the given `kind`.
791841
*/
792842
API::Node getASourceNode(string kind) { result = getASourceNode(kind, _) }
793843

794844
/**
795-
* Holds if a CSV sink model contributed `sink` with the given `kind`.
845+
* Holds if an external model contributed `sink` with the given `kind`.
796846
*/
797847
API::Node getASinkNode(string kind) { result = getASinkNode(kind, _) }
798848

849+
/**
850+
* Holds if an external model contributed `barrier` with the given `kind`.
851+
*/
852+
API::Node getABarrierNode(string kind) { result = getABarrierNode(kind, _) }
853+
854+
/**
855+
* Holds if an external model contributed `barrier-guard` with the given `kind` and `branch`.
856+
*/
857+
API::Node getABarrierGuardNode(string kind, boolean branch) {
858+
result = getABarrierGuardNode(kind, branch, _)
859+
}
860+
799861
private module KindValConfig implements SharedModelVal::KindValidationConfigSig {
800862
predicate summaryKind(string kind) { summaryModel(_, _, _, _, kind, _) }
801863

python/ql/lib/semmle/python/frameworks/data/internal/ApiGraphModelsExtensions.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,26 @@ extensible predicate sourceModel(
2020
*/
2121
extensible predicate sinkModel(string type, string path, string kind, QlBuiltins::ExtensionId madId);
2222

23+
/**
24+
* Holds if the value at `(type, path)` should be seen as a barrier
25+
* of the given `kind` and `madId` is the data extension row number.
26+
*/
27+
extensible predicate barrierModel(
28+
string type, string path, string kind, QlBuiltins::ExtensionId madId
29+
);
30+
31+
/**
32+
* Holds if the value at `(type, path)` should be seen as a barrier guard
33+
* of the given `kind` and `madId` is the data extension row number.
34+
* `path` is assumed to lead to a parameter of a call (possibly `self`), and
35+
* the call is guarding the parameter.
36+
* `branch` is either `true` or `false`, indicating which branch of the guard
37+
* is protecting the parameter.
38+
*/
39+
extensible predicate barrierGuardModel(
40+
string type, string path, string branch, string kind, QlBuiltins::ExtensionId madId
41+
);
42+
2343
/**
2444
* Holds if in calls to `(type, path)`, the value referred to by `input`
2545
* can flow to the value referred to by `output` and `madId` is the data

python/ql/lib/semmle/python/frameworks/data/internal/empty.model.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ extensions:
1111
extensible: sinkModel
1212
data: []
1313

14+
- addsTo:
15+
pack: codeql/python-all
16+
extensible: barrierModel
17+
data: []
18+
19+
- addsTo:
20+
pack: codeql/python-all
21+
extensible: barrierGuardModel
22+
data: []
23+
1424
- addsTo:
1525
pack: codeql/python-all
1626
extensible: summaryModel

python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
private import python
88
private import semmle.python.dataflow.new.DataFlow
99
private import semmle.python.Concepts
10+
private import semmle.python.ApiGraphs
1011
private import semmle.python.dataflow.new.RemoteFlowSources
1112
private import semmle.python.dataflow.new.BarrierGuards
1213
private import semmle.python.frameworks.data.ModelsAsData
@@ -156,4 +157,23 @@ module UrlRedirect {
156157

157158
/** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */
158159
deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard;
160+
161+
private predicate urlCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) {
162+
exists(API::CallNode call, API::Node parameter |
163+
parameter = call.getAParameter() and
164+
parameter = ModelOutput::getABarrierGuardNode("url-redirection", branch)
165+
|
166+
g = call.asCfgNode() and
167+
node = parameter.asSink().asCfgNode()
168+
)
169+
}
170+
171+
class SanitizerFromModel extends Sanitizer {
172+
SanitizerFromModel() { this = DataFlow::BarrierGuard<urlCheck/3>::getABarrierNode() }
173+
174+
override predicate sanitizes(FlowState state) {
175+
// sanitize all flow states
176+
any()
177+
}
178+
}
159179
}

python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ edges
5252
| test.py:81:17:81:46 | ControlFlowNode for Attribute() | test.py:81:5:81:13 | ControlFlowNode for untrusted | provenance | |
5353
| test.py:82:5:82:10 | ControlFlowNode for unsafe | test.py:83:21:83:26 | ControlFlowNode for unsafe | provenance | |
5454
| test.py:90:5:90:13 | ControlFlowNode for untrusted | test.py:93:18:93:26 | ControlFlowNode for untrusted | provenance | |
55-
| test.py:90:5:90:13 | ControlFlowNode for untrusted | test.py:95:25:95:33 | ControlFlowNode for untrusted | provenance | |
5655
| test.py:90:17:90:23 | ControlFlowNode for request | test.py:90:17:90:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep |
5756
| test.py:90:17:90:28 | ControlFlowNode for Attribute | test.py:90:17:90:46 | ControlFlowNode for Attribute() | provenance | dict.get |
5857
| test.py:90:17:90:46 | ControlFlowNode for Attribute() | test.py:90:5:90:13 | ControlFlowNode for untrusted | provenance | |
@@ -123,7 +122,6 @@ nodes
123122
| test.py:90:17:90:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
124123
| test.py:90:17:90:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
125124
| test.py:93:18:93:26 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted |
126-
| test.py:95:25:95:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted |
127125
| test.py:111:5:111:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted |
128126
| test.py:111:17:111:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
129127
| test.py:111:17:111:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
@@ -150,7 +148,6 @@ subpaths
150148
| test.py:76:21:76:26 | ControlFlowNode for unsafe | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:76:21:76:26 | ControlFlowNode for unsafe | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
151149
| test.py:83:21:83:26 | ControlFlowNode for unsafe | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:83:21:83:26 | ControlFlowNode for unsafe | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
152150
| test.py:93:18:93:26 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:93:18:93:26 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
153-
| test.py:95:25:95:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:95:25:95:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
154151
| test.py:114:25:114:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:114:25:114:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
155152
| test.py:140:25:140:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:140:25:140:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
156153
| test.py:148:25:148:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:148:25:148:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |

0 commit comments

Comments
 (0)