Skip to content

Commit 7619d0f

Browse files
committed
Python: FastAPI: Model WebSocket usage
1 parent b69977b commit 7619d0f

File tree

6 files changed

+310
-42
lines changed

6 files changed

+310
-42
lines changed

docs/codeql/support/reusables/frameworks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ Python built-in support
159159
Flask, Web framework
160160
Tornado, Web framework
161161
Twisted, Web framework
162+
starlette, Asynchronous Server Gateway Interface (ASGI)
162163
PyYAML, Serialization
163164
dill, Serialization
164165
simplejson, Serialization

python/ql/lib/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ private import semmle.python.frameworks.PyMySQL
2929
private import semmle.python.frameworks.Rsa
3030
private import semmle.python.frameworks.Simplejson
3131
private import semmle.python.frameworks.SqlAlchemy
32+
private import semmle.python.frameworks.Starlette
3233
private import semmle.python.frameworks.Stdlib
3334
private import semmle.python.frameworks.Tornado
3435
private import semmle.python.frameworks.Twisted

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ private import semmle.python.dataflow.new.TaintTracking
1010
private import semmle.python.Concepts
1111
private import semmle.python.ApiGraphs
1212
private import semmle.python.frameworks.Pydantic
13+
private import semmle.python.frameworks.Starlette
1314

