Skip to content

Commit 5c8c99d

Browse files
committed
Add header support for bottle and tornado
1 parent 642ec38 commit 5c8c99d

File tree

4 files changed

+129
-0
lines changed

4 files changed

+129
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: majorAnalysis
3+
---
4+
* Added modeling of the `bottle` and `tornado` framework, leading to new remote flow sources and header writes
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `bottle` PyPI package.
3+
* See https://www.tornadoweb.org/en/stable/.
4+
*/
5+
6+
private import python
7+
private import semmle.python.Concepts
8+
private import semmle.python.dataflow.new.DataFlow
9+
private import semmle.python.ApiGraphs
10+
private import semmle.python.dataflow.new.RemoteFlowSources
11+
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
12+
13+
/**
14+
* INTERNAL: Do not use.
15+
*
16+
* Provides models for the `bottle` PyPI package.
17+
* See https://www.tornadoweb.org/en/stable/.
18+
*/
19+
module Bottle {
20+
module BottleModule {
21+
API::Node bottle() { result = API::moduleImport("bottle") }
22+
23+
module Response {
24+
API::Node response() {
25+
result = bottle().getMember("response")
26+
//or
27+
//result = ModelOutput::getATypeNode("tornado.web.RequestHandler~Subclass").getASubclass*()
28+
}
29+
30+
/**
31+
* A call to the `bottle.web.RequestHandler.set_header` method.
32+
*
33+
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.set_header
34+
*/
35+
class BottleRequestHandlerSetHeaderCall extends Http::Server::ResponseHeaderWrite::Range,
36+
DataFlow::MethodCallNode
37+
{
38+
BottleRequestHandlerSetHeaderCall() {
39+
this = response().getMember(["set_header", "add_header"]).getACall()
40+
}
41+
42+
override DataFlow::Node getNameArg() {
43+
result in [this.getArg(0), this.getArgByName("name")]
44+
}
45+
46+
override DataFlow::Node getValueArg() {
47+
result in [this.getArg(1), this.getArgByName("value")]
48+
}
49+
50+
override predicate nameAllowsNewline() { none() }
51+
52+
override predicate valueAllowsNewline() { none() }
53+
}
54+
55+
module Request {
56+
API::Node request() { result = bottle().getMember("request") }
57+
58+
private class Request extends RemoteFlowSource::Range {
59+
Request() { this = request().asSource() }
60+
61+
//or
62+
//result = ModelOutput::getATypeNode("tornado.web.RequestHandler~Subclass").getASubclass*()
63+
override string getSourceType() { result = "bottle.request" }
64+
}
65+
66+
/**
67+
* Taint propagation for `bottle.request`.
68+
*
69+
* See https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request
70+
*/
71+
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
72+
InstanceTaintSteps() { this = "bottle.request" }
73+
74+
override DataFlow::Node getInstance() {
75+
result = request().getAValueReachableFromSource()
76+
}
77+
78+
override string getAttributeName() {
79+
result in ["headers", "query", "forms", "params", "json", "url"]
80+
}
81+
82+
override string getMethodName() { none() }
83+
84+
override string getAsyncMethodName() { none() }
85+
}
86+
}
87+
88+
module Header {
89+
API::Node instance() {
90+
result = bottle().getMember("response").getMember("headers")
91+
//or
92+
//result = ModelOutput::getATypeNode("tornado.web.RequestHandler~Subclass").getASubclass*()
93+
}
94+
95+
/** A dict-like write to a response header. */
96+
class HeaderWriteSubscript extends Http::Server::ResponseHeaderWrite::Range, DataFlow::Node {
97+
API::Node name;
98+
API::Node value;
99+
100+
HeaderWriteSubscript() {
101+
exists(API::Node holder |
102+
holder = instance() and
103+
this = holder.asSource() and
104+
value = holder.getSubscriptAt(name)
105+
)
106+
}
107+
108+
//name = instance().getASubscript().getIndex().asSink()
109+
override DataFlow::Node getNameArg() { result = name.asSink() }
110+
111+
override DataFlow::Node getValueArg() { result = value.asSink() }
112+
113+
// TODO: These checks perhaps could be made more precise.
114+
override predicate nameAllowsNewline() { none() }
115+
116+
override predicate valueAllowsNewline() { none() }
117+
}
118+
}
119+
}
120+
}
121+
}
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

0 commit comments

Comments
 (0)