-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Python: Model the Pyramid framework #16300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
88e3227
f85ee38
86d1e5b
2a04598
7df8b1b
ba054bd
4f22b91
c6372d5
fd55713
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
/** | ||
* 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.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.dataflow.new.FlowSummary | ||
private import semmle.python.frameworks.internal.PoorMansFunctionResolution | ||
private import semmle.python.frameworks.internal.InstanceTaintStepsHelper | ||
private import semmle.python.frameworks.data.ModelsAsData | ||
|
||
/** | ||
* Provides models for the `pyramid` PyPI package. | ||
* See https://docs.pylonsproject.org/projects/pyramid/. | ||
*/ | ||
module Pyramid { | ||
// TODO: qldoc | ||
module View { | ||
/** | ||
* A callable that could be used as a pyramid view callable. | ||
*/ | ||
private class PotentialViewCallable extends Function { | ||
PotentialViewCallable() { | ||
this.getPositionalParameterCount() = 1 and | ||
this.getArgName(0) = "request" | ||
|
||
or | ||
this.getPositionalParameterCount() = 2 and | ||
this.getArgName(0) = "context" and | ||
this.getArgName(1) = "request" | ||
} | ||
|
||
/** Gets the `request` parameter of this view callable. */ | ||
Parameter getRequestParameter() { result = this.getArgByName("request") } | ||
} | ||
|
||
abstract class ViewCallable extends PotentialViewCallable, Http::Server::RequestHandler::Range { | ||
override Parameter getARoutedParameter() { result = this.getRequestParameter() } | ||
|
||
|
||
override string getFramework() { result = "Pyramid" } | ||
} | ||
|
||
private class ViewCallableFromDecorator extends ViewCallable { | ||
ViewCallableFromDecorator() { | ||
this.getADecorator() = | ||
API::moduleImport("pyramid") | ||
.getMember("view") | ||
.getMember("view_config") | ||
.getACall() | ||
.asExpr() | ||
} | ||
} | ||
|
||
private class ViewCallableFromConfigurator extends ViewCallable { | ||
ViewCallableFromConfigurator() { | ||
any(Configurator::AddViewCall c).getViewArg() = poorMansFunctionTracker(this) | ||
} | ||
} | ||
} | ||
|
||
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) } | ||
|
||
class AddViewCall extends DataFlow::MethodCallNode { | ||
AddViewCall() { this.calls(instance(), "add_view") } | ||
|
||
DataFlow::Node getViewArg() { result = [this.getArg(0), this.getArgByName("view")] } | ||
} | ||
} | ||
|
||
module 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, DataFlow::ParameterNode { | ||
RequestParameter() { this.getParameter() = any(View::ViewCallable vc).getRequestParameter() } | ||
} | ||
|
||
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", "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_body", "copy_get", "path_info_peek", "path_info_pop"] | ||
} | ||
|
||
override string getAsyncMethodName() { none() } | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
argumentToEnsureNotTaintedNotMarkedAsSpurious | ||
untaintedArgumentToEnsureTaintedNotMarkedAsMissing | ||
testFailures | ||
failures |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import experimental.meta.InlineTaintTest | ||
import MakeInlineTaintTest<TestTaintTrackingConfig> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
from pyramid.view import view_config | ||
from pyramid.config import Configurator | ||
|
||
@view_config(route_name="test1") | ||
def test1(request): | ||
ensure_tainted( | ||
request, # $ tainted | ||
|
||
request.accept, # $ tainted | ||
request.accept_charset, # $ tainted | ||
request.accept_encoding, # $ tainted | ||
request.accept_language, # $ tainted | ||
request.authorization, # $ tainted | ||
request.cache_control, # $ tainted | ||
request.client_addr, # $ tainted | ||
request.content_type, # $ tainted | ||
request.domain, # $ tainted | ||
request.host, # $ tainted | ||
request.host_port, # $ tainted | ||
request.host_url, # $ tainted | ||
request.if_match, # $ tainted | ||
request.if_none_match, # $ tainted | ||
request.if_range, # $ tainted | ||
request.pragma, # $ tainted | ||
request.range, # $ tainted | ||
request.referer, # $ tainted | ||
request.referrer, # $ tainted | ||
request.user_agent, # $ tainted | ||
|
||
request.as_bytes, # $ tainted | ||
|
||
request.body, # $ tainted | ||
request.body_file, # $ tainted | ||
request.body_file_raw, # $ tainted | ||
request.body_file_seekable,# $ tainted | ||
request.body_file.read(), # $ MISSING:tainted | ||
|
||
request.json, # $ tainted | ||
request.json_body, # $ tainted | ||
request.json['a']['b'][0]['c'], # $ tainted | ||
|
||
request.text, # $ tainted | ||
|
||
request.path, # $ tainted | ||
request.path_info, # $ tainted | ||
request.path_info_peek(), # $ tainted | ||
request.path_info_pop(), # $ tainted | ||
request.path_qs, # $ tainted | ||
request.path_url, # $ tainted | ||
request.query_string, # $ tainted | ||
|
||
request.url, # $ tainted | ||
request.urlargs, # $ tainted | ||
request.urlvars, # $ tainted | ||
|
||
request.GET['a'], # $ tainted | ||
request.POST['b'], # $ tainted | ||
request.cookies['c'], # $ tainted | ||
request.params['d'], # $ tainted | ||
request.headers['X-My-Header'], # $ tainted | ||
request.GET.values(), # $ tainted | ||
|
||
request.copy(), # $ tainted | ||
request.copy_body(), # $ tainted | ||
request.copy_get(), # $ tainted | ||
request.copy().GET['a'] # $ MISSING:tainted | ||
) | ||
|
||
def test2(request): | ||
ensure_tainted(request) # $ tainted | ||
|
||
@view_config(route_name="test1") | ||
def test3(context, request): | ||
ensure_tainted(request) # $ tainted | ||
|
||
if __name__ == "__main__": | ||
with Configurator() as config: | ||
config.add_view(test2, route_name="test2") |
Uh oh!
There was an error while loading. Please reload this page.