Files
codeql/python/ql/lib/semmle/python/frameworks/Tornado.qll
2025-11-24 13:57:43 +00:00

675 lines
26 KiB
Plaintext

/**
* Provides classes modeling security-relevant aspects of the `tornado` PyPI package.
* See https://www.tornadoweb.org/en/stable/.
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.Concepts
private import semmle.python.ApiGraphs
private import semmle.python.regex
private import semmle.python.frameworks.Stdlib
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
private import semmle.python.frameworks.data.ModelsAsData
/**
* INTERNAL: Do not use.
*
* Provides models for the `tornado` PyPI package.
* See https://www.tornadoweb.org/en/stable/.
*/
module Tornado {
/**
* Provides models for the `tornado.httputil.HTTPHeaders` class
*
* See https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPHeaders.
*/
module HttpHeaders {
/**
* A source of instances of `tornado.httputil.HTTPHeaders`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `HTTPHeaders::instance()` to get references to instances of `tornado.httputil.HTTPHeaders`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
/** Gets a reference to an instance of `tornado.httputil.HttpHeaders`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `tornado.httputil.HttpHeaders`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/**
* Taint propagation for `tornado.httputil.HTTPHeaders`.
*/
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
InstanceTaintSteps() { this = "tornado.httputil.HTTPHeaders" }
override DataFlow::Node getInstance() { result = instance() }
override string getAttributeName() { none() }
override string getMethodName() { result in ["get_list", "get_all"] }
override string getAsyncMethodName() { none() }
}
/**
* A dict-like write to an item of an `HTTPHeaders` object.
*/
private class TornadoHeaderSubscriptWrite extends Http::Server::ResponseHeaderWrite::Range {
DataFlow::Node index;
DataFlow::Node value;
TornadoHeaderSubscriptWrite() {
exists(SubscriptNode subscript |
subscript.getObject() = instance().asCfgNode() and
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
index.asCfgNode() = subscript.getIndex() and
this.asCfgNode() = subscript
)
}
override DataFlow::Node getNameArg() { result = index }
override DataFlow::Node getValueArg() { result = value }
override predicate nameAllowsNewline() { none() }
override predicate valueAllowsNewline() { none() }
}
/**
* A call to `HTTPHeaders.add`.
*/
private class TornadoHeadersAppendCall extends Http::Server::ResponseHeaderWrite::Range,
DataFlow::MethodCallNode
{
TornadoHeadersAppendCall() { this.calls(instance(), "add") }
override DataFlow::Node getNameArg() { result = [this.getArg(0), this.getArgByName("name")] }
override DataFlow::Node getValueArg() {
result in [this.getArg(1), this.getArgByName("value")]
}
override predicate nameAllowsNewline() { none() }
override predicate valueAllowsNewline() { none() }
}
}
// ---------------------------------------------------------------------------
// tornado
// ---------------------------------------------------------------------------
/** Gets a reference to the `tornado` module. */
API::Node tornado() { result = API::moduleImport("tornado") }
/** Provides models for the `tornado` module. */
module TornadoModule {
// -------------------------------------------------------------------------
// tornado.web
// -------------------------------------------------------------------------
/** Gets a reference to the `tornado.web` module. */
API::Node web() { result = tornado().getMember("web") }
/** Provides models for the `tornado.web` module */
module Web {
/**
* Provides models for the `tornado.web.RequestHandler` class and subclasses.
*
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.
*/
module RequestHandler {
/** Gets a reference to the `tornado.web.RequestHandler` class or any subclass. */
API::Node subclassRef() {
result = web().getMember("RequestHandler").getASubclass*()
or
result = WebSocket::WebSocketHandler::subclassRef()
or
result = ModelOutput::getATypeNode("tornado.web.RequestHandler~Subclass").getASubclass*()
}
/** A RequestHandler class (most likely in project code). */
class RequestHandlerClass extends Class {
RequestHandlerClass() { this.getParent() = subclassRef().asSource().asExpr() }
/** Gets a function that could handle incoming requests, if any. */
Function getARequestHandler() {
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
result = this.getAMethod() and
result.getName() = Http::httpVerbLower()
}
/** Gets a reference to this class. */
private DataFlow::TypeTrackingNode getARef(DataFlow::TypeTracker t) {
t.start() and
result.asExpr() = this.getParent()
or
exists(DataFlow::TypeTracker t2 | result = this.getARef(t2).track(t2, t))
}
/** Gets a reference to this class. */
DataFlow::Node getARef() { this.getARef(DataFlow::TypeTracker::end()).flowsTo(result) }
}
/**
* A source of instances of the `tornado.web.RequestHandler` class or any subclass, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `RequestHandler::instance()` to get references to instances of the `tornado.web.RequestHandler` class or any subclass.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
/** The `self` parameter in a method on the `tornado.web.RequestHandler` class or any subclass. */
class SelfParam extends InstanceSource, RemoteFlowSource::Range, DataFlow::ParameterNode {
SelfParam() {
exists(RequestHandlerClass cls | cls.getAMethod().getArg(0) = this.getParameter())
}
override string getSourceType() { result = "tornado.web.RequestHandler" }
}
/** Gets a reference to an instance of the `tornado.web.RequestHandler` class or any subclass. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of the `tornado.web.RequestHandler` class or any subclass. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/** Gets a reference the `redirect` method. */
private DataFlow::TypeTrackingNode redirectMethod(DataFlow::TypeTracker t) {
t.startInAttr("redirect") and
result = instance()
or
exists(DataFlow::TypeTracker t2 | result = redirectMethod(t2).track(t2, t))
}
/** Gets a reference the `redirect` method. */
DataFlow::Node redirectMethod() {
redirectMethod(DataFlow::TypeTracker::end()).flowsTo(result)
}
/** Gets a reference to the `write` method. */
private DataFlow::TypeTrackingNode writeMethod(DataFlow::TypeTracker t) {
t.startInAttr("write") and
result = instance()
or
exists(DataFlow::TypeTracker t2 | result = writeMethod(t2).track(t2, t))
}
/** Gets a reference to the `write` method. */
DataFlow::Node writeMethod() { writeMethod(DataFlow::TypeTracker::end()).flowsTo(result) }
/**
* Taint propagation for `tornado.web.RequestHandler`.
*/
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
InstanceTaintSteps() { this = "tornado.web.RequestHandler" }
override DataFlow::Node getInstance() { result = instance() }
override string getAttributeName() {
result in [
// List[str]
"path_args",
// Dict[str, str]
"path_kwargs",
// tornado.httputil.HTTPServerRequest
"request"
]
}
override string getMethodName() {
result in [
"get_argument", "get_body_argument", "get_query_argument", "get_arguments",
"get_body_arguments", "get_query_arguments"
]
}
override string getAsyncMethodName() { none() }
}
private class RequestAttrAccess extends TornadoModule::HttpUtil::HttpServerRequest::InstanceSource
{
RequestAttrAccess() {
this.(DataFlow::AttrRead).getObject() = instance() and
this.(DataFlow::AttrRead).getAttributeName() = "request"
}
}
/** A call to `RequestHandler.set_header` or `RequestHandler.add_header` */
private class TornadoSetHeaderCall extends Http::Server::ResponseHeaderWrite::Range,
DataFlow::MethodCallNode
{
TornadoSetHeaderCall() { this.calls(instance(), ["set_header", "add_header"]) }
override DataFlow::Node getNameArg() {
result = [this.getArg(0), this.getArgByName("name")]
}
override DataFlow::Node getValueArg() {
result in [this.getArg(1), this.getArgByName("value")]
}
override predicate nameAllowsNewline() { none() }
override predicate valueAllowsNewline() { none() }
}
}
/**
* Provides models for the `tornado.web.Application` class
*
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.Application.
*/
module Application {
/** Gets a reference to the `tornado.web.Application` class. */
API::Node classRef() {
result = web().getMember("Application")
or
result = ModelOutput::getATypeNode("tornado.web.Application~Subclass").getASubclass*()
}
/**
* A source of instances of `tornado.web.Application`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `Application::instance()` to get references to instances of `tornado.web.Application`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
/** A direct instantiation of `tornado.web.Application`. */
class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
ClassInstantiation() { this = classRef().getACall() }
}
/** Gets a reference to an instance of `tornado.web.Application`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `tornado.web.Application`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/** Gets a reference to the `add_handlers` method. */
private DataFlow::TypeTrackingNode add_handlers(DataFlow::TypeTracker t) {
t.startInAttr("add_handlers") and
result = instance()
or
exists(DataFlow::TypeTracker t2 | result = add_handlers(t2).track(t2, t))
}
/** Gets a reference to the `add_handlers` method. */
DataFlow::Node add_handlers() { add_handlers(DataFlow::TypeTracker::end()).flowsTo(result) }
}
}
// -------------------------------------------------------------------------
// tornado.httputil
// -------------------------------------------------------------------------
/** Gets a reference to the `tornado.httputil` module. */
API::Node httputil() { result = tornado().getMember("httputil") }
/** Provides models for the `tornado.httputil` module */
module HttpUtil {
/**
* Provides models for the `tornado.httputil.HttpServerRequest` class
*
* See https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPServerRequest.
*/
module HttpServerRequest {
/** Gets a reference to the `tornado.httputil.HttpServerRequest` class. */
API::Node classRef() {
result = httputil().getMember("HttpServerRequest")
or
result =
ModelOutput::getATypeNode("tornado.httputil.HttpServerRequest~Subclass").getASubclass*()
}
/**
* A source of instances of `tornado.httputil.HttpServerRequest`, extend this class to model new instances.
*
* This can include instantiations of the class, return values from function
* calls, or a special parameter that will be set when functions are called by an external
* library.
*
* Use the predicate `HttpServerRequest::instance()` to get references to instances of `tornado.httputil.HttpServerRequest`.
*/
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
/** A direct instantiation of `tornado.httputil.HttpServerRequest`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
ClassInstantiation() { this = classRef().getACall() }
}
/** Gets a reference to an instance of `tornado.httputil.HttpServerRequest`. */
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
t.start() and
result instanceof InstanceSource
or
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
}
/** Gets a reference to an instance of `tornado.httputil.HttpServerRequest`. */
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
/**
* Taint propagation for `tornado.httputil.HttpServerRequest`.
*/
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
InstanceTaintSteps() { this = "tornado.httputil.HttpServerRequest" }
override DataFlow::Node getInstance() { result = instance() }
override string getAttributeName() {
result in [
// str / bytes
"uri", "path", "query", "remote_ip", "body",
// Dict[str, List[bytes]]
"arguments", "query_arguments", "body_arguments",
// dict-like, https://www.tornadoweb.org/en/stable/httputil.html#tornado.httputil.HTTPHeaders
"headers",
// Dict[str, http.cookies.Morsel]
"cookies"
]
}
override string getMethodName() { result = "full_url" }
override string getAsyncMethodName() { none() }
}
/** An `HttpHeaders` instance that originates from a Tornado request. */
private class TornadoRequestHttpHeadersInstances extends HttpHeaders::InstanceSource {
TornadoRequestHttpHeadersInstances() {
this.(DataFlow::AttrRead).accesses(instance(), "headers")
}
}
/** An `Morsel` instance that originates from a Tornado request. */
private class TornadoRequestMorselInstances extends Stdlib::Morsel::InstanceSource {
TornadoRequestMorselInstances() {
// TODO: this currently only works in local-scope, since writing type-trackers for
// this is a little too much effort. Once API-graphs are available for more
// things, we can rewrite this.
//
// TODO: This approach for identifying member-access is very adhoc, and we should
// be able to do something more structured for providing modeling of the members
// of a container-object.
exists(DataFlow::AttrRead files | files.accesses(instance(), "cookies") |
this.asCfgNode().(SubscriptNode).getObject() = files.asCfgNode()
or
this.(DataFlow::MethodCallNode).calls(files, "get")
)
}
}
}
}
// ---------------------------------------------------------------------------
// tornado.websocket
// ---------------------------------------------------------------------------
/** Gets a reference to the `tornado.websocket` module. */
API::Node websocket() { result = Tornado::tornado().getMember("websocket") }
/** Provides models for the `tornado.websocket` module */
module WebSocket {
/**
* Provides models for the `tornado.websocket.WebSocketHandler` class and subclasses.
*
* See https://www.tornadoweb.org/en/stable/websocket.html#tornado.websocket.WebSocketHandler.
*/
module WebSocketHandler {
/** Gets a reference to the `tornado.websocket.WebSocketHandler` class or any subclass. */
API::Node subclassRef() {
result = websocket().getMember("WebSocketHandler").getASubclass*()
or
result =
ModelOutput::getATypeNode("tornado.websocket.WebSocketHandler~Subclass").getASubclass*()
}
/** A subclass of `tornado.websocket.WebSocketHandler`. */
class WebSocketHandlerClass extends Web::RequestHandler::RequestHandlerClass {
WebSocketHandlerClass() { this.getParent() = subclassRef().asSource().asExpr() }
override Function getARequestHandler() {
result = super.getARequestHandler()
or
result = this.getAMethod() and
result.getName() = "open"
}
/** Gets a function that could handle incoming WebSocket events, if any. */
Function getAWebSocketEventHandler() {
result = this.getAMethod() and
result.getName() =
["on_message", "on_close", "on_ping", "on_pong", "select_subprotocol", "check_origin"]
}
}
}
}
}
// ---------------------------------------------------------------------------
// routing
// ---------------------------------------------------------------------------
/** Gets a sequence that defines a number of route rules */
SequenceNode routeSetupRuleList() {
exists(CallNode call |
call = any(TornadoModule::Web::Application::ClassInstantiation c).asCfgNode()
|
result in [call.getArg(0), call.getArgByName("handlers")]
)
or
exists(CallNode call |
call.getFunction() = TornadoModule::Web::Application::add_handlers().asCfgNode()
|
result in [call.getArg(1), call.getArgByName("host_handlers")]
)
or
result = routeSetupRuleList().getElement(_).(TupleNode).getElement(1)
}
/** A tornado route setup. */
abstract class TornadoRouteSetup extends Http::Server::RouteSetup::Range {
override string getFramework() { result = "Tornado" }
}
/**
* A regex that is used to set up a route.
*
* Needs this subclass to be considered a RegExpInterpretation.
*/
private class TornadoRouteRegex extends RegExpInterpretation::Range {
TornadoRouteSetup setup;
TornadoRouteRegex() { this = setup.getUrlPatternArg() }
TornadoRouteSetup getRouteSetup() { result = setup }
}
/** A route setup using a tuple. */
private class TornadoTupleRouteSetup extends TornadoRouteSetup, DataFlow::CfgNode {
override TupleNode node;
TornadoTupleRouteSetup() {
node = routeSetupRuleList().getElement(_) and
count(node.getElement(_)) = 2 and
not node.getElement(1) instanceof SequenceNode
}
override DataFlow::Node getUrlPatternArg() { result.asCfgNode() = node.getElement(0) }
override Function getARequestHandler() {
exists(TornadoModule::Web::RequestHandler::RequestHandlerClass cls |
cls.getARef().asCfgNode() = node.getElement(1) and
result = cls.getARequestHandler()
)
}
override Parameter getARoutedParameter() {
// If we don't know the URL pattern, we simply mark all parameters as a routed
// parameter. This should give us more RemoteFlowSources but could also lead to
// more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
exists(Function requestHandler | requestHandler = this.getARequestHandler() |
not exists(this.getUrlPattern()) and
result in [
requestHandler.getArg(_), requestHandler.getArgByName(_),
requestHandler.getVararg().(Parameter), requestHandler.getKwarg().(Parameter)
] and
not result = requestHandler.getArg(0)
)
or
exists(Function requestHandler, TornadoRouteRegex regexUse, RegExp regex |
regex.getAUse() = regexUse and
requestHandler = this.getARequestHandler() and
regexUse.getRouteSetup() = this
|
// first group will have group number 1
result = requestHandler.getArg(regex.getGroupNumber(_, _))
or
result = requestHandler.getArgByName(regex.getGroupName(_, _))
or
exists(regex.getGroupNumber(_, _)) and
result = requestHandler.getVararg()
or
exists(regex.getGroupName(_, _)) and
result = requestHandler.getKwarg()
)
}
}
/** A request handler defined in a tornado RequestHandler class, that has no known route. */
private class TornadoRequestHandlerWithoutKnownRoute extends Http::Server::RequestHandler::Range {
TornadoRequestHandlerWithoutKnownRoute() {
exists(TornadoModule::Web::RequestHandler::RequestHandlerClass cls |
cls.getARequestHandler() = this
) and
not exists(TornadoRouteSetup setup | setup.getARequestHandler() = this)
}
override Parameter getARoutedParameter() {
// Since we don't know the URL pattern, we simply mark all parameters as a routed
// parameter. This should give us more RemoteFlowSources but could also lead to
// more FPs. If this turns out to be the wrong tradeoff, we can always change our mind.
result in [
this.getArg(_), this.getArgByName(_), this.getVararg().(Parameter),
this.getKwarg().(Parameter)
] and
not result = this.getArg(0)
}
override string getFramework() { result = "Tornado" }
}
/** A request handler for WebSocket events. */
private class TornadoWebSocketEventHandler extends Http::Server::RequestHandler::Range {
TornadoWebSocketEventHandler() {
exists(TornadoModule::WebSocket::WebSocketHandler::WebSocketHandlerClass cls |
cls.getAWebSocketEventHandler() = this
)
}
override Parameter getARoutedParameter() {
// The `open` method is handled as a normal request handler in `TornadoRouteSetup` or `TornadoRequestHandlerWithoutKnownRoute`.
// For other event handlers (such as `on_message`), all parameters should be remote flow sources, as they are not affected by routing.
result in [
this.getArg(_), this.getArgByName(_), this.getVararg().(Parameter),
this.getKwarg().(Parameter)
] and
not result = this.getArg(0)
}
override string getFramework() { result = "Tornado" }
}
// ---------------------------------------------------------------------------
// Response modeling
// ---------------------------------------------------------------------------
/**
* A call to the `tornado.web.RequestHandler.redirect` method.
*
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.redirect
*/
private class TornadoRequestHandlerRedirectCall extends Http::Server::HttpRedirectResponse::Range,
DataFlow::CallCfgNode
{
TornadoRequestHandlerRedirectCall() {
this.getFunction() = TornadoModule::Web::RequestHandler::redirectMethod()
}
override DataFlow::Node getRedirectLocation() {
result in [this.getArg(0), this.getArgByName("url")]
}
override DataFlow::Node getBody() { none() }
override string getMimetypeDefault() { none() }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
}
/**
* A call to the `tornado.web.RequestHandler.write` method.
*
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write
*/
private class TornadoRequestHandlerWriteCall extends Http::Server::HttpResponse::Range,
DataFlow::CallCfgNode
{
TornadoRequestHandlerWriteCall() {
this.getFunction() = TornadoModule::Web::RequestHandler::writeMethod()
}
override DataFlow::Node getBody() { result in [this.getArg(0), this.getArgByName("chunk")] }
override string getMimetypeDefault() { result = "text/html" }
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
}
/**
* A call to the `tornado.web.RequestHandler.set_cookie` method.
*
* See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.set_cookie
*/
class TornadoRequestHandlerSetCookieCall extends Http::Server::SetCookieCall,
DataFlow::MethodCallNode
{
TornadoRequestHandlerSetCookieCall() {
this.calls(TornadoModule::Web::RequestHandler::instance(), "set_cookie")
}
override DataFlow::Node getHeaderArg() { none() }
override DataFlow::Node getNameArg() { result in [this.getArg(0), this.getArgByName("name")] }
override DataFlow::Node getValueArg() { result in [this.getArg(1), this.getArgByName("value")] }
}
}