Skip to content

Commit cbe3964

Browse files
authored
Merge pull request github#8275 from haby0/py/add-ssrf-sinks
Python: Add Server-side Request Forgery sinks
2 parents 6aad8d6 + 7e6666b commit cbe3964

File tree

26 files changed

+557
-0
lines changed

26 files changed

+557
-0
lines changed

docs/codeql/support/reusables/frameworks.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,12 @@ Python built-in support
173173
starlette, Asynchronous Server Gateway Interface (ASGI)
174174
python-ldap, Lightweight Directory Access Protocol (LDAP)
175175
ldap3, Lightweight Directory Access Protocol (LDAP)
176+
httpx, HTTP client
177+
pycurl, HTTP client
176178
requests, HTTP client
179+
urllib, HTTP client
180+
urllib2, HTTP client
181+
urllib3, HTTP client
177182
dill, Serialization
178183
PyYAML, Serialization
179184
ruamel.yaml, Serialization
@@ -206,5 +211,6 @@ Python built-in support
206211
pycryptodomex, Cryptography library
207212
rsa, Cryptography library
208213
MarkupSafe, Escaping Library
214+
libtaxii, TAXII utility library
209215
libxml2, XML processing library
210216
lxml, XML processing library
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added new SSRF sinks for `httpx`, `pycurl`, `urllib`, `urllib2`, `urllib3`, and `libtaxii`. This improvement was [submitted by @haby0](https://github.com/github/codeql/pull/8275).

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ private import semmle.python.frameworks.FastApi
1919
private import semmle.python.frameworks.Flask
2020
private import semmle.python.frameworks.FlaskAdmin
2121
private import semmle.python.frameworks.FlaskSqlAlchemy
22+
private import semmle.python.frameworks.Httpx
2223
private import semmle.python.frameworks.Idna
2324
private import semmle.python.frameworks.Invoke
2425
private import semmle.python.frameworks.Jmespath
2526
private import semmle.python.frameworks.Ldap
2627
private import semmle.python.frameworks.Ldap3
28+
private import semmle.python.frameworks.Libtaxii
2729
private import semmle.python.frameworks.Libxml2
2830
private import semmle.python.frameworks.Lxml
2931
private import semmle.python.frameworks.MarkupSafe
@@ -32,6 +34,7 @@ private import semmle.python.frameworks.Mysql
3234
private import semmle.python.frameworks.MySQLdb
3335
private import semmle.python.frameworks.Peewee
3436
private import semmle.python.frameworks.Psycopg2
37+
private import semmle.python.frameworks.Pycurl
3538
private import semmle.python.frameworks.Pydantic
3639
private import semmle.python.frameworks.PyMySQL
3740
private import semmle.python.frameworks.Requests
@@ -46,5 +49,6 @@ private import semmle.python.frameworks.Toml
4649
private import semmle.python.frameworks.Tornado
4750
private import semmle.python.frameworks.Twisted
4851
private import semmle.python.frameworks.Ujson
52+
private import semmle.python.frameworks.Urllib3
4953
private import semmle.python.frameworks.Yaml
5054
private import semmle.python.frameworks.Yarl

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,3 +639,53 @@ module AiohttpWebModel {
639639
override DataFlow::Node getValueArg() { result = value }
640640
}
641641
}
642+
643+
/**
644+
* Provides models for the web server part (`aiohttp.client`) of the `aiohttp` PyPI package.
645+
* See https://docs.aiohttp.org/en/stable/client.html
646+
*/
647+
private module AiohttpClientModel {
648+
/**
649+
* Provides models for the `aiohttp.ClientSession` class
650+
*
651+
* See https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.ClientSession.
652+
*/
653+
module ClientSession {
654+
/** Gets a reference to the `aiohttp.ClientSession` class. */
655+
private API::Node classRef() {
656+
result = API::moduleImport("aiohttp").getMember("ClientSession")
657+
}
658+
659+
/** Gets a reference to an instance of `aiohttp.ClientSession`. */
660+
private API::Node instance() { result = classRef().getReturn() }
661+
662+
/** A method call on a ClientSession that sends off a request */
663+
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
664+
string methodName;
665+
666+
OutgoingRequestCall() {
667+
methodName in [HTTP::httpVerbLower(), "request"] and
668+
this = instance().getMember(methodName).getACall()
669+
}
670+
671+
override DataFlow::Node getAUrlPart() {
672+
result = this.getArgByName("url")
673+
or
674+
not methodName = "request" and
675+
result = this.getArg(0)
676+
or
677+
methodName = "request" and
678+
result = this.getArg(1)
679+
}
680+
681+
override string getFramework() { result = "aiohttp.ClientSession" }
682+
683+
override predicate disablesCertificateValidation(
684+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
685+
) {
686+
// TODO: Look into disabling certificate validation
687+
none()
688+
}
689+
}
690+
}
691+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `httpx` PyPI package.
3+
*
4+
* See
5+
* - https://pypi.org/project/httpx/
6+
* - https://www.python-httpx.org/
7+
*/
8+
9+
private import python
10+
private import semmle.python.Concepts
11+
private import semmle.python.ApiGraphs
12+
13+
/**
14+
* Provides models for the `httpx` PyPI package.
15+
*
16+
* See
17+
* - https://pypi.org/project/httpx/
18+
* - https://www.python-httpx.org/
19+
*/
20+
private module HttpxModel {
21+
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
22+
string methodName;
23+
24+
RequestCall() {
25+
methodName in [HTTP::httpVerbLower(), "request", "stream"] and
26+
this = API::moduleImport("httpx").getMember(methodName).getACall()
27+
}
28+
29+
override DataFlow::Node getAUrlPart() {
30+
result = this.getArgByName("url")
31+
or
32+
if methodName in ["request", "stream"]
33+
then result = this.getArg(1)
34+
else result = this.getArg(0)
35+
}
36+
37+
override string getFramework() { result = "httpx" }
38+
39+
override predicate disablesCertificateValidation(
40+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
41+
) {
42+
// TODO: Look into disabling certificate validation
43+
none()
44+
}
45+
}
46+
47+
/**
48+
* Provides models for the `httpx.[Async]Client` class
49+
*
50+
* See https://www.python-httpx.org/async/
51+
*/
52+
module Client {
53+
/** Get a reference to the `httpx.Client` or `httpx.AsyncClient` class. */
54+
private API::Node classRef() {
55+
result = API::moduleImport("httpx").getMember(["Client", "AsyncClient"])
56+
}
57+
58+
/** Get a reference to an `httpx.Client` or `httpx.AsyncClient` instance. */
59+
private API::Node instance() { result = classRef().getReturn() }
60+
61+
/** A method call on a Client that sends off a request */
62+
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
63+
string methodName;
64+
65+
OutgoingRequestCall() {
66+
methodName in [HTTP::httpVerbLower(), "request", "stream"] and
67+
this = instance().getMember(methodName).getACall()
68+
}
69+
70+
override DataFlow::Node getAUrlPart() {
71+
result = this.getArgByName("url")
72+
or
73+
if methodName in ["request", "stream"]
74+
then result = this.getArg(1)
75+
else result = this.getArg(0)
76+
}
77+
78+
override string getFramework() { result = "httpx.[Async]Client" }
79+
80+
override predicate disablesCertificateValidation(
81+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
82+
) {
83+
// TODO: Look into disabling certificate validation
84+
none()
85+
}
86+
}
87+
}
88+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `libtaxii` PyPI package.
3+
*
4+
* See
5+
* - https://pypi.org/project/libtaxii/
6+
* - https://github.com/TAXIIProject/libtaxii
7+
*/
8+
9+
private import python
10+
private import semmle.python.Concepts
11+
private import semmle.python.ApiGraphs
12+
13+
/**
14+
* Provides models for the `libtaxii` PyPI package.
15+
*
16+
* See
17+
* - https://pypi.org/project/libtaxii/
18+
* - https://github.com/TAXIIProject/libtaxii
19+
*/
20+
private module Libtaxii {
21+
/**
22+
* A call to `libtaxii.common.parse`.
23+
* When the `allow_url` parameter value is set to `True`, there is an SSRF vulnerability..
24+
*/
25+
private class ParseCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
26+
ParseCall() {
27+
this = API::moduleImport("libtaxii").getMember("common").getMember("parse").getACall() and
28+
this.getArgByName("allow_url").getALocalSource().asExpr() = any(True t)
29+
}
30+
31+
override DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("s")] }
32+
33+
override string getFramework() { result = "libtaxii.common.parse" }
34+
35+
override predicate disablesCertificateValidation(
36+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
37+
) {
38+
// TODO: Look into disabling certificate validation
39+
none()
40+
}
41+
}
42+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `pycurl` PyPI package.
3+
*
4+
* See
5+
* - https://pypi.org/project/pycurl/
6+
* - https://pycurl.io/docs/latest/
7+
*/
8+
9+
private import python
10+
private import semmle.python.Concepts
11+
private import semmle.python.ApiGraphs
12+
13+
/**
14+
* Provides models for the `pycurl` PyPI package.
15+
*
16+
* See
17+
* - https://pypi.org/project/pycurl/
18+
* - https://pycurl.io/docs/latest/
19+
*/
20+
private module Pycurl {
21+
/**
22+
* Provides models for the `pycurl.Curl` class
23+
*
24+
* See https://pycurl.io/docs/latest/curl.html.
25+
*/
26+
module Curl {
27+
/** Gets a reference to the `pycurl.Curl` class. */
28+
private API::Node classRef() { result = API::moduleImport("pycurl").getMember("Curl") }
29+
30+
/** Gets a reference to an instance of `pycurl.Curl`. */
31+
private API::Node instance() { result = classRef().getReturn() }
32+
33+
/**
34+
* When the first parameter value of the `setopt` function is set to `pycurl.URL`,
35+
* the second parameter value is the request resource link.
36+
*
37+
* See http://pycurl.io/docs/latest/curlobject.html#pycurl.Curl.setopt.
38+
*/
39+
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
40+
OutgoingRequestCall() {
41+
this = instance().getMember("setopt").getACall() and
42+
this.getArg(0).asCfgNode().(AttrNode).getName() = "URL"
43+
}
44+
45+
override DataFlow::Node getAUrlPart() {
46+
result in [this.getArg(1), this.getArgByName("value")]
47+
}
48+
49+
override string getFramework() { result = "pycurl.Curl" }
50+
51+
override predicate disablesCertificateValidation(
52+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
53+
) {
54+
// TODO: Look into disabling certificate validation
55+
none()
56+
}
57+
}
58+
}
59+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ private import semmle.python.frameworks.PEP249
1313
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
1414
private import semmle.python.frameworks.internal.SelfRefMixin
1515
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
16+
// modeling split over multiple files to keep this file from becoming too big
17+
private import semmle.python.frameworks.Stdlib.Urllib
18+
private import semmle.python.frameworks.Stdlib.Urllib2
1619

