mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge pull request #20914 from joefarebrother/python-socketio
Python: Add models for socketio
This commit is contained in:
4
python/ql/lib/change-notes/2025-11-26-socketio.md
Normal file
4
python/ql/lib/change-notes/2025-11-26-socketio.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Remote flow sources for the `python-socketio` package have been modeled.
|
||||
@@ -78,6 +78,7 @@ private import semmle.python.frameworks.Sanic
|
||||
private import semmle.python.frameworks.ServerLess
|
||||
private import semmle.python.frameworks.Setuptools
|
||||
private import semmle.python.frameworks.Simplejson
|
||||
private import semmle.python.frameworks.Socketio
|
||||
private import semmle.python.frameworks.SqlAlchemy
|
||||
private import semmle.python.frameworks.Starlette
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
|
||||
119
python/ql/lib/semmle/python/frameworks/Socketio.qll
Normal file
119
python/ql/lib/semmle/python/frameworks/Socketio.qll
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Provides definitions and modeling for the `python-socketio` PyPI package.
|
||||
* See https://python-socketio.readthedocs.io/en/stable/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
|
||||
|
||||
/**
|
||||
* Provides models for the `python-socketio` PyPI package.
|
||||
* See https://python-socketio.readthedocs.io/en/stable/.
|
||||
*/
|
||||
module SocketIO {
|
||||
/** Provides models for socketio `Server` and `AsyncServer` classes. */
|
||||
module Server {
|
||||
/** Gets an instance of a socketio `Server` or `AsyncServer`. */
|
||||
API::Node server() {
|
||||
result = API::moduleImport("socketio").getMember(["Server", "AsyncServer"]).getAnInstance()
|
||||
}
|
||||
|
||||
/** Gets a decorator that indicates a socketio event handler. */
|
||||
private API::Node serverEventAnnotation() {
|
||||
result = server().getMember("event")
|
||||
or
|
||||
result = server().getMember("on").getReturn()
|
||||
}
|
||||
|
||||
private class EventHandler extends Http::Server::RequestHandler::Range {
|
||||
EventHandler() {
|
||||
serverEventAnnotation().getAValueReachableFromSource().asExpr() = this.getADecorator()
|
||||
or
|
||||
exists(DataFlow::CallCfgNode c, DataFlow::Node arg |
|
||||
c = server().getMember("on").getACall()
|
||||
|
|
||||
(
|
||||
arg = c.getArg(1)
|
||||
or
|
||||
arg = c.getArgByName("handler")
|
||||
) and
|
||||
poorMansFunctionTracker(this) = arg
|
||||
)
|
||||
}
|
||||
|
||||
override Parameter getARoutedParameter() {
|
||||
result = this.getAnArg() and
|
||||
not result = this.getArg(0) // First parameter is `sid`, which is not a remote flow source as it cannot be controlled by the client.
|
||||
}
|
||||
|
||||
override string getFramework() { result = "socketio" }
|
||||
}
|
||||
|
||||
private class CallbackArgument extends DataFlow::Node {
|
||||
CallbackArgument() {
|
||||
exists(DataFlow::CallCfgNode c |
|
||||
c = [server(), Namespace::instance()].getMember(["emit", "send"]).getACall()
|
||||
|
|
||||
this = c.getArgByName("callback")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class CallbackHandler extends Http::Server::RequestHandler::Range {
|
||||
CallbackHandler() { any(CallbackArgument ca) = poorMansFunctionTracker(this) }
|
||||
|
||||
override Parameter getARoutedParameter() { result = this.getAnArg() }
|
||||
|
||||
override string getFramework() { result = "socketio" }
|
||||
}
|
||||
|
||||
private class SocketIOCall extends RemoteFlowSource::Range {
|
||||
SocketIOCall() { this = [server(), Namespace::instance()].getMember("call").getACall() }
|
||||
|
||||
override string getSourceType() { result = "socketio call" }
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides modeling for socketio server Namespace/AsyncNamespace classes. */
|
||||
module Namespace {
|
||||
/** Gets a reference to the `socketio.Namespace` or `socketio.AsyncNamespace` classes or any subclass. */
|
||||
API::Node subclassRef() {
|
||||
result =
|
||||
API::moduleImport("socketio").getMember(["Namespace", "AsyncNamespace"]).getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of a subclass of `socketio.Namespace` or `socketio.AsyncNamespace`. */
|
||||
API::Node instance() {
|
||||
result = subclassRef().getAnInstance()
|
||||
or
|
||||
result = subclassRef().getAMember().getSelfParameter()
|
||||
}
|
||||
|
||||
/** A socketio Namespace class. */
|
||||
class NamespaceClass extends Class {
|
||||
NamespaceClass() { this.getABase() = subclassRef().asSource().asExpr() }
|
||||
|
||||
/** Gets a handler for socketio events. */
|
||||
Function getAnEventHandler() {
|
||||
result = this.getAMethod() and
|
||||
result.getName().matches("on_%")
|
||||
}
|
||||
}
|
||||
|
||||
private class NamespaceEventHandler extends Http::Server::RequestHandler::Range {
|
||||
NamespaceEventHandler() { this = any(NamespaceClass nc).getAnEventHandler() }
|
||||
|
||||
override Parameter getARoutedParameter() {
|
||||
result = this.getAnArg() and
|
||||
not result = this.getArg(0) and
|
||||
not result = this.getArg(1) // First 2 parameters are `self` and `sid`.
|
||||
}
|
||||
|
||||
override string getFramework() { result = "socketio" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import python
|
||||
import experimental.meta.ConceptsTest
|
||||
@@ -0,0 +1,3 @@
|
||||
argumentToEnsureNotTaintedNotMarkedAsSpurious
|
||||
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
|
||||
testFailures
|
||||
@@ -0,0 +1,2 @@
|
||||
import experimental.meta.InlineTaintTest
|
||||
import MakeInlineTaintTest<TestTaintTrackingConfig>
|
||||
@@ -0,0 +1,69 @@
|
||||
import sys
|
||||
import socketio
|
||||
|
||||
def ensure_tainted(*args):
|
||||
print("tainted", args)
|
||||
|
||||
def ensure_not_tainted(*args):
|
||||
print("not tainted", args)
|
||||
|
||||
sio = socketio.Server()
|
||||
|
||||
@sio.event
|
||||
def connect(sid, environ, auth): # $ requestHandler routedParameter=environ routedParameter=auth
|
||||
ensure_not_tainted(sid)
|
||||
ensure_tainted(environ, # $ tainted
|
||||
auth) # $ tainted
|
||||
|
||||
@sio.event
|
||||
def event1(sid, data): # $ requestHandler routedParameter=data
|
||||
ensure_not_tainted(sid)
|
||||
ensure_tainted(data) # $ tainted
|
||||
res = sio.call("e1", sid=sid)
|
||||
ensure_tainted(res) # $ tainted
|
||||
sio.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
|
||||
sio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
|
||||
|
||||
class MyNamespace(socketio.Namespace):
|
||||
def on_event2(self, sid, data): # $ requestHandler routedParameter=data
|
||||
ensure_not_tainted(self, sid)
|
||||
ensure_tainted(data) # $ tainted
|
||||
res = self.call("e1", sid=sid)
|
||||
ensure_tainted(res) # $ tainted
|
||||
self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
|
||||
self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
|
||||
|
||||
sio.register_namespace(MyNamespace("/ns"))
|
||||
|
||||
asio = socketio.AsyncServer(async_mode='asgi')
|
||||
|
||||
@asio.event
|
||||
async def event3(sid, data): # $ requestHandler routedParameter=data
|
||||
ensure_not_tainted(sid)
|
||||
ensure_tainted(data) # $ tainted
|
||||
res = await asio.call("e1", sid=sid)
|
||||
ensure_tainted(res) # $ tainted
|
||||
await asio.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
|
||||
await asio.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
|
||||
|
||||
class MyAsyncNamespace(socketio.AsyncNamespace):
|
||||
async def on_event4(self, sid, data): # $ requestHandler routedParameter=data
|
||||
ensure_not_tainted(self, sid)
|
||||
ensure_tainted(data) # $ tainted
|
||||
res = await self.call("e1", sid=sid)
|
||||
ensure_tainted(res) # $ tainted
|
||||
await self.emit("e2", "hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
|
||||
await self.send("hi", to=sid, callback=lambda x: ensure_tainted(x)) # $ tainted $ requestHandler routedParameter=x
|
||||
|
||||
asio.register_namespace(MyAsyncNamespace("/ns"))
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
if "--async" in sys.argv: # $ threatModelSource[commandargs]=sys.argv
|
||||
import uvicorn
|
||||
app = socketio.ASGIApp(asio)
|
||||
uvicorn.run(app, host='127.0.0.1', port=8000)
|
||||
else:
|
||||
import eventlet
|
||||
app = socketio.WSGIApp(sio)
|
||||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
|
||||
29
python/ql/test/library-tests/frameworks/socketio/test.py
Normal file
29
python/ql/test/library-tests/frameworks/socketio/test.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import socketio
|
||||
|
||||
sio = socketio.Server()
|
||||
|
||||
@sio.on("connect")
|
||||
def connect(sid, environ, auth): # $ requestHandler routedParameter=environ routedParameter=auth
|
||||
print("connect", sid, environ, auth)
|
||||
|
||||
@sio.on("event1")
|
||||
def handle(sid, data): # $ requestHandler routedParameter=data
|
||||
print("e1", sid, data)
|
||||
|
||||
@sio.event
|
||||
def event2(sid, data): # $ requestHandler routedParameter=data
|
||||
print("e2", sid, data)
|
||||
|
||||
def event3(sid, data): # $ requestHandler routedParameter=data
|
||||
print("e3", sid, data)
|
||||
|
||||
sio.on("event3", handler=event3)
|
||||
|
||||
sio.on("event4", lambda sid,data: print("e4", sid, data)) # $ requestHandler routedParameter=data
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = socketio.WSGIApp(sio)
|
||||
import eventlet
|
||||
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
|
||||
Reference in New Issue
Block a user