mirror of
https://github.com/github/codeql.git
synced 2025-12-21 03:06:31 +01:00
Merge branch 'master' into python-improve-file-taint
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")) }
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) }
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
124
python/ql/src/semmle/python/web/stdlib/Request.qll
Normal file
124
python/ql/src/semmle/python/web/stdlib/Request.qll
Normal 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
|
||||
}
|
||||
}
|
||||
43
python/ql/src/semmle/python/web/stdlib/Response.qll
Normal file
43
python/ql/src/semmle/python/web/stdlib/Response.qll
Normal 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 }
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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 |
|
||||
|
||||
@@ -2,4 +2,4 @@ import python
|
||||
import semmle.python.filters.Tests
|
||||
|
||||
from TestScope t
|
||||
select t.toString()
|
||||
select t
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
class TestCase:
|
||||
pass
|
||||
|
||||
|
||||
4
python/ql/test/library-tests/filters/tests/test_foo.py
Normal file
4
python/ql/test/library-tests/filters/tests/test_foo.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# This is running under some unknown framework, but is clearly a test!
|
||||
|
||||
def test_foo():
|
||||
assert True
|
||||
@@ -0,0 +1,5 @@
|
||||
import unittest
|
||||
|
||||
class FooTest(unittest.TestCase):
|
||||
def test_valid(self):
|
||||
pass
|
||||
@@ -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 | |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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] |
|
||||
@@ -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
|
||||
34
python/ql/test/library-tests/web/stdlib/HttpSources.expected
Normal file
34
python/ql/test/library-tests/web/stdlib/HttpSources.expected
Normal 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 |
|
||||
9
python/ql/test/library-tests/web/stdlib/HttpSources.ql
Normal file
9
python/ql/test/library-tests/web/stdlib/HttpSources.ql
Normal 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
|
||||
32
python/ql/test/library-tests/web/stdlib/TestTaint.expected
Normal file
32
python/ql/test/library-tests/web/stdlib/TestTaint.expected
Normal 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 |
|
||||
32
python/ql/test/library-tests/web/stdlib/TestTaint.ql
Normal file
32
python/ql/test/library-tests/web/stdlib/TestTaint.ql
Normal 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
|
||||
108
python/ql/test/library-tests/web/stdlib/test.py
Normal file
108
python/ql/test/library-tests/web/stdlib/test.py
Normal 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
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user