1720
/** Provides models for the Python standard library. */
1821
module Stdlib {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `urllib` module, part of
3+
* the Python standard library.
4+
*
5+
* See
6+
* - https://docs.python.org/2/library/urllib.html
7+
* - https://docs.python.org/3/library/urllib.html
8+
*/
9+
10+
private import python
11+
private import semmle.python.Concepts
12+
private import semmle.python.ApiGraphs
13+
14+
/**
15+
* Provides models for the `urllib` module, part of
16+
* the Python standard library.
17+
*
18+
* See
19+
* - https://docs.python.org/2/library/urllib.html
20+
* - https://docs.python.org/3/library/urllib.html
21+
*/
22+
private module Urllib {
23+
/**
24+
* Provides models for the `urllib.request` extension library
25+
*
26+
* See https://docs.python.org/3.9/library/urllib.request.html
27+
*/
28+
module Request {
29+
/**
30+
* See
31+
* - https://docs.python.org/3.9/library/urllib.request.html#urllib.request.Request
32+
*/
33+
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
34+
RequestCall() {
35+
this = API::moduleImport("urllib").getMember("request").getMember("Request").getACall()
36+
}
37+
38+
override DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("url")] }
39+
40+
override string getFramework() { result = "urllib.request.Request" }
41+
42+
override predicate disablesCertificateValidation(
43+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
44+
) {
45+
// TODO: Look into disabling certificate validation
46+
none()
47+
}
48+
}
49+
50+
/**
51+
* See
52+
* - https://docs.python.org/3.9/library/urllib.request.html#urllib.request.urlopen
53+
*/
54+
private class UrlOpenCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
55+
UrlOpenCall() {
56+
this = API::moduleImport("urllib").getMember("request").getMember("urlopen").getACall()
57+
}
58+
59+
override DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("url")] }
60+
61+
override string getFramework() { result = "urllib.request.urlopen" }
62+
63+
override predicate disablesCertificateValidation(
64+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
65+
) {
66+
// TODO: Look into disabling certificate validation
67+
none()
68+
}
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)