diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll index d502a421f40..59acaf91d99 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 3ec70643986..177b997acc2 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 00000000000..c1949ee6ba4 --- /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 00000000000..181ba82b74d --- /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 00000000000..c85faf5ceea --- /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 00000000000..85493612401 --- /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 00000000000..93ce324751a --- /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 00000000000..9578002c973 --- /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() + } + } + } +}