mirror of
https://github.com/github/codeql.git
synced 2026-05-01 11:45:14 +02:00
Merge pull request #1032 from xiemaisi/master-for-merge
Merge master into rc/1.20
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* @name Modification of parameter with default
|
||||
* @description Modifying the default value of a parameter can lead to unexpected
|
||||
* results.
|
||||
* @kind problem
|
||||
* @kind path-problem
|
||||
* @tags reliability
|
||||
* maintainability
|
||||
* @problem.severity error
|
||||
@@ -12,50 +12,85 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.security.Paths
|
||||
|
||||
predicate safe_method(string name) {
|
||||
name = "count" or name = "index" or name = "copy" or name = "get" or name = "has_key" or
|
||||
name = "items" or name = "keys" or name = "values" or name = "iteritems" or name = "iterkeys" or name = "itervalues"
|
||||
}
|
||||
|
||||
predicate maybe_parameter(SsaVariable var, Function f, Parameter p) {
|
||||
p = var.getAnUltimateDefinition().getDefinition().getNode() and
|
||||
f.getAnArg() = p
|
||||
}
|
||||
|
||||
Name use_of_parameter(Parameter p) {
|
||||
exists(SsaVariable var |
|
||||
p = var.getAnUltimateDefinition().getDefinition().getNode() and
|
||||
var.getAUse().getNode() = result
|
||||
/** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */
|
||||
private boolean mutableDefaultValue(Parameter p) {
|
||||
exists(Dict d |
|
||||
p.getDefault() = d |
|
||||
exists(d.getAKey()) and result = true
|
||||
or
|
||||
not exists(d.getAKey()) and result = false
|
||||
)
|
||||
}
|
||||
|
||||
predicate modifying_call(Call c, Parameter p) {
|
||||
exists(Attribute a |
|
||||
c.getFunc() = a |
|
||||
a.getObject() = use_of_parameter(p) and
|
||||
not safe_method(a.getName())
|
||||
)
|
||||
}
|
||||
|
||||
predicate is_modification(AstNode a, Parameter p) {
|
||||
modifying_call(a, p)
|
||||
or
|
||||
a.(AugAssign).getTarget() = use_of_parameter(p)
|
||||
}
|
||||
|
||||
predicate has_mutable_default(Parameter p) {
|
||||
exists(SsaVariable v, FunctionExpr f | maybe_parameter(v, f.getInnerScope(), p) and
|
||||
exists(int i, int def_cnt, int arg_cnt |
|
||||
def_cnt = count(f.getArgs().getADefault()) and
|
||||
arg_cnt = count(f.getInnerScope().getAnArg()) and
|
||||
i in [1 .. arg_cnt] and
|
||||
(f.getArgs().getDefault(def_cnt - i) instanceof Dict or f.getArgs().getDefault(def_cnt - i) instanceof List) and
|
||||
f.getInnerScope().getArgName(arg_cnt - i) = v.getId()
|
||||
)
|
||||
exists(List l |
|
||||
p.getDefault() = l |
|
||||
exists(l.getAnElt()) and result = true
|
||||
or
|
||||
not exists(l.getAnElt()) and result = false
|
||||
)
|
||||
}
|
||||
|
||||
from AstNode a, Parameter p
|
||||
where has_mutable_default(p) and is_modification(a, p)
|
||||
select a, "Modification of parameter $@, which has mutable default value.", p, p.asName().getId()
|
||||
|
||||
class NonEmptyMutableValue extends TaintKind {
|
||||
NonEmptyMutableValue() {
|
||||
this = "non-empty mutable value"
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyMutableValue extends TaintKind {
|
||||
EmptyMutableValue() {
|
||||
this = "empty mutable value"
|
||||
}
|
||||
|
||||
override boolean booleanValue() {
|
||||
result = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MutableDefaultValue extends TaintSource {
|
||||
|
||||
boolean nonEmpty;
|
||||
|
||||
MutableDefaultValue() {
|
||||
nonEmpty = mutableDefaultValue(this.(NameNode).getNode())
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
result = "mutable default value"
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
nonEmpty = false and kind instanceof EmptyMutableValue
|
||||
or
|
||||
nonEmpty = true and kind instanceof NonEmptyMutableValue
|
||||
}
|
||||
}
|
||||
|
||||
class Mutation extends TaintSink {
|
||||
Mutation() {
|
||||
exists(AugAssign a | a.getTarget().getAFlowNode() = this)
|
||||
or
|
||||
exists(Call c, Attribute a |
|
||||
c.getFunc() = a |
|
||||
a.getObject().getAFlowNode() = this and
|
||||
not safe_method(a.getName())
|
||||
)
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof EmptyMutableValue
|
||||
or
|
||||
kind instanceof NonEmptyMutableValue
|
||||
}
|
||||
}
|
||||
|
||||
from TaintedPathSource src, TaintedPathSink sink
|
||||
where src.flowsTo(sink)
|
||||
select sink.getSink(), src, sink, "$@ flows to here and is mutated.", src.getSource(), "Default value"
|
||||
|
||||
@@ -148,6 +148,16 @@ abstract class TaintKind extends string {
|
||||
none()
|
||||
}
|
||||
|
||||
/** Gets the boolean values (may be one, neither, or both) that
|
||||
* may result from the Python expression `bool(this)`
|
||||
*/
|
||||
boolean booleanValue() {
|
||||
/* Default to true as the vast majority of taint is strings and
|
||||
* the empty string is almost always benign.
|
||||
*/
|
||||
result = true
|
||||
}
|
||||
|
||||
string repr() { result = this }
|
||||
|
||||
}
|
||||
@@ -167,6 +177,7 @@ abstract class CollectionKind extends TaintKind {
|
||||
/* Prevent any collection kinds more than 2 deep */
|
||||
not this.charAt(2) = "[" and not this.charAt(2) = "{"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A taint kind representing a flat collections of kinds.
|
||||
@@ -193,7 +204,7 @@ class SequenceKind extends CollectionKind {
|
||||
tonode.(BinaryExprNode).getAnOperand() = fromnode
|
||||
)
|
||||
or
|
||||
result = this and copy_call(fromnode, tonode)
|
||||
result = this and TaintFlowImplementation::copyCall(fromnode, tonode)
|
||||
or
|
||||
exists(BinaryExprNode mod |
|
||||
mod = tonode and
|
||||
@@ -236,20 +247,6 @@ private predicate slice(ControlFlowNode fromnode, SubscriptNode tonode) {
|
||||
)
|
||||
}
|
||||
|
||||
/* A call that returns a copy (or similar) of the argument */
|
||||
private predicate copy_call(ControlFlowNode fromnode, CallNode tonode) {
|
||||
tonode.getFunction().(AttrNode).getObject("copy") = fromnode
|
||||
or
|
||||
exists(ModuleObject copy, string name |
|
||||
name = "copy" or name = "deepcopy" |
|
||||
copy.attr(name).(FunctionObject).getACall() = tonode and
|
||||
tonode.getArg(0) = fromnode
|
||||
)
|
||||
or
|
||||
tonode.getFunction().refersTo(Object::builtin("reversed")) and
|
||||
tonode.getArg(0) = fromnode
|
||||
}
|
||||
|
||||
/** A taint kind representing a mapping of objects to kinds.
|
||||
* Typically a dict, but can include other mappings.
|
||||
*/
|
||||
@@ -272,7 +269,7 @@ class DictKind extends CollectionKind {
|
||||
result = valueKind and
|
||||
tonode.(CallNode).getFunction().(AttrNode).getObject("get") = fromnode
|
||||
or
|
||||
result = this and copy_call(fromnode, tonode)
|
||||
result = this and TaintFlowImplementation::copyCall(fromnode, tonode)
|
||||
or
|
||||
result = this and
|
||||
tonode.(CallNode).getFunction().refersTo(theDictType()) and
|
||||
@@ -1203,7 +1200,8 @@ library module TaintFlowImplementation {
|
||||
sanitizer.sanitizingEdge(kind, test)
|
||||
)
|
||||
|
|
||||
not Filters::isinstance(test.getTest(), _, var.getSourceVariable().getAUse())
|
||||
not Filters::isinstance(test.getTest(), _, var.getSourceVariable().getAUse()) and
|
||||
not test.getTest() = var.getSourceVariable().getAUse()
|
||||
or
|
||||
exists(ControlFlowNode c, ClassObject cls |
|
||||
Filters::isinstance(test.getTest(), c, var.getSourceVariable().getAUse())
|
||||
@@ -1213,6 +1211,8 @@ library module TaintFlowImplementation {
|
||||
or
|
||||
test.getSense() = false and not kind.getClass().getAnImproperSuperType() = cls
|
||||
)
|
||||
or
|
||||
test.getTest() = var.getSourceVariable().getAUse() and kind.booleanValue() = test.getSense()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1263,6 +1263,20 @@ library module TaintFlowImplementation {
|
||||
context = fromnode.getContext()
|
||||
}
|
||||
|
||||
/* A call that returns a copy (or similar) of the argument */
|
||||
predicate copyCall(ControlFlowNode fromnode, CallNode tonode) {
|
||||
tonode.getFunction().(AttrNode).getObject("copy") = fromnode
|
||||
or
|
||||
exists(ModuleObject copy, string name |
|
||||
name = "copy" or name = "deepcopy" |
|
||||
copy.attr(name).(FunctionObject).getACall() = tonode and
|
||||
tonode.getArg(0) = fromnode
|
||||
)
|
||||
or
|
||||
tonode.getFunction().refersTo(Object::builtin("reversed")) and
|
||||
tonode.getArg(0) = fromnode
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Helper predicate for tainted_with */
|
||||
|
||||
@@ -96,3 +96,23 @@ private predicate json_load(ControlFlowNode fromnode, CallNode tonode) {
|
||||
)
|
||||
}
|
||||
|
||||
/** A kind of "taint", representing an open file-like object from an external source. */
|
||||
class ExternalFileObject extends TaintKind {
|
||||
|
||||
ExternalFileObject() {
|
||||
this = "file[" + any(ExternalStringKind key) + "]"
|
||||
}
|
||||
|
||||
|
||||
/** Gets the taint kind for the contents of this file */
|
||||
TaintKind getValue() {
|
||||
this = "file[" + result + "]"
|
||||
}
|
||||
|
||||
override TaintKind getTaintOfMethodResult(string name) {
|
||||
name = "read" and result = this.getValue()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,3 +23,69 @@ string httpVerb() {
|
||||
string httpVerbLower() {
|
||||
result = httpVerb().toLowerCase()
|
||||
}
|
||||
|
||||
/** Taint kind representing the WSGI environment.
|
||||
* As specified in PEP 3333. https://www.python.org/dev/peps/pep-3333/#environ-variables
|
||||
*/
|
||||
class WsgiEnvironment extends TaintKind {
|
||||
|
||||
WsgiEnvironment() { this = "wsgi.environment" }
|
||||
|
||||
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
|
||||
result = this and TaintFlowImplementation::copyCall(fromnode, tonode)
|
||||
or
|
||||
result = this and
|
||||
tonode.(CallNode).getFunction().refersTo(theDictType()) and
|
||||
tonode.(CallNode).getArg(0) = fromnode
|
||||
or
|
||||
exists(StringObject key, string text |
|
||||
tonode.(CallNode).getFunction().(AttrNode).getObject("get") = fromnode and
|
||||
tonode.(CallNode).getArg(0).refersTo(key)
|
||||
or
|
||||
tonode.(SubscriptNode).getValue() = fromnode and tonode.isLoad() and
|
||||
tonode.(SubscriptNode).getIndex().refersTo(key)
|
||||
|
|
||||
text = key.getText() and result instanceof ExternalStringKind and
|
||||
(
|
||||
text = "QUERY_STRING" or
|
||||
text = "PATH_INFO" or
|
||||
text.prefix(5) = "HTTP_"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A standard morsel object from a HTTP request, a value in a cookie,
|
||||
* typically an instance of `http.cookies.Morsel` */
|
||||
class UntrustedMorsel extends TaintKind {
|
||||
|
||||
UntrustedMorsel() {
|
||||
this = "http.Morsel"
|
||||
}
|
||||
|
||||
|
||||
override TaintKind getTaintOfAttribute(string name) {
|
||||
result instanceof ExternalStringKind and
|
||||
(
|
||||
name = "value"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** A standard cookie object from a HTTP request, typically an instance of `http.cookies.SimpleCookie` */
|
||||
class UntrustedCookie extends TaintKind {
|
||||
|
||||
UntrustedCookie() {
|
||||
this = "http.Cookie"
|
||||
}
|
||||
|
||||
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
|
||||
tonode.(SubscriptNode).getValue() = fromnode and
|
||||
result instanceof UntrustedMorsel
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,3 +5,5 @@ import semmle.python.web.pyramid.Request
|
||||
import semmle.python.web.twisted.Request
|
||||
import semmle.python.web.bottle.Request
|
||||
import semmle.python.web.turbogears.Request
|
||||
import semmle.python.web.falcon.Request
|
||||
import semmle.python.web.cherrypy.Request
|
||||
|
||||
@@ -5,3 +5,5 @@ import semmle.python.web.tornado.Response
|
||||
import semmle.python.web.twisted.Response
|
||||
import semmle.python.web.bottle.Response
|
||||
import semmle.python.web.turbogears.Response
|
||||
import semmle.python.web.falcon.Response
|
||||
import semmle.python.web.cherrypy.Response
|
||||
|
||||
@@ -32,7 +32,7 @@ class BottleResponseBodyAssignment extends TaintSink {
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof UntrustedStringKind
|
||||
kind instanceof StringKind
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class BottleHandlerFunctionResult extends TaintSink {
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof UntrustedStringKind
|
||||
kind instanceof StringKind
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
|
||||
56
python/ql/src/semmle/python/web/cherrypy/General.qll
Normal file
56
python/ql/src/semmle/python/web/cherrypy/General.qll
Normal file
@@ -0,0 +1,56 @@
|
||||
import python
|
||||
import semmle.python.web.Http
|
||||
|
||||
module CherryPy {
|
||||
|
||||
FunctionObject expose() {
|
||||
result = ModuleObject::named("cherrypy").attr("expose")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CherryPyExposedFunction extends Function {
|
||||
|
||||
CherryPyExposedFunction() {
|
||||
this.getADecorator().refersTo(CherryPy::expose())
|
||||
or
|
||||
this.getADecorator().(Call).getFunc().refersTo(CherryPy::expose())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CherryPyRoute extends CallNode {
|
||||
|
||||
CherryPyRoute() {
|
||||
/* cherrypy.quickstart(root, script_name, config) */
|
||||
ModuleObject::named("cherrypy").attr("quickstart").(FunctionObject).getACall() = this
|
||||
or
|
||||
/* cherrypy.tree.mount(root, script_name, config) */
|
||||
this.getFunction().(AttrNode).getObject("mount").refersTo(ModuleObject::named("cherrypy").attr("tree"))
|
||||
}
|
||||
|
||||
ClassObject getAppClass() {
|
||||
this.getArg(0).refersTo(_, result, _)
|
||||
or
|
||||
this.getArgByName("root").refersTo(_, result, _)
|
||||
}
|
||||
|
||||
string getPath() {
|
||||
exists(StringObject path |
|
||||
result = path.getText()
|
||||
|
|
||||
this.getArg(1).refersTo(path)
|
||||
or
|
||||
this.getArgByName("script_name").refersTo(path)
|
||||
)
|
||||
}
|
||||
|
||||
Object getConfig() {
|
||||
this.getArg(2).refersTo(_, result, _)
|
||||
or
|
||||
this.getArgByName("config").refersTo(_, result, _)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
69
python/ql/src/semmle/python/web/cherrypy/Request.qll
Normal file
69
python/ql/src/semmle/python/web/cherrypy/Request.qll
Normal file
@@ -0,0 +1,69 @@
|
||||
import python
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.strings.Basic
|
||||
import semmle.python.web.Http
|
||||
import semmle.python.web.cherrypy.General
|
||||
|
||||
/** The cherrypy.request local-proxy object */
|
||||
class CherryPyRequest extends TaintKind {
|
||||
|
||||
CherryPyRequest() {
|
||||
this = "cherrypy.request"
|
||||
}
|
||||
|
||||
override TaintKind getTaintOfAttribute(string name) {
|
||||
name = "params" and result instanceof ExternalStringDictKind
|
||||
or
|
||||
name = "cookie" and result instanceof UntrustedCookie
|
||||
}
|
||||
|
||||
override TaintKind getTaintOfMethodResult(string name) {
|
||||
(
|
||||
name = "getHeader" or
|
||||
name = "getCookie" or
|
||||
name = "getUser" or
|
||||
name = "getPassword"
|
||||
) and
|
||||
result instanceof ExternalStringKind
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class CherryPyExposedFunctionParameter extends TaintSource {
|
||||
|
||||
CherryPyExposedFunctionParameter() {
|
||||
exists(Parameter p |
|
||||
p = any(CherryPyExposedFunction f).getAnArg() and
|
||||
not p.isSelf() and
|
||||
p.asName().getAFlowNode() = this
|
||||
)
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
result = "CherryPy handler function parameter"
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
kind instanceof ExternalStringKind
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class CherryPyRequestSource extends TaintSource {
|
||||
|
||||
CherryPyRequestSource() {
|
||||
this.(ControlFlowNode).refersTo(ModuleObject::named("cherrypy").attr("request"))
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind kind) {
|
||||
kind instanceof CherryPyRequest
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
28
python/ql/src/semmle/python/web/cherrypy/Response.qll
Normal file
28
python/ql/src/semmle/python/web/cherrypy/Response.qll
Normal file
@@ -0,0 +1,28 @@
|
||||
import python
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.security.strings.Untrusted
|
||||
import semmle.python.web.Http
|
||||
import semmle.python.web.cherrypy.General
|
||||
|
||||
|
||||
|
||||
class CherryPyExposedFunctionResult extends TaintSink {
|
||||
|
||||
CherryPyExposedFunctionResult() {
|
||||
exists(Return ret |
|
||||
ret.getScope() instanceof CherryPyExposedFunction and
|
||||
ret.getValue().getAFlowNode() = this
|
||||
)
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof StringKind
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
result = "cherrypy handler function result"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
68
python/ql/src/semmle/python/web/falcon/General.qll
Normal file
68
python/ql/src/semmle/python/web/falcon/General.qll
Normal file
@@ -0,0 +1,68 @@
|
||||
import python
|
||||
import semmle.python.web.Http
|
||||
|
||||
|
||||
/** The falcon API class */
|
||||
ClassObject theFalconAPIClass() {
|
||||
result = ModuleObject::named("falcon").getAttribute("API")
|
||||
}
|
||||
|
||||
|
||||
/** Holds if `route` is routed to `resource`
|
||||
*/
|
||||
private predicate api_route(CallNode route_call, ControlFlowNode route, ClassObject resource) {
|
||||
route_call.getFunction().(AttrNode).getObject("add_route").refersTo(_, theFalconAPIClass(), _) and
|
||||
route_call.getArg(0) = route and
|
||||
route_call.getArg(1).refersTo(_, resource, _)
|
||||
}
|
||||
|
||||
private predicate route(FalconRoute route, Function target, string funcname) {
|
||||
route.getResourceClass().lookupAttribute("on_" + funcname).(FunctionObject).getFunction() = target
|
||||
}
|
||||
|
||||
class FalconRoute extends ControlFlowNode {
|
||||
|
||||
FalconRoute() {
|
||||
api_route(this, _, _)
|
||||
}
|
||||
|
||||
string getUrl() {
|
||||
exists(StrConst url |
|
||||
api_route(this, url.getAFlowNode(), _) and
|
||||
result = url.getText()
|
||||
)
|
||||
}
|
||||
|
||||
ClassObject getResourceClass() {
|
||||
api_route(this, _, result)
|
||||
}
|
||||
|
||||
FalconHandlerFunction getHandlerFunction(string method) {
|
||||
route(this, result, method)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FalconHandlerFunction extends Function {
|
||||
|
||||
FalconHandlerFunction() {
|
||||
route(_, this, _)
|
||||
}
|
||||
|
||||
private string methodName() {
|
||||
route(_, this, result)
|
||||
}
|
||||
|
||||
string getMethod() {
|
||||
result = this.methodName().toUpperCase()
|
||||
}
|
||||
|
||||
Parameter getRequest() {
|
||||
result = this.getArg(1)
|
||||
}
|
||||
|
||||
Parameter getResponse() {
|
||||
result = this.getArg(2)
|
||||
}
|
||||
|
||||
}
|
||||
56
python/ql/src/semmle/python/web/falcon/Request.qll
Normal file
56
python/ql/src/semmle/python/web/falcon/Request.qll
Normal file
@@ -0,0 +1,56 @@
|
||||
import python
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.web.Http
|
||||
import semmle.python.web.falcon.General
|
||||
import semmle.python.security.strings.External
|
||||
|
||||
/** https://falcon.readthedocs.io/en/stable/api/request_and_response.html */
|
||||
class FalconRequest extends TaintKind {
|
||||
|
||||
FalconRequest() {
|
||||
this = "falcon.request"
|
||||
}
|
||||
|
||||
override TaintKind getTaintOfAttribute(string name) {
|
||||
name = "env" and result instanceof WsgiEnvironment
|
||||
or
|
||||
result instanceof ExternalStringKind and
|
||||
(
|
||||
name = "uri" or name = "url" or
|
||||
name = "forwarded_uri" or
|
||||
name = "relative_uri" or
|
||||
name = "query_string"
|
||||
)
|
||||
or
|
||||
result instanceof ExternalStringDictKind and
|
||||
(
|
||||
name = "cookies" or name = "params"
|
||||
)
|
||||
or
|
||||
name = "stream" and result instanceof ExternalFileObject
|
||||
}
|
||||
|
||||
override TaintKind getTaintOfMethodResult(string name) {
|
||||
name = "get_param" and result instanceof ExternalStringKind
|
||||
or
|
||||
name = "get_param_as_json" and result instanceof ExternalJsonKind
|
||||
or
|
||||
name = "get_param_as_list" and result instanceof ExternalStringSequenceKind
|
||||
}
|
||||
}
|
||||
|
||||
class FalconRequestParameter extends TaintSource {
|
||||
|
||||
FalconRequestParameter() {
|
||||
exists(FalconHandlerFunction f |
|
||||
f.getRequest() = this.(ControlFlowNode).getNode()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind k) {
|
||||
k instanceof FalconRequest
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
48
python/ql/src/semmle/python/web/falcon/Response.qll
Normal file
48
python/ql/src/semmle/python/web/falcon/Response.qll
Normal file
@@ -0,0 +1,48 @@
|
||||
import python
|
||||
|
||||
|
||||
import semmle.python.security.TaintTracking
|
||||
import semmle.python.web.Http
|
||||
import semmle.python.web.falcon.General
|
||||
import semmle.python.security.strings.External
|
||||
|
||||
|
||||
/** https://falcon.readthedocs.io/en/stable/api/request_and_response.html */
|
||||
class FalconResponse extends TaintKind {
|
||||
|
||||
FalconResponse() {
|
||||
this = "falcon.response"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FalconResponseParameter extends TaintSource {
|
||||
|
||||
FalconResponseParameter() {
|
||||
exists(FalconHandlerFunction f |
|
||||
f.getResponse() = this.(ControlFlowNode).getNode()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isSourceOf(TaintKind k) {
|
||||
k instanceof FalconResponse
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FalconResponseBodySink extends TaintSink {
|
||||
|
||||
FalconResponseBodySink() {
|
||||
exists(AttrNode attr |
|
||||
any(FalconResponse f).taints(attr.getObject("body")) |
|
||||
attr.(DefinitionNode).getValue() = this
|
||||
)
|
||||
}
|
||||
|
||||
override predicate sinks(TaintKind kind) {
|
||||
kind instanceof StringKind
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -215,6 +215,11 @@
|
||||
| Taint simple.test | test.py:169 | SOURCE | |
|
||||
| Taint simple.test | test.py:172 | Subscript | |
|
||||
| Taint simple.test | test.py:173 | Subscript | |
|
||||
| Taint simple.test | test.py:178 | SOURCE | |
|
||||
| Taint simple.test | test.py:179 | t | |
|
||||
| Taint simple.test | test.py:180 | t | |
|
||||
| Taint simple.test | test.py:183 | t | |
|
||||
| Taint simple.test | test.py:186 | t | |
|
||||
| Taint {simple.test} | test.py:169 | Dict | |
|
||||
| Taint {simple.test} | test.py:171 | d | |
|
||||
| Taint {simple.test} | test.py:173 | y | |
|
||||
|
||||
@@ -32,3 +32,5 @@
|
||||
| simple.test | test.py:159 | 160 | t | simple.test |
|
||||
| simple.test | test.py:168 | 172 | Subscript | simple.test |
|
||||
| simple.test | test.py:169 | 173 | Subscript | simple.test |
|
||||
| simple.test | test.py:178 | 180 | t | simple.test |
|
||||
| simple.test | test.py:178 | 186 | t | simple.test |
|
||||
|
||||
@@ -40,3 +40,4 @@
|
||||
| test.py:163 | SOURCE | simple.test |
|
||||
| test.py:168 | SOURCE | simple.test |
|
||||
| test.py:169 | SOURCE | simple.test |
|
||||
| test.py:178 | SOURCE | simple.test |
|
||||
|
||||
@@ -173,6 +173,10 @@
|
||||
| Taint simple.test | test.py:163 | SOURCE | | --> | Taint simple.test | test.py:164 | s | |
|
||||
| Taint simple.test | test.py:168 | SOURCE | | --> | Taint [simple.test] | test.py:168 | List | |
|
||||
| Taint simple.test | test.py:169 | SOURCE | | --> | Taint {simple.test} | test.py:169 | Dict | |
|
||||
| Taint simple.test | test.py:178 | SOURCE | | --> | Taint simple.test | test.py:179 | t | |
|
||||
| Taint simple.test | test.py:178 | SOURCE | | --> | Taint simple.test | test.py:180 | t | |
|
||||
| Taint simple.test | test.py:178 | SOURCE | | --> | Taint simple.test | test.py:183 | t | |
|
||||
| Taint simple.test | test.py:178 | SOURCE | | --> | Taint simple.test | test.py:186 | t | |
|
||||
| Taint {simple.test} | test.py:169 | Dict | | --> | Taint {simple.test} | test.py:171 | d | |
|
||||
| Taint {simple.test} | test.py:169 | Dict | | --> | Taint {simple.test} | test.py:175 | d | |
|
||||
| Taint {simple.test} | test.py:171 | d | | --> | Taint {simple.test} | test.py:173 | y | |
|
||||
|
||||
@@ -177,3 +177,8 @@
|
||||
| test.py:174 | l_2 | test.py:168 | Taint [simple.test] | List |
|
||||
| test.py:175 | d2_0 | test.py:175 | Taint {simple.test} | dict() |
|
||||
| test.py:175 | d_2 | test.py:169 | Taint {simple.test} | Dict |
|
||||
| test.py:178 | t_0 | test.py:178 | Taint simple.test | SOURCE |
|
||||
| test.py:180 | t_1 | test.py:178 | Taint simple.test | SOURCE |
|
||||
| test.py:180 | t_2 | test.py:178 | Taint simple.test | SOURCE |
|
||||
| test.py:183 | t_3 | test.py:178 | Taint simple.test | SOURCE |
|
||||
| test.py:186 | t_4 | test.py:178 | Taint simple.test | SOURCE |
|
||||
|
||||
@@ -173,3 +173,14 @@ def test_update_extend(x, y):
|
||||
SINK(y["key"])
|
||||
l2 = list(l)
|
||||
d2 = dict(d)
|
||||
|
||||
def test_truth():
|
||||
t = SOURCE
|
||||
if t:
|
||||
SINK(t)
|
||||
else:
|
||||
SINK(t)
|
||||
if not t:
|
||||
SINK(t)
|
||||
else:
|
||||
SINK(t)
|
||||
|
||||
3
python/ql/test/library-tests/web/cherrypy/Sinks.expected
Normal file
3
python/ql/test/library-tests/web/cherrypy/Sinks.expected
Normal file
@@ -0,0 +1,3 @@
|
||||
| red.py:8 | Str | externally controlled string |
|
||||
| test.py:11 | BinaryExpr | externally controlled string |
|
||||
| test.py:17 | BinaryExpr | externally controlled string |
|
||||
10
python/ql/test/library-tests/web/cherrypy/Sinks.ql
Normal file
10
python/ql/test/library-tests/web/cherrypy/Sinks.ql
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
import python
|
||||
|
||||
import semmle.python.web.HttpRequest
|
||||
import semmle.python.web.HttpResponse
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
from TaintSink sink, TaintKind kind
|
||||
where sink.sinks(kind)
|
||||
select sink.getLocation().toString(), sink.(ControlFlowNode).getNode().toString(), kind
|
||||
@@ -0,0 +1,4 @@
|
||||
| ../../../query-tests/Security/lib/cherrypy/__init__.py:10 | _ThreadLocalProxy() | cherrypy.request |
|
||||
| ../../../query-tests/Security/lib/cherrypy/__init__.py:10 | request | cherrypy.request |
|
||||
| test.py:10 | arg | externally controlled string |
|
||||
| test.py:16 | arg | externally controlled string |
|
||||
10
python/ql/test/library-tests/web/cherrypy/Sources.ql
Normal file
10
python/ql/test/library-tests/web/cherrypy/Sources.ql
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
import python
|
||||
|
||||
import semmle.python.web.HttpRequest
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
|
||||
from TaintSource src, TaintKind kind
|
||||
where src.isSourceOf(kind) and not kind.matches("tornado%")
|
||||
select src.getLocation().toString(), src.(ControlFlowNode).getNode().toString(), kind
|
||||
2
python/ql/test/library-tests/web/cherrypy/options
Normal file
2
python/ql/test/library-tests/web/cherrypy/options
Normal file
@@ -0,0 +1,2 @@
|
||||
semmle-extractor-options: --max-import-depth=3 --lang=3 -p ../../../query-tests/Security/lib/
|
||||
optimize: true
|
||||
11
python/ql/test/library-tests/web/cherrypy/red.py
Normal file
11
python/ql/test/library-tests/web/cherrypy/red.py
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
import cherrypy
|
||||
|
||||
class MultiPath(object):
|
||||
|
||||
@cherrypy.expose(['color', 'colour'])
|
||||
def red(self):
|
||||
return "RED"
|
||||
|
||||
if __name__ == '__main__':
|
||||
cherrypy.quickstart(MultiPath())
|
||||
23
python/ql/test/library-tests/web/cherrypy/test.py
Normal file
23
python/ql/test/library-tests/web/cherrypy/test.py
Normal file
@@ -0,0 +1,23 @@
|
||||
|
||||
import random
|
||||
import string
|
||||
|
||||
import cherrypy
|
||||
|
||||
class A(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def a(self, arg):
|
||||
return "hello " + arg
|
||||
|
||||
class B(object):
|
||||
|
||||
@cherrypy.expose
|
||||
def b(self, arg):
|
||||
return "bye " + arg
|
||||
|
||||
cherrypy.tree.mount(A(), '/a', a_conf)
|
||||
cherrypy.tree.mount(B(), '/b', b_conf)
|
||||
|
||||
cherrypy.engine.start()
|
||||
cherrypy.engine.block()
|
||||
3
python/ql/test/library-tests/web/falcon/Routing.expected
Normal file
3
python/ql/test/library-tests/web/falcon/Routing.expected
Normal file
@@ -0,0 +1,3 @@
|
||||
| /hello | delete | test.py:22:5:22:35 | Function on_delete |
|
||||
| /hello | get | test.py:9:5:9:32 | Function on_get |
|
||||
| /hello | post | test.py:19:5:19:33 | Function on_post |
|
||||
8
python/ql/test/library-tests/web/falcon/Routing.ql
Normal file
8
python/ql/test/library-tests/web/falcon/Routing.ql
Normal file
@@ -0,0 +1,8 @@
|
||||
import python
|
||||
|
||||
import semmle.python.web.falcon.General
|
||||
|
||||
from FalconRoute route, string method
|
||||
|
||||
select route.getUrl(), method, route.getHandlerFunction(method)
|
||||
|
||||
1
python/ql/test/library-tests/web/falcon/Sinks.expected
Normal file
1
python/ql/test/library-tests/web/falcon/Sinks.expected
Normal file
@@ -0,0 +1 @@
|
||||
| test.py:17 | Attribute() | externally controlled string |
|
||||
10
python/ql/test/library-tests/web/falcon/Sinks.ql
Normal file
10
python/ql/test/library-tests/web/falcon/Sinks.ql
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
import python
|
||||
|
||||
import semmle.python.web.HttpRequest
|
||||
import semmle.python.web.HttpResponse
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
from TaintSink sink, TaintKind kind
|
||||
where sink.sinks(kind)
|
||||
select sink.getLocation().toString(), sink.(ControlFlowNode).getNode().toString(), kind
|
||||
3
python/ql/test/library-tests/web/falcon/Sources.expected
Normal file
3
python/ql/test/library-tests/web/falcon/Sources.expected
Normal file
@@ -0,0 +1,3 @@
|
||||
| test.py:9 | req | falcon.request |
|
||||
| test.py:19 | req | falcon.request |
|
||||
| test.py:22 | req | falcon.request |
|
||||
10
python/ql/test/library-tests/web/falcon/Sources.ql
Normal file
10
python/ql/test/library-tests/web/falcon/Sources.ql
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
import python
|
||||
|
||||
import semmle.python.web.HttpRequest
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
|
||||
from TaintSource src, TaintKind kind
|
||||
where src.isSourceOf(kind) and not kind.matches("tornado%")
|
||||
select src.getLocation().toString(), src.(ControlFlowNode).getNode().toString(), kind
|
||||
25
python/ql/test/library-tests/web/falcon/Taint.expected
Normal file
25
python/ql/test/library-tests/web/falcon/Taint.expected
Normal file
@@ -0,0 +1,25 @@
|
||||
| test.py:9 | req | falcon.request |
|
||||
| test.py:9 | resp | falcon.response |
|
||||
| test.py:10 | Attribute | file[externally controlled string] |
|
||||
| test.py:10 | Attribute() | externally controlled string |
|
||||
| test.py:10 | req | falcon.request |
|
||||
| test.py:11 | Attribute() | externally controlled string |
|
||||
| test.py:11 | Attribute() | json[externally controlled string] |
|
||||
| test.py:11 | raw_json | externally controlled string |
|
||||
| test.py:12 | resp | falcon.response |
|
||||
| test.py:13 | Dict | {externally controlled string} |
|
||||
| test.py:13 | Dict | {json[externally controlled string]} |
|
||||
| test.py:15 | result | externally controlled string |
|
||||
| test.py:15 | result | json[externally controlled string] |
|
||||
| test.py:17 | resp | falcon.response |
|
||||
| test.py:17 | result | {externally controlled string} |
|
||||
| test.py:17 | result | {json[externally controlled string]} |
|
||||
| test.py:19 | req | falcon.request |
|
||||
| test.py:19 | resp | falcon.response |
|
||||
| test.py:22 | req | falcon.request |
|
||||
| test.py:22 | resp | falcon.response |
|
||||
| test.py:23 | Attribute | wsgi.environment |
|
||||
| test.py:23 | req | falcon.request |
|
||||
| test.py:24 | Subscript | externally controlled string |
|
||||
| test.py:24 | env | wsgi.environment |
|
||||
| test.py:25 | qs | externally controlled string |
|
||||
13
python/ql/test/library-tests/web/falcon/Taint.ql
Normal file
13
python/ql/test/library-tests/web/falcon/Taint.ql
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
import python
|
||||
|
||||
|
||||
import semmle.python.web.HttpRequest
|
||||
import semmle.python.web.HttpResponse
|
||||
import semmle.python.security.strings.Untrusted
|
||||
|
||||
|
||||
from TaintedNode node
|
||||
where node.getLocation().getFile().getName().matches("%falcon/test.py")
|
||||
select node.getLocation().toString(), node.getNode().getNode().toString(), node.getTaintKind()
|
||||
|
||||
2
python/ql/test/library-tests/web/falcon/options
Normal file
2
python/ql/test/library-tests/web/falcon/options
Normal file
@@ -0,0 +1,2 @@
|
||||
semmle-extractor-options: --max-import-depth=3 --lang=3 -p ../../../query-tests/Security/lib/
|
||||
optimize: true
|
||||
28
python/ql/test/library-tests/web/falcon/test.py
Normal file
28
python/ql/test/library-tests/web/falcon/test.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import json
|
||||
|
||||
from falcon import API
|
||||
|
||||
app = API()
|
||||
|
||||
class Handler(object):
|
||||
|
||||
def on_get(self, req, resp):
|
||||
raw_json = req.stream.read()
|
||||
result = json.loads(raw_json)
|
||||
resp.status = 200
|
||||
result = {
|
||||
'status': 'success',
|
||||
'data': result
|
||||
}
|
||||
resp.body = json.dumps(result)
|
||||
|
||||
def on_post(self, req, resp):
|
||||
pass
|
||||
|
||||
def on_delete(self, req, resp):
|
||||
env = req.env
|
||||
qs = env["QUERY_STRING"]
|
||||
return qs
|
||||
|
||||
app.add_route('/hello', Handler())
|
||||
|
||||
@@ -1,2 +1,22 @@
|
||||
| functions_test.py:40:5:40:17 | Attribute() | Modification of parameter $@, which has mutable default value. | functions_test.py:39:9:39:9 | Parameter | x |
|
||||
| functions_test.py:239:5:239:14 | AugAssign | Modification of parameter $@, which has mutable default value. | functions_test.py:238:15:238:15 | Parameter | x |
|
||||
edges
|
||||
| functions_test.py:36:9:36:9 | empty mutable value | functions_test.py:37:16:37:16 | empty mutable value |
|
||||
| functions_test.py:39:9:39:9 | empty mutable value | functions_test.py:40:5:40:5 | empty mutable value |
|
||||
| functions_test.py:238:15:238:15 | empty mutable value | functions_test.py:239:5:239:5 | empty mutable value |
|
||||
| functions_test.py:290:25:290:25 | empty mutable value | functions_test.py:291:5:291:5 | empty mutable value |
|
||||
| functions_test.py:293:21:293:21 | empty mutable value | functions_test.py:294:5:294:5 | empty mutable value |
|
||||
| functions_test.py:296:27:296:27 | empty mutable value | functions_test.py:297:25:297:25 | empty mutable value |
|
||||
| functions_test.py:296:27:296:27 | empty mutable value | functions_test.py:298:21:298:21 | empty mutable value |
|
||||
| functions_test.py:297:25:297:25 | empty mutable value | functions_test.py:290:25:290:25 | empty mutable value |
|
||||
| functions_test.py:298:21:298:21 | empty mutable value | functions_test.py:293:21:293:21 | empty mutable value |
|
||||
| functions_test.py:300:26:300:26 | empty mutable value | functions_test.py:301:8:301:8 | empty mutable value |
|
||||
| functions_test.py:300:26:300:26 | empty mutable value | functions_test.py:303:12:303:12 | empty mutable value |
|
||||
parents
|
||||
| functions_test.py:290:25:290:25 | empty mutable value | functions_test.py:297:25:297:25 | empty mutable value |
|
||||
| functions_test.py:291:5:291:5 | empty mutable value | functions_test.py:297:25:297:25 | empty mutable value |
|
||||
| functions_test.py:293:21:293:21 | empty mutable value | functions_test.py:298:21:298:21 | empty mutable value |
|
||||
| functions_test.py:294:5:294:5 | empty mutable value | functions_test.py:298:21:298:21 | empty mutable value |
|
||||
#select
|
||||
| functions_test.py:40:5:40:5 | Taint sink | functions_test.py:39:9:39:9 | empty mutable value | functions_test.py:40:5:40:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:39:9:39:9 | mutable default value | Default value |
|
||||
| functions_test.py:239:5:239:5 | Taint sink | functions_test.py:238:15:238:15 | empty mutable value | functions_test.py:239:5:239:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:238:15:238:15 | mutable default value | Default value |
|
||||
| functions_test.py:291:5:291:5 | Taint sink | functions_test.py:296:27:296:27 | empty mutable value | functions_test.py:291:5:291:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:296:27:296:27 | mutable default value | Default value |
|
||||
| functions_test.py:294:5:294:5 | Taint sink | functions_test.py:296:27:296:27 | empty mutable value | functions_test.py:294:5:294:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:296:27:296:27 | mutable default value | Default value |
|
||||
|
||||
@@ -286,3 +286,19 @@ class Z(zope.interface.Interface):
|
||||
|
||||
Z().meth(0)
|
||||
|
||||
# indirect modification of parameter with default
|
||||
def aug_assign_argument(x):
|
||||
x += ['x']
|
||||
|
||||
def mutate_argument(x):
|
||||
x.append('x')
|
||||
|
||||
def indirect_modification(y = []):
|
||||
aug_assign_argument(y)
|
||||
mutate_argument(y)
|
||||
|
||||
def guarded_modification(z=[]):
|
||||
if z:
|
||||
z.append(0)
|
||||
return z
|
||||
|
||||
|
||||
15
python/ql/test/query-tests/Security/lib/cherrypy/__init__.py
Normal file
15
python/ql/test/query-tests/Security/lib/cherrypy/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
from ._helper import expose, popargs, url
|
||||
|
||||
class _ThreadLocalProxy(object):
|
||||
def __getattr__(self, name):
|
||||
pass
|
||||
|
||||
|
||||
request = _ThreadLocalProxy('request')
|
||||
response = _ThreadLocalProxy('response')
|
||||
|
||||
def quickstart(root=None, script_name='', config=None):
|
||||
"""Mount the given root, start the builtin server (and engine), then block."""
|
||||
pass
|
||||
31
python/ql/test/query-tests/Security/lib/cherrypy/_helper.py
Normal file
31
python/ql/test/query-tests/Security/lib/cherrypy/_helper.py
Normal file
@@ -0,0 +1,31 @@
|
||||
def expose(func=None, alias=None):
|
||||
"""Expose the function or class.
|
||||
Optionally provide an alias or set of aliases.
|
||||
"""
|
||||
def expose_(func):
|
||||
func.exposed = True
|
||||
return func
|
||||
|
||||
return expose_
|
||||
|
||||
|
||||
def popargs(*args, **kwargs):
|
||||
"""Decorate _cp_dispatch."""
|
||||
|
||||
def decorated(cls_or_self=None, vpath=None):
|
||||
if inspect.isclass(cls_or_self):
|
||||
# cherrypy.popargs is a class decorator
|
||||
return cls
|
||||
|
||||
# We're in the actual function
|
||||
self = cls_or_self
|
||||
if vpath:
|
||||
return getattr(self, vpath.pop(0), None)
|
||||
else:
|
||||
return self
|
||||
|
||||
return decorated
|
||||
|
||||
def url(path='', qs='', script_name=None, base=None, relative=None):
|
||||
#Do some opaque stuff here...
|
||||
return new_url
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
from falcon.api import API
|
||||
from falcon.request import Request
|
||||
from falcon.response import Response
|
||||
14
python/ql/test/query-tests/Security/lib/falcon/api.py
Normal file
14
python/ql/test/query-tests/Security/lib/falcon/api.py
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
"""Falcon API class."""
|
||||
|
||||
class API(object):
|
||||
|
||||
def add_route(self, uri_template, resource, **kwargs):
|
||||
pass
|
||||
|
||||
def add_sink(self, sink, prefix=r'/'):
|
||||
pass
|
||||
|
||||
def add_error_handler(self, exception, handler=None):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
class Request(object):
|
||||
pass
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
class Response(object):
|
||||
pass
|
||||
Reference in New Issue
Block a user