1415
/**
1516
* Provides models for the `fastapi` PyPI package.
@@ -49,7 +50,7 @@ private module FastApi {
4950
exists(string routeAddingMethod |
5051
routeAddingMethod = HTTP::httpVerbLower()
5152
or
52-
routeAddingMethod = "api_route"
53+
routeAddingMethod in ["api_route", "websocket"]
5354
|
5455
this = App::instance().getMember(routeAddingMethod).getACall()
5556
or
@@ -94,6 +95,17 @@ private module FastApi {
9495
// ---------------------------------------------------------------------------
9596
// Response modeling
9697
// ---------------------------------------------------------------------------
98+
/**
99+
* A parameter to a request handler that has a WebSocket type-annotation.
100+
*/
101+
private class WebSocketRequestHandlerParam extends Starlette::WebSocket::InstanceSource,
102+
DataFlow::ParameterNode {
103+
WebSocketRequestHandlerParam() {
104+
this.getParameter().getAnnotation() = Starlette::WebSocket::classRef().getAUse().asExpr() and
105+
any(FastApiRouteSetup rs).getARequestHandler().getArgByName(_) = this.getParameter()
106+
}
107+
}
108+
97109
/**
98110
* Provides models for the `fastapi.Response` class and subclasses.
99111
*
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `starlette` PyPI package.
3+
*
4+
* See
5+
* - https://pypi.org/project/starlette/
6+
* - https://www.starlette.io/
7+
*/
8+
9+
private import python
10+
private import semmle.python.dataflow.new.DataFlow
11+
private import semmle.python.dataflow.new.TaintTracking
12+
private import semmle.python.Concepts
13+
private import semmle.python.ApiGraphs
14+
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
15+
private import semmle.python.frameworks.Stdlib
16+
17+
/**
18+
* INTERNAL: Do not use.
19+
*
20+
* Provides models for `starlette` PyPI package.
21+
*
22+
* See
23+
* - https://pypi.org/project/starlette/
24+
* - https://www.starlette.io/
25+
*/
26+
module Starlette {
27+
/**
28+
* Provides models for the `starlette.websockets.WebSocket` class
29+
*
30+
* See https://www.starlette.io/websockets/.
31+
*/
32+
module WebSocket {
33+
/** Gets a reference to the `starlette.websockets.WebSocket` class. */
34+
API::Node classRef() {
35+
result = API::moduleImport("starlette").getMember("websockets").getMember("WebSocket")
36+
or
37+
result = API::moduleImport("fastapi").getMember("WebSocket")
38+
}
39+
40+
/**
41+
* A source of instances of `starlette.websockets.WebSocket`, extend this class to model new instances.
42+
*
43+
* This can include instantiations of the class, return values from function
44+
* calls, or a special parameter that will be set when functions are called by an external
45+
* library.
46+
*
47+
* Use the predicate `WebSocket::instance()` to get references to instances of `starlette.websockets.WebSocket`.
48+
*/
49+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
50+
51+
/** A direct instantiation of `starlette.websockets.WebSocket`. */
52+
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
53+
ClassInstantiation() { this = classRef().getACall() }
54+
}
55+
56+
/** Gets a reference to an instance of `starlette.websockets.WebSocket`. */
57+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
58+
t.start() and
59+
result instanceof InstanceSource
60+
or
61+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
62+
}
63+
64+
/** Gets a reference to an instance of `starlette.websockets.WebSocket`. */
65+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
66+
67+
/**
68+
* Taint propagation for `starlette.websockets.WebSocket`.
69+
*/
70+
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
71+
InstanceTaintSteps() { this = "starlette.websockets.WebSocket" }
72+
73+
override DataFlow::Node getInstance() { result = instance() }
74+
75+
override string getAttributeName() { result in ["url", "headers", "query_params", "cookies"] }
76+
77+
override string getMethodName() { none() }
78+
79+
override string getAsyncMethodName() {
80+
result in [
81+
"receive", "receive_bytes", "receive_text", "receive_json", "iter_bytes", "iter_text",
82+
"iter_json"
83+
]
84+
}
85+
}
86+
87+
/** An attribute read on a `starlette.websockets.WebSocket` instance that is a `starlette.requests.URL` instance. */
88+
private class UrlInstances extends URL::InstanceSource {
89+
UrlInstances() {
90+
this.(DataFlow::AttrRead).getObject() = instance() and
91+
this.(DataFlow::AttrRead).getAttributeName() = "url"
92+
}
93+
}
94+
}
95+
96+
/**
97+
* Provides models for the `starlette.requests.URL` class
98+
*
99+
* See the URL part of https://www.starlette.io/websockets/.
100+
*/
101+
module URL {
102+
/** Gets a reference to the `starlette.requests.URL` class. */
103+
private API::Node classRef() {
104+
result = API::moduleImport("starlette").getMember("requests").getMember("URL")
105+
}
106+
107+
/**
108+
* A source of instances of `starlette.requests.URL`, extend this class to model new instances.
109+
*
110+
* This can include instantiations of the class, return values from function
111+
* calls, or a special parameter that will be set when functions are called by an external
112+
* library.
113+
*
114+
* Use the predicate `URL::instance()` to get references to instances of `starlette.requests.URL`.
115+
*/
116+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
117+
118+
/** A direct instantiation of `starlette.requests.URL`. */
119+
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
120+
ClassInstantiation() { this = classRef().getACall() }
121+
}
122+
123+
/** Gets a reference to an instance of `starlette.requests.URL`. */
124+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
125+
t.start() and
126+
result instanceof InstanceSource
127+
or
128+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
129+
}
130+
131+
/** Gets a reference to an instance of `starlette.requests.URL`. */
132+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
133+
134+
/**
135+
* Taint propagation for `starlette.requests.URL`.
136+
*/
137+
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
138+
InstanceTaintSteps() { this = "starlette.requests.URL" }
139+
140+
override DataFlow::Node getInstance() { result = instance() }
141+
142+
override string getAttributeName() {
143+
result in [
144+
"components", "netloc", "path", "query", "fragment", "username", "password", "hostname",
145+
"port"
146+
]
147+
}
148+
149+
override string getMethodName() { none() }
150+
151+
override string getAsyncMethodName() { none() }
152+
}
153+
154+
/** An attribute read on a `starlette.requests.URL` instance that is a `urllib.parse.SplitResult` instance. */
155+
private class UrlSplitInstances extends Stdlib::SplitResult::InstanceSource {
156+
UrlSplitInstances() {
157+
this.(DataFlow::AttrRead).getObject() = instance() and
158+
this.(DataFlow::AttrRead).getAttributeName() = "components"
159+
}
160+
}
161+
}
162+
}

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

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,74 @@ module Stdlib {
167167
override string getAsyncMethodName() { none() }
168168
}
169169
}
170+
171+
/**
172+
* Provides models for the `urllib.parse.SplitResult` class
173+
*
174+
* See https://docs.python.org/3.9/library/urllib.parse.html#urllib.parse.SplitResult.
175+
*/
176+
module SplitResult {
177+
/** Gets a reference to the `urllib.parse.SplitResult` class. */
178+
private API::Node classRef() {
179+
result = API::moduleImport("urllib").getMember("parse").getMember("SplitResult")
180+
}
181+
182+
/**
183+
* A source of instances of `urllib.parse.SplitResult`, extend this class to model new instances.
184+
*
185+
* This can include instantiations of the class, return values from function
186+
* calls, or a special parameter that will be set when functions are called by an external
187+
* library.
188+
*
189+
* Use the predicate `SplitResult::instance()` to get references to instances of `urllib.parse.SplitResult`.
190+
*/
191+
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
192+
193+
/** A direct instantiation of `urllib.parse.SplitResult`. */
194+
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
195+
ClassInstantiation() { this = classRef().getACall() }
196+
}
197+
198+
/** Gets a reference to an instance of `urllib.parse.SplitResult`. */
199+
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
200+
t.start() and
201+
result instanceof InstanceSource
202+
or
203+
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
204+
}
205+
206+
/** Gets a reference to an instance of `urllib.parse.SplitResult`. */
207+
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
208+
209+
/**
210+
* Taint propagation for `urllib.parse.SplitResult`.
211+
*/
212+
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
213+
InstanceTaintSteps() { this = "urllib.parse.SplitResult" }
214+
215+
override DataFlow::Node getInstance() { result = instance() }
216+
217+
override string getAttributeName() {
218+
result in [
219+
"netloc", "path", "query", "fragment", "username", "password", "hostname", "port"
220+
]
221+
}
222+
223+
override string getMethodName() { none() }
224+
225+
override string getAsyncMethodName() { none() }
226+
}
227+
228+
/**
229+
* Extra taint propagation for `urllib.parse.SplitResult`, not covered by `InstanceTaintSteps`.
230+
*/
231+
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
232+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
233+
// TODO
234+
none()
235+
}
236+
}
237+
}
170238
}
171239

