mirror of
https://github.com/github/codeql.git
synced 2026-04-04 14:48:20 +02:00
fix conflict
This commit is contained in:
@@ -101,6 +101,19 @@ module Flask {
|
||||
/** Gets a reference to the `flask.request` object. */
|
||||
API::Node request() {
|
||||
result = API::moduleImport(["flask", "flask_restful"]).getMember("request")
|
||||
or
|
||||
result = sessionInterfaceRequestParam()
|
||||
}
|
||||
|
||||
/** Gets a `request` parameter of an implementation of `open_session` in a subclass of `flask.sessions.SessionInterface` */
|
||||
private API::Node sessionInterfaceRequestParam() {
|
||||
result =
|
||||
API::moduleImport("flask")
|
||||
.getMember("sessions")
|
||||
.getMember("SessionInterface")
|
||||
.getASubclass+()
|
||||
.getMember("open_session")
|
||||
.getParameter(1)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,6 +233,43 @@ module Flask {
|
||||
|
||||
/** Gets a reference to an instance of `flask.Response`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/** An `Headers` instance that is part of a Flask response. */
|
||||
private class FlaskResponseHeadersInstances extends Werkzeug::Headers::InstanceSource {
|
||||
FlaskResponseHeadersInstances() {
|
||||
this.(DataFlow::AttrRead).getObject() = instance() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() = "headers"
|
||||
}
|
||||
}
|
||||
|
||||
/** A class instantiation of `Response` that sets response headers. */
|
||||
private class ResponseClassHeadersWrite extends Http::Server::ResponseHeaderBulkWrite::Range,
|
||||
ClassInstantiation
|
||||
{
|
||||
override DataFlow::Node getBulkArg() {
|
||||
result = [this.getArg(2), this.getArgByName("headers")]
|
||||
}
|
||||
|
||||
override predicate nameAllowsNewline() { any() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
|
||||
/** A call to `make_response that sets response headers. */
|
||||
private class MakeResponseHeadersWrite extends Http::Server::ResponseHeaderBulkWrite::Range,
|
||||
FlaskMakeResponseCall
|
||||
{
|
||||
override DataFlow::Node getBulkArg() {
|
||||
result = this.getArg(2)
|
||||
or
|
||||
strictcount(this.getArg(_)) = 2 and
|
||||
result = this.getArg(1)
|
||||
}
|
||||
|
||||
override predicate nameAllowsNewline() { any() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
123
python/ql/lib/semmle/python/frameworks/Gradio.qll
Normal file
123
python/ql/lib/semmle/python/frameworks/Gradio.qll
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `gradio` PyPI package.
|
||||
* See https://pypi.org/project/gradio/.
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.RemoteFlowSources
|
||||
import semmle.python.dataflow.new.TaintTracking
|
||||
import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides models for the `gradio` PyPI package.
|
||||
* See https://pypi.org/project/gradio/.
|
||||
*/
|
||||
module Gradio {
|
||||
/**
|
||||
* The event handlers, Interface and gradio.ChatInterface classes, which take untrusted data.
|
||||
*/
|
||||
private class GradioInput extends API::CallNode {
|
||||
GradioInput() {
|
||||
this =
|
||||
API::moduleImport("gradio")
|
||||
.getMember([
|
||||
"Button", "Textbox", "UploadButton", "Slider", "JSON", "HTML", "Markdown", "File",
|
||||
"AnnotatedImage", "Audio", "BarPlot", "Chatbot", "Checkbox", "CheckboxGroup",
|
||||
"ClearButton", "Code", "ColorPicker", "Dataframe", "Dataset", "DownloadButton",
|
||||
"Dropdown", "DuplicateButton", "FileExplorer", "Gallery", "HighlightedText",
|
||||
"Image", "ImageEditor", "Label", "LinePlot", "LoginButton", "LogoutButton",
|
||||
"Model3D", "Number", "ParamViewer", "Plot", "Radio", "ScatterPlot", "SimpleImage",
|
||||
"State", "Video"
|
||||
])
|
||||
.getReturn()
|
||||
.getMember([
|
||||
"change", "input", "click", "submit", "edit", "clear", "play", "pause", "stop",
|
||||
"end", "start_recording", "pause_recording", "stop_recording", "focus", "blur",
|
||||
"upload", "release", "select", "stream", "like", "load", "key_up",
|
||||
])
|
||||
.getACall()
|
||||
or
|
||||
this = API::moduleImport("gradio").getMember(["Interface", "ChatInterface"]).getACall()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `inputs` parameters in Gradio event handlers, that are lists and are sources of untrusted data.
|
||||
* This model allows tracking each element list back to source, f.ex. `gr.Textbox(...)`.
|
||||
*/
|
||||
private class GradioInputList extends RemoteFlowSource::Range {
|
||||
GradioInputList() {
|
||||
exists(GradioInput call |
|
||||
// limit only to lists of parameters given to `inputs`.
|
||||
(
|
||||
(
|
||||
call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
|
||||
or
|
||||
call.getParameter(1).asSink().asCfgNode() instanceof ListNode
|
||||
) and
|
||||
(
|
||||
this = call.getKeywordParameter("inputs").getASubscript().getAValueReachingSink()
|
||||
or
|
||||
this = call.getParameter(1).getASubscript().getAValueReachingSink()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Gradio untrusted input" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `inputs` parameters in Gradio event handlers, that are not lists and are sources of untrusted data.
|
||||
*/
|
||||
private class GradioInputParameter extends RemoteFlowSource::Range {
|
||||
GradioInputParameter() {
|
||||
exists(GradioInput call |
|
||||
this = call.getParameter(0, "fn").getParameter(_).asSource() and
|
||||
// exclude lists of parameters given to `inputs`
|
||||
not call.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode and
|
||||
not call.getParameter(1).asSink().asCfgNode() instanceof ListNode
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Gradio untrusted input" }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `inputs` parameters in Gradio decorators to event handlers, that are sources of untrusted data.
|
||||
*/
|
||||
private class GradioInputDecorator extends RemoteFlowSource::Range {
|
||||
GradioInputDecorator() {
|
||||
exists(GradioInput call |
|
||||
this = call.getReturn().getACall().getParameter(0).getParameter(_).asSource()
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "Gradio untrusted input" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra taint propagation for tracking `inputs` parameters in Gradio event handlers, that are lists.
|
||||
*/
|
||||
private class ListTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
exists(GradioInput node |
|
||||
// handle cases where there are multiple arguments passed as a list to `inputs`
|
||||
(
|
||||
(
|
||||
node.getKeywordParameter("inputs").asSink().asCfgNode() instanceof ListNode
|
||||
or
|
||||
node.getParameter(1).asSink().asCfgNode() instanceof ListNode
|
||||
) and
|
||||
exists(int i | nodeTo = node.getParameter(0, "fn").getParameter(i).asSource() |
|
||||
nodeFrom.asCfgNode() =
|
||||
node.getKeywordParameter("inputs").asSink().asCfgNode().(ListNode).getElement(i)
|
||||
or
|
||||
nodeFrom.asCfgNode() =
|
||||
node.getParameter(1).asSink().asCfgNode().(ListNode).getElement(i)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
64
python/ql/lib/semmle/python/frameworks/Opml.qll
Normal file
64
python/ql/lib/semmle/python/frameworks/Opml.qll
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `opml` PyPI package.
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/opml/
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `opml` PyPI package
|
||||
*
|
||||
* See
|
||||
* - https://pypi.org/project/opml/
|
||||
*/
|
||||
private module Opml {
|
||||
/**
|
||||
* A call to the `xpath` method of a parsed document.
|
||||
*
|
||||
* import opml
|
||||
* root = opml.from_string(file(XML_DB).read())
|
||||
* find_text = root.xpath("`sink`")
|
||||
*/
|
||||
private class XPathCall extends XML::XPathExecution::Range, DataFlow::CallCfgNode {
|
||||
XPathCall() {
|
||||
exists(API::Node parseResult |
|
||||
parseResult = API::moduleImport("opml").getMember(["parse", "from_string"]).getReturn()
|
||||
|
|
||||
this = parseResult.getMember("xpath").getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getXPath() { result = this.getArg(0) }
|
||||
|
||||
override string getName() { result = "opml" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to either of:
|
||||
* - `opml.parse`
|
||||
* - `opml.from_string`
|
||||
*/
|
||||
private class OpmlParsing extends DataFlow::CallCfgNode, XML::XmlParsing::Range {
|
||||
OpmlParsing() {
|
||||
this = API::moduleImport("opml").getMember(["parse", "from_string"]).getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
|
||||
DataFlow::Node getParserArg() { none() }
|
||||
|
||||
/**
|
||||
* The same as `Lxml::LxmlParsing::vulnerableTo`, because `opml` uses `lxml` for parsing.
|
||||
*/
|
||||
override predicate vulnerableTo(XML::XmlParsingVulnerabilityKind kind) { kind.isXxe() }
|
||||
|
||||
override predicate mayExecuteInput() { none() }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
}
|
||||
}
|
||||
299
python/ql/lib/semmle/python/frameworks/Pyramid.qll
Normal file
299
python/ql/lib/semmle/python/frameworks/Pyramid.qll
Normal file
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `pyramid` PyPI package.
|
||||
* See https://docs.pylonsproject.org/projects/pyramid/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
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.dataflow.new.FlowSummary
|
||||
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
|
||||
/**
|
||||
* Provides models for the `pyramid` PyPI package.
|
||||
* See https://docs.pylonsproject.org/projects/pyramid/.
|
||||
*/
|
||||
module Pyramid {
|
||||
/** Provides models for pyramid View callables. */
|
||||
module View {
|
||||
/** A dataflow node that sets up a route on a server using the Pyramid framework. */
|
||||
abstract private class PyramidRouteSetup extends Http::Server::RouteSetup::Range {
|
||||
override string getFramework() { result = "Pyramid" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A Pyramid view callable, that handles incoming requests.
|
||||
*/
|
||||
class ViewCallable extends Function {
|
||||
ViewCallable() { this = any(PyramidRouteSetup rs).getARequestHandler() }
|
||||
|
||||
/** Gets the `request` parameter of this callable. */
|
||||
Parameter getRequestParameter() {
|
||||
this.getPositionalParameterCount() = 1 and
|
||||
result = this.getArg(0)
|
||||
or
|
||||
this.getPositionalParameterCount() = 2 and
|
||||
result = this.getArg(1)
|
||||
}
|
||||
}
|
||||
|
||||
/** A pyramid route setup using the `pyramid.view.view_config` decorator. */
|
||||
private class DecoratorSetup extends PyramidRouteSetup {
|
||||
DecoratorSetup() {
|
||||
this = API::moduleImport("pyramid").getMember("view").getMember("view_config").getACall()
|
||||
}
|
||||
|
||||
override Function getARequestHandler() { result.getADecorator() = this.asExpr() }
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() { none() } // there is a `route_name` arg, but that does not contain the url pattern
|
||||
|
||||
override Parameter getARoutedParameter() { none() }
|
||||
}
|
||||
|
||||
/** A pyramid route setup using a call to `pyramid.config.Configurator.add_view`. */
|
||||
private class ConfiguratorSetup extends PyramidRouteSetup instanceof Configurator::AddViewCall {
|
||||
override Function getARequestHandler() {
|
||||
this.(Configurator::AddViewCall).getViewArg() = poorMansFunctionTracker(result)
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() { none() } // there is a `route_name` arg, but that does not contain the url pattern
|
||||
|
||||
override Parameter getARoutedParameter() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides models for `pyramid.config.Configurator` */
|
||||
module Configurator {
|
||||
/** Gets a reference to the class `pyramid.config.Configurator`. */
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("pyramid").getMember("config").getMember("Configurator")
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `pyramid.config.Configurator`. */
|
||||
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = classRef().getACall()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `pyramid.config.Configurator`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/** A call to the `add_view` method of an instance of `pyramid.config.Configurator`. */
|
||||
class AddViewCall extends DataFlow::MethodCallNode {
|
||||
AddViewCall() { this.calls(instance(), "add_view") }
|
||||
|
||||
/** Gets the `view` argument of this call. */
|
||||
DataFlow::Node getViewArg() { result = [this.getArg(0), this.getArgByName("view")] }
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides modeling for pyramid requests. */
|
||||
module Request {
|
||||
/**
|
||||
* A source of instances of `pyramid.request.Request`, extend this class to model new instances.
|
||||
*
|
||||
* Use the predicate `Request::instance()` to get references to instances of `pyramid.request.Request`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** Gets a reference to an instance of `pyramid.request.Request`. */
|
||||
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 `pyramid.request.Request`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
private class RequestParameter extends InstanceSource, RemoteFlowSource::Range instanceof DataFlow::ParameterNode
|
||||
{
|
||||
RequestParameter() { this.getParameter() = any(View::ViewCallable vc).getRequestParameter() }
|
||||
|
||||
override string getSourceType() { result = "Pyramid request parameter" }
|
||||
}
|
||||
|
||||
/** Taint steps for request instances. */
|
||||
private class InstanceTaintSteps extends InstanceTaintStepsHelper {
|
||||
InstanceTaintSteps() { this = "pyramid.request.Request" }
|
||||
|
||||
override DataFlow::Node getInstance() { result = instance() }
|
||||
|
||||
override string getAttributeName() {
|
||||
result in [
|
||||
"accept", "accept_charset", "accept_encoding", "accept_language", "application_url",
|
||||
"as_bytes", "authorization", "body", "body_file", "body_file_raw", "body_file_seekable",
|
||||
"cache_control", "client_addr", "content_type", "cookies", "domain", "headers", "host",
|
||||
"host_port", "host_url", "GET", "if_match", "if_none_match", "if_range",
|
||||
"if_none_match", "json", "json_body", "matchdict", "params", "path", "path_info",
|
||||
"path_qs", "path_url", "POST", "pragma", "query_string", "range", "referer", "referrer",
|
||||
"text", "url", "urlargs", "urlvars", "user_agent"
|
||||
]
|
||||
}
|
||||
|
||||
override string getMethodName() {
|
||||
result in ["as_bytes", "copy", "copy_get", "path_info_peek", "path_info_pop"]
|
||||
}
|
||||
|
||||
override string getAsyncMethodName() { none() }
|
||||
}
|
||||
|
||||
/** A call to a method of a `request` that copies the request. */
|
||||
private class RequestCopyCall extends InstanceSource, DataFlow::MethodCallNode {
|
||||
RequestCopyCall() { this.calls(instance(), ["copy", "copy_get"]) }
|
||||
}
|
||||
|
||||
/** A member of a request that is a file-like object. */
|
||||
private class RequestBodyFileLike extends Stdlib::FileLikeObject::InstanceSource instanceof DataFlow::AttrRead
|
||||
{
|
||||
RequestBodyFileLike() {
|
||||
this.getObject() = instance() and
|
||||
this.getAttributeName() = ["body_file", "body_file_raw", "body_file_seekable"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides modeling for pyramid responses. */
|
||||
module Response {
|
||||
/** A response returned by a view callable. */
|
||||
private class PyramidReturnResponse extends Http::Server::HttpResponse::Range {
|
||||
PyramidReturnResponse() {
|
||||
this.asCfgNode() = any(View::ViewCallable vc).getAReturnValueFlowNode() and
|
||||
not this = instance()
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { result = this }
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
|
||||
|
||||
override string getMimetypeDefault() { result = "text/html" }
|
||||
}
|
||||
|
||||
/** Gets a reference to the class `pyramid.response.Response`. */
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("pyramid").getMember("response").getMember("Response")
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `pyramid.response.Response`, 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 `Response::instance()` to get references to instances of `pyramid.response.Response`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode,
|
||||
Http::Server::HttpResponse::Range
|
||||
{ }
|
||||
|
||||
/** Gets a reference to an instance of `pyramid.response.Response`. */
|
||||
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 `pyramid.response.Response`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/** An instantiation of the class `pyramid.response.Response` or a subclass. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
|
||||
override DataFlow::Node getBody() { result = [this.getArg(0), this.getArgByName("body")] }
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() {
|
||||
result = [this.getArg(4), this.getArgByName("content_type")]
|
||||
}
|
||||
|
||||
override string getMimetypeDefault() { result = "text/html" }
|
||||
}
|
||||
|
||||
/** A write to a field that sets the body of a response. */
|
||||
private class ResponseBodySet extends Http::Server::HttpResponse::Range instanceof DataFlow::AttrWrite
|
||||
{
|
||||
string attrName;
|
||||
|
||||
ResponseBodySet() {
|
||||
this.getObject() = instance() and
|
||||
this.getAttributeName() = attrName and
|
||||
attrName in ["body", "body_file", "json", "json_body", "text", "ubody", "unicode_body"]
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { result = this.(DataFlow::AttrWrite).getValue() }
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
|
||||
|
||||
override string getMimetypeDefault() {
|
||||
if attrName in ["json", "json_body"]
|
||||
then result = "application/json"
|
||||
else result = "text/html"
|
||||
}
|
||||
}
|
||||
|
||||
/** A use of the `response` attribute of a `Request`. */
|
||||
private class RequestResponseAttr extends InstanceSource instanceof DataFlow::AttrRead {
|
||||
RequestResponseAttr() {
|
||||
this.getObject() = Request::instance() and this.getAttributeName() = "response"
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { none() }
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
|
||||
|
||||
override string getMimetypeDefault() { result = "text/html" }
|
||||
}
|
||||
|
||||
/** A call to `response.set_cookie`. */
|
||||
private class SetCookieCall extends Http::Server::CookieWrite::Range, DataFlow::MethodCallNode {
|
||||
SetCookieCall() { this.calls(instance(), "set_cookie") }
|
||||
|
||||
override DataFlow::Node getHeaderArg() { none() }
|
||||
|
||||
override DataFlow::Node getNameArg() { result = [this.getArg(0), this.getArgByName("name")] }
|
||||
|
||||
override DataFlow::Node getValueArg() {
|
||||
result = [this.getArg(1), this.getArgByName("value")]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides models for pyramid http redirects. */
|
||||
module Redirect {
|
||||
/** Gets a reference to a class that represents an HTTP redirect response.. */
|
||||
API::Node classRef() {
|
||||
result =
|
||||
API::moduleImport("pyramid")
|
||||
.getMember("httpexceptions")
|
||||
.getMember([
|
||||
"HTTPMultipleChoices", "HTTPMovedPermanently", "HTTPFound", "HTTPSeeOther",
|
||||
"HTTPUseProxy", "HTTPTemporaryRedirect", "HTTPPermanentRedirect"
|
||||
])
|
||||
}
|
||||
|
||||
/** A call to a pyramid HTTP exception class that represents an HTTP redirect response. */
|
||||
class PyramidRedirect extends Http::Server::HttpRedirectResponse::Range, DataFlow::CallCfgNode {
|
||||
PyramidRedirect() { this = classRef().getACall() }
|
||||
|
||||
override DataFlow::Node getRedirectLocation() {
|
||||
result = [this.getArg(0), this.getArgByName("location")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() { none() }
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
|
||||
|
||||
override string getMimetypeDefault() { result = "text/html" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2183,17 +2183,35 @@ module StdlibPrivate {
|
||||
* for how a request is processed and given to an application.
|
||||
*/
|
||||
class WsgirefSimpleServerApplication extends Http::Server::RequestHandler::Range {
|
||||
boolean validator;
|
||||
|
||||
WsgirefSimpleServerApplication() {
|
||||
exists(DataFlow::Node appArg, DataFlow::CallCfgNode setAppCall |
|
||||
(
|
||||
setAppCall =
|
||||
WsgirefSimpleServer::subclassRef().getReturn().getMember("set_app").getACall()
|
||||
WsgirefSimpleServer::subclassRef().getReturn().getMember("set_app").getACall() and
|
||||
validator = false
|
||||
or
|
||||
setAppCall
|
||||
.(DataFlow::MethodCallNode)
|
||||
.calls(any(WsgiServerSubclass cls).getASelfRef(), "set_app")
|
||||
.calls(any(WsgiServerSubclass cls).getASelfRef(), "set_app") and
|
||||
validator = false
|
||||
or
|
||||
// assume an application that is passed to `wsgiref.validate.validator` is eventually passed to `set_app`
|
||||
setAppCall =
|
||||
API::moduleImport("wsgiref").getMember("validate").getMember("validator").getACall() and
|
||||
validator = true
|
||||
) and
|
||||
appArg in [setAppCall.getArg(0), setAppCall.getArgByName("application")]
|
||||
or
|
||||
// `make_server` calls `set_app`
|
||||
setAppCall =
|
||||
API::moduleImport("wsgiref")
|
||||
.getMember("simple_server")
|
||||
.getMember("make_server")
|
||||
.getACall() and
|
||||
appArg in [setAppCall.getArg(2), setAppCall.getArgByName("app")] and
|
||||
validator = false
|
||||
|
|
||||
appArg = poorMansFunctionTracker(this)
|
||||
)
|
||||
@@ -2202,6 +2220,9 @@ module StdlibPrivate {
|
||||
override Parameter getARoutedParameter() { none() }
|
||||
|
||||
override string getFramework() { result = "Stdlib: wsgiref.simple_server application" }
|
||||
|
||||
/** Holds if this simple server application was passed to `wsgiref.validate.validator`. */
|
||||
predicate isValidated() { validator = true }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2305,6 +2326,114 @@ module StdlibPrivate {
|
||||
|
||||
override string getMimetypeDefault() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `wsgiref.headers.Headers` class
|
||||
*
|
||||
* See https://docs.python.org/3/library/wsgiref.html#module-wsgiref.headers.
|
||||
*/
|
||||
module Headers {
|
||||
/** Gets a reference to the `wsgiref.headers.Headers` class. */
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("wsgiref").getMember("headers").getMember("Headers")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("wsgiref.headers.Headers~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `wsgiref.headers.Headers`. */
|
||||
private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = classRef().getACall()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `wsgiref.headers.Headers`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/** Holds if there exists an application that is validated by `wsgiref.validate.validator`. */
|
||||
private predicate existsValidatedApplication() {
|
||||
exists(WsgirefSimpleServerApplication app | app.isValidated())
|
||||
}
|
||||
|
||||
/** A class instantiation of `wsgiref.headers.Headers`, conidered as a write to a response header. */
|
||||
private class WsgirefHeadersInstantiation extends Http::Server::ResponseHeaderBulkWrite::Range,
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
WsgirefHeadersInstantiation() { this = classRef().getACall() }
|
||||
|
||||
override DataFlow::Node getBulkArg() {
|
||||
result = [this.getArg(0), this.getArgByName("headers")]
|
||||
}
|
||||
|
||||
// TODO: These checks perhaps could be made more precise.
|
||||
override predicate nameAllowsNewline() { not existsValidatedApplication() }
|
||||
|
||||
override predicate valueAllowsNewline() { not existsValidatedApplication() }
|
||||
}
|
||||
|
||||
/** A call to a method that writes to a response header. */
|
||||
private class HeaderWriteCall extends Http::Server::ResponseHeaderWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
HeaderWriteCall() {
|
||||
this.calls(instance(), ["add_header", "set", "setdefault", "__setitem__"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = this.getArg(0) }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = this.getArg(1) }
|
||||
|
||||
// TODO: These checks perhaps could be made more precise.
|
||||
override predicate nameAllowsNewline() { not existsValidatedApplication() }
|
||||
|
||||
override predicate valueAllowsNewline() { not existsValidatedApplication() }
|
||||
}
|
||||
|
||||
/** A dict-like write to a response header. */
|
||||
private class HeaderWriteSubscript extends Http::Server::ResponseHeaderWrite::Range,
|
||||
DataFlow::Node
|
||||
{
|
||||
DataFlow::Node name;
|
||||
DataFlow::Node value;
|
||||
|
||||
HeaderWriteSubscript() {
|
||||
exists(SubscriptNode subscript |
|
||||
this.asCfgNode() = subscript and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
name.asCfgNode() = subscript.getIndex() and
|
||||
subscript.getObject() = instance().asCfgNode()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = name }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
// TODO: These checks perhaps could be made more precise.
|
||||
override predicate nameAllowsNewline() { not existsValidatedApplication() }
|
||||
|
||||
override predicate valueAllowsNewline() { not existsValidatedApplication() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to a `start_response` function that sets the response headers.
|
||||
*/
|
||||
private class WsgirefSimpleServerSetHeaders extends Http::Server::ResponseHeaderBulkWrite::Range,
|
||||
DataFlow::CallCfgNode
|
||||
{
|
||||
WsgirefSimpleServerSetHeaders() { this.getFunction() = startResponse() }
|
||||
|
||||
override DataFlow::Node getBulkArg() {
|
||||
result = [this.getArg(1), this.getArgByName("headers")]
|
||||
}
|
||||
|
||||
// TODO: These checks perhaps could be made more precise.
|
||||
override predicate nameAllowsNewline() { not existsValidatedApplication() }
|
||||
|
||||
override predicate valueAllowsNewline() { not existsValidatedApplication() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -12,6 +12,7 @@ private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.Stdlib
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper
|
||||
private import semmle.python.frameworks.data.ModelsAsData
|
||||
|
||||
/**
|
||||
* Provides models for the `Werkzeug` PyPI package.
|
||||
@@ -144,6 +145,18 @@ module Werkzeug {
|
||||
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.
|
||||
*/
|
||||
module Headers {
|
||||
/** Gets a reference to the `werkzeug.datastructures.Headers` class. */
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("werkzeug").getMember("datastructures").getMember("Headers")
|
||||
or
|
||||
result = ModelOutput::getATypeNode("werkzeug.datastructures.Headers~Subclass").getASubclass*()
|
||||
}
|
||||
|
||||
/** A direct instantiation of `werkzeug.datastructures.Headers`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `werkzeug.datastructures.Headers`, extend this class to model new instances.
|
||||
*
|
||||
@@ -182,6 +195,61 @@ module Werkzeug {
|
||||
|
||||
override string getAsyncMethodName() { none() }
|
||||
}
|
||||
|
||||
/** A call to a method that writes to a header, assumed to be a response header. */
|
||||
private class HeaderWriteCall extends Http::Server::ResponseHeaderWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
HeaderWriteCall() {
|
||||
this.calls(instance(), ["add", "add_header", "set", "setdefault", "__setitem__"])
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = this.getArg(0) }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = this.getArg(1) }
|
||||
|
||||
override predicate nameAllowsNewline() { any() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
|
||||
/** A dict-like write to a header, assumed to be a response header. */
|
||||
private class HeaderWriteSubscript extends Http::Server::ResponseHeaderWrite::Range,
|
||||
DataFlow::Node
|
||||
{
|
||||
DataFlow::Node name;
|
||||
DataFlow::Node value;
|
||||
|
||||
HeaderWriteSubscript() {
|
||||
exists(SubscriptNode subscript |
|
||||
this.asCfgNode() = subscript and
|
||||
value.asCfgNode() = subscript.(DefinitionNode).getValue() and
|
||||
name.asCfgNode() = subscript.getIndex() and
|
||||
subscript.getObject() = instance().asCfgNode()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getNameArg() { result = name }
|
||||
|
||||
override DataFlow::Node getValueArg() { result = value }
|
||||
|
||||
override predicate nameAllowsNewline() { any() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
|
||||
/** A call to `Headers.extend`, assumed to be a response header. */
|
||||
private class HeaderExtendCall extends Http::Server::ResponseHeaderBulkWrite::Range,
|
||||
DataFlow::MethodCallNode
|
||||
{
|
||||
HeaderExtendCall() { this.calls(instance(), "extend") }
|
||||
|
||||
override DataFlow::Node getBulkArg() { result = this.getArg(0) }
|
||||
|
||||
override predicate nameAllowsNewline() { any() }
|
||||
|
||||
override predicate valueAllowsNewline() { none() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -168,9 +168,20 @@ module ModelInput {
|
||||
* A unit class for adding additional type model rows from CodeQL models.
|
||||
*/
|
||||
class TypeModel extends Unit {
|
||||
/**
|
||||
* Holds if any of the other predicates in this class might have a result
|
||||
* for the given `type`.
|
||||
*
|
||||
* The implementation of this predicate should not depend on `DataFlow::Node`.
|
||||
*/
|
||||
bindingset[type]
|
||||
predicate isTypeUsed(string type) { none() }
|
||||
|
||||
/**
|
||||
* Gets a data-flow node that is a source of the given `type`.
|
||||
*
|
||||
* Note that `type` should also be included in `isTypeUsed`.
|
||||
*
|
||||
* This must not depend on API graphs, but ensures that an API node is generated for
|
||||
* the source.
|
||||
*/
|
||||
@@ -180,6 +191,8 @@ module ModelInput {
|
||||
* Gets a data-flow node that is a sink of the given `type`,
|
||||
* usually because it is an argument passed to a parameter of that type.
|
||||
*
|
||||
* Note that `type` should also be included in `isTypeUsed`.
|
||||
*
|
||||
* This must not depend on API graphs, but ensures that an API node is generated for
|
||||
* the sink.
|
||||
*/
|
||||
@@ -188,6 +201,8 @@ module ModelInput {
|
||||
/**
|
||||
* Gets an API node that is a source or sink of the given `type`.
|
||||
*
|
||||
* Note that `type` should also be included in `isTypeUsed`.
|
||||
*
|
||||
* Unlike `getASource` and `getASink`, this may depend on API graphs.
|
||||
*/
|
||||
API::Node getAnApiNode(string type) { none() }
|
||||
@@ -354,6 +369,28 @@ private predicate typeVariableModel(string name, string path) {
|
||||
Extensions::typeVariableModel(name, path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the given extension tuple `madId` should pretty-print as `model`.
|
||||
*
|
||||
* This predicate should only be used in tests.
|
||||
*/
|
||||
predicate interpretModelForTest(QlBuiltins::ExtensionId madId, string model) {
|
||||
exists(string type, string path, string kind |
|
||||
Extensions::sourceModel(type, path, kind, madId) and
|
||||
model = "Source: " + type + "; " + path + "; " + kind
|
||||
)
|
||||
or
|
||||
exists(string type, string path, string kind |
|
||||
Extensions::sinkModel(type, path, kind, madId) and
|
||||
model = "Sink: " + type + "; " + path + "; " + kind
|
||||
)
|
||||
or
|
||||
exists(string type, string path, string input, string output, string kind |
|
||||
Extensions::summaryModel(type, path, input, output, kind, madId) and
|
||||
model = "Summary: " + type + "; " + path + "; " + input + "; " + output + "; " + kind
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if rows involving `type` might be relevant for the analysis of this database.
|
||||
*/
|
||||
@@ -367,6 +404,8 @@ predicate isRelevantType(string type) {
|
||||
(
|
||||
Specific::isTypeUsed(type)
|
||||
or
|
||||
any(TypeModel model).isTypeUsed(type)
|
||||
or
|
||||
exists(TestAllModels t)
|
||||
)
|
||||
or
|
||||
|
||||
@@ -29,7 +29,11 @@ import semmle.python.dataflow.new.DataFlow::DataFlow as DataFlow
|
||||
/**
|
||||
* Holds if models describing `type` may be relevant for the analysis of this database.
|
||||
*/
|
||||
predicate isTypeUsed(string type) { API::moduleImportExists(type) }
|
||||
bindingset[type]
|
||||
predicate isTypeUsed(string type) {
|
||||
// If `type` is a path, then it is the first component that should be imported.
|
||||
API::moduleImportExists(type.splitAt(".", 0))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `type` can be obtained from an instance of `otherType` due to
|
||||
@@ -41,8 +45,59 @@ predicate hasImplicitTypeModel(string type, string otherType) { none() }
|
||||
bindingset[type, path]
|
||||
API::Node getExtraNodeFromPath(string type, AccessPath path, int n) { none() }
|
||||
|
||||
/**
|
||||
* Holds if `type` = `typePath`+`suffix` and `suffix` is either empty or "!".
|
||||
*/
|
||||
bindingset[type]
|
||||
private predicate parseType(string type, string typePath, string suffix) {
|
||||
exists(string regexp |
|
||||
regexp = "([^!]+)(!|)" and
|
||||
typePath = type.regexpCapture(regexp, 1) and
|
||||
suffix = type.regexpCapture(regexp, 2)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate parseRelevantType(string type, string typePath, string suffix) {
|
||||
isRelevantType(type) and
|
||||
parseType(type, typePath, suffix)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private string getTypePathComponent(string typePath, int n) {
|
||||
parseRelevantType(_, typePath, _) and
|
||||
result = typePath.splitAt(".", n)
|
||||
}
|
||||
|
||||
private int getNumTypePathComponents(string typePath) {
|
||||
result = strictcount(int n | exists(getTypePathComponent(typePath, n)))
|
||||
}
|
||||
|
||||
private API::Node getNodeFromTypePath(string typePath, int n) {
|
||||
n = 1 and
|
||||
result = API::moduleImport(getTypePathComponent(typePath, 0))
|
||||
or
|
||||
result = getNodeFromTypePath(typePath, n - 1).getMember(getTypePathComponent(typePath, n - 1))
|
||||
}
|
||||
|
||||
private API::Node getNodeFromTypePath(string typePath) {
|
||||
result = getNodeFromTypePath(typePath, getNumTypePathComponents(typePath))
|
||||
}
|
||||
|
||||
/** Gets a Python-specific interpretation of the given `type`. */
|
||||
API::Node getExtraNodeFromType(string type) { result = API::moduleImport(type) }
|
||||
API::Node getExtraNodeFromType(string type) {
|
||||
result = API::moduleImport(type)
|
||||
or
|
||||
exists(string typePath, string suffix, API::Node node |
|
||||
parseRelevantType(type, typePath, suffix) and
|
||||
node = getNodeFromTypePath(typePath)
|
||||
|
|
||||
suffix = "!" and
|
||||
result = node
|
||||
or
|
||||
suffix = "" and
|
||||
result = node.getAnInstance()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Python-specific API graph successor of `node` reachable by resolving `token`.
|
||||
@@ -53,7 +108,7 @@ API::Node getExtraSuccessorFromNode(API::Node node, AccessPathTokenBase token) {
|
||||
result = node.getMember(token.getAnArgument())
|
||||
or
|
||||
token.getName() = "Instance" and
|
||||
result = node.getReturn() // In Python `Instance` is just an alias for `ReturnValue`
|
||||
result = node.getAnInstance()
|
||||
or
|
||||
token.getName() = "Awaited" and
|
||||
result = node.getAwaited()
|
||||
|
||||
Reference in New Issue
Block a user