Skip to content

Commit b68d280

Browse files
committed
Python: Add modeling of requests
1 parent 1ff56d5 commit b68d280

File tree

4 files changed

+103
-15
lines changed

4 files changed

+103
-15
lines changed

docs/codeql/support/reusables/frameworks.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ Python built-in support
171171
Twisted, Web framework
172172
Flask-Admin, Web framework
173173
starlette, Asynchronous Server Gateway Interface (ASGI)
174+
requests, HTTP client
174175
dill, Serialization
175176
PyYAML, Serialization
176177
ruamel.yaml, Serialization

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ private import semmle.python.frameworks.Peewee
3030
private import semmle.python.frameworks.Psycopg2
3131
private import semmle.python.frameworks.Pydantic
3232
private import semmle.python.frameworks.PyMySQL
33+
private import semmle.python.frameworks.Requests
3334
private import semmle.python.frameworks.RestFramework
3435
private import semmle.python.frameworks.Rsa
3536
private import semmle.python.frameworks.RuamelYaml
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `requests` PyPI package.
3+
*
4+
* See
5+
* - https://pypi.org/project/requests/
6+
* - https://docs.python-requests.org/en/latest/
7+
*/
8+
9+
private import python
10+
private import semmle.python.Concepts
11+
private import semmle.python.ApiGraphs
12+
private import semmle.python.dataflow.new.DataFlow
13+
14+
/**
15+
* INTERNAL: Do not use.
16+
*
17+
* Provides models for the `requests` PyPI package.
18+
*
19+
* See
20+
* - https://pypi.org/project/requests/
21+
* - https://docs.python-requests.org/en/latest/
22+
*/
23+
private module Requests {
24+
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
25+
string methodName;
26+
27+
OutgoingRequestCall() {
28+
methodName in [HTTP::httpVerbLower(), "request"] and
29+
(
30+
this = API::moduleImport("requests").getMember(methodName).getACall()
31+
or
32+
exists(API::Node moduleExporting, API::Node sessionInstance |
33+
moduleExporting in [
34+
API::moduleImport("requests"), //
35+
API::moduleImport("requests").getMember("sessions")
36+
] and
37+
sessionInstance = moduleExporting.getMember(["Session", "session"]).getReturn()
38+
|
39+
this = sessionInstance.getMember(methodName).getACall()
40+
)
41+
)
42+
}
43+
44+
override DataFlow::Node getUrl() {
45+
result = this.getArgByName("url")
46+
or
47+
not methodName = "request" and
48+
result = this.getArg(0)
49+
or
50+
methodName = "request" and
51+
result = this.getArg(1)
52+
}
53+
54+
override DataFlow::Node getResponseBody() { none() }
55+
56+
/** Gets the `verify` argument to this outgoing requests call. */
57+
DataFlow::Node getVerifyArg() { result = this.getArgByName("verify") }
58+
59+
override predicate disablesCertificateValidation(
60+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
61+
) {
62+
disablingNode = this.getVerifyArg() and
63+
argumentOrigin = verifyArgBacktracker(disablingNode) and
64+
argumentOrigin.asExpr().(ImmutableLiteral).booleanValue() = false and
65+
not argumentOrigin.asExpr() instanceof None
66+
}
67+
68+
override string getFramework() { result = "requests" }
69+
}
70+
71+
/** Gets a back-reference to the verify argument `arg`. */
72+
private DataFlow::TypeTrackingNode verifyArgBacktracker(
73+
DataFlow::TypeBackTracker t, DataFlow::Node arg
74+
) {
75+
t.start() and
76+
arg = any(OutgoingRequestCall c).getVerifyArg() and
77+
result = arg.getALocalSource()
78+
or
79+
exists(DataFlow::TypeBackTracker t2 | result = verifyArgBacktracker(t2, arg).backtrack(t2, t))
80+
}
81+
82+
/** Gets a back-reference to the verify argument `arg`. */
83+
private DataFlow::LocalSourceNode verifyArgBacktracker(DataFlow::Node arg) {
84+
result = verifyArgBacktracker(DataFlow::TypeBackTracker::end(), arg)
85+
}
86+
}
Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,50 @@
11
import requests
22

