Skip to content

Commit 6f89b3f

Browse files
committed
Init Header Injection query
1 parent 8adaee0 commit 6f89b3f

File tree

4 files changed

+151
-0
lines changed

4 files changed

+151
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @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.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @id python/header-injection
8+
* @tags security
9+
* external/cwe/cwe-113
10+
* external/cwe/cwe-079
11+
*/
12+
13+
// determine precision above
14+
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
19+
import DataFlow::PathGraph
20+
import semmle.python.frameworks.Flask
21+
22+
class WerkzeugHeader extends DataFlow::Node {
23+
WerkzeugHeader() {
24+
exists(DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead addMethod |
25+
headerInstance =
26+
API::moduleImport("werkzeug").getMember("datastructures").getMember("Headers").getACall() and
27+
addMethod.getAttributeName() = "add" and
28+
addMethod.getObject().getALocalSource() = headerInstance and
29+
this = addMethod.(DataFlow::CallCfgNode).getArg(1)
30+
)
31+
}
32+
}
33+
34+
class FlaskHeader extends DataFlow::Node {
35+
FlaskHeader() {
36+
exists(
37+
DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
38+
AssignStmt sinkDeclaration
39+
|
40+
headerInstance = API::moduleImport("flask").getMember("Response").getACall() and
41+
responseMethod.getAttributeName() = "headers" and
42+
responseMethod.getObject().getALocalSource() = headerInstance and
43+
sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
44+
this.asExpr() = sinkDeclaration.getValue()
45+
)
46+
}
47+
}
48+
49+
class FlaskMakeResponse extends DataFlow::Node {
50+
FlaskMakeResponse() {
51+
exists(
52+
DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
53+
AssignStmt sinkDeclaration
54+
|
55+
headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
56+
responseMethod.getAttributeName() = "headers" and
57+
responseMethod.getObject().getALocalSource() = headerInstance and
58+
(
59+
sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
60+
this.asExpr() = sinkDeclaration.getValue()
61+
)
62+
//or
63+
//extendMethod.getAttributeName() = "extend" and
64+
//extendMethod.getObject().getALocalSource() = responseMethod and
65+
//this = extendMethod.(DataFlow::CallCfgNode).getArg(0)
66+
)
67+
}
68+
}
69+
70+
class HeaderInjectionSink extends DataFlow::Node {
71+
HeaderInjectionSink() {
72+
this instanceof WerkzeugHeader or
73+
this instanceof FlaskHeader or
74+
this instanceof FlaskMakeResponse
75+
}
76+
}
77+
78+
class HeaderInjectionFlowConfig extends TaintTracking::Configuration {
79+
HeaderInjectionFlowConfig() { this = "HeaderInjectionFlowConfig" }
80+
81+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
82+
83+
override predicate isSink(DataFlow::Node sink) { sink instanceof HeaderInjectionSink }
84+
}
85+
86+
from HeaderInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
87+
where config.hasFlowPath(source, sink)
88+
select sink.getNode(), source, sink, "$@ header is constructed from a $@.", sink.getNode(), "This",
89+
source.getNode(), "user-provided value"

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

Whitespace-only changes.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import django.http
2+
3+
4+
def django_setitem(request):
5+
rfs_header = request.GET.get("rfs_header")
6+
response = django.http.HttpResponse()
7+
response.__setitem__('HeaderName', rfs_header)
8+
return response
9+
10+
11+
def django_response(request):
12+
rfs_header = request.GET.get("rfs_header")
13+
response = django.http.HttpResponse()
14+
response['HeaderName'] = rfs_header
15+
return response
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from flask import Response, request, Flask
2+
from werkzeug.datastructures import Headers
3+
4+
app = Flask(__name__)
5+
6+
7+
@app.route('/werkzeug_headers')
8+
def werkzeug_headers():
9+
rfs_header = request.args["rfs_header"]
10+
response = Response()
11+
headers = Headers()
12+
headers.add("HeaderName", rfs_header)
13+
response.headers = headers
14+
return response
15+
16+
17+
@app.route("/flask_Response")
18+
def flask_Response():
19+
rfs_header = request.args["rfs_header"]
20+
response = Response()
21+
response.headers['HeaderName'] = rfs_header
22+
return response
23+
24+
25+
@app.route("/flask_make_response")
26+
def flask_make_response():
27+
rfs_header = request.args["rfs_header"]
28+
resp = make_response("hello")
29+
resp.headers['HeaderName'] = rfs_header
30+
return resp
31+
32+
33+
@app.route("/flask_make_response_extend")
34+
def flask_make_response_extend():
35+
rfs_header = request.args["rfs_header"]
36+
resp = make_response("hello")
37+
resp.headers.extend(
38+
{'HeaderName': request.args["rfs_header"]})
39+
return resp
40+
41+
42+
@app.route("/Response_arg")
43+
def Response_arg():
44+
return Response(headers={'HeaderName': request.args["rfs_header"]})
45+
46+
# if __name__ == "__main__":
47+
# app.run(debug=True)

0 commit comments

Comments
 (0)