172240
/**
@@ -1749,6 +1817,30 @@ private module StdlibPrivate {
17491817

17501818
override string getKind() { result = Escaping::getRegexKind() }
17511819
}
1820+
1821+
// ---------------------------------------------------------------------------
1822+
// urllib
1823+
// ---------------------------------------------------------------------------
1824+
/**
1825+
* A call to `urllib.parse.urlsplit`
1826+
*
1827+
* See https://docs.python.org/3.9/library/urllib.parse.html#urllib.parse.urlsplit
1828+
*/
1829+
class UrllibParseUrlsplitCall extends Stdlib::SplitResult::InstanceSource, DataFlow::CallCfgNode {
1830+
UrllibParseUrlsplitCall() {
1831+
this = API::moduleImport("urllib").getMember("parse").getMember("urlsplit").getACall()
1832+
}
1833+
1834+
/** Gets the argument that specifies the URL. */
1835+
DataFlow::Node getUrl() { result in [this.getArg(0), this.getArgByName("url")] }
1836+
}
1837+
1838+
/** Extra taint-step such that the result of `urllib.parse.urlsplit(tainted_string)` is tainted. */
1839+
private class UrllibParseUrlsplitCallAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
1840+
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
1841+
nodeTo.(UrllibParseUrlsplitCall).getUrl() = nodeFrom
1842+
}
1843+
}
17521844
}
17531845

17541846
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)