3-
resp = requests.get("url") # $ MISSING: clientRequestUrl="url"
4-
resp = requests.get(url="url") # $ MISSING: clientRequestUrl="url"
3+
resp = requests.get("url") # $ clientRequestUrl="url"
4+
resp = requests.get(url="url") # $ clientRequestUrl="url"
55

6-
resp = requests.request("GET", "url") # $ MISSING: clientRequestUrl="url"
6+
resp = requests.request("GET", "url") # $ clientRequestUrl="url"
77

88
with requests.Session() as session:
9-
resp = session.get("url") # $ MISSING: clientRequestUrl="url"
10-
resp = session.request(method="GET", url="url") # $ MISSING: clientRequestUrl="url"
9+
resp = session.get("url") # $ clientRequestUrl="url"
10+
resp = session.request(method="GET", url="url") # $ clientRequestUrl="url"
1111

1212
s = requests.Session()
13-
resp = s.get("url") # $ MISSING: clientRequestUrl="url"
13+
resp = s.get("url") # $ clientRequestUrl="url"
1414

1515
s = requests.session()
16-
resp = s.get("url") # $ MISSING: clientRequestUrl="url"
16+
resp = s.get("url") # $ clientRequestUrl="url"
1717

1818
# test full import path for Session
1919
with requests.sessions.Session() as session:
20-
resp = session.get("url") # $ MISSING: clientRequestUrl="url"
20+
resp = session.get("url") # $ clientRequestUrl="url"
2121

2222
# Low level access
2323
req = requests.Request("GET", "url") # $ MISSING: clientRequestUrl="url"
2424
resp = s.send(req.prepare())
2525

2626
# other methods than GET
27-
resp = requests.post("url") # $ MISSING: clientRequestUrl="url"
28-
resp = requests.patch("url") # $ MISSING: clientRequestUrl="url"
29-
resp = requests.options("url") # $ MISSING: clientRequestUrl="url"
27+
resp = requests.post("url") # $ clientRequestUrl="url"
28+
resp = requests.patch("url") # $ clientRequestUrl="url"
29+
resp = requests.options("url") # $ clientRequestUrl="url"
3030

3131
# ==============================================================================
3232
# Disabling certificate validation
3333
# ==============================================================================
3434

35-
resp = requests.get("url", verify=False) # $ MISSING: clientRequestUrl="url" clientRequestCertValidationDisabled
35+
resp = requests.get("url", verify=False) # $ clientRequestUrl="url" clientRequestCertValidationDisabled
3636

3737
def make_get(verify_arg):
38-
resp = requests.get("url", verify=verify_arg) # $ MISSING: clientRequestUrl="url" clientRequestCertValidationDisabled
38+
resp = requests.get("url", verify=verify_arg) # $ clientRequestUrl="url" clientRequestCertValidationDisabled
3939

4040
make_get(False)
4141

4242

4343
with requests.Session() as session:
4444
# see https://github.com/psf/requests/blob/39d0fdd9096f7dceccbc8f82e1eda7dd64717a8e/requests/sessions.py#L621
4545
session.verify = False
46-
resp = session.get("url") # $ MISSING: clientRequestUrl="url" clientRequestCertValidationDisabled
47-
resp = session.get("url", verify=True) # $ MISSING: clientRequestUrl="url"
46+
resp = session.get("url") # $ clientRequestUrl="url" MISSING: clientRequestCertValidationDisabled
47+
resp = session.get("url", verify=True) # $ clientRequestUrl="url"
4848

4949
req = requests.Request("GET", "url") # $ MISSING: clientRequestUrl="url"
5050
resp = session.send(req.prepare()) # $ MISSING: clientRequestCertValidationDisabled

0 commit comments

Comments
 (0)