Skip to content

Commit e9c4574

Browse files
committed
Apply structure
1 parent 789c585 commit e9c4574

File tree

6 files changed

+150
-148
lines changed

6 files changed

+150
-148
lines changed
Lines changed: 6 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,21 @@
11
/**
22
* @name HTTP Header Injection
3-
* @description User input should not be used in HTTP headers without first being escaped,
4-
* otherwise a malicious user may be able to inject a value that could manipulate the response.
3+
* @description User input should not be used in HTTP headers, otherwise a malicious user
4+
* may be able to inject a value that could manipulate the response.
55
* @kind path-problem
66
* @problem.severity error
7-
* @id python/header-injection
7+
* @id py/header-injection
88
* @tags security
99
* external/cwe/cwe-113
1010
* external/cwe/cwe-079
1111
*/
1212

1313
// determine precision above
1414
import python
15-
import semmle.python.dataflow.new.RemoteFlowSources
16-
import semmle.python.dataflow.new.DataFlow
17-
import semmle.python.dataflow.new.TaintTracking
18-
import semmle.python.ApiGraphs
15+
import experimental.semmle.python.security.injection.HTTPHeaders
1916
import DataFlow::PathGraph
2017

21-
class WerkzeugHeaderCall extends DataFlow::CallCfgNode {
22-
WerkzeugHeaderCall() {
23-
exists(DataFlow::AttrRead addMethod |
24-
this.getFunction() = addMethod and
25-
addMethod.getObject().getALocalSource() =
26-
API::moduleImport("werkzeug").getMember("datastructures").getMember("Headers").getACall() and
27-
addMethod.getAttributeName() = "add"
28-
)
29-
}
30-
31-
DataFlow::Node getHeaderInputNode() { result = this.getArg(1) }
32-
}
33-
34-
class FlaskHeaderCall extends DataFlow::Node {
35-
DataFlow::Node headerInputNode;
36-
37-
FlaskHeaderCall() {
38-
exists(
39-
DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
40-
AssignStmt sinkDeclaration
41-
|
42-
headerInstance = API::moduleImport("flask").getMember("Response").getACall() and
43-
responseMethod.getAttributeName() = "headers" and
44-
responseMethod.getObject().getALocalSource() = headerInstance and
45-
sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
46-
headerInputNode.asExpr() = sinkDeclaration.getValue() and
47-
this.asExpr() = sinkDeclaration.getATarget()
48-
)
49-
}
50-
51-
DataFlow::Node getHeaderInputNode() { result = headerInputNode }
52-
}
53-
54-
class FlaskMakeResponseCall extends DataFlow::Node {
55-
DataFlow::Node headerInputNode;
56-
57-
FlaskMakeResponseCall() {
58-
exists(
59-
DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
60-
AssignStmt sinkDeclaration
61-
|
62-
headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
63-
responseMethod.getAttributeName() = "headers" and
64-
responseMethod.getObject().getALocalSource() = headerInstance and
65-
sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
66-
this.asExpr() = sinkDeclaration.getATarget() and
67-
headerInputNode.asExpr() = sinkDeclaration.getValue()
68-
)
69-
}
70-
71-
DataFlow::Node getHeaderInputNode() { result = headerInputNode }
72-
}
73-
74-
class FlaskMakeResponseExtendCall extends DataFlow::CallCfgNode {
75-
DataFlow::Node headerInputNode;
76-
77-
FlaskMakeResponseExtendCall() {
78-
exists(
79-
DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
80-
DataFlow::AttrRead extendMethod
81-
|
82-
headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
83-
responseMethod.getAttributeName() = "headers" and
84-
responseMethod.getObject().getALocalSource() = headerInstance and
85-
extendMethod.getAttributeName() = "extend" and
86-
extendMethod.getObject().getALocalSource() = responseMethod and
87-
this.getFunction() = extendMethod and
88-
headerInputNode = this.getArg(0)
89-
)
90-
}
91-
92-
DataFlow::Node getHeaderInputNode() { result = headerInputNode }
93-
}
94-
95-
class FlaskResponseArg extends DataFlow::CallCfgNode {
96-
DataFlow::Node headerInputNode;
97-
98-
FlaskResponseArg() {
99-
this = API::moduleImport("flask").getMember("Response").getACall() and
100-
headerInputNode = this.getArgByName("headers")
101-
}
102-
103-
DataFlow::Node getHeaderInputNode() { result = headerInputNode }
104-
}
105-
106-
class DjangoResponseSetItemCall extends DataFlow::CallCfgNode {
107-
DjangoResponseSetItemCall() {
108-
exists(DataFlow::AttrRead setItemMethod |
109-
this.getFunction() = setItemMethod and
110-
setItemMethod.getObject().getALocalSource() =
111-
API::moduleImport("django").getMember("http").getMember("HttpResponse").getACall() and
112-
setItemMethod.getAttributeName() = "__setitem__"
113-
)
114-
}
115-
116-
DataFlow::Node getHeaderInputNode() { result = this.getArg(1) }
117-
}
118-
119-
class DjangoResponseAssignCall extends DataFlow::Node {
120-
DataFlow::Node headerInputNode;
121-
122-
DjangoResponseAssignCall() {
123-
exists(
124-
DataFlow::CallCfgNode headerInstance, Subscript responseMethod, DataFlow::Node responseToNode,
125-
AssignStmt sinkDeclaration
126-
|
127-
headerInstance =
128-
API::moduleImport("django").getMember("http").getMember("HttpResponse").getACall() and
129-
responseMethod.getValue() = responseToNode.asExpr() and
130-
responseToNode.getALocalSource().asExpr() = headerInstance.asExpr() and
131-
sinkDeclaration.getATarget() = responseMethod and
132-
this.asExpr() = sinkDeclaration.getATarget() and
133-
headerInputNode.asExpr() = sinkDeclaration.getValue()
134-
)
135-
}
136-
137-
DataFlow::Node getHeaderInputNode() { result = headerInputNode }
138-
}
139-
140-
class HeaderInjectionSink extends DataFlow::Node {
141-
HeaderInjectionSink() {
142-
this = any(WerkzeugHeaderCall a).getHeaderInputNode() or
143-
this = any(FlaskHeaderCall a).getHeaderInputNode() or
144-
this = any(FlaskMakeResponseCall a).getHeaderInputNode() or
145-
this = any(FlaskMakeResponseExtendCall a).getHeaderInputNode() or
146-
this = any(FlaskResponseArg a).getHeaderInputNode() or
147-
this = any(DjangoResponseSetItemCall a).getHeaderInputNode() or
148-
this = any(DjangoResponseAssignCall a).getHeaderInputNode()
149-
}
150-
}
151-
152-
class HeaderInjectionFlowConfig extends TaintTracking::Configuration {
153-
HeaderInjectionFlowConfig() { this = "HeaderInjectionFlowConfig" }
154-
155-
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
156-
157-
override predicate isSink(DataFlow::Node sink) { sink instanceof HeaderInjectionSink }
158-
}
159-
16018
from HeaderInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
16119
where config.hasFlowPath(source, sink)
162-
select sink.getNode(), source, sink, "$@ header is constructed from a $@.", sink.getNode(), "This",
163-
source.getNode(), "user-provided value"
20+
select sink.getNode(), source, sink, "$@ HTTP header is constructed from a $@.", sink.getNode(),
21+
"This", source.getNode(), "user-provided value"

