8000 Python: Add Server-side Request Forgery sinks by haby0 · Pull Request #8275 · github/codeql · GitHub
[go: up one dir, main page]

Skip to content

Python: Add Server-side Request Forgery sinks #8275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 8, 2022
Merged
Next Next commit
add Server-side Request Forgery sinks
  • Loading branch information
haby0 committed Feb 28, 2022
commit b23e28a1e659ca2174fd64a06e29c3dac7a7550c
6 changes: 6 additions & 0 deletions python/ql/lib/semmle/python/Frameworks.qll
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,20 @@ private import semmle.python.frameworks.FastApi
private import semmle.python.frameworks.Flask
private import semmle.python.frameworks.FlaskAdmin
private import semmle.python.frameworks.FlaskSqlAlchemy
private import semmle.python.frameworks.Httpx
private import semmle.python.frameworks.Idna
private import semmle.python.frameworks.Invoke
private import semmle.python.frameworks.Jmespath
private import semmle.python.frameworks.Ldap
private import semmle.python.frameworks.Ldap3
private import semmle.python.frameworks.Libtaxii
private import semmle.python.frameworks.MarkupSafe
private import semmle.python.frameworks.Multidict
private import semmle.python.frameworks.Mysql
private import semmle.python.frameworks.MySQLdb
private import semmle.python.frameworks.Peewee
private import semmle.python.frameworks.Psycopg2
private import semmle.python.frameworks.Pycurl
private import semmle.python.frameworks.Pydantic
private import semmle.python.frameworks.PyMySQL
private import semmle.python.frameworks.Requests
Expand All @@ -44,5 +47,8 @@ private import semmle.python.frameworks.Toml
private import semmle.python.frameworks.Tornado
private import semmle.python.frameworks.Twisted
private import semmle.python.frameworks.Ujson
private import semmle.python.frameworks.Urllib
private import semmle.python.frameworks.Urllib2
private import semmle.python.frameworks.Urllib3
private import semmle.python.frameworks.Yaml
private import semmle.python.frameworks.Yarl
51 changes: 51 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Aiohttp.qll
Original file line number Diff line number Diff line change
Expand Up @@ -639,3 +639,54 @@ module AiohttpWebModel {
override DataFlow::Node getValueArg() { result = value }
}
}

/**
* Provides models for the web server part (`aiohttp.client`) of the `aiohttp` PyPI package.
* See https://docs.aiohttp.org/en/stable/client.html
*/
module AiohttpClientModel {
/**
* Provides models for the `aiohttp.ClientSession` class
*
* See https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.ClientSession.
*/
module ClientSession {
/** Gets a reference to the `aiohttp.ClientSession` class. */
private API::Node classRef() {
result = API::moduleImport("aiohttp").getMember("ClientSession")
}

/** Gets a reference to an instance of `aiohttp.ClientSession`. */
private API::Node instance() { result = classRef().getReturn() }

/** A method call on a ClientSession that sends off a request */
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
string methodName;

OutgoingRequestCall() {
methodName in [HTTP::httpVerbLower(), "request"] and
this = instance().getMember(methodName).getACall()
}

DataFlow::Node getUrlArg() {
result = this.getArgByName("url")
or
not methodName = "request" and
result = this.getArg(0)
or
methodName = "request" and
result = this.getArg(1)
}

override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any reason to have this separate predicate, can we just do

Suggested change
DataFlow::Node getUrlArg() {
result = this.getArgByName("url")
or
not methodName = "request" and
result = this.getArg(0)
or
methodName = "request" and
result = this.getArg(1)
}
override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }
override DataFlow::Node getAUrlPart() {
result = this.getArgByName("url")
or
not methodName = "request" and
result = this.getArg(0)
or
methodName = "request" and
result = this.getArg(1)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please add tests to highlight the cases we can now handle, like in https://github.com/github/codeql/blob/main/python/ql/test/library-tests/frameworks/requests/test.py

Please review whether the test file is correct, thank you!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They looked great, but there was a failure for httpx. Next time you should run the tests in all directories before submitting 😉


override string getFramework() { result = "aiohttp.ClientSession" }

override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
none()
}
}
}
}
88 changes: 88 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Httpx.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Provides classes modeling security-relevant aspects of the `httpx` PyPI package.
* See https://www.python-httpx.org/
*/

private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for the `httpx` PyPI package.
* see https://www.python-httpx.org/
*/
module HttpxModel {
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
string methodName;

RequestCall() {
methodName in [HTTP::httpVerbLower(), "request", "stream"] and
this = API::moduleImport("httpx").getMember(methodName).getACall()
}

DataFlow::Node getUrlArg() {
result = this.getArgByName("url")
or
not methodName = "request" and
result = this.getArg(0)
or
methodName in ["request", "stream"] and
result = this.getArg(1)
}

override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }

override string getFramework() { result = "httpx" }

override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
none()
}
}

/**
* Provides models for the `httpx.[Async]Client` class
*
* See https://www.python-httpx.org/async/
*/
module Client {
/** Get a reference to the `httpx.Client` or `httpx.AsyncClient` class. */
private API::Node classRef() {
result = API::moduleImport("httpx").getMember(["Client", "AsyncClient"])
}

/** Get a reference to an `httpx.Client` or `httpx.AsyncClient` instance. */
private API::Node instance() { result = classRef().getReturn() }

/** A method call on a Client that sends off a request */
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
string methodName;

OutgoingRequestCall() {
methodName in [HTTP::httpVerbLower(), "request", "stream"] and
this = instance().getMember(methodName).getACall()
}

DataFlow::Node getUrlArg() {
result = this.getArgByName("url")
or
not methodName = "request" and
result = this.getArg(0)
or
methodName in ["request", "stream"] and
result = this.getArg(1)
}

override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }

