Skip to content

Commit b23e28a

Browse files
committed
add Server-side Request Forgery sinks
1 parent 958fd9b commit b23e28a

File tree

8 files changed

+417
-0
lines changed

8 files changed

+417
-0
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@ 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.MarkupSafe
2830
private import semmle.python.frameworks.Multidict
2931
private import semmle.python.frameworks.Mysql
3032
private import semmle.python.frameworks.MySQLdb
3133
private import semmle.python.frameworks.Peewee
3234
private import semmle.python.frameworks.Psycopg2
35+
private import semmle.python.frameworks.Pycurl
3336
private import semmle.python.frameworks.Pydantic
3437
private import semmle.python.frameworks.PyMySQL
3538
private import semmle.python.frameworks.Requests
@@ -44,5 +47,8 @@ private import semmle.python.frameworks.Toml
4447
private import semmle.python.frameworks.Tornado
4548
private import semmle.python.frameworks.Twisted
4649
private import semmle.python.frameworks.Ujson
50+
private import semmle.python.frameworks.Urllib
51+
private import semmle.python.frameworks.Urllib2
52+
private import semmle.python.frameworks.Urllib3
4753
private import semmle.python.frameworks.Yaml
4854
private import semmle.python.frameworks.Yarl

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,3 +639,54 @@ 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+
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+
DataFlow::Node getUrlArg() {
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 DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
682+
683+
override string getFramework() { result = "aiohttp.ClientSession" }
684+
685+
override predicate disablesCertificateValidation(
686+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
687+
) {
688+
none()
689+
}
690+
}
691+
}
692+
}
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+
* See https://www.python-httpx.org/
4+
*/
5+
6+
private import python
7+
private import semmle.python.Concepts
8+
private import semmle.python.ApiGraphs
9+
10+
/**
11+
* Provides models for the `httpx` PyPI package.
12+
* see https://www.python-httpx.org/
13+
*/
14+
module HttpxModel {
15+
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
16+
string methodName;
17+
18+
RequestCall() {
19+
methodName in [HTTP::httpVerbLower(), "request", "stream"] and
20+
this = API::moduleImport("httpx").getMember(methodName).getACall()
21+
}
22+
23+
DataFlow::Node getUrlArg() {
24+
result = this.getArgByName("url")
25+
or
26+
not methodName = "request" and
27+
result = this.getArg(0)
28+
or
29+
methodName in ["request", "stream"] and
30+
result = this.getArg(1)
31+
}
32+
33+
override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
34+
35+
override string getFramework() { result = "httpx" }
36+
37+
override predicate disablesCertificateValidation(
38+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
39+
) {
40+
none()
41+
}
42+
}
43+
44+
/**
45+
* Provides models for the `httpx.[Async]Client` class
46+
*
47+
* See https://www.python-httpx.org/async/
48+
*/
49+
module Client {
50+
/** Get a reference to the `httpx.Client` or `httpx.AsyncClient` class. */
51+
private API::Node classRef() {
52+
result = API::moduleImport("httpx").getMember(["Client", "AsyncClient"])
53+
}
54+
55+
/** Get a reference to an `httpx.Client` or `httpx.AsyncClient` instance. */
56+
private API::Node instance() { result = classRef().getReturn() }
57+
58+
/** A method call on a Client that sends off a request */
59+
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
60+
string methodName;
61+
62+
OutgoingRequestCall() {
63+
methodName in [HTTP::httpVerbLower(), "request", "stream"] and
64+
this = instance().getMember(methodName).getACall()
65+
}
66+
67+
DataFlow::Node getUrlArg() {
68+
result = this.getArgByName("url")
69+
or
70+
not methodName = "request" and
71+
result = this.getArg(0)
72+
or
73+
methodName in ["request", "stream"] and
74+
result = this.getArg(1)
75+
}
76+
77+
override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
78+
79+
override string getFramework() { result = "httpx.[Async]Client" }
80+
81+
override predicate disablesCertificateValidation(
82+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
83+
) {
84+
none()
85+
}
86+
}
87+
}
88+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `libtaxii` PyPI package.
3+
* See https://github.com/TAXIIProject/libtaxii
4+
*/
5+
6+
private import python
7+
private import semmle.python.Concepts
8+
private import semmle.python.ApiGraphs
9+
10+
/**
11+
* Provides models for the `libtaxii` PyPI package.
12+
* see https://github.com/TAXIIProject/libtaxii
13+
*/
14+
module Libtaxii {
15+
/**
16+
* A call to `libtaxii.common.parse`.
17+
* When the `allow_url` parameter value is set to `True`, there is an SSRF vulnerability..
18+
*/
19+
private class ParseCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
20+
ParseCall() {
21+
this = API::moduleImport("libtaxii").getMember("common").getMember("parse").getACall() and
22+
this.getArgByName("allow_url").asExpr().toString() = "True"
23+
}
24+
25+
DataFlow::Node getUrlArg() { result in [this.getArg(0), this.getArgByName("s")] }
26+
27+
override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
28+
29+
override string getFramework() { result = "libtaxii.common.parse" }
30+
31+
override predicate disablesCertificateValidation(
32+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
33+
) {
34+
none()
35+
}
36+
}
37+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `pycurl` PyPI package.
3+
* See https://pycurl.io/docs/latest/
4+
*/
5+
6+
private import python
7+
private import semmle.python.Concepts
8+
private import semmle.python.ApiGraphs
9+
10+
/**
11+
* Provides models for the `pycurl` PyPI package.
12+
* see https://pycurl.io/docs/latest/
13+
*/
14+
module Pycurl {
15+
/**
16+
* Provides models for the `pycurl.Curl` class
17+
*
18+
* See https://pycurl.io/docs/latest/curl.html.
19+
*/
20+
module Curl {
21+
/** Gets a reference to the `pycurl.Curl` class. */
22+
private API::Node classRef() { result = API::moduleImport("pycurl").getMember("Curl") }
23+
24+
/** Gets a reference to an instance of `pycurl.Curl`. */
25+
private API::Node instance() { result = classRef().getReturn() }
26+
27+
/**
28+
* When the first parameter value of the `setopt` function is set to `pycurl.URL`,
29+
* the second parameter value is the request resource link.
30+
*
31+
* See https://pycurl.io/docs/latest/curl.html#set_option.
32+
*/
33+
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
34+
OutgoingRequestCall() {
35+
this = instance().getMember("setopt").getACall() and
36+
this.getArg(0).asCfgNode().(AttrNode).getName() = "URL"
37+
}
38+
39+
DataFlow::Node getUrlArg() { result in [this.getArg(1), this.getArgByName("value")] }
40+
41+
override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
42+
43+
override string getFramework() { result = "pycurl.Curl" }
44+
45+
override predicate disablesCertificateValidation(
46+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
47+
) {
48+
none()
49+
}
50+
}
51+
}
52+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `urllib` PyPI package.
3+
* See https://docs.python.org/3.9/library/urllib.html
4+
*/
5+
6+
private import python
7+
private import semmle.python.Concepts
8+
private import semmle.python.ApiGraphs
9+
10+
/**
11+
* Provides models for the `Urllib` PyPI package.
12+
* see https://docs.python.org/3.9/library/urllib.html
13+
*/
14+
module Urllib {
15+
/**
16+
* Provides models for the `urllib.request` extension library
17+
*
18+
* See https://docs.python.org/3.9/library/urllib.request.html
19+
*/
20+
module Request {
21+
/**
22+
* See
23+
* - https://docs.python.org/3.9/library/urllib.request.html#urllib.request.Request
24+
*/
25+
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
26+
RequestCall() {
27+
this = API::moduleImport("urllib").getMember("request").getMember("Request").getACall()
28+
}
29+
30+
DataFlow::Node getUrlArg() { result in [this.getArg(0), this.getArgByName("url")] }
31+
32+
override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
33+
34+
override string getFramework() { result = "urllib.request.Request" }
35+
36+
override predicate disablesCertificateValidation(
37+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
38+
) {
39+
none()
40+
}
41+
}
42+
43+
/**
44+
* See
45+
* - https://docs.python.org/3.9/library/urllib.request.html#urllib.request.urlopen
46+
*/
47+
private class UrlOpenCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
48+
UrlOpenCall() {
49+
this = API::moduleImport("urllib").getMember("request").getMember("urlopen").getACall()
50+
}
51+
52+
DataFlow::Node getUrlArg() { result in [this.getArg(0), this.getArgByName("url")] }
53+
54+
override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
55+
56+
override string getFramework() { result = "urllib.request.urlopen" }
57+
58+
override predicate disablesCertificateValidation(
59+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
60+
) {
61+
none()
62+
}
63+
}
64+
}
65+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `urllib2` PyPI package.
3+
* See https://docs.python.org/2/library/urllib2.html
4+
*/
5+
6+
private import python
7+
private import semmle.python.Concepts
8+
private import semmle.python.ApiGraphs
9+
10+
/**
11+
* Provides models for the `urllib2` PyPI package.
12+
* see https://docs.python.org/2/library/urllib2.html
13+
*/
14+
module Urllib2 {
15+
/**
16+
* See
17+
* - https://docs.python.org/2/library/urllib2.html#urllib2.Request
18+
*/
19+
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
20+
RequestCall() {
21+
this = API::moduleImport("urllib2").getMember("Request").getACall()
22+
}
23+
24+
DataFlow::Node getUrlArg() { result in [this.getArg(0), this.getArgByName("url")] }
25+
26+
override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
27+
28+
override string getFramework() { result = "urllib2.Request" }
29+
30+
override predicate disablesCertificateValidation(
31+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
32+
) {
33+
none()
34+
}
35+
}
36+
37+
/**
38+
* See
39+
* - https://docs.python.org/2/library/urllib2.html#urllib2.urlopen
40+
*/
41+
private class UrlOpenCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
42+
UrlOpenCall() { this = API::moduleImport("urllib2").getMember("urlopen").getACall() }
43+
44+
DataFlow::Node getUrlArg() { result in [this.getArg(0), this.getArgByName("url")] }
45+
46+
override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
47+
48+
override string getFramework() { result = "urllib2.urlopen" }
49+
50+
override predicate disablesCertificateValidation(
51+
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
52+
) {
53+
none()
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)