Merge branch 'master' into python-improve-file-taint

This commit is contained in:
Rasmus Wriedt Larsen
2020-04-30 11:24:29 +02:00
274 changed files with 8969 additions and 1057 deletions

View File

@@ -154,14 +154,17 @@ predicate too_few_args(Call call, Value callable, int limit) {
not exists(call.getKwargs()) and
arg_count(call) < limit and
exists(FunctionValue func | func = get_function_or_initializer(callable) |
call = func.getACall().getNode() and
call = func.getAFunctionCall().getNode() and
limit = func.minParameters() and
// The combination of misuse of `mox.Mox().StubOutWithMock()`
// and a bug in mox's implementation of methods results in having to
// pass 1 too few arguments to the mocked function.
/*
* The combination of misuse of `mox.Mox().StubOutWithMock()`
* and a bug in mox's implementation of methods results in having to
* pass 1 too few arguments to the mocked function.
*/
not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod())
or
call = func.getACall().getNode() and limit = func.minParameters() - 1
call = func.getAMethodCall().getNode() and limit = func.minParameters() - 1
or
callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and
@@ -198,9 +201,9 @@ predicate too_many_args(Call call, Value callable, int limit) {
not func.getScope().hasVarArg() and
limit >= 0
|
call = func.getACall().getNode() and limit = func.maxParameters()
call = func.getAFunctionCall().getNode() and limit = func.maxParameters()
or
call = func.getACall().getNode() and limit = func.maxParameters() - 1
call = func.getAMethodCall().getNode() and limit = func.maxParameters() - 1
or
callable instanceof ClassValue and
call.getAFlowNode() = get_a_call(callable) and
@@ -254,3 +257,8 @@ predicate overridden_call(FunctionValue func, FunctionValue overriding, Call cal
overriding.overrides(func) and
overriding.getACall().getNode() = call
}
/** Holds if `func` will raise a `NotImplemented` error. */
predicate isAbstract(FunctionValue func) {
func.getARaisedType() = ClassValue::notImplementedError()
}

View File

@@ -14,22 +14,17 @@
import python
import CallArgs
from Call call, FunctionObject func, string too, string should, int limit
from Call call, FunctionValue func, string too, string should, int limit
where
(
too_many_args_objectapi(call, func, limit) and
too = "too many arguments" and
should = "no more than "
or
too_few_args_objectapi(call, func, limit) and
too = "too few arguments" and
should = "no fewer than "
) and
not func.isAbstract() and
not exists(FunctionObject overridden |
func.overrides(overridden) and correct_args_if_called_as_method_objectapi(call, overridden)
) and
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
not func.getName() = "__new__"
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func,
func.descriptiveString()
(
too_many_args(call, func, limit) and too = "too many arguments" and should = "no more than "
or
too_few_args(call, func, limit) and too = "too few arguments" and should = "no fewer than "
) and
not isAbstract(func) and
not exists(FunctionValue overridden | func.overrides(overridden) and correct_args_if_called_as_method(call, overridden))
/* The semantics of `__new__` can be a bit subtle, so we simply exclude `__new__` methods */
and not func.getName() = "__new__"
select call, "Call to $@ with " + too + "; should be " + should + limit.toString() + ".", func, func.descriptiveString()

View File

@@ -12,17 +12,17 @@
import python
import Expressions.CallArgs
from Call call, FunctionObject func, FunctionObject overridden, string problem
from Call call, FunctionValue func, FunctionValue overridden, string problem
where
func.overrides(overridden) and
(
wrong_args_objectapi(call, func, _, problem) and
correct_args_if_called_as_method_objectapi(call, overridden)
wrong_args(call, func, _, problem) and
correct_args_if_called_as_method(call, overridden)
or
exists(string name |
illegally_named_parameter_objectapi(call, func, name) and
illegally_named_parameter(call, func, name) and
problem = "an argument named '" + name + "'" and
overridden.getFunction().getAnArg().(Name).getId() = name
overridden.getScope().getAnArg().(Name).getId() = name
)
)
select func,

View File

@@ -17,7 +17,7 @@ import semmle.python.filters.Tests
from Assert a, string value
where
/* Exclude asserts inside test cases */
not a.getScope() instanceof Test and
not a.getScope().getScope*() instanceof TestScope and
exists(Expr test | test = a.getTest() |
value = test.(IntegerLiteral).getN()
or

View File

@@ -5,18 +5,17 @@ abstract class TestScope extends Scope { }
// don't extend Class directly to avoid ambiguous method warnings
class UnitTestClass extends TestScope {
UnitTestClass() {
exists(ClassObject c | this = c.getPyClass() |
c.getASuperType() = theUnitTestPackage().attr(_)
exists(ClassValue cls | this = cls.getScope() |
cls.getABaseType+() = Module::named("unittest").attr(_)
or
c.getASuperType().getName().toLowerCase() = "testcase"
cls.getABaseType+().getName().toLowerCase() = "testcase"
)
}
}
PackageObject theUnitTestPackage() { result.getName() = "unittest" }
abstract class Test extends TestScope { }
/** Class of test function that uses the `unittest` framework */
class UnitTestFunction extends Test {
UnitTestFunction() {
this.getScope+() instanceof UnitTestClass and
@@ -37,3 +36,11 @@ class NoseTestFunction extends Test {
this.(Function).getName().matches("test%")
}
}
/** Class of functions that are clearly tests, but don't belong to a specific framework */
class UnknownTestFunction extends Test {
UnknownTestFunction() {
this.(Function).getName().matches("test%") and
this.getEnclosingModule().getFile().getShortName().matches("test_%.py")
}
}

View File

@@ -608,6 +608,12 @@ abstract class FunctionValue extends CallableValue {
)
}
/** Gets a class that may be raised by this function */
abstract ClassValue getARaisedType();
/** Gets a call-site from where this function is called as a function */
CallNode getAFunctionCall() { result.getFunction().pointsTo() = this }
/** Gets a call-site from where this function is called as a method */
CallNode getAMethodCall() {
exists(BoundMethodObjectInternal bm |
@@ -656,6 +662,8 @@ class PythonFunctionValue extends FunctionValue {
/** Gets a control flow node corresponding to a return statement in this function */
ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() }
override ClassValue getARaisedType() { scope_raises(result, this.getScope()) }
override ClassValue getAnInferredReturnType() {
/* We have to do a special version of this because builtin functions have no
* explicit return nodes that we can query and get the class of.
@@ -676,6 +684,11 @@ class BuiltinFunctionValue extends FunctionValue {
override int maxParameters() { none() }
override ClassValue getARaisedType() {
/* Information is unavailable for C code in general */
none()
}
override ClassValue getAnInferredReturnType() {
/* We have to do a special version of this because builtin functions have no
* explicit return nodes that we can query and get the class of.
@@ -702,6 +715,11 @@ class BuiltinMethodValue extends FunctionValue {
override int maxParameters() { none() }
override ClassValue getARaisedType() {
/* Information is unavailable for C code in general */
none()
}
override ClassValue getAnInferredReturnType() {
result = TBuiltinClassObject(this.(BuiltinMethodObjectInternal).getReturnType())
}
@@ -929,6 +947,9 @@ module ClassValue {
/** Get the `ClassValue` for the `LookupError` class. */
ClassValue lookupError() { result = TBuiltinClassObject(Builtin::builtin("LookupError")) }
/** Get the `ClassValue` for the `IndexError` class. */
ClassValue indexError() { result = TBuiltinClassObject(Builtin::builtin("IndexError")) }
/** Get the `ClassValue` for the `IOError` class. */
ClassValue ioError() { result = TBuiltinClassObject(Builtin::builtin("IOError")) }
@@ -949,4 +970,7 @@ module ClassValue {
ClassValue unicodeDecodeError() {
result = TBuiltinClassObject(Builtin::builtin("UnicodeDecodeError"))
}
/** Get the `ClassValue` for the `SystemExit` class. */
ClassValue systemExit() { result = TBuiltinClassObject(Builtin::builtin("SystemExit")) }
}

View File

@@ -10,12 +10,6 @@ import python
import semmle.python.security.TaintTracking
import semmle.python.security.strings.Untrusted
private FunctionObject exec_or_eval() {
result = Object::builtin("exec")
or
result = Object::builtin("eval")
}
/**
* A taint sink that represents an argument to exec or eval that is vulnerable to malicious input.
* The `vuln` in `exec(vuln)` or similar.
@@ -26,10 +20,9 @@ class StringEvaluationNode extends TaintSink {
StringEvaluationNode() {
exists(Exec exec | exec.getASubExpression().getAFlowNode() = this)
or
exists(CallNode call |
exec_or_eval().getACall() = call and
call.getAnArg() = this
)
Value::named("exec").getACall().getAnArg() = this
or
Value::named("eval").getACall().getAnArg() = this
}
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }

View File

@@ -64,8 +64,12 @@ class OpenNode extends TaintSink {
OpenNode() {
exists(CallNode call |
call.getFunction().refersTo(Object::builtin("open")) and
call.getAnArg() = this
call = Value::named("open").getACall() and
(
call.getArg(0) = this
or
call.getArgByName("file") = this
)
)
}

View File

@@ -7,10 +7,21 @@ abstract class StringKind extends TaintKind {
bindingset[this]
StringKind() { this = this }
override TaintKind getTaintOfMethodResult(string name) {
name in ["capitalize", "casefold", "center", "expandtabs", "format", "format_map", "ljust",
"lstrip", "lower", "replace", "rjust", "rstrip", "strip", "swapcase", "title", "upper",
"zfill",
/* encode/decode is technically not correct, but close enough */
"encode", "decode"] and
result = this
or
name in ["partition", "rpartition", "rsplit", "split", "splitlines"] and
result.(SequenceKind).getItem() = this
}
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
result = this and
(
str_method_call(fromnode, tonode) or
slice(fromnode, tonode) or
tonode.(BinaryExprNode).getAnOperand() = fromnode or
os_path_join(fromnode, tonode) or
@@ -50,20 +61,6 @@ private class StringEqualitySanitizer extends Sanitizer {
}
}
/* tonode = fromnode.xxx() where the call to xxx returns an identical or similar string */
private predicate str_method_call(ControlFlowNode fromnode, CallNode tonode) {
exists(string method_name | tonode.getFunction().(AttrNode).getObject(method_name) = fromnode |
method_name = "strip" or
method_name = "format" or
method_name = "lstrip" or
method_name = "rstrip" or
method_name = "ljust" or
method_name = "rjust" or
method_name = "title" or
method_name = "capitalize"
)
}
/* tonode = ....format(fromnode) */
private predicate str_format(ControlFlowNode fromnode, CallNode tonode) {
tonode.getFunction().(AttrNode).getName() = "format" and

View File

@@ -36,19 +36,36 @@ class RaisingNode extends ControlFlowNode {
* Gets the type of an exception that may be raised
* at this control flow node
*/
ClassObject getARaisedType() {
result = this.localRaisedType()
ClassObject getARaisedType_objectapi() {
result = this.localRaisedType_objectapi()
or
exists(FunctionObject func | this = func.getACall() | result = func.getARaisedType())
or
result = systemExitRaise_objectapi()
}
/**
* Gets the type of an exception that may be raised
* at this control flow node
*/
ClassValue getARaisedType() {
result = this.localRaisedType()
or
exists(FunctionValue func | this = func.getACall() | result = func.getARaisedType())
or
result = systemExitRaise()
}
pragma[noinline]
private ClassObject systemExitRaise() { this.quits() and result = Object::builtin("SystemExit") }
private ClassObject systemExitRaise_objectapi() {
this.quits() and result = Object::builtin("SystemExit")
}
pragma[noinline]
private ClassValue systemExitRaise() { this.quits() and result = ClassValue::systemExit() }
pragma[noinline, nomagic]
private ClassObject localRaisedType() {
private ClassObject localRaisedType_objectapi() {
result.isSubclassOf(theBaseExceptionType()) and
(
exists(ControlFlowNode ex |
@@ -62,8 +79,8 @@ class RaisingNode extends ControlFlowNode {
or
exists(ExceptFlowNode except |
except = this.getAnExceptionalSuccessor() and
except.handles(result) and
result = this.innateException()
except.handles_objectapi(result) and
result = this.innateException_objectapi()
)
or
not exists(ExceptFlowNode except | except = this.getAnExceptionalSuccessor()) and
@@ -74,8 +91,36 @@ class RaisingNode extends ControlFlowNode {
)
}
pragma[noinline, nomagic]
private ClassValue localRaisedType() {
result.getASuperType() = ClassValue::baseException() and
(
exists(ControlFlowNode ex |
ex = this.getExceptionNode() and
(ex.pointsTo(result) or ex.pointsTo().getClass() = result)
)
or
this.getNode() instanceof ImportExpr and result = ClassValue::importError()
or
this.getNode() instanceof Print and result = ClassValue::ioError()
or
exists(ExceptFlowNode except |
except = this.getAnExceptionalSuccessor() and
except.handles(result) and
result = this.innateException()
)
or
not exists(ExceptFlowNode except | except = this.getAnExceptionalSuccessor()) and
sequence_or_mapping(this) and
result = ClassValue::lookupError()
or
this.read_write_call() and result = ClassValue::ioError()
)
}
/** Holds if this is an innate exception (AttributeError, NameError, IndexError, or KeyError). */
pragma[noinline]
ClassObject innateException() {
ClassObject innateException_objectapi() {
this.getNode() instanceof Attribute and result = theAttributeErrorType()
or
this.getNode() instanceof Name and result = theNameErrorType()
@@ -85,6 +130,18 @@ class RaisingNode extends ControlFlowNode {
this.getNode() instanceof Subscript and result = theKeyErrorType()
}
/** Holds if this is an innate exception (AttributeError, NameError, IndexError, or KeyError). */
pragma[noinline]
ClassValue innateException() {
this.getNode() instanceof Attribute and result = ClassValue::attributeError()
or
this.getNode() instanceof Name and result = ClassValue::nameError()
or
this.getNode() instanceof Subscript and result = ClassValue::indexError()
or
this.getNode() instanceof Subscript and result = ClassValue::keyError()
}
/**
* Whether this control flow node raises an exception,
* but the type of the exception it raises cannot be inferred.
@@ -114,7 +171,7 @@ class RaisingNode extends ControlFlowNode {
/** Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ. */
predicate unlikelySuccessor(ControlFlowNode succ) {
succ = this.getAnExceptionalSuccessor() and
not this.viableExceptionEdge(succ, _) and
not this.viableExceptionEdge_objectapi(succ, _) and
not this.raisesUnknownType()
or
exists(FunctionObject func |
@@ -132,7 +189,30 @@ class RaisingNode extends ControlFlowNode {
}
/** Whether it is considered plausible that 'raised' can be raised across the edge this-succ */
predicate viableExceptionEdge(ControlFlowNode succ, ClassObject raised) {
predicate viableExceptionEdge_objectapi(ControlFlowNode succ, ClassObject raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType_objectapi() and
succ = this.getAnExceptionalSuccessor() and
(
/* An 'except' that handles raised and there is no more previous handler */
succ.(ExceptFlowNode).handles_objectapi(raised) and
not exists(ExceptFlowNode other, StmtList s, int i, int j |
not other = succ and
other.handles_objectapi(raised) and
s.getItem(i) = succ.getNode() and
s.getItem(j) = other.getNode()
|
j < i
)
or
/* Any successor that is not an 'except', provided that 'raised' is not handled by a different successor. */
not this.getAnExceptionalSuccessor().(ExceptFlowNode).handles_objectapi(raised) and
not succ instanceof ExceptFlowNode
)
}
/** Whether it is considered plausible that 'raised' can be raised across the edge this-succ */
predicate viableExceptionEdge(ControlFlowNode succ, ClassValue raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType() and
succ = this.getAnExceptionalSuccessor() and
@@ -159,7 +239,19 @@ class RaisingNode extends ControlFlowNode {
* plausible that the scope `s` can be exited with exception `raised`
* at this point.
*/
predicate viableExceptionalExit(Scope s, ClassObject raised) {
predicate viableExceptionalExit_objectapi(Scope s, ClassObject raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType_objectapi() and
this.isExceptionalExit(s) and
not this.getAnExceptionalSuccessor().(ExceptFlowNode).handles_objectapi(raised)
}
/**
* Whether this exceptional exit is viable. That is, is it
* plausible that the scope `s` can be exited with exception `raised`
* at this point.
*/
predicate viableExceptionalExit(Scope s, ClassValue raised) {
raised.isLegalExceptionType() and
raised = this.getARaisedType() and
this.isExceptionalExit(s) and
@@ -170,7 +262,29 @@ class RaisingNode extends ControlFlowNode {
/** Is this a sequence or mapping subscript x[i]? */
private predicate sequence_or_mapping(RaisingNode r) { r.getNode() instanceof Subscript }
private predicate current_exception(ClassObject ex, BasicBlock b) {
private predicate current_exception_objectapi(ClassObject ex, BasicBlock b) {
exists(RaisingNode r |
r.viableExceptionEdge_objectapi(b.getNode(0), ex) and not b.getNode(0) instanceof ExceptFlowNode
)
or
exists(BasicBlock prev |
current_exception_objectapi(ex, prev) and
exists(ControlFlowNode pred, ControlFlowNode succ |
pred = prev.getLastNode() and succ = b.getNode(0)
|
pred.getASuccessor() = succ and
(
/* Normal control flow */
not pred.getAnExceptionalSuccessor() = succ
or
/* Re-raise the current exception, propagating to the successor */
pred instanceof ReraisingNode
)
)
)
}
private predicate current_exception(ClassValue ex, BasicBlock b) {
exists(RaisingNode r |
r.viableExceptionEdge(b.getNode(0), ex) and not b.getNode(0) instanceof ExceptFlowNode
)
@@ -211,7 +325,19 @@ private predicate unknown_current_exception(BasicBlock b) {
}
/** INTERNAL -- Use FunctionObject.getARaisedType() instead */
predicate scope_raises(ClassObject ex, Scope s) {
predicate scope_raises_objectapi(ClassObject ex, Scope s) {
exists(BasicBlock b |
current_exception_objectapi(ex, b) and
b.getLastNode().isExceptionalExit(s)
|
b.getLastNode() instanceof ReraisingNode
)
or
exists(RaisingNode r | r.viableExceptionalExit_objectapi(s, ex))
}
/** INTERNAL -- Use FunctionObject.getARaisedType() instead */
predicate scope_raises(ClassValue ex, Scope s) {
exists(BasicBlock b |
current_exception(ex, b) and
b.getLastNode().isExceptionalExit(s)
@@ -299,11 +425,18 @@ class ExceptFlowNode extends ControlFlowNode {
}
/** Whether this `except` handles `cls` */
predicate handles(ClassObject cls) {
predicate handles_objectapi(ClassObject cls) {
exists(ClassObject handled | this.handledException_objectapi(handled, _, _) |
cls.getAnImproperSuperType() = handled
)
}
/** Whether this `except` handles `cls` */
predicate handles(ClassValue cls) {
exists(ClassValue handled | this.handledException(handled, _, _) |
cls.getASuperType() = handled
)
}
}
private ControlFlowNode element_from_tuple_objectapi(Object tuple) {
@@ -324,7 +457,15 @@ class ReraisingNode extends RaisingNode {
}
/** Gets a class that may be raised by this node */
override ClassObject getARaisedType() {
override ClassObject getARaisedType_objectapi() {
exists(BasicBlock b |
current_exception_objectapi(result, b) and
b.getNode(_) = this
)
}
/** Gets a class that may be raised by this node */
override ClassValue getARaisedType() {
exists(BasicBlock b |
current_exception(result, b) and
b.getNode(_) = this

View File

@@ -135,7 +135,7 @@ class PyFunctionObject extends FunctionObject {
/** Whether this function is a procedure, that is, it has no explicit return statement and is not a generator function */
override predicate isProcedure() { this.getFunction().isProcedure() }
override ClassObject getARaisedType() { scope_raises(result, this.getFunction()) }
override ClassObject getARaisedType() { scope_raises_objectapi(result, this.getFunction()) }
override predicate raisesUnknownType() { scope_raises_unknown(this.getFunction()) }

View File

@@ -7,3 +7,4 @@ import semmle.python.web.bottle.Request
import semmle.python.web.turbogears.Request
import semmle.python.web.falcon.Request
import semmle.python.web.cherrypy.Request
import semmle.python.web.stdlib.Request

View File

@@ -7,3 +7,4 @@ import semmle.python.web.bottle.Response
import semmle.python.web.turbogears.Response
import semmle.python.web.falcon.Response
import semmle.python.web.cherrypy.Response
import semmle.python.web.stdlib.Response

View File

@@ -0,0 +1,124 @@
/**
* Provides the sources and taint-flow for HTTP servers defined using the standard library (stdlib).
* Specifically, we model `HttpRequestTaintSource`s from instances of `BaseHTTPRequestHandler`
* (or subclasses) and form parsing using `cgi.FieldStorage`.
*/
import python
import semmle.python.security.TaintTracking
import semmle.python.web.Http
/** Source of BaseHTTPRequestHandler instances. */
class StdLibRequestSource extends HttpRequestTaintSource {
StdLibRequestSource() {
exists(ClassValue cls |
cls.getABaseType+() = Value::named("BaseHTTPServer.BaseHTTPRequestHandler")
or
cls.getABaseType+() = Value::named("http.server.BaseHTTPRequestHandler")
|
this.(ControlFlowNode).pointsTo().getClass() = cls
)
}
override predicate isSourceOf(TaintKind kind) { kind instanceof BaseHTTPRequestHandlerKind }
}
/** TaintKind for an instance of BaseHTTPRequestHandler. */
class BaseHTTPRequestHandlerKind extends TaintKind {
BaseHTTPRequestHandlerKind() { this = "BaseHTTPRequestHandlerKind" }
override TaintKind getTaintOfAttribute(string name) {
name in ["requestline", "path"] and
result instanceof ExternalStringKind
or
name = "headers" and
result instanceof HTTPMessageKind
or
name = "rfile" and
result instanceof ExternalFileObject
}
}
/** TaintKind for headers (instance of HTTPMessage). */
class HTTPMessageKind extends ExternalStringDictKind {
override TaintKind getTaintOfMethodResult(string name) {
result = super.getTaintOfMethodResult(name)
or
name = "get_all" and
result.(SequenceKind).getItem() = this.getValue()
or
name in ["as_bytes", "as_string"] and
result instanceof ExternalStringKind
}
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
result = super.getTaintForFlowStep(fromnode, tonode)
or
exists(ClassValue cls | cls = ClassValue::unicode() or cls = ClassValue::bytes() |
tonode = cls.getACall() and
tonode.(CallNode).getArg(0) = fromnode and
result instanceof ExternalStringKind
)
}
}
/** Source of parsed HTTP forms (by using the `cgi` module). */
class CgiFieldStorageSource extends HttpRequestTaintSource {
CgiFieldStorageSource() { this = Value::named("cgi.FieldStorage").getACall() }
override predicate isSourceOf(TaintKind kind) { kind instanceof CgiFieldStorageFormKind }
}
/** TaintKind for a parsed HTTP form. */
class CgiFieldStorageFormKind extends TaintKind {
/*
* There is a slight difference between how we model form/fields and how it is handled by the code.
* In the code
* ```
* form = cgi.FieldStorage()
* field = form['myfield']
* ```
* both `form` and `field` have the type `cgi.FieldStorage`. This allows the code to represent
* nested forms as `form['nested_form']['myfield']`. However, since HTML forms can't be nested
* we ignore that detail since it allows for a more clean modeling.
*/
CgiFieldStorageFormKind() { this = "CgiFieldStorageFormKind" }
override TaintKind getTaintOfAttribute(string name) {
name = "value" and result.(SequenceKind).getItem() instanceof CgiFieldStorageFieldKind
}
override TaintKind getTaintOfMethodResult(string name) {
name = "getvalue" and
(
result instanceof ExternalStringKind
or
result.(SequenceKind).getItem() instanceof ExternalStringKind
)
or
name = "getfirst" and
result instanceof ExternalStringKind
or
name = "getlist" and
result.(SequenceKind).getItem() instanceof ExternalStringKind
}
override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
tonode.(SubscriptNode).getObject() = fromnode and
(
result instanceof CgiFieldStorageFieldKind
or
result.(SequenceKind).getItem() instanceof CgiFieldStorageFieldKind
)
}
}
/** TaintKind for the field of a parsed HTTP form. */
class CgiFieldStorageFieldKind extends TaintKind {
CgiFieldStorageFieldKind() { this = "CgiFieldStorageFieldKind" }
override TaintKind getTaintOfAttribute(string name) {
name in ["filename", "value"] and result instanceof ExternalStringKind
or
name = "file" and result instanceof ExternalFileObject
}
}

View File

@@ -0,0 +1,43 @@
/**
* Provides the sinks for HTTP servers defined with standard library (stdlib).
*/
import python
import semmle.python.security.TaintTracking
import semmle.python.web.Http
private predicate is_wfile(AttrNode wfile) {
exists(ClassValue cls |
// Python 2
cls.getABaseType+() = Value::named("BaseHTTPServer.BaseHTTPRequestHandler")
or
// Python 3
cls.getABaseType+() = Value::named("http.server.BaseHTTPRequestHandler")
|
wfile.getObject("wfile").pointsTo().getClass() = cls
)
}
/** Sink for `h.wfile.write` where `h` is an instance of BaseHTTPRequestHandler. */
class StdLibWFileWriteSink extends HttpResponseTaintSink {
StdLibWFileWriteSink() {
exists(CallNode call |
is_wfile(call.getFunction().(AttrNode).getObject("write")) and
call.getArg(0) = this
)
}
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringKind }
}
/** Sink for `h.wfile.writelines` where `h` is an instance of BaseHTTPRequestHandler. */
class StdLibWFileWritelinesSink extends HttpResponseTaintSink {
StdLibWFileWritelinesSink() {
exists(CallNode call |
is_wfile(call.getFunction().(AttrNode).getObject("writelines")) and
call.getArg(0) = this
)
}
override predicate sinks(TaintKind kind) { kind instanceof ExternalStringSequenceKind }
}

View File

@@ -1,5 +1,5 @@
import python
from RaisingNode r, Scope s, ClassObject cls
where r.viableExceptionalExit(s, cls)
where r.viableExceptionalExit_objectapi(s, cls)
select r.getLocation().getStartLine(), r.toString(), s.toString(), cls.toString()

View File

@@ -1,6 +1,6 @@
import python
from RaisingNode r, ControlFlowNode n, ClassObject ex
where r.viableExceptionEdge(n, ex)
where r.viableExceptionEdge_objectapi(n, ex)
select r.getLocation().getStartLine(), n.getLocation().getStartLine(), r.getNode().toString(),
n.getNode().toString(), ex.toString()

View File

@@ -1,10 +1,10 @@
import python
from NameNode name, CallNode call, string debug
from ControlFlowNode arg, CallNode call, string debug
where
call.getAnArg() = name and
call.getAnArg() = arg and
call.getFunction().(NameNode).getId() = "check" and
if exists(name.pointsTo())
then debug = name.pointsTo().toString()
if exists(arg.pointsTo())
then debug = arg.pointsTo().toString()
else debug = "<MISSING pointsTo()>"
select name, debug
select arg, debug

View File

@@ -1,10 +1,10 @@
import python
from NameNode name, CallNode call, string debug
from ControlFlowNode arg, CallNode call, string debug
where
call.getAnArg() = name and
call.getAnArg() = arg and
call.getFunction().(NameNode).getId() = "check" and
if exists(name.pointsTo())
then debug = name.pointsTo().toString()
if exists(arg.pointsTo())
then debug = arg.pointsTo().toString()
else debug = "<MISSING pointsTo()>"
select name, debug
select arg, debug

View File

@@ -0,0 +1,2 @@
| test.py:10:11:10:14 | ControlFlowNode for open | <MISSING pointsTo()> |
| test.py:14:11:14:14 | ControlFlowNode for open | Builtin-function open |

View File

@@ -0,0 +1,10 @@
import python
from ControlFlowNode arg, CallNode call, string debug
where
call.getAnArg() = arg and
call.getFunction().(NameNode).getId() = "check" and
if exists(arg.pointsTo())
then debug = arg.pointsTo().toString()
else debug = "<MISSING pointsTo()>"
select arg, debug

View File

@@ -0,0 +1,18 @@
# Points-to information seems to be missing if our analysis thinks the enclosing function
# is never called. However, as illustrated by the code below, it's easy to fool our
# analysis :(
# This was inspired by a problem in real code, where our analysis doesn't have any
# points-to information about the `open` call in
# https://google-gruyere.appspot.com/code/gruyere.py on line 227
def _func_not_called(filename, mode='rb'):
check(open)
return open(filename, mode)
def _func_called(filename, mode='rb'):
check(open)
return open(filename, mode)
globals()['_func_not_called']('test.txt')
_func_called('test.txt')

View File

@@ -1,3 +1,6 @@
| Class MyTest |
| Function test_1 |
| Function test_2 |
| test.py:4:1:4:23 | Class MyTest |
| test.py:6:5:6:21 | Function test_1 |
| test.py:9:5:9:21 | Function test_2 |
| test_foo.py:3:1:3:15 | Function test_foo |
| unittest_test.py:3:1:3:33 | Class FooTest |
| unittest_test.py:4:5:4:25 | Function test_valid |

View File

@@ -2,4 +2,4 @@ import python
import semmle.python.filters.Tests
from TestScope t
select t.toString()
select t

View File

@@ -1,5 +1,3 @@
class TestCase:
pass

View File

@@ -0,0 +1,4 @@
# This is running under some unknown framework, but is clearly a test!
def test_foo():
assert True

View File

@@ -0,0 +1,5 @@
import unittest
class FooTest(unittest.TestCase):
def test_valid(self):
pass

View File

@@ -1,5 +1,10 @@
| Taint [externally controlled string] | test.py:67 | test.py:67:20:67:43 | urlsplit() | | --> | Taint [externally controlled string] | test.py:69 | test.py:69:10:69:21 | urlsplit_res | |
| Taint [externally controlled string] | test.py:68 | test.py:68:20:68:43 | urlparse() | | --> | Taint [externally controlled string] | test.py:69 | test.py:69:24:69:35 | urlparse_res | |
| Taint [externally controlled string] | test.py:98 | test.py:98:9:98:37 | Attribute() | | --> | Taint externally controlled string | test.py:98 | test.py:98:9:98:40 | Subscript | |
| Taint [externally controlled string] | test.py:102 | test.py:102:9:102:38 | Attribute() | | --> | Taint externally controlled string | test.py:102 | test.py:102:9:102:41 | Subscript | |
| Taint [externally controlled string] | test.py:104 | test.py:104:9:104:37 | Attribute() | | --> | Taint externally controlled string | test.py:104 | test.py:104:9:104:41 | Subscript | |
| Taint [externally controlled string] | test.py:107 | test.py:107:9:107:30 | Attribute() | | --> | Taint externally controlled string | test.py:107 | test.py:107:9:107:33 | Subscript | |
| Taint [externally controlled string] | test.py:109 | test.py:109:9:109:35 | Attribute() | | --> | Taint externally controlled string | test.py:109 | test.py:109:9:109:38 | Subscript | |
| Taint exception.info | test.py:44 | test.py:44:22:44:26 | taint | p1 = exception.info | --> | Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info |
| Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | --> | Taint exception.info | test.py:45 | test.py:45:12:45:22 | func() | p1 = exception.info |
| Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | --> | Taint exception.info | test.py:52 | test.py:52:19:52:21 | arg | p0 = exception.info |
@@ -62,16 +67,81 @@
| Taint externally controlled string | test.py:66 | test.py:66:22:66:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:68 | test.py:68:29:68:42 | tainted_string | |
| Taint externally controlled string | test.py:67 | test.py:67:29:67:42 | tainted_string | | --> | Taint [externally controlled string] | test.py:67 | test.py:67:20:67:43 | urlsplit() | |
| Taint externally controlled string | test.py:68 | test.py:68:29:68:42 | tainted_string | | --> | Taint [externally controlled string] | test.py:68 | test.py:68:20:68:43 | urlparse() | |
| Taint externally controlled string | test.py:79 | test.py:79:5:79:29 | For | | --> | Taint externally controlled string | test.py:80 | test.py:80:14:80:17 | line | |
| Taint file[externally controlled string] | test.py:72 | test.py:72:20:72:31 | TAINTED_FILE | | --> | Taint file[externally controlled string] | test.py:74 | test.py:74:9:74:20 | tainted_file | |
| Taint file[externally controlled string] | test.py:72 | test.py:72:20:72:31 | TAINTED_FILE | | --> | Taint file[externally controlled string] | test.py:75 | test.py:75:9:75:20 | tainted_file | |
| Taint file[externally controlled string] | test.py:72 | test.py:72:20:72:31 | TAINTED_FILE | | --> | Taint file[externally controlled string] | test.py:76 | test.py:76:9:76:20 | tainted_file | |
| Taint file[externally controlled string] | test.py:72 | test.py:72:20:72:31 | TAINTED_FILE | | --> | Taint file[externally controlled string] | test.py:77 | test.py:77:9:77:20 | tainted_file | |
| Taint file[externally controlled string] | test.py:72 | test.py:72:20:72:31 | TAINTED_FILE | | --> | Taint file[externally controlled string] | test.py:79 | test.py:79:17:79:28 | tainted_file | |
| Taint file[externally controlled string] | test.py:75 | test.py:75:9:75:20 | tainted_file | | --> | Taint externally controlled string | test.py:75 | test.py:75:9:75:27 | Attribute() | |
| Taint file[externally controlled string] | test.py:76 | test.py:76:9:76:20 | tainted_file | | --> | Taint externally controlled string | test.py:76 | test.py:76:9:76:31 | Attribute() | |
| Taint file[externally controlled string] | test.py:77 | test.py:77:9:77:20 | tainted_file | | --> | Taint [externally controlled string] | test.py:77 | test.py:77:9:77:32 | Attribute() | |
| Taint file[externally controlled string] | test.py:79 | test.py:79:17:79:28 | tainted_file | | --> | Taint externally controlled string | test.py:79 | test.py:79:5:79:29 | For | |
| Taint externally controlled string | test.py:72 | test.py:72:22:72:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:74 | test.py:74:9:74:22 | tainted_string | |
| Taint externally controlled string | test.py:72 | test.py:72:22:72:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:76 | test.py:76:12:76:25 | tainted_string | |
| Taint externally controlled string | test.py:74 | test.py:74:9:74:22 | tainted_string | | --> | Taint externally controlled string | test.py:74 | test.py:74:9:74:30 | Attribute() | |
| Taint externally controlled string | test.py:74 | test.py:74:9:74:30 | Attribute() | | --> | Taint externally controlled string | test.py:79 | test.py:79:10:79:10 | a | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:85 | test.py:85:9:85:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:86 | test.py:86:9:86:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:87 | test.py:87:9:87:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:88 | test.py:88:9:88:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:89 | test.py:89:9:89:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:90 | test.py:90:9:90:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:91 | test.py:91:9:91:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:92 | test.py:92:9:92:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:93 | test.py:93:9:93:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:94 | test.py:94:9:94:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:95 | test.py:95:9:95:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:96 | test.py:96:9:96:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:97 | test.py:97:9:97:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:98 | test.py:98:9:98:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:99 | test.py:99:9:99:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:100 | test.py:100:9:100:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:101 | test.py:101:9:101:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:102 | test.py:102:9:102:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:103 | test.py:103:9:103:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:104 | test.py:104:9:104:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:105 | test.py:105:9:105:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:106 | test.py:106:9:106:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:107 | test.py:107:9:107:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:108 | test.py:108:9:108:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:109 | test.py:109:9:109:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:110 | test.py:110:9:110:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:111 | test.py:111:9:111:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:112 | test.py:112:9:112:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:115 | test.py:115:9:115:22 | tainted_string | |
| Taint externally controlled string | test.py:82 | test.py:82:22:82:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:116 | test.py:116:9:116:22 | tainted_string | |
| Taint externally controlled string | test.py:85 | test.py:85:9:85:22 | tainted_string | | --> | Taint externally controlled string | test.py:85 | test.py:85:9:85:35 | Attribute() | |
| Taint externally controlled string | test.py:86 | test.py:86:9:86:22 | tainted_string | | --> | Taint externally controlled string | test.py:86 | test.py:86:9:86:33 | Attribute() | |
| Taint externally controlled string | test.py:87 | test.py:87:9:87:22 | tainted_string | | --> | Taint externally controlled string | test.py:87 | test.py:87:9:87:31 | Attribute() | |
| Taint externally controlled string | test.py:88 | test.py:88:9:88:22 | tainted_string | | --> | Taint externally controlled string | test.py:88 | test.py:88:9:88:38 | Attribute() | |
| Taint externally controlled string | test.py:89 | test.py:89:9:89:22 | tainted_string | | --> | Taint externally controlled string | test.py:89 | test.py:89:9:89:38 | Attribute() | |
| Taint externally controlled string | test.py:89 | test.py:89:9:89:38 | Attribute() | | --> | Taint externally controlled string | test.py:89 | test.py:89:9:89:54 | Attribute() | |
| Taint externally controlled string | test.py:90 | test.py:90:9:90:22 | tainted_string | | --> | Taint externally controlled string | test.py:90 | test.py:90:9:90:35 | Attribute() | |
| Taint externally controlled string | test.py:91 | test.py:91:9:91:22 | tainted_string | | --> | Taint externally controlled string | test.py:91 | test.py:91:9:91:37 | Attribute() | |
| Taint externally controlled string | test.py:92 | test.py:92:9:92:22 | tainted_string | | --> | Taint externally controlled string | test.py:92 | test.py:92:9:92:46 | Attribute() | |
| Taint externally controlled string | test.py:93 | test.py:93:9:93:22 | tainted_string | | --> | Taint externally controlled string | test.py:93 | test.py:93:9:93:33 | Attribute() | |
| Taint externally controlled string | test.py:94 | test.py:94:9:94:22 | tainted_string | | --> | Taint externally controlled string | test.py:94 | test.py:94:9:94:30 | Attribute() | |
| Taint externally controlled string | test.py:95 | test.py:95:9:95:22 | tainted_string | | --> | Taint externally controlled string | test.py:95 | test.py:95:9:95:31 | Attribute() | |
| Taint externally controlled string | test.py:96 | test.py:96:9:96:22 | tainted_string | | --> | Taint externally controlled string | test.py:96 | test.py:96:9:96:35 | Attribute() | |
| Taint externally controlled string | test.py:97 | test.py:97:9:97:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:97 | test.py:97:9:97:37 | Attribute() | |
| Taint externally controlled string | test.py:98 | test.py:98:9:98:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:98 | test.py:98:9:98:37 | Attribute() | |
| Taint externally controlled string | test.py:99 | test.py:99:9:99:22 | tainted_string | | --> | Taint externally controlled string | test.py:99 | test.py:99:9:99:42 | Attribute() | |
| Taint externally controlled string | test.py:100 | test.py:100:9:100:22 | tainted_string | | --> | Taint externally controlled string | test.py:100 | test.py:100:9:100:33 | Attribute() | |
| Taint externally controlled string | test.py:101 | test.py:101:9:101:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:101 | test.py:101:9:101:38 | Attribute() | |
| Taint externally controlled string | test.py:102 | test.py:102:9:102:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:102 | test.py:102:9:102:38 | Attribute() | |
| Taint externally controlled string | test.py:103 | test.py:103:9:103:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:103 | test.py:103:9:103:37 | Attribute() | |
| Taint externally controlled string | test.py:104 | test.py:104:9:104:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:104 | test.py:104:9:104:37 | Attribute() | |
| Taint externally controlled string | test.py:105 | test.py:105:9:105:22 | tainted_string | | --> | Taint externally controlled string | test.py:105 | test.py:105:9:105:31 | Attribute() | |
| Taint externally controlled string | test.py:106 | test.py:106:9:106:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:106 | test.py:106:9:106:30 | Attribute() | |
| Taint externally controlled string | test.py:107 | test.py:107:9:107:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:107 | test.py:107:9:107:30 | Attribute() | |
| Taint externally controlled string | test.py:108 | test.py:108:9:108:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:108 | test.py:108:9:108:35 | Attribute() | |
| Taint externally controlled string | test.py:109 | test.py:109:9:109:22 | tainted_string | | --> | Taint [externally controlled string] | test.py:109 | test.py:109:9:109:35 | Attribute() | |
| Taint externally controlled string | test.py:110 | test.py:110:9:110:22 | tainted_string | | --> | Taint externally controlled string | test.py:110 | test.py:110:9:110:30 | Attribute() | |
| Taint externally controlled string | test.py:111 | test.py:111:9:111:22 | tainted_string | | --> | Taint externally controlled string | test.py:111 | test.py:111:9:111:33 | Attribute() | |
| Taint externally controlled string | test.py:112 | test.py:112:9:112:22 | tainted_string | | --> | Taint externally controlled string | test.py:112 | test.py:112:9:112:30 | Attribute() | |
| Taint externally controlled string | test.py:115 | test.py:115:9:115:22 | tainted_string | | --> | Taint externally controlled string | test.py:115 | test.py:115:9:115:30 | Attribute() | |
| Taint externally controlled string | test.py:116 | test.py:116:9:116:22 | tainted_string | | --> | Taint externally controlled string | test.py:116 | test.py:116:9:116:33 | Attribute() | |
| Taint externally controlled string | test.py:127 | test.py:127:5:127:29 | For | | --> | Taint externally controlled string | test.py:128 | test.py:128:14:128:17 | line | |
| Taint file[externally controlled string] | test.py:120 | test.py:120:20:120:31 | TAINTED_FILE | | --> | Taint file[externally controlled string] | test.py:122 | test.py:122:9:122:20 | tainted_file | |
| Taint file[externally controlled string] | test.py:120 | test.py:120:20:120:31 | TAINTED_FILE | | --> | Taint file[externally controlled string] | test.py:123 | test.py:123:9:123:20 | tainted_file | |
| Taint file[externally controlled string] | test.py:120 | test.py:120:20:120:31 | TAINTED_FILE | | --> | Taint file[externally controlled string] | test.py:124 | test.py:124:9:124:20 | tainted_file | |
| Taint file[externally controlled string] | test.py:120 | test.py:120:20:120:31 | TAINTED_FILE | | --> | Taint file[externally controlled string] | test.py:125 | test.py:125:9:125:20 | tainted_file | |
| Taint file[externally controlled string] | test.py:120 | test.py:120:20:120:31 | TAINTED_FILE | | --> | Taint file[externally controlled string] | test.py:127 | test.py:127:17:127:28 | tainted_file | |
| Taint file[externally controlled string] | test.py:123 | test.py:123:9:123:20 | tainted_file | | --> | Taint externally controlled string | test.py:123 | test.py:123:9:123:27 | Attribute() | |
| Taint file[externally controlled string] | test.py:124 | test.py:124:9:124:20 | tainted_file | | --> | Taint externally controlled string | test.py:124 | test.py:124:9:124:31 | Attribute() | |
| Taint file[externally controlled string] | test.py:125 | test.py:125:9:125:20 | tainted_file | | --> | Taint [externally controlled string] | test.py:125 | test.py:125:9:125:32 | Attribute() | |
| Taint file[externally controlled string] | test.py:127 | test.py:127:17:127:28 | tainted_file | | --> | Taint externally controlled string | test.py:127 | test.py:127:5:127:29 | For | |
| Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | |
| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | |
| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | |

View File

@@ -22,8 +22,40 @@
| test.py:58 | test_untrusted | res | externally controlled string |
| test.py:69 | test_urlsplit_urlparse | urlparse_res | [externally controlled string] |
| test.py:69 | test_urlsplit_urlparse | urlsplit_res | [externally controlled string] |
| test.py:74 | test_tainted_file | tainted_file | file[externally controlled string] |
| test.py:75 | test_tainted_file | Attribute() | externally controlled string |
| test.py:76 | test_tainted_file | Attribute() | externally controlled string |
| test.py:77 | test_tainted_file | Attribute() | [externally controlled string] |
| test.py:80 | test_tainted_file | line | externally controlled string |
| test.py:79 | test_method_reference | a | externally controlled string |
| test.py:79 | test_method_reference | b | NO TAINT |
| test.py:85 | test_str_methods | Attribute() | externally controlled string |
| test.py:86 | test_str_methods | Attribute() | externally controlled string |
| test.py:87 | test_str_methods | Attribute() | externally controlled string |
| test.py:88 | test_str_methods | Attribute() | externally controlled string |
| test.py:89 | test_str_methods | Attribute() | externally controlled string |
| test.py:90 | test_str_methods | Attribute() | externally controlled string |
| test.py:91 | test_str_methods | Attribute() | externally controlled string |
| test.py:92 | test_str_methods | Attribute() | externally controlled string |
| test.py:93 | test_str_methods | Attribute() | externally controlled string |
| test.py:94 | test_str_methods | Attribute() | externally controlled string |
| test.py:95 | test_str_methods | Attribute() | externally controlled string |
| test.py:96 | test_str_methods | Attribute() | externally controlled string |
| test.py:97 | test_str_methods | Attribute() | [externally controlled string] |
| test.py:98 | test_str_methods | Subscript | externally controlled string |
| test.py:99 | test_str_methods | Attribute() | externally controlled string |
| test.py:100 | test_str_methods | Attribute() | externally controlled string |
| test.py:101 | test_str_methods | Attribute() | [externally controlled string] |
| test.py:102 | test_str_methods | Subscript | externally controlled string |
| test.py:103 | test_str_methods | Attribute() | [externally controlled string] |
| test.py:104 | test_str_methods | Subscript | externally controlled string |
| test.py:105 | test_str_methods | Attribute() | externally controlled string |
| test.py:106 | test_str_methods | Attribute() | [externally controlled string] |
| test.py:107 | test_str_methods | Subscript | externally controlled string |
| test.py:108 | test_str_methods | Attribute() | [externally controlled string] |
| test.py:109 | test_str_methods | Subscript | externally controlled string |
| test.py:110 | test_str_methods | Attribute() | externally controlled string |
| test.py:111 | test_str_methods | Attribute() | externally controlled string |
| test.py:112 | test_str_methods | Attribute() | externally controlled string |
| test.py:115 | test_str_methods | Attribute() | externally controlled string |
| test.py:116 | test_str_methods | Attribute() | externally controlled string |
| test.py:122 | test_tainted_file | tainted_file | file[externally controlled string] |
| test.py:123 | test_tainted_file | Attribute() | externally controlled string |
| test.py:124 | test_tainted_file | Attribute() | externally controlled string |
| test.py:125 | test_tainted_file | Attribute() | [externally controlled string] |
| test.py:128 | test_tainted_file | line | externally controlled string |

View File

@@ -68,6 +68,54 @@ def test_urlsplit_urlparse():
urlparse_res = urlparse(tainted_string)
test(urlsplit_res, urlparse_res)
def test_method_reference():
tainted_string = TAINTED_STRING
a = tainted_string.title()
func = tainted_string.title
b = func()
test(a, b) # TODO: `b` not tainted
def test_str_methods():
tainted_string = TAINTED_STRING
test(
tainted_string.capitalize(),
tainted_string.casefold(),
tainted_string.center(),
tainted_string.encode('utf-8'),
tainted_string.encode('utf-8').decode('utf-8'),
tainted_string.expandtabs(),
tainted_string.format(foo=42),
tainted_string.format_map({'foo': 42}),
tainted_string.ljust(100),
tainted_string.lower(),
tainted_string.lstrip(),
tainted_string.lstrip('w.'),
tainted_string.partition(';'),
tainted_string.partition(';')[0],
tainted_string.replace('/', '', 1),
tainted_string.rjust(100),
tainted_string.rpartition(';'),
tainted_string.rpartition(';')[2],
tainted_string.rsplit(';', 4),
tainted_string.rsplit(';', 4)[-1],
tainted_string.rstrip(),
tainted_string.split(),
tainted_string.split()[0],
tainted_string.splitlines(),
tainted_string.splitlines()[0],
tainted_string.strip(),
tainted_string.swapcase(),
tainted_string.title(),
# ignoring, as I have never seen this in practice
# tainted_string.translate(translation_table),
tainted_string.upper(),
tainted_string.zfill(100),
)
def test_tainted_file():
tainted_file = TAINTED_FILE
test(

View File

@@ -1,5 +1,5 @@
import python
from RaisingNode r, Scope s, ClassObject cls
where r.viableExceptionalExit(s, cls)
where r.viableExceptionalExit_objectapi(s, cls)
select r.getLocation().getStartLine(), r, s.toString(), cls

View File

@@ -1,5 +1,5 @@
import python
from ExceptFlowNode n, ClassObject cls
where n.handles(cls)
where n.handles_objectapi(cls)
select n.getLocation().getStartLine(), cls.toString()

View File

@@ -1,6 +1,6 @@
import python
from RaisingNode r, ControlFlowNode n, ClassObject ex
where r.viableExceptionEdge(n, ex)
where r.viableExceptionEdge_objectapi(n, ex)
select r.getLocation().getStartLine(), n.getLocation().getStartLine(), r.getNode().toString(),
n.getNode().toString(), ex.toString()

View File

@@ -0,0 +1,2 @@
| test.py:72:26:72:58 | Taint sink | externally controlled string |
| test.py:73:31:73:54 | Taint sink | [externally controlled string] |

View File

@@ -0,0 +1,7 @@
import python
import semmle.python.web.HttpResponse
import semmle.python.security.strings.Untrusted
from HttpResponseTaintSink sink, TaintKind kind
where sink.sinks(kind)
select sink, kind

View File

@@ -0,0 +1,34 @@
| test.py:18:13:18:16 | self | BaseHTTPRequestHandlerKind |
| test.py:20:13:20:16 | self | BaseHTTPRequestHandlerKind |
| test.py:22:13:22:16 | self | BaseHTTPRequestHandlerKind |
| test.py:24:13:24:16 | self | BaseHTTPRequestHandlerKind |
| test.py:25:13:25:16 | self | BaseHTTPRequestHandlerKind |
| test.py:26:13:26:16 | self | BaseHTTPRequestHandlerKind |
| test.py:27:13:27:16 | self | BaseHTTPRequestHandlerKind |
| test.py:28:13:28:16 | self | BaseHTTPRequestHandlerKind |
| test.py:29:13:29:16 | self | BaseHTTPRequestHandlerKind |
| test.py:30:13:30:16 | self | BaseHTTPRequestHandlerKind |
| test.py:31:13:31:16 | self | BaseHTTPRequestHandlerKind |
| test.py:32:13:32:16 | self | BaseHTTPRequestHandlerKind |
| test.py:33:17:33:20 | self | BaseHTTPRequestHandlerKind |
| test.py:34:19:34:22 | self | BaseHTTPRequestHandlerKind |
| test.py:36:13:36:16 | self | BaseHTTPRequestHandlerKind |
| test.py:37:13:37:16 | self | BaseHTTPRequestHandlerKind |
| test.py:40:16:44:9 | Attribute() | CgiFieldStorageFormKind |
| test.py:41:13:41:16 | self | BaseHTTPRequestHandlerKind |
| test.py:42:13:42:16 | self | BaseHTTPRequestHandlerKind |
| test.py:43:64:43:67 | self | BaseHTTPRequestHandlerKind |
| test.py:69:9:69:12 | self | BaseHTTPRequestHandlerKind |
| test.py:70:9:70:12 | self | BaseHTTPRequestHandlerKind |
| test.py:71:9:71:12 | self | BaseHTTPRequestHandlerKind |
| test.py:72:9:72:12 | self | BaseHTTPRequestHandlerKind |
| test.py:73:9:73:12 | self | BaseHTTPRequestHandlerKind |
| test.py:74:15:74:18 | self | BaseHTTPRequestHandlerKind |
| test.py:78:16:82:9 | Attribute() | CgiFieldStorageFormKind |
| test.py:79:13:79:16 | self | BaseHTTPRequestHandlerKind |
| test.py:80:13:80:16 | self | BaseHTTPRequestHandlerKind |
| test.py:81:64:81:67 | self | BaseHTTPRequestHandlerKind |
| test.py:85:13:85:16 | self | BaseHTTPRequestHandlerKind |
| test.py:86:13:86:16 | self | BaseHTTPRequestHandlerKind |
| test.py:96:9:96:12 | self | BaseHTTPRequestHandlerKind |
| test.py:97:9:97:12 | self | BaseHTTPRequestHandlerKind |

View File

@@ -0,0 +1,9 @@
import python
import semmle.python.web.HttpRequest
import semmle.python.security.strings.Untrusted
from HttpRequestTaintSource source, TaintKind kind
where
source.isSourceOf(kind) and
source.getLocation().getFile().getShortName() != "cgi.py"
select source.(ControlFlowNode).getNode(), kind

View File

@@ -0,0 +1,32 @@
| test.py:18 | ok | taint_sources | self | BaseHTTPRequestHandlerKind |
| test.py:20 | ok | taint_sources | Attribute | externally controlled string |
| test.py:22 | ok | taint_sources | Attribute | externally controlled string |
| test.py:24 | ok | taint_sources | Attribute | {externally controlled string} |
| test.py:25 | ok | taint_sources | Subscript | externally controlled string |
| test.py:26 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:27 | ok | taint_sources | Attribute() | [externally controlled string] |
| test.py:28 | fail | taint_sources | Attribute() | <NO TAINT> |
| test.py:29 | ok | taint_sources | Attribute() | [externally controlled string] |
| test.py:30 | fail | taint_sources | Attribute() | <NO TAINT> |
| test.py:31 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:32 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:33 | ok | taint_sources | str() | externally controlled string |
| test.py:34 | ok | taint_sources | bytes() | externally controlled string |
| test.py:36 | ok | taint_sources | Attribute | file[externally controlled string] |
| test.py:37 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:47 | ok | taint_sources | form | CgiFieldStorageFormKind |
| test.py:49 | ok | taint_sources | Subscript | CgiFieldStorageFieldKind |
| test.py:49 | ok | taint_sources | Subscript | [CgiFieldStorageFieldKind] |
| test.py:50 | ok | taint_sources | Attribute | externally controlled string |
| test.py:51 | ok | taint_sources | Attribute | file[externally controlled string] |
| test.py:52 | ok | taint_sources | Attribute | externally controlled string |
| test.py:53 | ok | taint_sources | Subscript | CgiFieldStorageFieldKind |
| test.py:54 | ok | taint_sources | Attribute | externally controlled string |
| test.py:55 | ok | taint_sources | Attribute | file[externally controlled string] |
| test.py:56 | ok | taint_sources | Attribute | externally controlled string |
| test.py:58 | ok | taint_sources | Attribute() | [externally controlled string] |
| test.py:58 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:59 | ok | taint_sources | Subscript | externally controlled string |
| test.py:61 | ok | taint_sources | Attribute() | externally controlled string |
| test.py:63 | ok | taint_sources | Attribute() | [externally controlled string] |
| test.py:64 | ok | taint_sources | Subscript | externally controlled string |

View File

@@ -0,0 +1,32 @@
import python
import semmle.python.security.TaintTracking
import semmle.python.web.HttpRequest
import semmle.python.security.strings.Untrusted
from
Call call, Expr arg, boolean expected_taint, boolean has_taint, string test_res,
string taint_string
where
call.getLocation().getFile().getShortName() = "test.py" and
(
call.getFunc().(Name).getId() = "ensure_tainted" and
expected_taint = true
or
call.getFunc().(Name).getId() = "ensure_not_tainted" and
expected_taint = false
) and
arg = call.getAnArg() and
(
not exists(TaintedNode tainted | tainted.getAstNode() = arg) and
taint_string = "<NO TAINT>" and
has_taint = false
or
exists(TaintedNode tainted | tainted.getAstNode() = arg |
taint_string = tainted.getTaintKind().toString()
) and
has_taint = true
) and
if expected_taint = has_taint then test_res = "ok " else test_res = "fail"
// if expected_taint = has_taint then test_res = "✓" else test_res = "✕"
select arg.getLocation().toString(), test_res, call.getScope().(Function).getName(), arg.toString(),
taint_string

View File

@@ -0,0 +1,108 @@
import sys
import os
import cgi
if sys.version_info[0] == 2:
from BaseHTTPServer import BaseHTTPRequestHandler
from BaseHTTPServer import HTTPServer
if sys.version_info[0] == 3:
from http.server import HTTPServer, BaseHTTPRequestHandler
class MyHandler(BaseHTTPRequestHandler):
def taint_sources(self):
ensure_tainted(
self,
self.requestline,
self.path,
self.headers,
self.headers['Foo'],
self.headers.get('Foo'),
self.headers.get_all('Foo'),
self.headers.keys(),
self.headers.values(),
self.headers.items(),
self.headers.as_bytes(),
self.headers.as_string(),
str(self.headers),
bytes(self.headers),
self.rfile,
self.rfile.read(),
)
form = cgi.FieldStorage(
self.rfile,
self.headers,
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('content-type')},
)
ensure_tainted(
form,
form['key'],
form['key'].value,
form['key'].file,
form['key'].filename,
form['key'][0], # will be a list, if multiple fields named "key" are provided
form['key'][0].value,
form['key'][0].file,
form['key'][0].filename,
form.getvalue('key'),
form.getvalue('key')[0], # will be a list, if multiple fields named "key" are provided
form.getfirst('key'),
form.getlist('key'),
form.getlist('key')[0],
)
def do_GET(self):
# send_response will log a line to stderr
self.send_response(200)
self.send_header("Content-type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(b"Hello BaseHTTPRequestHandler\n")
self.wfile.writelines([b"1\n", b"2\n", b"3\n"])
print(self.headers)
def do_POST(self):
form = cgi.FieldStorage(
self.rfile,
self.headers,
environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers.get('content-type')},
)
if 'myfile' not in form:
self.send_response(422)
self.end_headers()
return
field = form['myfile']
field.file.seek(0, os.SEEK_END)
filesize = field.file.tell()
print("Uploaded {!r} with {} bytes".format(field.filename, filesize))
self.send_response(200)
self.end_headers()
if __name__ == "__main__":
server = HTTPServer(("127.0.0.1", 8080), MyHandler)
server.serve_forever()
# Headers works case insensitvely, so self.headers['foo'] == self.headers['FOO']
# curl localhost:8080 --header "Foo: 1" --header "foo: 2"
# To test file submission through forms, use
# curl -F myfile=@<yourfile> localhost:8080

View File

@@ -1,7 +1,7 @@
| use_mox.py:28:1:28:4 | f0() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:7:1:7:10 | Function f0 | function f0 |
| use_mox.py:29:1:29:5 | f1() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:10:1:10:13 | Function f1 | function f1 |
| use_mox.py:32:1:32:8 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:15:5:15:20 | Function m0 | method C.m0 |
| use_mox.py:33:1:33:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:18:5:18:23 | Function m1 | method C.m1 |
| use_mox.py:32:1:32:8 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | use_mox.py:15:5:15:20 | Function C.m0 | method C.m0 |
| use_mox.py:33:1:33:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | use_mox.py:18:5:18:23 | Function C.m1 | method C.m1 |
| wrong_arguments.py:29:1:29:4 | f0() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:3:1:3:10 | Function f0 | function f0 |
| wrong_arguments.py:30:1:30:4 | f1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:6:1:6:20 | Function f1 | function f1 |
| wrong_arguments.py:31:1:31:4 | f2() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:9:1:9:14 | Function f2 | function f2 |
@@ -21,5 +21,5 @@
| wrong_arguments.py:86:1:86:4 | l1() | Call to $@ with too few arguments; should be no fewer than 1. | wrong_arguments.py:71:6:71:21 | Function lambda | function lambda |
| wrong_arguments.py:96:1:96:12 | f6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 |
| wrong_arguments.py:97:1:97:7 | f6() | Call to $@ with too many arguments; should be no more than 2. | wrong_arguments.py:21:1:21:13 | Function f6 | function f6 |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:126:5:126:31 | Function spam | method Eggs2.spam |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | wrong_arguments.py:121:5:121:19 | Function spam | method Eggs1.spam |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too few arguments; should be no fewer than 2. | wrong_arguments.py:126:5:126:31 | Function Eggs2.spam | method Eggs2.spam |
| wrong_arguments.py:130:1:130:9 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | wrong_arguments.py:121:5:121:19 | Function Eggs1.spam | method Eggs1.spam |

View File

@@ -1,6 +1,6 @@
| test.py:24:5:24:26 | Function meth1 | Overriding method signature does not match $@, where it is passed too few. Overridden method $@ is correctly specified. | test.py:15:9:15:20 | Attribute() | here | test.py:5:5:5:20 | Function meth1 | method Base.meth1 |
| test.py:24:5:24:26 | Function meth1 | Overriding method signature does not match $@, where it is passed too few. Overridden method $@ is correctly specified. | test.py:34:9:34:20 | Attribute() | here | test.py:5:5:5:20 | Function meth1 | method Base.meth1 |
| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed an argument named 'spam'. Overridden method $@ is correctly specified. | test.py:20:9:20:31 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed an argument named 'spam'. Overridden method $@ is correctly specified. | test.py:39:9:39:31 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed too many. Overridden method $@ is correctly specified. | test.py:18:9:18:21 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function meth2 | Overriding method signature does not match $@, where it is passed too many. Overridden method $@ is correctly specified. | test.py:37:9:37:21 | Attribute() | here | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:24:5:24:26 | Function Derived.meth1 | Overriding method signature does not match $@, where it is passed too few. Overridden method $@ is correctly specified. | test.py:15:9:15:20 | Attribute() | here | test.py:5:5:5:20 | Function Base.meth1 | method Base.meth1 |
| test.py:24:5:24:26 | Function Derived.meth1 | Overriding method signature does not match $@, where it is passed too few. Overridden method $@ is correctly specified. | test.py:34:9:34:20 | Attribute() | here | test.py:5:5:5:20 | Function Base.meth1 | method Base.meth1 |
| test.py:27:5:27:20 | Function Derived.meth2 | Overriding method signature does not match $@, where it is passed an argument named 'spam'. Overridden method $@ is correctly specified. | test.py:20:9:20:31 | Attribute() | here | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function Derived.meth2 | Overriding method signature does not match $@, where it is passed an argument named 'spam'. Overridden method $@ is correctly specified. | test.py:39:9:39:31 | Attribute() | here | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function Derived.meth2 | Overriding method signature does not match $@, where it is passed too many. Overridden method $@ is correctly specified. | test.py:18:9:18:21 | Attribute() | here | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |
| test.py:27:5:27:20 | Function Derived.meth2 | Overriding method signature does not match $@, where it is passed too many. Overridden method $@ is correctly specified. | test.py:37:9:37:21 | Attribute() | here | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |

View File

@@ -1,2 +1,2 @@
| test.py:16:9:16:21 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | test.py:5:5:5:20 | Function meth1 | method Base.meth1 |
| test.py:17:9:17:20 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | test.py:8:5:8:26 | Function meth2 | method Base.meth2 |
| test.py:16:9:16:21 | Attribute() | Call to $@ with too many arguments; should be no more than 0. | test.py:5:5:5:20 | Function Base.meth1 | method Base.meth1 |
| test.py:17:9:17:20 | Attribute() | Call to $@ with too few arguments; should be no fewer than 1. | test.py:8:5:8:26 | Function Base.meth2 | method Base.meth2 |