Skip to content

Commit 2a04598

Browse files
Add models for responses
1 parent 86d1e5b commit 2a04598

File tree

4 files changed

+121
-6
lines changed

4 files changed

+121
-6
lines changed

python/ql/lib/semmle/python/frameworks/Pyramid.qll

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
private import python
7-
private import semmle.python.dataflow.new.DataFlow
87
private import semmle.python.dataflow.new.RemoteFlowSources
98
private import semmle.python.dataflow.new.TaintTracking
109
private import semmle.python.Concepts
@@ -141,4 +140,95 @@ module Pyramid {
141140
}
142141
}
143142
}
143+
144+
module Response {
145+
private class PyramidReturnResponse extends Http::Server::HttpResponse::Range {
146+
PyramidReturnResponse() {
147+
this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() and
148+
not this = instance()
149+
}
150+
151+
override DataFlow::Node getBody() { result = this }
152+
153+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
154+
155+
override string getMimetypeDefault() { result = "text/html" }
156+
}
157+
158+
private API::Node classRef() {
159+
result = API::moduleImport("pyramid").getMember("response").getMember("Response")
160+
}
161+
162+
abstract class InstanceSource extends DataFlow::LocalSourceNode,
163+
Http::Server::HttpResponse::Range
164+
{ }
165+
166+
/** Gets a reference to an instance of `pyramid.request.Request`. */
167+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
168+
t.start() and
169+
result instanceof InstanceSource
170+
or
171+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
172+
}
173+
174+
/** Gets a reference to an instance of `pyramid.request.Request`. */
175+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
176+
177+
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
178+
ClassInstantiation() { this = classRef().getACall() }
179+
180+
override DataFlow::Node getBody() { result = [this.getArg(0), this.getArgByName("body")] }
181+
182+
override DataFlow::Node getMimetypeOrContentTypeArg() {
183+
result = [this.getArg(4), this.getArgByName("content_type")]
184+
}
185+
186+
override string getMimetypeDefault() { result = "text/html" }
187+
}
188+
189+
private class ResponseBodySet extends Http::Server::HttpResponse::Range instanceof DataFlow::AttrWrite
190+
{
191+
string attrName;
192+
193+
ResponseBodySet() {
194+
this.getObject() = instance() and
195+
this.getAttributeName() = attrName and
196+
attrName in ["body", "body_file", "json", "json_body", "text", "ubody", "unicode_body"]
197+
}
198+
199+
override DataFlow::Node getBody() { result = this.(DataFlow::AttrWrite).getValue() }
200+
201+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
202+
203+
override string getMimetypeDefault() {
204+
if attrName in ["json", "json_body"]
205+
then result = "application/json"
206+
else result = "text/html"
207+
}
208+
}
209+
210+
private class RequestResponseAttr extends InstanceSource instanceof DataFlow::AttrRead {
211+
RequestResponseAttr() {
212+
this.getObject() = Request::instance() and this.getAttributeName() = "response"
213+
}
214+
215+
override DataFlow::Node getBody() { none() }
216+
217+
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
218+
219+
override string getMimetypeDefault() { result = "text/html" }
220+
}
221+
222+
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
223+
SetCookieCall() { this.calls(instance(), "set_cookie") }
224+
225+
override DataFlow::Node getHeaderArg() { none() }
226+
227+
override DataFlow::Node getNameArg() { result = [this.getArg(0), this.getArgByName("name")] }
228+
229+
override DataFlow::Node getValueArg() {
230+
result = [this.getArg(1), this.getArgByName("value")]
231+
}
232+
}
233+
}
144234
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
testFailures
2+
failures
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import python
2+
import experimental.meta.ConceptsTest

python/ql/test/library-tests/frameworks/pyramid/pyramid_test.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
from pyramid.view import view_config
22
from pyramid.config import Configurator
3+
from pyramid.response import Response
4+
from wsgiref.simple_server import make_server
5+
6+
def ignore(*args, **kwargs): pass
7+
ensure_tainted = ensure_not_tainted = ignore
38

49
@view_config(route_name="test1")
5-
def test1(request):
10+
def test1(request): # $ requestHandler routedParameter=request
611
ensure_tainted(
712
request, # $ tainted
813

@@ -65,13 +70,29 @@ def test1(request):
6570
request.copy_get().body # $ tainted
6671
)
6772

68-
def test2(request):
73+
return Response("Ok") # $ HttpResponse responseBody="Ok" mimetype=text/html
74+
75+
def test2(request): # $ requestHandler routedParameter=request
6976
ensure_tainted(request) # $ tainted
7077

71-
@view_config(route_name="test1")
72-
def test3(context, request):
78+
resp = Response("Ok", content_type="text/plain") # $ HttpResponse responseBody="Ok" mimetype=text/plain
79+
resp.body = "Ok2" # $ HttpResponse responseBody="Ok2" SPURIOUS: mimetype=text/html
80+
return resp
81+
82+
@view_config(route_name="test3", renderer="string")
83+
def test3(context, request): # $ requestHandler routedParameter=request
7384
ensure_tainted(request) # $ tainted
85+
resp = request.response # $ HttpResponse mimetype=text/html
86+
resp.set_cookie("hi", "there") # $ CookieWrite CookieName="hi" CookieValue="there"
87+
resp.set_cookie(value="there", name="hi") # $ CookieWrite CookieName="hi" CookieValue="there"
88+
return "Ok" # $ HttpResponse responseBody="Ok" mimetype=text/html
7489

7590
if __name__ == "__main__":
7691
with Configurator() as config:
77-
config.add_view(test2, route_name="test2")
92+
for i in range(1,4):
93+
config.add_route(f"test{i}", f"/test{i}")
94+
config.add_view(test2, route_name="test2")
95+
config.scan()
96+
server = make_server('127.0.0.1', 8000, config.make_wsgi_app())
97+
print("serving")
98+
server.serve_forever()

0 commit comments

Comments
 (0)