override string getFramework() { result = "httpx.[Async]Client" }

override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
none()
}
}
}
}
37 changes: 37 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Libtaxii.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Provides classes modeling security-relevant aspects of the `libtaxii` PyPI package.
* See https://github.com/TAXIIProject/libtaxii
*/

private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for the `libtaxii` PyPI package.
* see https://github.com/TAXIIProject/libtaxii
*/
module Libtaxii {
/**
* A call to `libtaxii.common.parse`.
* When the `allow_url` parameter value is set to `True`, there is an SSRF vulnerability..
*/
private class ParseCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
ParseCall() {
this = API::moduleImport("libtaxii").getMember("common").getMember("parse").getACall() and
this.getArgByName("allow_url").asExpr().toString() = "True"
}

DataFlow::Node getUrlArg() { result in [this.getArg(0), this.getArgByName("s")] }

override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }

override string getFramework() { result = "libtaxii.common.parse" }

override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
none()
}
}
}
52 changes: 52 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Pycurl.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Provides classes modeling security-relevant aspects of the `pycurl` PyPI package.
* See https://pycurl.io/docs/latest/
*/

private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for the `pycurl` PyPI package.
* see https://pycurl.io/docs/latest/
*/
module Pycurl {
/**
* Provides models for the `pycurl.Curl` class
*
* See https://pycurl.io/docs/latest/curl.html.
*/
module Curl {
/** Gets a reference to the `pycurl.Curl` class. */
private API::Node classRef() { result = API::moduleImport("pycurl").getMember("Curl") }

/** Gets a reference to an instance of `pycurl.Curl`. */
private API::Node instance() { result = classRef().getReturn() }

/**
* When the first parameter value of the `setopt` function is set to `pycurl.URL`,
* the second parameter value is the request resource link.
*
* See https://pycurl.io/docs/latest/curl.html#set_option.
*/
private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
OutgoingRequestCall() {
this = instance().getMember("setopt").getACall() and
this.getArg(0).asCfgNode().(AttrNode).getName() = "URL"
}

DataFlow::Node getUrlArg() { result in [this.getArg(1), this.getArgByName("value")] }

override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }

override string getFramework() { result = "pycurl.Curl" }

override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
none()
}
}
}
}
65 changes: 65 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Urllib.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* Provides classes modeling security-relevant aspects of the `urllib` PyPI package.
* See https://docs.python.org/3.9/library/urllib.html
*/

private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for the `Urllib` PyPI package.
* see https://docs.python.org/3.9/library/urllib.html
*/
module Urllib {
/**
* Provides models for the `urllib.request` extension library
*
* See https://docs.python.org/3.9/library/urllib.request.html
*/
module Request {
/**
* See
* - https://docs.python.org/3.9/library/urllib.request.html#urllib.request.Request
*/
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
RequestCall() {
this = API::moduleImport("urllib").getMember("request").getMember("Request").getACall()
}

DataFlow::Node getUrlArg() { result in [this.getArg(0), this.getArgByName("url")] }

override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }

override string getFramework() { result = "urllib.request.Request" }

override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
none()
}
}

/**
* See
* - https://docs.python.org/3.9/library/urllib.request.html#urllib.request.urlopen
*/
private class UrlOpenCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
UrlOpenCall() {
this = API::moduleImport("urllib").getMember("request").getMember("urlopen").getACall()
}

DataFlow::Node getUrlArg() { result in [this.getArg(0), this.getArgByName("url")] }

override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }

override string getFramework() { result = "urllib.request.urlopen" }

override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
none()
}
}
}
}
56 changes: 56 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Urllib2.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Provides classes modeling security-relevant aspects of the `urllib2` PyPI package.
* See https://docs.python.org/2/library/urllib2.html
*/

private import python
private import semmle.python.Concepts
private import semmle.python.ApiGraphs

/**
* Provides models for the `urllib2` PyPI package.
* see https://docs.python.org/2/library/urllib2.html
*/
module Urllib2 {
/**
* See
* - https://docs.python.org/2/library/urllib2.html#urllib2.Request
*/
private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
RequestCall() {
this = API::moduleImport("urllib2").getMember("Request").getACall()
}

DataFlow::Node getUrlArg() { result in [this.getArg(0), this.getArgByName("url")] }

override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }

override string getFramework() { result = "urllib2.Request" }

override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
none()
}
}

/**
* See
* - https://docs.python.org/2/library/urllib2.html#urllib2.urlopen
*/
private class UrlOpenCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode {
UrlOpenCall() { this = API::moduleImport("urllib2").getMember("urlopen").getACall() }

DataFlow::Node getUrlArg() { result in [this.getArg(0), this.getArgByName("url")] }

override DataFlow::Node getAUrlPart() { result = this.getUrlArg() }

override string getFramework() { result = "urllib2.urlopen" }

override predicate disablesCertificateValidation(
DataFlow::Node disablingNode, DataFlow::Node argumentOrigin
) {
none()
}
}
}
Loading
0