python/ql/src/experimental/Security/CWE-113/HeaderInjection.qlref

Whitespace-only changes.

python/ql/src/experimental/semmle/python/Concepts.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,17 @@ private import semmle.python.dataflow.new.DataFlow
1313
private import semmle.python.dataflow.new.RemoteFlowSources
1414
private import semmle.python.dataflow.new.TaintTracking
1515
private import experimental.semmle.python.Frameworks
16+
17+
module HeaderDeclaration {
18+
abstract class Range extends DataFlow::Node {
19+
abstract DataFlow::Node getHeaderInputNode();
20+
}
21+
}
22+
23+
class HeaderDeclaration extends DataFlow::Node {
24+
HeaderDeclaration::Range range;
25+
26+
HeaderDeclaration() { this = range }
27+
28+
DataFlow::Node getHeaderInputNode() { result = range.getHeaderInputNode() }
29+
}

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

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,133 @@ private import semmle.python.dataflow.new.TaintTracking
99
private import semmle.python.dataflow.new.RemoteFlowSources
1010
private import experimental.semmle.python.Concepts
1111
private import semmle.python.ApiGraphs
12+
13+
private module Headers {
14+
private module Werkzeug {
15+
class WerkzeugHeaderCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
16+
WerkzeugHeaderCall() {
17+
exists(DataFlow::AttrRead addMethod |
18+
this.getFunction() = addMethod and
19+
addMethod.getObject().getALocalSource() =
20+
API::moduleImport("werkzeug")
21+
.getMember("datastructures")
22+
.getMember("Headers")
23+
.getACall() and
24+
addMethod.getAttributeName() = "add"
25+
)
26+
}
27+
28+
override DataFlow::Node getHeaderInputNode() { result = this.getArg(1) }
29+
}
30+
}
31+
32+
private module Flask {
33+
class FlaskHeaderCall extends DataFlow::Node, HeaderDeclaration::Range {
34+
DataFlow::Node headerInputNode;
35+
36+
FlaskHeaderCall() {
37+
exists(
38+
DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
39+
AssignStmt sinkDeclaration
40+
|
41+
headerInstance = API::moduleImport("flask").getMember("Response").getACall() and
42+
responseMethod.getAttributeName() = "headers" and
43+
responseMethod.getObject().getALocalSource() = headerInstance and
44+
sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
45+
headerInputNode.asExpr() = sinkDeclaration.getValue() and
46+
this.asExpr() = sinkDeclaration.getATarget()
47+
)
48+
}
49+
50+
override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
51+
}
52+
53+
class FlaskMakeResponseCall extends DataFlow::Node, HeaderDeclaration::Range {
54+
DataFlow::Node headerInputNode;
55+
56+
FlaskMakeResponseCall() {
57+
exists(
58+
DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
59+
AssignStmt sinkDeclaration
60+
|
61+
headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
62+
responseMethod.getAttributeName() = "headers" and
63+
responseMethod.getObject().getALocalSource() = headerInstance and
64+
sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
65+
this.asExpr() = sinkDeclaration.getATarget() and
66+
headerInputNode.asExpr() = sinkDeclaration.getValue()
67+
)
68+
}
69+
70+
override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
71+
}
72+
73+
class FlaskMakeResponseExtendCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
74+
DataFlow::Node headerInputNode;
75+
76+
FlaskMakeResponseExtendCall() {
77+
exists(
78+
DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
79+
DataFlow::AttrRead extendMethod
80+
|
81+
headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
82+
responseMethod.getAttributeName() = "headers" and
83+
responseMethod.getObject().getALocalSource() = headerInstance and
84+
extendMethod.getAttributeName() = "extend" and
85+
extendMethod.getObject().getALocalSource() = responseMethod and
86+
this.getFunction() = extendMethod and
87+
headerInputNode = this.getArg(0)
88+
)
89+
}
90+
91+
override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
92+
}
93+
94+
class FlaskResponseArg extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
95+
DataFlow::Node headerInputNode;
96+
97+
FlaskResponseArg() {
98+
this = API::moduleImport("flask").getMember("Response").getACall() and
99+
headerInputNode = this.getArgByName("headers")
100+
}
101+
102+
override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
103+
}
104+
105+
class DjangoResponseSetItemCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
106+
DjangoResponseSetItemCall() {
107+
exists(DataFlow::AttrRead setItemMethod |
108+
this.getFunction() = setItemMethod and
109+
setItemMethod.getObject().getALocalSource() =
110+
API::moduleImport("django").getMember("http").getMember("HttpResponse").getACall() and
111+
setItemMethod.getAttributeName() = "__setitem__"
112+
)
113+
}
114+
115+
override DataFlow::Node getHeaderInputNode() { result = this.getArg(1) }
116+
}
117+
}
118+
119+
private module Django {
120+
class DjangoResponseAssignCall extends DataFlow::Node, HeaderDeclaration::Range {
121+
DataFlow::Node headerInputNode;
122+
123+
DjangoResponseAssignCall() {
124+
exists(
125+
DataFlow::CallCfgNode headerInstance, Subscript responseMethod,
126+
DataFlow::Node responseToNode, AssignStmt sinkDeclaration
127+
|
128+
headerInstance =
129+
API::moduleImport("django").getMember("http").getMember("HttpResponse").getACall() and
130+
responseMethod.getValue() = responseToNode.asExpr() and
131+
responseToNode.getALocalSource().asExpr() = headerInstance.asExpr() and
132+
sinkDeclaration.getATarget() = responseMethod and
133+
this.asExpr() = sinkDeclaration.getATarget() and
134+
headerInputNode.asExpr() = sinkDeclaration.getValue()
135+
)
136+
}
137+
138+
override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)