From b23e28a1e659ca2174fd64a06e29c3dac7a7550c Mon Sep 17 00:00:00 2001 From: haby0 Date: Mon, 28 Feb 2022 15:24:02 +0800 Subject: [PATCH 01/11] add Server-side Request Forgery sinks --- python/ql/lib/semmle/python/Frameworks.qll | 6 ++ .../lib/semmle/python/frameworks/Aiohttp.qll | 51 +++++++++++ .../ql/lib/semmle/python/frameworks/Httpx.qll | 88 +++++++++++++++++++ .../lib/semmle/python/frameworks/Libtaxii.qll | 37 ++++++++ .../lib/semmle/python/frameworks/Pycurl.qll | 52 +++++++++++ .../lib/semmle/python/frameworks/Urllib.qll | 65 ++++++++++++++ .../lib/semmle/python/frameworks/Urllib2.qll | 56 ++++++++++++ .../lib/semmle/python/frameworks/Urllib3.qll | 62 +++++++++++++ 8 files changed, 417 insertions(+) create mode 100644 python/ql/lib/semmle/python/frameworks/Httpx.qll create mode 100644 python/ql/lib/semmle/python/frameworks/Libtaxii.qll create mode 100644 python/ql/lib/semmle/python/frameworks/Pycurl.qll create mode 100644 python/ql/lib/semmle/python/frameworks/Urllib.qll create mode 100644 python/ql/lib/semmle/python/frameworks/Urllib2.qll create mode 100644 python/ql/lib/semmle/python/frameworks/Urllib3.qll diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll index d502a421f409..59acaf91d994 100644 --- a/python/ql/lib/semmle/python/Frameworks.qll +++ b/python/ql/lib/semmle/python/Frameworks.qll @@ -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 @@ -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 diff --git a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll index 3ec706439869..177b997acc20 100644 --- a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll +++ b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll @@ -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() } + + override string getFramework() { result = "aiohttp.ClientSession" } + + override predicate disablesCertificateValidation( + DataFlow::Node disablingNode, DataFlow::Node argumentOrigin + ) { + none() + } + } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Httpx.qll b/python/ql/lib/semmle/python/frameworks/Httpx.qll new file mode 100644 index 000000000000..c1949ee6ba46 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Httpx.qll @@ -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() + } + } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Libtaxii.qll b/python/ql/lib/semmle/python/frameworks/Libtaxii.qll new file mode 100644 index 000000000000..181ba82b74d4 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Libtaxii.qll @@ -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() + } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Pycurl.qll b/python/ql/lib/semmle/python/frameworks/Pycurl.qll new file mode 100644 index 000000000000..c85faf5ceea8 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Pycurl.qll @@ -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() + } + } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Urllib.qll b/python/ql/lib/semmle/python/frameworks/Urllib.qll new file mode 100644 index 000000000000..854936124015 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Urllib.qll @@ -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() + } + } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Urllib2.qll b/python/ql/lib/semmle/python/frameworks/Urllib2.qll new file mode 100644 index 000000000000..93ce324751a3 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Urllib2.qll @@ -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() + } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Urllib3.qll b/python/ql/lib/semmle/python/frameworks/Urllib3.qll new file mode 100644 index 000000000000..9578002c9733 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Urllib3.qll @@ -0,0 +1,62 @@ +/** + * Provides classes modeling security-relevant aspects of the `urllib3` PyPI package. + * See https://urllib3.readthedocs.io/en/stable/reference/ + */ + +private import python +private import semmle.python.Concepts +private import semmle.python.ApiGraphs + +/** + * Provides models for the `Urllib3` PyPI package. + * see https://urllib3.readthedocs.io/en/stable/reference/ + */ +module Urllib3 { + /** + * Provides models for the `urllib3.PoolManager` class + * + * See https://urllib3.readthedocs.io/en/stable/reference/urllib3.poolmanager.html. + */ + module PoolManager { + /** Gets a reference to the `urllib3.PoolManager` class. */ + private API::Node classRef() { result = API::moduleImport("urllib3").getMember("PoolManager") } + + /** Gets a reference to an instance of `urllib3.PoolManager`. */ + private API::Node instance() { result = classRef().getReturn() } + + private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode { + RequestCall() { + this = + instance().getMember(["request", "request_encode_url", "request_encode_body"]).getACall() + } + + DataFlow::Node getUrlArg() { result in [this.getArg(1), this.getArgByName("url")] } + + override DataFlow::Node getAUrlPart() { result = this.getUrlArg() } + + override string getFramework() { result = "urllib3.PoolManager" } + + override predicate disablesCertificateValidation( + DataFlow::Node disablingNode, DataFlow::Node argumentOrigin + ) { + none() + } + } + + private class UrlOpenCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode { + UrlOpenCall() { this = instance().getMember("urlopen").getACall() } + + DataFlow::Node getUrlArg() { result in [this.getArg(1), this.getArgByName("url")] } + + override DataFlow::Node getAUrlPart() { result = this.getUrlArg() } + + override string getFramework() { result = "urllib3.PoolManager" } + + override predicate disablesCertificateValidation( + DataFlow::Node disablingNode, DataFlow::Node argumentOrigin + ) { + none() + } + } + } +} From be40b54b9fd02b674504a0e92f50c34d30ee60d0 Mon Sep 17 00:00:00 2001 From: haby0 Date: Mon, 28 Feb 2022 20:34:58 +0800 Subject: [PATCH 02/11] add test --- .../lib/semmle/python/frameworks/Aiohttp.qll | 7 +++--- .../ql/lib/semmle/python/frameworks/Httpx.qll | 10 ++++---- .../lib/semmle/python/frameworks/Libtaxii.qll | 5 ++-- .../lib/semmle/python/frameworks/Pycurl.qll | 9 +++---- .../lib/semmle/python/frameworks/Urllib.qll | 10 ++++---- .../lib/semmle/python/frameworks/Urllib2.qll | 14 ++++------- .../lib/semmle/python/frameworks/Urllib3.qll | 10 ++++---- .../frameworks/aiohttp/client_request.py | 16 +++++++++++++ .../frameworks/httpx/ConceptsTest.expected | 0 .../frameworks/httpx/ConceptsTest.ql | 2 ++ .../library-tests/frameworks/httpx/test.py | 24 +++++++++++++++++++ .../frameworks/libtaxii/ConceptsTest.expected | 0 .../frameworks/libtaxii/ConceptsTest.ql | 2 ++ .../library-tests/frameworks/libtaxii/test.py | 4 ++++ .../frameworks/pycurl/ConceptsTest.expected | 0 .../frameworks/pycurl/ConceptsTest.ql | 2 ++ .../library-tests/frameworks/pycurl/test.py | 4 ++++ .../frameworks/urllib/ConceptsTest.expected | 0 .../frameworks/urllib/ConceptsTest.ql | 2 ++ .../library-tests/frameworks/urllib/test.py | 7 ++++++ .../frameworks/urllib2/ConceptsTest.expected | 0 .../frameworks/urllib2/ConceptsTest.ql | 2 ++ .../library-tests/frameworks/urllib2/test.py | 7 ++++++ .../frameworks/urllib3/ConceptsTest.expected | 0 .../frameworks/urllib3/ConceptsTest.ql | 2 ++ .../library-tests/frameworks/urllib3/test.py | 8 +++++++ 26 files changed, 109 insertions(+), 38 deletions(-) create mode 100644 python/ql/test/library-tests/frameworks/aiohttp/client_request.py create mode 100644 python/ql/test/library-tests/frameworks/httpx/ConceptsTest.expected create mode 100644 python/ql/test/library-tests/frameworks/httpx/ConceptsTest.ql create mode 100644 python/ql/test/library-tests/frameworks/httpx/test.py create mode 100644 python/ql/test/library-tests/frameworks/libtaxii/ConceptsTest.expected create mode 100644 python/ql/test/library-tests/frameworks/libtaxii/ConceptsTest.ql create mode 100644 python/ql/test/library-tests/frameworks/libtaxii/test.py create mode 100644 python/ql/test/library-tests/frameworks/pycurl/ConceptsTest.expected create mode 100644 python/ql/test/library-tests/frameworks/pycurl/ConceptsTest.ql create mode 100644 python/ql/test/library-tests/frameworks/pycurl/test.py create mode 100644 python/ql/test/library-tests/frameworks/urllib/ConceptsTest.expected create mode 100644 python/ql/test/library-tests/frameworks/urllib/ConceptsTest.ql create mode 100644 python/ql/test/library-tests/frameworks/urllib/test.py create mode 100644 python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.expected create mode 100644 python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.ql create mode 100644 python/ql/test/library-tests/frameworks/urllib2/test.py create mode 100644 python/ql/test/library-tests/frameworks/urllib3/ConceptsTest.expected create mode 100644 python/ql/test/library-tests/frameworks/urllib3/ConceptsTest.ql create mode 100644 python/ql/test/library-tests/frameworks/urllib3/test.py diff --git a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll index 177b997acc20..8579e5039a99 100644 --- a/python/ql/lib/semmle/python/frameworks/Aiohttp.qll +++ b/python/ql/lib/semmle/python/frameworks/Aiohttp.qll @@ -644,7 +644,7 @@ module AiohttpWebModel { * 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 { +private module AiohttpClientModel { /** * Provides models for the `aiohttp.ClientSession` class * @@ -668,7 +668,7 @@ module AiohttpClientModel { this = instance().getMember(methodName).getACall() } - DataFlow::Node getUrlArg() { + override DataFlow::Node getAUrlPart() { result = this.getArgByName("url") or not methodName = "request" and @@ -678,13 +678,12 @@ module AiohttpClientModel { result = this.getArg(1) } - override DataFlow::Node getAUrlPart() { result = this.getUrlArg() } - override string getFramework() { result = "aiohttp.ClientSession" } override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { + // TODO: Look into disabling certificate validation none() } } diff --git a/python/ql/lib/semmle/python/frameworks/Httpx.qll b/python/ql/lib/semmle/python/frameworks/Httpx.qll index c1949ee6ba46..d01566b048a5 100644 --- a/python/ql/lib/semmle/python/frameworks/Httpx.qll +++ b/python/ql/lib/semmle/python/frameworks/Httpx.qll @@ -20,7 +20,7 @@ module HttpxModel { this = API::moduleImport("httpx").getMember(methodName).getACall() } - DataFlow::Node getUrlArg() { + override DataFlow::Node getAUrlPart() { result = this.getArgByName("url") or not methodName = "request" and @@ -30,13 +30,12 @@ module HttpxModel { 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 ) { + // TODO: Look into disabling certificate validation none() } } @@ -64,7 +63,7 @@ module HttpxModel { this = instance().getMember(methodName).getACall() } - DataFlow::Node getUrlArg() { + override DataFlow::Node getAUrlPart() { result = this.getArgByName("url") or not methodName = "request" and @@ -74,13 +73,12 @@ module HttpxModel { 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 ) { + // TODO: Look into disabling certificate validation none() } } diff --git a/python/ql/lib/semmle/python/frameworks/Libtaxii.qll b/python/ql/lib/semmle/python/frameworks/Libtaxii.qll index 181ba82b74d4..e704272237da 100644 --- a/python/ql/lib/semmle/python/frameworks/Libtaxii.qll +++ b/python/ql/lib/semmle/python/frameworks/Libtaxii.qll @@ -22,15 +22,14 @@ module Libtaxii { 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 DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("s")] } override string getFramework() { result = "libtaxii.common.parse" } override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { + // TODO: Look into disabling certificate validation none() } } diff --git a/python/ql/lib/semmle/python/frameworks/Pycurl.qll b/python/ql/lib/semmle/python/frameworks/Pycurl.qll index c85faf5ceea8..017bf335d3a3 100644 --- a/python/ql/lib/semmle/python/frameworks/Pycurl.qll +++ b/python/ql/lib/semmle/python/frameworks/Pycurl.qll @@ -25,7 +25,7 @@ module Pycurl { private API::Node instance() { result = classRef().getReturn() } /** - * When the first parameter value of the `setopt` function is set to `pycurl.URL`, + * 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. @@ -36,15 +36,16 @@ module Pycurl { 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 DataFlow::Node getAUrlPart() { + result in [this.getArg(1), this.getArgByName("value")] + } override string getFramework() { result = "pycurl.Curl" } override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { + // TODO: Look into disabling certificate validation none() } } diff --git a/python/ql/lib/semmle/python/frameworks/Urllib.qll b/python/ql/lib/semmle/python/frameworks/Urllib.qll index 854936124015..6379bd2ee0af 100644 --- a/python/ql/lib/semmle/python/frameworks/Urllib.qll +++ b/python/ql/lib/semmle/python/frameworks/Urllib.qll @@ -27,15 +27,14 @@ module Urllib { 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 DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("url")] } override string getFramework() { result = "urllib.request.Request" } override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { + // TODO: Look into disabling certificate validation none() } } @@ -49,15 +48,14 @@ module Urllib { 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 DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("url")] } override string getFramework() { result = "urllib.request.urlopen" } override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { + // TODO: Look into disabling certificate validation none() } } diff --git a/python/ql/lib/semmle/python/frameworks/Urllib2.qll b/python/ql/lib/semmle/python/frameworks/Urllib2.qll index 93ce324751a3..eaeb762b4440 100644 --- a/python/ql/lib/semmle/python/frameworks/Urllib2.qll +++ b/python/ql/lib/semmle/python/frameworks/Urllib2.qll @@ -17,19 +17,16 @@ module Urllib2 { * - 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")] } + RequestCall() { this = API::moduleImport("urllib2").getMember("Request").getACall() } - override DataFlow::Node getAUrlPart() { result = this.getUrlArg() } + override DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("url")] } override string getFramework() { result = "urllib2.Request" } override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { + // TODO: Look into disabling certificate validation none() } } @@ -41,15 +38,14 @@ module Urllib2 { 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 DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("url")] } override string getFramework() { result = "urllib2.urlopen" } override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { + // TODO: Look into disabling certificate validation none() } } diff --git a/python/ql/lib/semmle/python/frameworks/Urllib3.qll b/python/ql/lib/semmle/python/frameworks/Urllib3.qll index 9578002c9733..6bba21590372 100644 --- a/python/ql/lib/semmle/python/frameworks/Urllib3.qll +++ b/python/ql/lib/semmle/python/frameworks/Urllib3.qll @@ -30,15 +30,14 @@ module Urllib3 { instance().getMember(["request", "request_encode_url", "request_encode_body"]).getACall() } - DataFlow::Node getUrlArg() { result in [this.getArg(1), this.getArgByName("url")] } - - override DataFlow::Node getAUrlPart() { result = this.getUrlArg() } + override DataFlow::Node getAUrlPart() { result in [this.getArg(1), this.getArgByName("url")] } override string getFramework() { result = "urllib3.PoolManager" } override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { + // TODO: Look into disabling certificate validation none() } } @@ -46,15 +45,14 @@ module Urllib3 { private class UrlOpenCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode { UrlOpenCall() { this = instance().getMember("urlopen").getACall() } - DataFlow::Node getUrlArg() { result in [this.getArg(1), this.getArgByName("url")] } - - override DataFlow::Node getAUrlPart() { result = this.getUrlArg() } + override DataFlow::Node getAUrlPart() { result in [this.getArg(1), this.getArgByName("url")] } override string getFramework() { result = "urllib3.PoolManager" } override predicate disablesCertificateValidation( DataFlow::Node disablingNode, DataFlow::Node argumentOrigin ) { + // TODO: Look into disabling certificate validation none() } } diff --git a/python/ql/test/library-tests/frameworks/aiohttp/client_request.py b/python/ql/test/library-tests/frameworks/aiohttp/client_request.py new file mode 100644 index 000000000000..8422853e61b9 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/aiohttp/client_request.py @@ -0,0 +1,16 @@ +import aiohttp +import asyncio + +s = aiohttp.ClientSession() +resp = s.request("method", "url") # $ clientRequestUrlPart="url" +resp = s.request("method", url="url") # $ clientRequestUrlPart="url" + +with aiohttp.ClientSession() as session: + resp = session.get("url") # $ clientRequestUrlPart="url" + resp = session.request(method="GET", url="url") # $ clientRequestUrlPart="url" + +# other methods than GET +s = aiohttp.ClientSession() +resp = s.post("url") # $ clientRequestUrlPart="url" +resp = s.patch("url") # $ clientRequestUrlPart="url" +resp = s.options("url") # $ clientRequestUrlPart="url" \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/httpx/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/httpx/ConceptsTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/frameworks/httpx/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/httpx/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/httpx/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/httpx/test.py b/python/ql/test/library-tests/frameworks/httpx/test.py new file mode 100644 index 000000000000..defc8eade396 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/httpx/test.py @@ -0,0 +1,24 @@ +import httpx + +httpx.get("url") # $ clientRequestUrlPart="url" +httpx.post("url") # $ clientRequestUrlPart="url" +httpx.patch("url") # $ clientRequestUrlPart="url" +httpx.options("url") # $ clientRequestUrlPart="url" +httpx.request("method", url="url") # $ clientRequestUrlPart="url" +httpx.stream("method", url="url") # $ clientRequestUrlPart="url" + +client = httpx.Client() +response = client.get("url") # $ clientRequestUrlPart="url" +response = client.post("url") # $ clientRequestUrlPart="url" +response = client.patch("url") # $ clientRequestUrlPart="url" +response = client.options("url") # $ clientRequestUrlPart="url" +response = client.request("method", url="url") # $ clientRequestUrlPart="url" +response = client.stream("method", url="url") # $ clientRequestUrlPart="url" + +client = httpx.AsyncClient() +response = client.get("url") # $ clientRequestUrlPart="url" +response = client.post("url") # $ clientRequestUrlPart="url" +response = client.patch("url") # $ clientRequestUrlPart="url" +response = client.options("url") # $ clientRequestUrlPart="url" +response = client.request("method", url="url") # $ clientRequestUrlPart="url" +response = client.stream("method", url="url") # $ clientRequestUrlPart="url" \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/libtaxii/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/libtaxii/ConceptsTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/frameworks/libtaxii/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/libtaxii/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/libtaxii/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/libtaxii/test.py b/python/ql/test/library-tests/frameworks/libtaxii/test.py new file mode 100644 index 000000000000..a7cdd4e3c0ee --- /dev/null +++ b/python/ql/test/library-tests/frameworks/libtaxii/test.py @@ -0,0 +1,4 @@ +from libtaxii.common import parse + +result = parse("url", allow_url=True) # $ clientRequestUrlPart="url" +result = parse(s="url", allow_url=True) # $ clientRequestUrlPart="url" \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/pycurl/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/pycurl/ConceptsTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/frameworks/pycurl/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/pycurl/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/pycurl/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/pycurl/test.py b/python/ql/test/library-tests/frameworks/pycurl/test.py new file mode 100644 index 000000000000..055152d7b2f8 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/pycurl/test.py @@ -0,0 +1,4 @@ +import pycurl + +c = pycurl.Curl() +c.setopt(pycurl.URL, "url") # $ clientRequestUrlPart="url" \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/urllib/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/urllib/ConceptsTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/frameworks/urllib/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/urllib/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/urllib/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/urllib/test.py b/python/ql/test/library-tests/frameworks/urllib/test.py new file mode 100644 index 000000000000..2371052e9a62 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/urllib/test.py @@ -0,0 +1,7 @@ +from urllib.request import Request, urlopen + +Request("url") # $ clientRequestUrlPart="url" +Request(url="url") # $ clientRequestUrlPart="url" + +urlopen("url") # $ clientRequestUrlPart="url" +urlopen(url="url") # $ clientRequestUrlPart="url" \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/urllib2/test.py b/python/ql/test/library-tests/frameworks/urllib2/test.py new file mode 100644 index 000000000000..77eed7df94f0 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/urllib2/test.py @@ -0,0 +1,7 @@ +import urllib2 + +resp = urllib2.Request("url") # $ clientRequestUrlPart="url" +resp = urllib2.Request(url="url") # $ clientRequestUrlPart="url" + +resp = urllib2.urlopen("url") # $ clientRequestUrlPart="url" +resp = urllib2.urlopen(url="url") # $ clientRequestUrlPart="url" \ No newline at end of file diff --git a/python/ql/test/library-tests/frameworks/urllib3/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/urllib3/ConceptsTest.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/ql/test/library-tests/frameworks/urllib3/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/urllib3/ConceptsTest.ql new file mode 100644 index 000000000000..b557a0bccb69 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/urllib3/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/urllib3/test.py b/python/ql/test/library-tests/frameworks/urllib3/test.py new file mode 100644 index 000000000000..b15e238404e1 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/urllib3/test.py @@ -0,0 +1,8 @@ +import urllib3 + +http = urllib3.PoolManager() + +resp = http.request("method", "url") # $ clientRequestUrlPart="url" +resp = http.request("method", url="url") # $ clientRequestUrlPart="url" +resp = http.urlopen("method", "url") # $ clientRequestUrlPart="url" +resp = http.urlopen("method", url="url") # $ clientRequestUrlPart="url" \ No newline at end of file From 40feb1fb8dd9ff272cfc77caa04c3c6013552aa4 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 4 Mar 2022 11:03:32 +0100 Subject: [PATCH 03/11] Python: SPURIOUS results for httpx --- python/ql/test/library-tests/frameworks/httpx/test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ql/test/library-tests/frameworks/httpx/test.py b/python/ql/test/library-tests/frameworks/httpx/test.py index defc8eade396..d9120a1046e1 100644 --- a/python/ql/test/library-tests/frameworks/httpx/test.py +++ b/python/ql/test/library-tests/frameworks/httpx/test.py @@ -5,7 +5,7 @@ httpx.patch("url") # $ clientRequestUrlPart="url" httpx.options("url") # $ clientRequestUrlPart="url" httpx.request("method", url="url") # $ clientRequestUrlPart="url" -httpx.stream("method", url="url") # $ clientRequestUrlPart="url" +httpx.stream("method", url="url") # $ clientRequestUrlPart="url" SPURIOUS: clientRequestUrlPart="method" client = httpx.Client() response = client.get("url") # $ clientRequestUrlPart="url" @@ -13,7 +13,7 @@ response = client.patch("url") # $ clientRequestUrlPart="url" response = client.options("url") # $ clientRequestUrlPart="url" response = client.request("method", url="url") # $ clientRequestUrlPart="url" -response = client.stream("method", url="url") # $ clientRequestUrlPart="url" +response = client.stream("method", url="url") # $ clientRequestUrlPart="url" SPURIOUS: clientRequestUrlPart="method" client = httpx.AsyncClient() response = client.get("url") # $ clientRequestUrlPart="url" @@ -21,4 +21,4 @@ response = client.patch("url") # $ clientRequestUrlPart="url" response = client.options("url") # $ clientRequestUrlPart="url" response = client.request("method", url="url") # $ clientRequestUrlPart="url" -response = client.stream("method", url="url") # $ clientRequestUrlPart="url" \ No newline at end of file +response = client.stream("method", url="url") # $ clientRequestUrlPart="url" SPURIOUS: clientRequestUrlPart="method" From 56901ea84113353c84ba89bda34014cddc46e67c Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 4 Mar 2022 11:04:18 +0100 Subject: [PATCH 04/11] Python: Make new SSRF sink modules private --- python/ql/lib/semmle/python/frameworks/Httpx.qll | 2 +- python/ql/lib/semmle/python/frameworks/Libtaxii.qll | 2 +- python/ql/lib/semmle/python/frameworks/Pycurl.qll | 2 +- python/ql/lib/semmle/python/frameworks/Urllib.qll | 2 +- python/ql/lib/semmle/python/frameworks/Urllib2.qll | 2 +- python/ql/lib/semmle/python/frameworks/Urllib3.qll | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/ql/lib/semmle/python/frameworks/Httpx.qll b/python/ql/lib/semmle/python/frameworks/Httpx.qll index d01566b048a5..c18da7defb16 100644 --- a/python/ql/lib/semmle/python/frameworks/Httpx.qll +++ b/python/ql/lib/semmle/python/frameworks/Httpx.qll @@ -11,7 +11,7 @@ private import semmle.python.ApiGraphs * Provides models for the `httpx` PyPI package. * see https://www.python-httpx.org/ */ -module HttpxModel { +private module HttpxModel { private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode { string methodName; diff --git a/python/ql/lib/semmle/python/frameworks/Libtaxii.qll b/python/ql/lib/semmle/python/frameworks/Libtaxii.qll index e704272237da..a4ad81c322a8 100644 --- a/python/ql/lib/semmle/python/frameworks/Libtaxii.qll +++ b/python/ql/lib/semmle/python/frameworks/Libtaxii.qll @@ -11,7 +11,7 @@ private import semmle.python.ApiGraphs * Provides models for the `libtaxii` PyPI package. * see https://github.com/TAXIIProject/libtaxii */ -module Libtaxii { +private module Libtaxii { /** * A call to `libtaxii.common.parse`. * When the `allow_url` parameter value is set to `True`, there is an SSRF vulnerability.. diff --git a/python/ql/lib/semmle/python/frameworks/Pycurl.qll b/python/ql/lib/semmle/python/frameworks/Pycurl.qll index 017bf335d3a3..d0e905365725 100644 --- a/python/ql/lib/semmle/python/frameworks/Pycurl.qll +++ b/python/ql/lib/semmle/python/frameworks/Pycurl.qll @@ -11,7 +11,7 @@ private import semmle.python.ApiGraphs * Provides models for the `pycurl` PyPI package. * see https://pycurl.io/docs/latest/ */ -module Pycurl { +private module Pycurl { /** * Provides models for the `pycurl.Curl` class * diff --git a/python/ql/lib/semmle/python/frameworks/Urllib.qll b/python/ql/lib/semmle/python/frameworks/Urllib.qll index 6379bd2ee0af..cc7f6cc0031c 100644 --- a/python/ql/lib/semmle/python/frameworks/Urllib.qll +++ b/python/ql/lib/semmle/python/frameworks/Urllib.qll @@ -11,7 +11,7 @@ private import semmle.python.ApiGraphs * Provides models for the `Urllib` PyPI package. * see https://docs.python.org/3.9/library/urllib.html */ -module Urllib { +private module Urllib { /** * Provides models for the `urllib.request` extension library * diff --git a/python/ql/lib/semmle/python/frameworks/Urllib2.qll b/python/ql/lib/semmle/python/frameworks/Urllib2.qll index eaeb762b4440..272994d450f6 100644 --- a/python/ql/lib/semmle/python/frameworks/Urllib2.qll +++ b/python/ql/lib/semmle/python/frameworks/Urllib2.qll @@ -11,7 +11,7 @@ private import semmle.python.ApiGraphs * Provides models for the `urllib2` PyPI package. * see https://docs.python.org/2/library/urllib2.html */ -module Urllib2 { +private module Urllib2 { /** * See * - https://docs.python.org/2/library/urllib2.html#urllib2.Request diff --git a/python/ql/lib/semmle/python/frameworks/Urllib3.qll b/python/ql/lib/semmle/python/frameworks/Urllib3.qll index 6bba21590372..a88171cea493 100644 --- a/python/ql/lib/semmle/python/frameworks/Urllib3.qll +++ b/python/ql/lib/semmle/python/frameworks/Urllib3.qll @@ -11,7 +11,7 @@ private import semmle.python.ApiGraphs * Provides models for the `Urllib3` PyPI package. * see https://urllib3.readthedocs.io/en/stable/reference/ */ -module Urllib3 { +private module Urllib3 { /** * Provides models for the `urllib3.PoolManager` class * From 7d6d8be1791e4f784ffbd38f9a887dd9645ed672 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 4 Mar 2022 11:07:51 +0100 Subject: [PATCH 05/11] Python: Fix httpx modeling --- python/ql/lib/semmle/python/frameworks/Httpx.qll | 16 ++++++---------- .../test/library-tests/frameworks/httpx/test.py | 6 +++--- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/python/ql/lib/semmle/python/frameworks/Httpx.qll b/python/ql/lib/semmle/python/frameworks/Httpx.qll index c18da7defb16..189de1dc68ee 100644 --- a/python/ql/lib/semmle/python/frameworks/Httpx.qll +++ b/python/ql/lib/semmle/python/frameworks/Httpx.qll @@ -23,11 +23,9 @@ private module HttpxModel { override DataFlow::Node getAUrlPart() { result = this.getArgByName("url") or - not methodName = "request" and - result = this.getArg(0) - or - methodName in ["request", "stream"] and - result = this.getArg(1) + if methodName in ["request", "stream"] + then result = this.getArg(1) + else result = this.getArg(0) } override string getFramework() { result = "httpx" } @@ -66,11 +64,9 @@ private module HttpxModel { override DataFlow::Node getAUrlPart() { result = this.getArgByName("url") or - not methodName = "request" and - result = this.getArg(0) - or - methodName in ["request", "stream"] and - result = this.getArg(1) + if methodName in ["request", "stream"] + then result = this.getArg(1) + else result = this.getArg(0) } override string getFramework() { result = "httpx.[Async]Client" } diff --git a/python/ql/test/library-tests/frameworks/httpx/test.py b/python/ql/test/library-tests/frameworks/httpx/test.py index d9120a1046e1..cf519a7ac1a6 100644 --- a/python/ql/test/library-tests/frameworks/httpx/test.py +++ b/python/ql/test/library-tests/frameworks/httpx/test.py @@ -5,7 +5,7 @@ httpx.patch("url") # $ clientRequestUrlPart="url" httpx.options("url") # $ clientRequestUrlPart="url" httpx.request("method", url="url") # $ clientRequestUrlPart="url" -httpx.stream("method", url="url") # $ clientRequestUrlPart="url" SPURIOUS: clientRequestUrlPart="method" +httpx.stream("method", url="url") # $ clientRequestUrlPart="url" client = httpx.Client() response = client.get("url") # $ clientRequestUrlPart="url" @@ -13,7 +13,7 @@ response = client.patch("url") # $ clientRequestUrlPart="url" response = client.options("url") # $ clientRequestUrlPart="url" response = client.request("method", url="url") # $ clientRequestUrlPart="url" -response = client.stream("method", url="url") # $ clientRequestUrlPart="url" SPURIOUS: clientRequestUrlPart="method" +response = client.stream("method", url="url") # $ clientRequestUrlPart="url" client = httpx.AsyncClient() response = client.get("url") # $ clientRequestUrlPart="url" @@ -21,4 +21,4 @@ response = client.patch("url") # $ clientRequestUrlPart="url" response = client.options("url") # $ clientRequestUrlPart="url" response = client.request("method", url="url") # $ clientRequestUrlPart="url" -response = client.stream("method", url="url") # $ clientRequestUrlPart="url" SPURIOUS: clientRequestUrlPart="method" +response = client.stream("method", url="url") # $ clientRequestUrlPart="url" From c65839bb77839fa44e6f6b8426ad1a2f4bb48a49 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 4 Mar 2022 11:25:14 +0100 Subject: [PATCH 06/11] Python: improve urllib3 modeling --- .../lib/semmle/python/frameworks/Urllib3.qll | 61 ++++++++++++------- .../library-tests/frameworks/urllib3/test.py | 31 ++++++++-- 2 files changed, 64 insertions(+), 28 deletions(-) diff --git a/python/ql/lib/semmle/python/frameworks/Urllib3.qll b/python/ql/lib/semmle/python/frameworks/Urllib3.qll index a88171cea493..ab94a668c2ba 100644 --- a/python/ql/lib/semmle/python/frameworks/Urllib3.qll +++ b/python/ql/lib/semmle/python/frameworks/Urllib3.qll @@ -1,6 +1,9 @@ /** * Provides classes modeling security-relevant aspects of the `urllib3` PyPI package. - * See https://urllib3.readthedocs.io/en/stable/reference/ + * + * See + * - https://pypi.org/project/urllib3/ + * - https://urllib3.readthedocs.io/en/stable/reference/ */ private import python @@ -8,42 +11,54 @@ private import semmle.python.Concepts private import semmle.python.ApiGraphs /** - * Provides models for the `Urllib3` PyPI package. - * see https://urllib3.readthedocs.io/en/stable/reference/ + * Provides models for the `urllib3` PyPI package. + * + * See + * - https://pypi.org/project/urllib3/ + * - https://urllib3.readthedocs.io/en/stable/reference/ */ private module Urllib3 { /** - * Provides models for the `urllib3.PoolManager` class + * Provides models for the `urllib3.request.RequestMethods` class and subclasses, such + * as the `urllib3.PoolManager` class * - * See https://urllib3.readthedocs.io/en/stable/reference/urllib3.poolmanager.html. + * See + * - https://urllib3.readthedocs.io/en/stable/reference/urllib3.request.html#urllib3.request.RequestMethods + * + * + * https://urllib3.readthedocs.io/en/stable/reference/urllib3.poolmanager.html. */ module PoolManager { /** Gets a reference to the `urllib3.PoolManager` class. */ - private API::Node classRef() { result = API::moduleImport("urllib3").getMember("PoolManager") } + private API::Node classRef() { + result = + API::moduleImport("urllib3") + .getMember(["PoolManager", "ProxyManager", "HTTPConnectionPool", "HTTPSConnectionPool"]) + or + result = + API::moduleImport("urllib3") + .getMember("request") + .getMember("RequestMethods") + .getASubclass+() + } - /** Gets a reference to an instance of `urllib3.PoolManager`. */ + /** Gets a reference to an instance of a `urllib3.request.RequestMethods` subclass. */ private API::Node instance() { result = classRef().getReturn() } + /** + * A call to a method making an outgoing request. + * + * See + * - https://urllib3.readthedocs.io/en/stable/reference/urllib3.request.html#urllib3.request.RequestMethods + * - https://urllib3.readthedocs.io/en/stable/reference/urllib3.connectionpool.html#urllib3.HTTPConnectionPool.urlopen + */ private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode { RequestCall() { this = - instance().getMember(["request", "request_encode_url", "request_encode_body"]).getACall() - } - - override DataFlow::Node getAUrlPart() { result in [this.getArg(1), this.getArgByName("url")] } - - override string getFramework() { result = "urllib3.PoolManager" } - - override predicate disablesCertificateValidation( - DataFlow::Node disablingNode, DataFlow::Node argumentOrigin - ) { - // TODO: Look into disabling certificate validation - none() + instance() + .getMember(["request", "request_encode_url", "request_encode_body", "urlopen"]) + .getACall() } - } - - private class UrlOpenCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode { - UrlOpenCall() { this = instance().getMember("urlopen").getACall() } override DataFlow::Node getAUrlPart() { result in [this.getArg(1), this.getArgByName("url")] } diff --git a/python/ql/test/library-tests/frameworks/urllib3/test.py b/python/ql/test/library-tests/frameworks/urllib3/test.py index b15e238404e1..921c100ef957 100644 --- a/python/ql/test/library-tests/frameworks/urllib3/test.py +++ b/python/ql/test/library-tests/frameworks/urllib3/test.py @@ -1,8 +1,29 @@ import urllib3 -http = urllib3.PoolManager() +pool = urllib3.PoolManager() -resp = http.request("method", "url") # $ clientRequestUrlPart="url" -resp = http.request("method", url="url") # $ clientRequestUrlPart="url" -resp = http.urlopen("method", "url") # $ clientRequestUrlPart="url" -resp = http.urlopen("method", url="url") # $ clientRequestUrlPart="url" \ No newline at end of file +resp = pool.request("method", "url") # $ clientRequestUrlPart="url" +resp = pool.request("method", url="url") # $ clientRequestUrlPart="url" +resp = pool.urlopen("method", "url") # $ clientRequestUrlPart="url" +resp = pool.urlopen("method", url="url") # $ clientRequestUrlPart="url" + +pool = urllib3.ProxyManager("http://proxy") + +resp = pool.request("method", "url") # $ clientRequestUrlPart="url" +resp = pool.request("method", url="url") # $ clientRequestUrlPart="url" +resp = pool.urlopen("method", "url") # $ clientRequestUrlPart="url" +resp = pool.urlopen("method", url="url") # $ clientRequestUrlPart="url" + +pool = urllib3.HTTPConnectionPool("host") + +resp = pool.request("method", "url") # $ clientRequestUrlPart="url" +resp = pool.request("method", url="url") # $ clientRequestUrlPart="url" +resp = pool.urlopen("method", "url") # $ clientRequestUrlPart="url" +resp = pool.urlopen("method", url="url") # $ clientRequestUrlPart="url" + +pool = urllib3.HTTPSConnectionPool("host") + +resp = pool.request("method", "url") # $ clientRequestUrlPart="url" +resp = pool.request("method", url="url") # $ clientRequestUrlPart="url" +resp = pool.urlopen("method", "url") # $ clientRequestUrlPart="url" +resp = pool.urlopen("method", url="url") # $ clientRequestUrlPart="url" From 02a97b08bbd60a03142cd61d9b0bc54e081a5610 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 4 Mar 2022 11:31:47 +0100 Subject: [PATCH 07/11] Python: Move `urllib` and `urllib2` to be part of stdlib modeling --- python/ql/lib/semmle/python/Frameworks.qll | 2 -- .../ql/lib/semmle/python/frameworks/Stdlib.qll | 3 +++ .../python/frameworks/{ => Stdlib}/Urllib.qll | 16 ++++++++++++---- .../python/frameworks/{ => Stdlib}/Urllib2.qll | 10 +++++++--- .../{urllib2/test.py => stdlib-py2/urllib2.py} | 0 .../{urllib/test.py => stdlib/urllib.py} | 0 .../frameworks/urllib/ConceptsTest.expected | 0 .../frameworks/urllib/ConceptsTest.ql | 2 -- .../frameworks/urllib2/ConceptsTest.expected | 0 .../frameworks/urllib2/ConceptsTest.ql | 2 -- 10 files changed, 22 insertions(+), 13 deletions(-) rename python/ql/lib/semmle/python/frameworks/{ => Stdlib}/Urllib.qll (83%) rename python/ql/lib/semmle/python/frameworks/{ => Stdlib}/Urllib2.qll (88%) rename python/ql/test/library-tests/frameworks/{urllib2/test.py => stdlib-py2/urllib2.py} (100%) rename python/ql/test/library-tests/frameworks/{urllib/test.py => stdlib/urllib.py} (100%) delete mode 100644 python/ql/test/library-tests/frameworks/urllib/ConceptsTest.expected delete mode 100644 python/ql/test/library-tests/frameworks/urllib/ConceptsTest.ql delete mode 100644 python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.expected delete mode 100644 python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.ql diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll index 59acaf91d994..b355c4662ee8 100644 --- a/python/ql/lib/semmle/python/Frameworks.qll +++ b/python/ql/lib/semmle/python/Frameworks.qll @@ -47,8 +47,6 @@ 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 diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index 6d60cd59c568..ae5fed627b3e 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -13,6 +13,9 @@ private import semmle.python.frameworks.PEP249 private import semmle.python.frameworks.internal.PoorMansFunctionResolution private import semmle.python.frameworks.internal.SelfRefMixin private import semmle.python.frameworks.internal.InstanceTaintStepsHelper +// modeling split over multiple files to keep this file from becoming too big +private import semmle.python.frameworks.Stdlib.Urllib +private import semmle.python.frameworks.Stdlib.Urllib2 /** Provides models for the Python standard library. */ module Stdlib { diff --git a/python/ql/lib/semmle/python/frameworks/Urllib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll similarity index 83% rename from python/ql/lib/semmle/python/frameworks/Urllib.qll rename to python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll index cc7f6cc0031c..79aacc0818fb 100644 --- a/python/ql/lib/semmle/python/frameworks/Urllib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll @@ -1,6 +1,10 @@ /** - * Provides classes modeling security-relevant aspects of the `urllib` PyPI package. - * See https://docs.python.org/3.9/library/urllib.html + * Provides classes modeling security-relevant aspects of the `urllib` module, part of + * the Python standard library. + * + * See + * - https://docs.python.org/2/library/urllib.html + * - https://docs.python.org/3/library/urllib.html */ private import python @@ -8,8 +12,12 @@ 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 + * Provides models for the `urllib` module, part of + * the Python standard library. + * + * See + * - https://docs.python.org/2/library/urllib.html + * - https://docs.python.org/3/library/urllib.html */ private module Urllib { /** diff --git a/python/ql/lib/semmle/python/frameworks/Urllib2.qll b/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib2.qll similarity index 88% rename from python/ql/lib/semmle/python/frameworks/Urllib2.qll rename to python/ql/lib/semmle/python/frameworks/Stdlib/Urllib2.qll index 272994d450f6..ea15ae4bd333 100644 --- a/python/ql/lib/semmle/python/frameworks/Urllib2.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib2.qll @@ -1,5 +1,7 @@ /** - * Provides classes modeling security-relevant aspects of the `urllib2` PyPI package. + * Provides classes modeling security-relevant aspects of the `urllib2` module, part of + * the Python 2 standard library. + * * See https://docs.python.org/2/library/urllib2.html */ @@ -8,8 +10,10 @@ 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 + * Provides models for the the `urllib2` module, part of + * the Python 2 standard library. + * + * See https://docs.python.org/2/library/urllib2.html */ private module Urllib2 { /** diff --git a/python/ql/test/library-tests/frameworks/urllib2/test.py b/python/ql/test/library-tests/frameworks/stdlib-py2/urllib2.py similarity index 100% rename from python/ql/test/library-tests/frameworks/urllib2/test.py rename to python/ql/test/library-tests/frameworks/stdlib-py2/urllib2.py diff --git a/python/ql/test/library-tests/frameworks/urllib/test.py b/python/ql/test/library-tests/frameworks/stdlib/urllib.py similarity index 100% rename from python/ql/test/library-tests/frameworks/urllib/test.py rename to python/ql/test/library-tests/frameworks/stdlib/urllib.py diff --git a/python/ql/test/library-tests/frameworks/urllib/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/urllib/ConceptsTest.expected deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/python/ql/test/library-tests/frameworks/urllib/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/urllib/ConceptsTest.ql deleted file mode 100644 index b557a0bccb69..000000000000 --- a/python/ql/test/library-tests/frameworks/urllib/ConceptsTest.ql +++ /dev/null @@ -1,2 +0,0 @@ -import python -import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.expected deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.ql deleted file mode 100644 index b557a0bccb69..000000000000 --- a/python/ql/test/library-tests/frameworks/urllib2/ConceptsTest.ql +++ /dev/null @@ -1,2 +0,0 @@ -import python -import experimental.meta.ConceptsTest From 866e6156897bf6424aaf2d4f14bfb4f3b10977de Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 4 Mar 2022 11:40:03 +0100 Subject: [PATCH 08/11] Python: Add PyPI links in qldocs --- python/ql/lib/semmle/python/frameworks/Httpx.qll | 10 ++++++++-- python/ql/lib/semmle/python/frameworks/Libtaxii.qll | 10 ++++++++-- python/ql/lib/semmle/python/frameworks/Pycurl.qll | 12 +++++++++--- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/python/ql/lib/semmle/python/frameworks/Httpx.qll b/python/ql/lib/semmle/python/frameworks/Httpx.qll index 189de1dc68ee..315791f5b8a7 100644 --- a/python/ql/lib/semmle/python/frameworks/Httpx.qll +++ b/python/ql/lib/semmle/python/frameworks/Httpx.qll @@ -1,6 +1,9 @@ /** * Provides classes modeling security-relevant aspects of the `httpx` PyPI package. - * See https://www.python-httpx.org/ + * + * See + * - https://pypi.org/project/httpx/ + * - https://www.python-httpx.org/ */ private import python @@ -9,7 +12,10 @@ private import semmle.python.ApiGraphs /** * Provides models for the `httpx` PyPI package. - * see https://www.python-httpx.org/ + * + * See + * - https://pypi.org/project/httpx/ + * - https://www.python-httpx.org/ */ private module HttpxModel { private class RequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode { diff --git a/python/ql/lib/semmle/python/frameworks/Libtaxii.qll b/python/ql/lib/semmle/python/frameworks/Libtaxii.qll index a4ad81c322a8..176deeb4f975 100644 --- a/python/ql/lib/semmle/python/frameworks/Libtaxii.qll +++ b/python/ql/lib/semmle/python/frameworks/Libtaxii.qll @@ -1,6 +1,9 @@ /** * Provides classes modeling security-relevant aspects of the `libtaxii` PyPI package. - * See https://github.com/TAXIIProject/libtaxii + * + * See + * - https://pypi.org/project/libtaxii/ + * - https://github.com/TAXIIProject/libtaxii */ private import python @@ -9,7 +12,10 @@ private import semmle.python.ApiGraphs /** * Provides models for the `libtaxii` PyPI package. - * see https://github.com/TAXIIProject/libtaxii + * + * See + * - https://pypi.org/project/libtaxii/ + * - https://github.com/TAXIIProject/libtaxii */ private module Libtaxii { /** diff --git a/python/ql/lib/semmle/python/frameworks/Pycurl.qll b/python/ql/lib/semmle/python/frameworks/Pycurl.qll index d0e905365725..6a5fbcbcfeb5 100644 --- a/python/ql/lib/semmle/python/frameworks/Pycurl.qll +++ b/python/ql/lib/semmle/python/frameworks/Pycurl.qll @@ -1,6 +1,9 @@ /** * Provides classes modeling security-relevant aspects of the `pycurl` PyPI package. - * See https://pycurl.io/docs/latest/ + * + * See + * - https://pypi.org/project/pycurl/ + * - https://pycurl.io/docs/latest/ */ private import python @@ -9,7 +12,10 @@ private import semmle.python.ApiGraphs /** * Provides models for the `pycurl` PyPI package. - * see https://pycurl.io/docs/latest/ + * + * See + * - https://pypi.org/project/pycurl/ + * - https://pycurl.io/docs/latest/ */ private module Pycurl { /** @@ -28,7 +34,7 @@ private module Pycurl { * 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. + * See http://pycurl.io/docs/latest/curlobject.html#pycurl.Curl.setopt. */ private class OutgoingRequestCall extends HTTP::Client::Request::Range, DataFlow::CallCfgNode { OutgoingRequestCall() { From 75bc532d10ed28d758d4b1cf5bb92f88c3848344 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 4 Mar 2022 11:40:20 +0100 Subject: [PATCH 09/11] Python: Avoid `toString` usage :O --- python/ql/lib/semmle/python/frameworks/Libtaxii.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/lib/semmle/python/frameworks/Libtaxii.qll b/python/ql/lib/semmle/python/frameworks/Libtaxii.qll index 176deeb4f975..44298b7c08f3 100644 --- a/python/ql/lib/semmle/python/frameworks/Libtaxii.qll +++ b/python/ql/lib/semmle/python/frameworks/Libtaxii.qll @@ -25,7 +25,7 @@ private module Libtaxii { 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" + this.getArgByName("allow_url").getALocalSource().asExpr() = any(True t) } override DataFlow::Node getAUrlPart() { result in [this.getArg(0), this.getArgByName("s")] } From d86284bf320f809440ca10385dd7f9c136413945 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 4 Mar 2022 11:48:06 +0100 Subject: [PATCH 10/11] Python: Update frameworks.rst --- docs/codeql/support/reusables/frameworks.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/codeql/support/reusables/frameworks.rst b/docs/codeql/support/reusables/frameworks.rst index 246b03ee85f2..41b0696bb765 100644 --- a/docs/codeql/support/reusables/frameworks.rst +++ b/docs/codeql/support/reusables/frameworks.rst @@ -173,7 +173,12 @@ Python built-in support starlette, Asynchronous Server Gateway Interface (ASGI) python-ldap, Lightweight Directory Access Protocol (LDAP) ldap3, Lightweight Directory Access Protocol (LDAP) + httpx, HTTP client + pycurl, HTTP client requests, HTTP client + urllib, HTTP client + urllib2, HTTP client + urllib3, HTTP client dill, Serialization PyYAML, Serialization ruamel.yaml, Serialization @@ -206,3 +211,4 @@ Python built-in support pycryptodomex, Cryptography library rsa, Cryptography library MarkupSafe, Escaping Library + libtaxii, TAXII utility library From e47f726e740d86107edfe20dcb74250a5ce85a54 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 4 Mar 2022 11:48:17 +0100 Subject: [PATCH 11/11] Python: Add change-note --- python/ql/lib/change-notes/2022-03-04-add-ssrf-sinks.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 python/ql/lib/change-notes/2022-03-04-add-ssrf-sinks.md diff --git a/python/ql/lib/change-notes/2022-03-04-add-ssrf-sinks.md b/python/ql/lib/change-notes/2022-03-04-add-ssrf-sinks.md new file mode 100644 index 000000000000..e7949cc1d00c --- /dev/null +++ b/python/ql/lib/change-notes/2022-03-04-add-ssrf-sinks.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* 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).