Skip to content

Commit

Permalink
Merge pull request #16300 from joefarebrother/python-pyramid
Browse files Browse the repository at this point in the history
Python: Model the Pyramid framework
  • Loading branch information
joefarebrother committed May 14, 2024
2 parents 904799b + fd55713 commit 027e5e7
Show file tree
Hide file tree
Showing 8 changed files with 467 additions and 0 deletions.
4 changes: 4 additions & 0 deletions python/ql/lib/change-notes/2024-04-30-pyramid.md
@@ -0,0 +1,4 @@
---
category: majorAnalysis
---
* Added modeling of the `pyramid` framework, leading to new remote flow sources and sinks.
1 change: 1 addition & 0 deletions python/ql/lib/semmle/python/Frameworks.qll
Expand Up @@ -56,6 +56,7 @@ private import semmle.python.frameworks.PyMongo
private import semmle.python.frameworks.Pymssql
private import semmle.python.frameworks.PyMySQL
private import semmle.python.frameworks.Pyodbc
private import semmle.python.frameworks.Pyramid
private import semmle.python.frameworks.Requests
private import semmle.python.frameworks.RestFramework
private import semmle.python.frameworks.Rsa
Expand Down
299 changes: 299 additions & 0 deletions python/ql/lib/semmle/python/frameworks/Pyramid.qll
@@ -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" }
}
}
}
@@ -0,0 +1,2 @@
testFailures
failures
@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest
@@ -0,0 +1,4 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
testFailures
failures
@@ -0,0 +1,2 @@
import experimental.meta.InlineTaintTest
import MakeInlineTaintTest<TestTaintTrackingConfig>

0 comments on commit 027e5e7

Please sign in to comment.