diff --git a/python/ql/lib/semmle/python/frameworks/Eventlet.model.yml b/python/ql/lib/semmle/python/frameworks/Eventlet.model.yml new file mode 100644 index 00000000000..f60b9218819 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Eventlet.model.yml @@ -0,0 +1,9 @@ +extensions: + - addsTo: + pack: codeql/python-all + extensible: typeModel + data: + # See https://eventlet.readthedocs.io/en/latest/patching.html + - ['socket.socket', 'eventlet', 'Member[green].Member[socket].Member[socket].ReturnValue'] + # eventlet also re-exports as eventlet.socket for convenience + - ['socket.socket', 'eventlet', 'Member[socket].Member[socket].ReturnValue'] diff --git a/python/ql/lib/semmle/python/frameworks/Gevent.model.yml b/python/ql/lib/semmle/python/frameworks/Gevent.model.yml new file mode 100644 index 00000000000..974ecedd073 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Gevent.model.yml @@ -0,0 +1,7 @@ +extensions: + - addsTo: + pack: codeql/python-all + extensible: typeModel + data: + # See https://www.gevent.org/api/gevent.socket.html + - ['socket.socket', 'gevent', 'Member[socket].Member[socket].ReturnValue'] diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.model.yml b/python/ql/lib/semmle/python/frameworks/Stdlib.model.yml index a01bf1b40ba..5b50dff313e 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.model.yml +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.model.yml @@ -27,6 +27,8 @@ extensions: extensible: sinkModel data: - ["zipfile.ZipFile","Member[extractall].Argument[0,path:]", "path-injection"] + # See https://docs.python.org/3/library/socket.html#socket.socket.bind + - ["socket.socket", "Member[bind].Argument[0,address:]", "bind-socket-all-interfaces"] - addsTo: pack: codeql/python-all @@ -184,6 +186,8 @@ extensions: pack: codeql/python-all extensible: typeModel data: + # See https://docs.python.org/3/library/socket.html#socket.socket + - ['socket.socket', 'socket', 'Member[socket].ReturnValue'] # See https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse - ["urllib.parse.ParseResult~Subclass", 'urllib', 'Member[parse].Member[urlparse]'] diff --git a/python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql b/python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql index 5e2e27b3bf4..39d0c6b6237 100644 --- a/python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql +++ b/python/ql/src/Security/CVE-2018-1281/BindToAllInterfaces.ql @@ -14,7 +14,8 @@ import python import semmle.python.dataflow.new.DataFlow -import semmle.python.ApiGraphs +import semmle.python.dataflow.new.TaintTracking +private import semmle.python.frameworks.data.ModelsAsData /** Gets a hostname that can be used to bind to all interfaces. */ private string vulnerableHostname() { @@ -26,45 +27,21 @@ private string vulnerableHostname() { ] } -/** Gets a reference to a hostname that can be used to bind to all interfaces. */ -private DataFlow::TypeTrackingNode vulnerableHostnameRef(DataFlow::TypeTracker t, string hostname) { - t.start() and - exists(StringLiteral allInterfacesStringLiteral | hostname = vulnerableHostname() | - allInterfacesStringLiteral.getText() = hostname and - result.asExpr() = allInterfacesStringLiteral - ) - or - exists(DataFlow::TypeTracker t2 | result = vulnerableHostnameRef(t2, hostname).track(t2, t)) +private module BindToAllInterfacesConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source.asExpr().(StringLiteral).getText() = vulnerableHostname() + } + + predicate isSink(DataFlow::Node sink) { + ModelOutput::sinkNode(sink, "bind-socket-all-interfaces") + } } -/** Gets a reference to a hostname that can be used to bind to all interfaces. */ -DataFlow::Node vulnerableHostnameRef(string hostname) { - vulnerableHostnameRef(DataFlow::TypeTracker::end(), hostname).flowsTo(result) -} +private module BindToAllInterfacesFlow = TaintTracking::Global; -/** Gets a reference to a tuple for which the first element is a hostname that can be used to bind to all interfaces. */ -private DataFlow::TypeTrackingNode vulnerableAddressTuple(DataFlow::TypeTracker t, string hostname) { - t.start() and - result.asExpr() = any(Tuple tup | tup.getElt(0) = vulnerableHostnameRef(hostname).asExpr()) - or - exists(DataFlow::TypeTracker t2 | result = vulnerableAddressTuple(t2, hostname).track(t2, t)) -} - -/** Gets a reference to a tuple for which the first element is a hostname that can be used to bind to all interfaces. */ -DataFlow::Node vulnerableAddressTuple(string hostname) { - vulnerableAddressTuple(DataFlow::TypeTracker::end(), hostname).flowsTo(result) -} - -/** - * Gets an instance of `socket.socket` using _some_ address family. - * - * See https://docs.python.org/3/library/socket.html - */ -API::Node socketInstance() { result = API::moduleImport("socket").getMember("socket").getReturn() } - -from DataFlow::CallCfgNode bindCall, DataFlow::Node addressArg, string hostname +from DataFlow::Node source, DataFlow::Node sink, DataFlow::CallCfgNode bindCall, string hostname where - bindCall = socketInstance().getMember("bind").getACall() and - addressArg = bindCall.getArg(0) and - addressArg = vulnerableAddressTuple(hostname) + BindToAllInterfacesFlow::flow(source, sink) and + bindCall.getArg(0) = sink and + hostname = source.asExpr().(StringLiteral).getText() select bindCall.asExpr(), "'" + hostname + "' binds a socket to all interfaces." diff --git a/python/ql/test/query-tests/Security/CVE-2018-1281/BindToAllInterfaces.expected b/python/ql/test/query-tests/Security/CVE-2018-1281/BindToAllInterfaces.expected index 86c67af4eae..d657c2f14db 100644 --- a/python/ql/test/query-tests/Security/CVE-2018-1281/BindToAllInterfaces.expected +++ b/python/ql/test/query-tests/Security/CVE-2018-1281/BindToAllInterfaces.expected @@ -3,3 +3,7 @@ | BindToAllInterfaces_test.py:17:1:17:26 | Attribute() | '0.0.0.0' binds a socket to all interfaces. | | BindToAllInterfaces_test.py:21:1:21:11 | Attribute() | '0.0.0.0' binds a socket to all interfaces. | | BindToAllInterfaces_test.py:26:1:26:20 | Attribute() | '::' binds a socket to all interfaces. | +| BindToAllInterfaces_test.py:39:9:39:43 | Attribute() | '0.0.0.0' binds a socket to all interfaces. | +| BindToAllInterfaces_test.py:48:1:48:20 | Attribute() | '0.0.0.0' binds a socket to all interfaces. | +| BindToAllInterfaces_test.py:53:1:53:27 | Attribute() | '0.0.0.0' binds a socket to all interfaces. | +| BindToAllInterfaces_test.py:58:1:58:27 | Attribute() | '0.0.0.0' binds a socket to all interfaces. | diff --git a/python/ql/test/query-tests/Security/CVE-2018-1281/BindToAllInterfaces_test.py b/python/ql/test/query-tests/Security/CVE-2018-1281/BindToAllInterfaces_test.py index 5a13aa9c4e3..8d5d7998101 100644 --- a/python/ql/test/query-tests/Security/CVE-2018-1281/BindToAllInterfaces_test.py +++ b/python/ql/test/query-tests/Security/CVE-2018-1281/BindToAllInterfaces_test.py @@ -36,7 +36,7 @@ class Server: def start(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind((self.bind_addr, self.port)) # $ MISSING: Alert[py/bind-socket-all-network-interfaces] + s.bind((self.bind_addr, self.port)) # $ Alert[py/bind-socket-all-network-interfaces] server = Server() server.start() @@ -45,14 +45,14 @@ server.start() import os host = os.environ.get('APP_HOST', '0.0.0.0') s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.bind((host, 8080)) # $ MISSING: Alert[py/bind-socket-all-network-interfaces] +s.bind((host, 8080)) # $ Alert[py/bind-socket-all-network-interfaces] # gevent.socket (alternative socket module) from gevent import socket as gsocket gs = gsocket.socket(gsocket.AF_INET, gsocket.SOCK_STREAM) -gs.bind(('0.0.0.0', 31137)) # $ MISSING: Alert[py/bind-socket-all-network-interfaces] +gs.bind(('0.0.0.0', 31137)) # $ Alert[py/bind-socket-all-network-interfaces] # eventlet.green.socket (another alternative socket module) from eventlet.green import socket as esocket es = esocket.socket(esocket.AF_INET, esocket.SOCK_STREAM) -es.bind(('0.0.0.0', 31137)) # $ MISSING: Alert[py/bind-socket-all-network-interfaces] +es.bind(('0.0.0.0', 31137)) # $ Alert[py/bind-socket-all-network-interfaces] diff --git a/shared/mad/codeql/mad/ModelValidation.qll b/shared/mad/codeql/mad/ModelValidation.qll index 042fb4200dd..5eaa78626ab 100644 --- a/shared/mad/codeql/mad/ModelValidation.qll +++ b/shared/mad/codeql/mad/ModelValidation.qll @@ -48,7 +48,7 @@ module KindValidation { // CPP-only currently "remote-sink", // Python-only currently, but may be shared in the future - "prompt-injection" + "bind-socket-all-interfaces", "prompt-injection" ] or this.matches([