Merge branch 'main' into python/simple-csrf

This commit is contained in:
yoff
2022-05-10 10:55:28 +02:00
committed by GitHub
3170 changed files with 146491 additions and 85969 deletions

View File

@@ -55,6 +55,7 @@
| Dict | 46 | 54 | 46 | 55 |
| Dict | 48 | 9 | 48 | 19 |
| DictUnpacking | 46 | 52 | 46 | 55 |
| DjangoViewClassHelper | 4 | 1 | 4 | 8 |
| Ellipsis | 7 | 7 | 7 | 9 |
| Ellipsis | 50 | 14 | 50 | 16 |
| ExceptStmt | 32 | 9 | 32 | 31 |

View File

@@ -44,6 +44,7 @@
| Dict | 46 | 54 | 46 | 55 |
| Dict | 48 | 9 | 48 | 19 |
| DictUnpacking | 46 | 52 | 46 | 55 |
| DjangoViewClassHelper | 4 | 1 | 4 | 8 |
| Ellipsis | 7 | 7 | 7 | 9 |
| Ellipsis | 50 | 14 | 50 | 16 |
| ExceptStmt | 32 | 9 | 32 | 31 |

View File

@@ -11,6 +11,4 @@
| test.py:4:5:4:17 | CtxManager3() |
| test.py:4:5:4:29 | With |
| test.py:4:22:4:29 | example3 |
| test.py:4:31:4:30 | |
| test.py:4:31:4:30 | With |
| test.py:6:5:6:8 | Pass |

View File

@@ -124,7 +124,9 @@ abstract class InlineExpectationsTest extends string {
abstract predicate hasActualResult(Location location, string element, string tag, string value);
/**
* Like `hasActualResult`, but returns results that do not require a matching annotation.
* Holds if there is an optional result on the specified location.
*
* This is similar to `hasActualResult`, but returns results that do not require a matching annotation.
* A failure will still arise if there is an annotation that does not match any results, but not vice versa.
* Override this predicate to specify optional results.
*/

View File

@@ -0,0 +1,135 @@
/**
* A test query that verifies assertions about the API graph embedded in source-code comments.
*
* An assertion is a comment of the form `def=<path>` or `use=<path>`, and asserts that
* there is a def/use feature reachable from the root along the given path, and its
* associated data-flow node must start on the same line as the comment.
*
* We also support negative assertions of the form `MISSING: def <path>` or `MISSING: use <path>`, which assert
* that there _isn't_ a node with the given path on the same line.
*
* The query only produces output for failed assertions, meaning that it should have no output
* under normal circumstances.
*
* The syntax is made to look exactly like inline expectation tests, so that the tests
* can remain consistent with other Python tests.
*/
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.ApiGraphs
private DataFlow::Node getNode(API::Node nd, string kind) {
kind = "def" and
result = nd.getARhs()
or
kind = "use" and
result = nd.getAUse()
}
private string getLocStr(Location loc) {
exists(string filepath, int startline |
loc.hasLocationInfo(filepath, startline, _, _, _) and
result = filepath + ":" + startline
)
}
/**
* An assertion matching a data-flow node against an API-graph feature.
*/
class Assertion extends Comment {
string expectedKind;
string expectedLoc;
string path;
string polarity;
Assertion() {
exists(string txt, string rex |
txt = this.getText().trim() and
rex = "#\\$.*?((?:MISSING: )?)(def|use)=([\\w\\(\\)\"\\.]*).*"
|
polarity = txt.regexpCapture(rex, 1) and
expectedKind = txt.regexpCapture(rex, 2) and
path = txt.regexpCapture(rex, 3) and
expectedLoc = getLocStr(this.getLocation())
)
}
string getEdgeLabel(int i) {
// matches a single edge. E.g. `getParameter(1)` or `getMember("foo")`.
// The lookbehind/lookahead ensure that the boundary is correct, that is
// either the edge is next to a ".", or it's the end of the string.
result = path.regexpFind("(?<=\\.|^)([\\w\\(\\)\"]+)(?=\\.|$)", i, _).trim()
}
int getPathLength() { result = max(int i | exists(this.getEdgeLabel(i))) + 1 }
predicate isNegative() { polarity = "MISSING: " }
API::Node lookup(int i) {
i = 0 and
result = API::root()
or
result =
this.lookup(i - 1)
.getASuccessor(any(API::Label::ApiLabel label |
label.toString() = this.getEdgeLabel(i - 1)
))
}
API::Node lookup() { result = this.lookup(this.getPathLength()) }
predicate holds() { getLocStr(getNode(this.lookup(), expectedKind).getLocation()) = expectedLoc }
string tryExplainFailure() {
exists(int i, API::Node nd, string prefix, string suffix |
nd = this.lookup(i) and
i < getPathLength() and
not exists(this.lookup([i + 1 .. getPathLength()])) and
prefix = nd + " has no outgoing edge labelled " + this.getEdgeLabel(i) + ";" and
if exists(nd.getASuccessor())
then
suffix =
"it does have outgoing edges labelled " +
concat(string lbl |
exists(nd.getASuccessor(any(API::Label::ApiLabel label | label.toString() = lbl)))
|
lbl, ", "
) + "."
else suffix = "it has no outgoing edges at all."
|
result = prefix + " " + suffix
)
or
exists(API::Node nd, string kind | nd = this.lookup() |
exists(getNode(nd, kind)) and
not exists(getNode(nd, expectedKind)) and
result = "Expected " + expectedKind + " node, but found " + kind + " node."
)
or
exists(DataFlow::Node nd | nd = getNode(this.lookup(), expectedKind) |
not getLocStr(nd.getLocation()) = expectedLoc and
result =
"Node not found on this line (but there is one on line " + min(getLocStr(nd.getLocation())) +
")."
)
}
string explainFailure() {
if this.isNegative()
then (
this.holds() and
result = "Negative assertion failed."
) else (
not this.holds() and
(
result = this.tryExplainFailure()
or
not exists(this.tryExplainFailure()) and
result = "Positive assertion failed for unknown reasons."
)
)
}
}
query predicate failed(Assertion a, string explanation) { explanation = a.explainFailure() }

View File

@@ -1,26 +0,0 @@
import python
import semmle.python.dataflow.new.DataFlow
import TestUtilities.InlineExpectationsTest
import semmle.python.ApiGraphs
class AwaitedTest extends InlineExpectationsTest {
AwaitedTest() { this = "AwaitedTest" }
override string getARelevantTag() { result = "awaited" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(API::Node awaited, DataFlow::Node use, API::Node pred |
awaited = pred.getAwaited() and
use = awaited.getAUse() and
location = use.getLocation() and
// Module variable nodes have no suitable location, so it's best to simply exclude them entirely
// from the inline tests.
not use instanceof DataFlow::ModuleVariableNode and
exists(location.getFile().getRelativePath())
|
tag = "awaited" and
value = pred.getPath() and
element = use.toString()
)
}
}

View File

@@ -1,35 +0,0 @@
import python
import semmle.python.dataflow.new.DataFlow
import TestUtilities.InlineExpectationsTest
import semmle.python.ApiGraphs
class ApiUseTest extends InlineExpectationsTest {
ApiUseTest() { this = "ApiUseTest" }
override string getARelevantTag() { result = "use" }
private predicate relevant_node(API::Node a, DataFlow::Node n, Location l) {
n = a.getAUse() and
l = n.getLocation() and
// Module variable nodes have no suitable location, so it's best to simply exclude them entirely
// from the inline tests.
not n instanceof DataFlow::ModuleVariableNode and
exists(l.getFile().getRelativePath())
}
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(API::Node a, DataFlow::Node n | relevant_node(a, n, location) |
tag = "use" and
// Only report the longest path on this line:
value =
max(API::Node a2, Location l2 |
relevant_node(a2, _, l2) and
l2.getFile() = location.getFile() and
l2.getStartLine() = location.getStartLine()
|
a2.getPath()
) and
element = n.toString()
)
}
}

View File

@@ -7,7 +7,7 @@ import TestUtilities.InlineExpectationsTest
class UnresolvedCallExpectations extends InlineExpectationsTest {
UnresolvedCallExpectations() { this = "UnresolvedCallExpectations" }
override string getARelevantTag() { result = ["unresolved_call"] }
override string getARelevantTag() { result = "unresolved_call" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and

View File

@@ -0,0 +1,3 @@
Sometimes we accidentally re-export too much from `DataFlow` such that for example we can access `Add` from `DataFlow::Add` :disappointed:
This test should always FAIL to compile!

View File

@@ -0,0 +1 @@
ERROR: Could not resolve type DataFlow::Add (Test.ql:7,6-19)

View File

@@ -0,0 +1,8 @@
import python
private import semmle.python.dataflow.new.DataFlow
// Sometimes we accidentally re-export too much from `DataFlow` such that for example we can access `Add` from `DataFlow::Add` :(
//
// This test should always FAIL to compile!
from DataFlow::Add this_should_not_work
select this_should_not_work

View File

@@ -0,0 +1 @@
1+1

View File

@@ -49,6 +49,24 @@ def test_create_with_foo():
x = create_with_foo() # $ tracked=foo
print(x.foo) # $ tracked=foo tracked
def test_global_attribute_assignment():
global global_var
global_var.foo = tracked # $ tracked tracked=foo
def test_global_attribute_read():
x = global_var.foo # $ tracked tracked=foo
def test_local_attribute_assignment():
# Same as `test_global_attribute_assignment`, but the assigned variable is not global
# In this case, we don't want flow going to the `ModuleVariableNode` for `local_var`
# (which is referenced in `test_local_attribute_read`).
local_var = object() # $ tracked=foo
local_var.foo = tracked # $ tracked tracked=foo
def test_local_attribute_read():
x = local_var.foo
# ------------------------------------------------------------------------------
# Attributes assigned statically to a class
# ------------------------------------------------------------------------------

View File

@@ -5,7 +5,7 @@ import semmle.python.ApiGraphs
private DataFlow::TypeTrackingNode module_tracker(TypeTracker t) {
t.start() and
result = API::moduleImport("module").getAUse()
result = API::moduleImport("module").getAnImmediateUse()
or
exists(TypeTracker t2 | result = module_tracker(t2).track(t2, t))
}

View File

@@ -120,7 +120,7 @@ class TrackedSelfTest extends InlineExpectationsTest {
/** Gets a reference to `foo` (fictive module). */
private DataFlow::TypeTrackingNode foo(DataFlow::TypeTracker t) {
t.start() and
result = API::moduleImport("foo").getAUse()
result = API::moduleImport("foo").getAnImmediateUse()
or
exists(DataFlow::TypeTracker t2 | result = foo(t2).track(t2, t))
}
@@ -131,7 +131,7 @@ DataFlow::Node foo() { foo(DataFlow::TypeTracker::end()).flowsTo(result) }
/** Gets a reference to `foo.bar` (fictive module). */
private DataFlow::TypeTrackingNode foo_bar(DataFlow::TypeTracker t) {
t.start() and
result = API::moduleImport("foo.bar").getAUse()
result = API::moduleImport("foo.bar").getAnImmediateUse()
or
t.startInAttr("bar") and
result = foo()
@@ -145,7 +145,7 @@ DataFlow::Node foo_bar() { foo_bar(DataFlow::TypeTracker::end()).flowsTo(result)
/** Gets a reference to `foo.bar.baz` (fictive attribute on `foo.bar` module). */
private DataFlow::TypeTrackingNode foo_bar_baz(DataFlow::TypeTracker t) {
t.start() and
result = API::moduleImport("foo.bar.baz").getAUse()
result = API::moduleImport("foo.bar.baz").getAnImmediateUse()
or
t.startInAttr("baz") and
result = foo_bar()

View File

@@ -88,7 +88,7 @@ private newtype TCallGraphResolver =
TPointsToResolver() or
TTypeTrackerResolver()
/** Describes a method of call graph resolution */
/** A method of call graph resolution */
abstract class CallGraphResolver extends TCallGraphResolver {
abstract predicate callEdge(Call call, Function callable);

View File

@@ -164,6 +164,42 @@ class SqlExecutionTest extends InlineExpectationsTest {
}
}
class XPathConstructionTest extends InlineExpectationsTest {
XPathConstructionTest() { this = "XPathConstructionTest" }
override string getARelevantTag() { result = "constructedXPath" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(XML::XPathConstruction e, DataFlow::Node xpath |
exists(location.getFile().getRelativePath()) and
xpath = e.getXPath() and
location = e.getLocation() and
element = xpath.toString() and
value = prettyNodeForInlineTest(xpath) and
tag = "constructedXPath"
)
}
}
class XPathExecutionTest extends InlineExpectationsTest {
XPathExecutionTest() { this = "XPathExecutionTest" }
override string getARelevantTag() { result = "getXPath" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(XML::XPathExecution e, DataFlow::Node xpath |
exists(location.getFile().getRelativePath()) and
xpath = e.getXPath() and
location = e.getLocation() and
element = xpath.toString() and
value = prettyNodeForInlineTest(xpath) and
tag = "getXPath"
)
}
}
class EscapingTest extends InlineExpectationsTest {
EscapingTest() { this = "EscapingTest" }
@@ -534,6 +570,20 @@ class CsrfLocalProtectionSettingTest extends InlineExpectationsTest {
if p.csrfEnabled()
then tag = "CsrfLocalProtectionEnabled"
else tag = "CsrfLocalProtectionDisabled"
class XmlParsingTest extends InlineExpectationsTest {
XmlParsingTest() { this = "XmlParsingTest" }
override string getARelevantTag() { result = "xmlVuln" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
exists(location.getFile().getRelativePath()) and
exists(XML::XmlParsing parsing, XML::XmlParsingVulnerabilityKind kind |
parsing.vulnerableTo(kind) and
location = parsing.getLocation() and
element = parsing.toString() and
value = "'" + kind + "'" and
tag = "xmlVuln"
)
}
}

View File

@@ -0,0 +1,34 @@
edges
| zipslip_bad.py:8:10:8:31 | ControlFlowNode for Attribute() | zipslip_bad.py:10:13:10:17 | SSA variable entry |
| zipslip_bad.py:10:13:10:17 | SSA variable entry | zipslip_bad.py:11:25:11:29 | ControlFlowNode for entry |
| zipslip_bad.py:14:10:14:28 | ControlFlowNode for Attribute() | zipslip_bad.py:16:13:16:17 | SSA variable entry |
| zipslip_bad.py:16:13:16:17 | SSA variable entry | zipslip_bad.py:17:26:17:30 | ControlFlowNode for entry |
| zipslip_bad.py:20:10:20:27 | ControlFlowNode for Attribute() | zipslip_bad.py:22:13:22:17 | SSA variable entry |
| zipslip_bad.py:22:13:22:17 | SSA variable entry | zipslip_bad.py:23:29:23:33 | ControlFlowNode for entry |
| zipslip_bad.py:27:10:27:22 | ControlFlowNode for Attribute() | zipslip_bad.py:29:13:29:13 | SSA variable x |
| zipslip_bad.py:29:13:29:13 | SSA variable x | zipslip_bad.py:30:25:30:25 | ControlFlowNode for x |
| zipslip_bad.py:34:16:34:28 | ControlFlowNode for Attribute() | zipslip_bad.py:35:9:35:9 | SSA variable x |
| zipslip_bad.py:35:9:35:9 | SSA variable x | zipslip_bad.py:37:32:37:32 | ControlFlowNode for x |
nodes
| zipslip_bad.py:8:10:8:31 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| zipslip_bad.py:10:13:10:17 | SSA variable entry | semmle.label | SSA variable entry |
| zipslip_bad.py:11:25:11:29 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry |
| zipslip_bad.py:14:10:14:28 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| zipslip_bad.py:16:13:16:17 | SSA variable entry | semmle.label | SSA variable entry |
| zipslip_bad.py:17:26:17:30 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry |
| zipslip_bad.py:20:10:20:27 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| zipslip_bad.py:22:13:22:17 | SSA variable entry | semmle.label | SSA variable entry |
| zipslip_bad.py:23:29:23:33 | ControlFlowNode for entry | semmle.label | ControlFlowNode for entry |
| zipslip_bad.py:27:10:27:22 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| zipslip_bad.py:29:13:29:13 | SSA variable x | semmle.label | SSA variable x |
| zipslip_bad.py:30:25:30:25 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
| zipslip_bad.py:34:16:34:28 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| zipslip_bad.py:35:9:35:9 | SSA variable x | semmle.label | SSA variable x |
| zipslip_bad.py:37:32:37:32 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
subpaths
#select
| zipslip_bad.py:11:25:11:29 | ControlFlowNode for entry | zipslip_bad.py:8:10:8:31 | ControlFlowNode for Attribute() | zipslip_bad.py:11:25:11:29 | ControlFlowNode for entry | Extraction of zipfile from $@ | zipslip_bad.py:8:10:8:31 | ControlFlowNode for Attribute() | a potentially untrusted source |
| zipslip_bad.py:17:26:17:30 | ControlFlowNode for entry | zipslip_bad.py:14:10:14:28 | ControlFlowNode for Attribute() | zipslip_bad.py:17:26:17:30 | ControlFlowNode for entry | Extraction of zipfile from $@ | zipslip_bad.py:14:10:14:28 | ControlFlowNode for Attribute() | a potentially untrusted source |
| zipslip_bad.py:23:29:23:33 | ControlFlowNode for entry | zipslip_bad.py:20:10:20:27 | ControlFlowNode for Attribute() | zipslip_bad.py:23:29:23:33 | ControlFlowNode for entry | Extraction of zipfile from $@ | zipslip_bad.py:20:10:20:27 | ControlFlowNode for Attribute() | a potentially untrusted source |
| zipslip_bad.py:30:25:30:25 | ControlFlowNode for x | zipslip_bad.py:27:10:27:22 | ControlFlowNode for Attribute() | zipslip_bad.py:30:25:30:25 | ControlFlowNode for x | Extraction of zipfile from $@ | zipslip_bad.py:27:10:27:22 | ControlFlowNode for Attribute() | a potentially untrusted source |
| zipslip_bad.py:37:32:37:32 | ControlFlowNode for x | zipslip_bad.py:34:16:34:28 | ControlFlowNode for Attribute() | zipslip_bad.py:37:32:37:32 | ControlFlowNode for x | Extraction of zipfile from $@ | zipslip_bad.py:34:16:34:28 | ControlFlowNode for Attribute() | a potentially untrusted source |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-022/ZipSlip.ql

View File

@@ -0,0 +1,39 @@
import tarfile
import shutil
import bz2
import gzip
import zipfile
def unzip(filename):
with tarfile.open(filename) as zipf:
#BAD : This could write any file on the filesystem.
for entry in zipf:
shutil.move(entry, "/tmp/unpack/")
def unzip1(filename):
with gzip.open(filename) as zipf:
#BAD : This could write any file on the filesystem.
for entry in zipf:
shutil.copy2(entry, "/tmp/unpack/")
def unzip2(filename):
with bz2.open(filename) as zipf:
#BAD : This could write any file on the filesystem.
for entry in zipf:
shutil.copyfile(entry, "/tmp/unpack/")
def unzip3(filename):
zf = zipfile.ZipFile(filename)
with zf.namelist() as filelist:
#BAD : This could write any file on the filesystem.
for x in filelist:
shutil.copy(x, "/tmp/unpack/")
def unzip4(filename):
zf = zipfile.ZipFile(filename)
filelist = zf.namelist()
for x in filelist:
with zf.open(x) as srcf:
shutil.copyfileobj(x, "/tmp/unpack/")
import tty # to set the import root so we can identify the standard library

View File

@@ -0,0 +1,14 @@
import zipfile
import tarfile
import shutil
def unzip(filename, dir):
zf = zipfile.ZipFile(filename)
zf.extractall(dir)
def unzip1(filename, dir):
zf = zipfile.ZipFile(filename)
zf.extract(dir)

View File

@@ -0,0 +1 @@
| xmlrpc_server.py:7:10:7:48 | ControlFlowNode for SimpleXMLRPCServer() | SimpleXMLRPCServer is vulnerable to XML bombs |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-611/SimpleXmlRpcServer.ql

View File

@@ -0,0 +1,12 @@
from xmlrpc.server import SimpleXMLRPCServer
def foo(n: str):
print("foo called with arg:", n, type(n))
return "ok"
server = SimpleXMLRPCServer(("127.0.0.1", 8000))
server.register_function(foo, "foo")
server.serve_forever()
# normal: curl 127.0.0.1:8000 --data-raw '<?xml version="1.0"?><methodCall><methodName>foo</methodName><params><param><value>42</value></param></params></methodCall>'
# billion_laughs: curl 127.0.0.1:8000 --data-raw '<?xml version="1.0"?><!DOCTYPE lolz [<!ENTITY lol "lol"><!ELEMENT lolz (#PCDATA)><!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"><!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"><!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"><!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"><!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;"><!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;"><!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;"><!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;"><!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">]><methodCall><methodName>foo</methodName><params><param><value>&lol9;</value></param></params></methodCall>'

View File

@@ -1 +0,0 @@
experimental/Security/CWE-643/XpathInjection.ql

View File

@@ -1,74 +1,114 @@
edges
| flask_mongoengine_bad.py:19:21:19:27 | ControlFlowNode for request | flask_mongoengine_bad.py:19:21:19:32 | ControlFlowNode for Attribute |
| flask_mongoengine_bad.py:19:21:19:32 | ControlFlowNode for Attribute | flask_mongoengine_bad.py:19:21:19:42 | ControlFlowNode for Subscript |
| flask_mongoengine_bad.py:19:21:19:42 | ControlFlowNode for Subscript | flask_mongoengine_bad.py:22:34:22:44 | ControlFlowNode for json_search |
| flask_mongoengine_bad.py:19:21:19:42 | ControlFlowNode for Subscript | flask_mongoengine_bad.py:20:30:20:42 | ControlFlowNode for unsafe_search |
| flask_mongoengine_bad.py:20:19:20:43 | ControlFlowNode for Attribute() | flask_mongoengine_bad.py:22:34:22:44 | ControlFlowNode for json_search |
| flask_mongoengine_bad.py:20:30:20:42 | ControlFlowNode for unsafe_search | flask_mongoengine_bad.py:20:19:20:43 | ControlFlowNode for Attribute() |
| flask_mongoengine_bad.py:26:21:26:27 | ControlFlowNode for request | flask_mongoengine_bad.py:26:21:26:32 | ControlFlowNode for Attribute |
| flask_mongoengine_bad.py:26:21:26:32 | ControlFlowNode for Attribute | flask_mongoengine_bad.py:26:21:26:42 | ControlFlowNode for Subscript |
| flask_mongoengine_bad.py:26:21:26:42 | ControlFlowNode for Subscript | flask_mongoengine_bad.py:30:39:30:59 | ControlFlowNode for Dict |
| flask_mongoengine_bad.py:26:21:26:42 | ControlFlowNode for Subscript | flask_mongoengine_bad.py:27:30:27:42 | ControlFlowNode for unsafe_search |
| flask_mongoengine_bad.py:27:19:27:43 | ControlFlowNode for Attribute() | flask_mongoengine_bad.py:30:39:30:59 | ControlFlowNode for Dict |
| flask_mongoengine_bad.py:27:30:27:42 | ControlFlowNode for unsafe_search | flask_mongoengine_bad.py:27:19:27:43 | ControlFlowNode for Attribute() |
| flask_pymongo_bad.py:11:21:11:27 | ControlFlowNode for request | flask_pymongo_bad.py:11:21:11:32 | ControlFlowNode for Attribute |
| flask_pymongo_bad.py:11:21:11:32 | ControlFlowNode for Attribute | flask_pymongo_bad.py:11:21:11:42 | ControlFlowNode for Subscript |
| flask_pymongo_bad.py:11:21:11:42 | ControlFlowNode for Subscript | flask_pymongo_bad.py:14:31:14:51 | ControlFlowNode for Dict |
| flask_pymongo_bad.py:11:21:11:42 | ControlFlowNode for Subscript | flask_pymongo_bad.py:12:30:12:42 | ControlFlowNode for unsafe_search |
| flask_pymongo_bad.py:12:19:12:43 | ControlFlowNode for Attribute() | flask_pymongo_bad.py:14:31:14:51 | ControlFlowNode for Dict |
| flask_pymongo_bad.py:12:30:12:42 | ControlFlowNode for unsafe_search | flask_pymongo_bad.py:12:19:12:43 | ControlFlowNode for Attribute() |
| mongoengine_bad.py:18:21:18:27 | ControlFlowNode for request | mongoengine_bad.py:18:21:18:32 | ControlFlowNode for Attribute |
| mongoengine_bad.py:18:21:18:32 | ControlFlowNode for Attribute | mongoengine_bad.py:18:21:18:42 | ControlFlowNode for Subscript |
| mongoengine_bad.py:18:21:18:42 | ControlFlowNode for Subscript | mongoengine_bad.py:22:26:22:46 | ControlFlowNode for Dict |
| mongoengine_bad.py:18:21:18:42 | ControlFlowNode for Subscript | mongoengine_bad.py:19:30:19:42 | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:19:19:19:43 | ControlFlowNode for Attribute() | mongoengine_bad.py:22:26:22:46 | ControlFlowNode for Dict |
| mongoengine_bad.py:19:30:19:42 | ControlFlowNode for unsafe_search | mongoengine_bad.py:19:19:19:43 | ControlFlowNode for Attribute() |
| mongoengine_bad.py:26:21:26:27 | ControlFlowNode for request | mongoengine_bad.py:26:21:26:32 | ControlFlowNode for Attribute |
| mongoengine_bad.py:26:21:26:32 | ControlFlowNode for Attribute | mongoengine_bad.py:26:21:26:42 | ControlFlowNode for Subscript |
| mongoengine_bad.py:26:21:26:42 | ControlFlowNode for Subscript | mongoengine_bad.py:30:26:30:46 | ControlFlowNode for Dict |
| mongoengine_bad.py:26:21:26:42 | ControlFlowNode for Subscript | mongoengine_bad.py:27:30:27:42 | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:27:19:27:43 | ControlFlowNode for Attribute() | mongoengine_bad.py:30:26:30:46 | ControlFlowNode for Dict |
| mongoengine_bad.py:27:30:27:42 | ControlFlowNode for unsafe_search | mongoengine_bad.py:27:19:27:43 | ControlFlowNode for Attribute() |
| mongoengine_bad.py:34:21:34:27 | ControlFlowNode for request | mongoengine_bad.py:34:21:34:32 | ControlFlowNode for Attribute |
| mongoengine_bad.py:34:21:34:32 | ControlFlowNode for Attribute | mongoengine_bad.py:34:21:34:42 | ControlFlowNode for Subscript |
| mongoengine_bad.py:34:21:34:42 | ControlFlowNode for Subscript | mongoengine_bad.py:38:26:38:46 | ControlFlowNode for Dict |
| mongoengine_bad.py:34:21:34:42 | ControlFlowNode for Subscript | mongoengine_bad.py:35:30:35:42 | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:35:19:35:43 | ControlFlowNode for Attribute() | mongoengine_bad.py:38:26:38:46 | ControlFlowNode for Dict |
| mongoengine_bad.py:35:30:35:42 | ControlFlowNode for unsafe_search | mongoengine_bad.py:35:19:35:43 | ControlFlowNode for Attribute() |
| mongoengine_bad.py:42:21:42:27 | ControlFlowNode for request | mongoengine_bad.py:42:21:42:32 | ControlFlowNode for Attribute |
| mongoengine_bad.py:42:21:42:32 | ControlFlowNode for Attribute | mongoengine_bad.py:42:21:42:42 | ControlFlowNode for Subscript |
| mongoengine_bad.py:42:21:42:42 | ControlFlowNode for Subscript | mongoengine_bad.py:46:26:46:46 | ControlFlowNode for Dict |
| mongoengine_bad.py:42:21:42:42 | ControlFlowNode for Subscript | mongoengine_bad.py:43:30:43:42 | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:43:19:43:43 | ControlFlowNode for Attribute() | mongoengine_bad.py:46:26:46:46 | ControlFlowNode for Dict |
| mongoengine_bad.py:43:30:43:42 | ControlFlowNode for unsafe_search | mongoengine_bad.py:43:19:43:43 | ControlFlowNode for Attribute() |
| mongoengine_bad.py:50:21:50:27 | ControlFlowNode for request | mongoengine_bad.py:50:21:50:32 | ControlFlowNode for Attribute |
| mongoengine_bad.py:50:21:50:32 | ControlFlowNode for Attribute | mongoengine_bad.py:50:21:50:42 | ControlFlowNode for Subscript |
| mongoengine_bad.py:50:21:50:42 | ControlFlowNode for Subscript | mongoengine_bad.py:53:34:53:44 | ControlFlowNode for json_search |
| mongoengine_bad.py:50:21:50:42 | ControlFlowNode for Subscript | mongoengine_bad.py:51:30:51:42 | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:51:19:51:43 | ControlFlowNode for Attribute() | mongoengine_bad.py:53:34:53:44 | ControlFlowNode for json_search |
| mongoengine_bad.py:51:30:51:42 | ControlFlowNode for unsafe_search | mongoengine_bad.py:51:19:51:43 | ControlFlowNode for Attribute() |
| mongoengine_bad.py:57:21:57:27 | ControlFlowNode for request | mongoengine_bad.py:57:21:57:32 | ControlFlowNode for Attribute |
| mongoengine_bad.py:57:21:57:32 | ControlFlowNode for Attribute | mongoengine_bad.py:57:21:57:42 | ControlFlowNode for Subscript |
| mongoengine_bad.py:57:21:57:42 | ControlFlowNode for Subscript | mongoengine_bad.py:61:29:61:49 | ControlFlowNode for Dict |
| mongoengine_bad.py:57:21:57:42 | ControlFlowNode for Subscript | mongoengine_bad.py:58:30:58:42 | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:58:19:58:43 | ControlFlowNode for Attribute() | mongoengine_bad.py:61:29:61:49 | ControlFlowNode for Dict |
| mongoengine_bad.py:58:30:58:42 | ControlFlowNode for unsafe_search | mongoengine_bad.py:58:19:58:43 | ControlFlowNode for Attribute() |
| pymongo_bad.py:11:21:11:27 | ControlFlowNode for request | pymongo_bad.py:11:21:11:32 | ControlFlowNode for Attribute |
| pymongo_bad.py:11:21:11:32 | ControlFlowNode for Attribute | pymongo_bad.py:11:21:11:42 | ControlFlowNode for Subscript |
| pymongo_bad.py:11:21:11:42 | ControlFlowNode for Subscript | pymongo_bad.py:14:42:14:62 | ControlFlowNode for Dict |
| pymongo_bad.py:11:21:11:42 | ControlFlowNode for Subscript | pymongo_bad.py:12:30:12:42 | ControlFlowNode for unsafe_search |
| pymongo_bad.py:12:19:12:43 | ControlFlowNode for Attribute() | pymongo_bad.py:14:42:14:62 | ControlFlowNode for Dict |
| pymongo_bad.py:12:30:12:42 | ControlFlowNode for unsafe_search | pymongo_bad.py:12:19:12:43 | ControlFlowNode for Attribute() |
nodes
| flask_mongoengine_bad.py:19:21:19:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_mongoengine_bad.py:19:21:19:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| flask_mongoengine_bad.py:19:21:19:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| flask_mongoengine_bad.py:20:19:20:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| flask_mongoengine_bad.py:20:30:20:42 | ControlFlowNode for unsafe_search | semmle.label | ControlFlowNode for unsafe_search |
| flask_mongoengine_bad.py:22:34:22:44 | ControlFlowNode for json_search | semmle.label | ControlFlowNode for json_search |
| flask_mongoengine_bad.py:26:21:26:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_mongoengine_bad.py:26:21:26:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| flask_mongoengine_bad.py:26:21:26:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| flask_mongoengine_bad.py:27:19:27:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| flask_mongoengine_bad.py:27:30:27:42 | ControlFlowNode for unsafe_search | semmle.label | ControlFlowNode for unsafe_search |
| flask_mongoengine_bad.py:30:39:30:59 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
| flask_pymongo_bad.py:11:21:11:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| flask_pymongo_bad.py:11:21:11:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| flask_pymongo_bad.py:11:21:11:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| flask_pymongo_bad.py:12:19:12:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| flask_pymongo_bad.py:12:30:12:42 | ControlFlowNode for unsafe_search | semmle.label | ControlFlowNode for unsafe_search |
| flask_pymongo_bad.py:14:31:14:51 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
| mongoengine_bad.py:18:21:18:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| mongoengine_bad.py:18:21:18:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| mongoengine_bad.py:18:21:18:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| mongoengine_bad.py:19:19:19:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| mongoengine_bad.py:19:30:19:42 | ControlFlowNode for unsafe_search | semmle.label | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:22:26:22:46 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
| mongoengine_bad.py:26:21:26:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| mongoengine_bad.py:26:21:26:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| mongoengine_bad.py:26:21:26:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| mongoengine_bad.py:27:19:27:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| mongoengine_bad.py:27:30:27:42 | ControlFlowNode for unsafe_search | semmle.label | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:30:26:30:46 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
| mongoengine_bad.py:34:21:34:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| mongoengine_bad.py:34:21:34:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| mongoengine_bad.py:34:21:34:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| mongoengine_bad.py:35:19:35:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| mongoengine_bad.py:35:30:35:42 | ControlFlowNode for unsafe_search | semmle.label | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:38:26:38:46 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
| mongoengine_bad.py:42:21:42:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| mongoengine_bad.py:42:21:42:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| mongoengine_bad.py:42:21:42:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| mongoengine_bad.py:43:19:43:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| mongoengine_bad.py:43:30:43:42 | ControlFlowNode for unsafe_search | semmle.label | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:46:26:46:46 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
| mongoengine_bad.py:50:21:50:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| mongoengine_bad.py:50:21:50:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| mongoengine_bad.py:50:21:50:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| mongoengine_bad.py:51:19:51:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| mongoengine_bad.py:51:30:51:42 | ControlFlowNode for unsafe_search | semmle.label | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:53:34:53:44 | ControlFlowNode for json_search | semmle.label | ControlFlowNode for json_search |
| mongoengine_bad.py:57:21:57:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| mongoengine_bad.py:57:21:57:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| mongoengine_bad.py:57:21:57:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| mongoengine_bad.py:58:19:58:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| mongoengine_bad.py:58:30:58:42 | ControlFlowNode for unsafe_search | semmle.label | ControlFlowNode for unsafe_search |
| mongoengine_bad.py:61:29:61:49 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
| pymongo_bad.py:11:21:11:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| pymongo_bad.py:11:21:11:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| pymongo_bad.py:11:21:11:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| pymongo_bad.py:12:19:12:43 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| pymongo_bad.py:12:30:12:42 | ControlFlowNode for unsafe_search | semmle.label | ControlFlowNode for unsafe_search |
| pymongo_bad.py:14:42:14:62 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
subpaths
#select

View File

@@ -0,0 +1 @@
import TestUtilities.VerifyApiGraphs

View File

@@ -0,0 +1,61 @@
from mypkg import foo #$ use=moduleImport("mypkg").getMember("foo")
def callback(x): #$ use=moduleImport("mypkg").getMember("foo").getMember("bar").getParameter(0).getParameter(0)
x.baz() #$ use=moduleImport("mypkg").getMember("foo").getMember("bar").getParameter(0).getParameter(0).getMember("baz").getReturn()
foo.bar(callback) #$ def=moduleImport("mypkg").getMember("foo").getMember("bar").getParameter(0) use=moduleImport("mypkg").getMember("foo").getMember("bar").getReturn()
def callback2(x): #$ use=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("c").getParameter(0)
x.baz2() #$ use=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("c").getParameter(0).getMember("baz2").getReturn()
mydict = {
"c": callback2, #$ def=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("c")
"other": "whatever" #$ def=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("other")
}
foo.baz(mydict) #$ def=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0) use=moduleImport("mypkg").getMember("foo").getMember("baz").getReturn()
def callback3(x): #$ use=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("third").getParameter(0)
x.baz3() #$ use=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("third").getParameter(0).getMember("baz3").getReturn()
mydict.third = callback3 #$ def=moduleImport("mypkg").getMember("foo").getMember("baz").getParameter(0).getMember("third")
foo.blab(mydict) #$ def=moduleImport("mypkg").getMember("foo").getMember("blab").getParameter(0) use=moduleImport("mypkg").getMember("foo").getMember("blab").getReturn()
def callback4(x): #$ use=moduleImport("mypkg").getMember("foo").getMember("quack").getParameter(0).getParameter(0)
x.baz4() #$ use=moduleImport("mypkg").getMember("foo").getMember("quack").getParameter(0).getParameter(0).getMember("baz4").getReturn()
otherDict = {
# TODO: Backtracking through a property set using a dict doesn't work, but I can backtrack through explicit property writes, e.g. the `otherDict.fourth` below.
# TODO: There is a related TODO in ApiGraphs.qll
"blab": "whatever"
}
otherDict.fourth = callback4
foo.quack(otherDict.fourth) #$ def=moduleImport("mypkg").getMember("foo").getMember("quack").getParameter(0) use=moduleImport("mypkg").getMember("foo").getMember("quack").getReturn()
def namedCallback(myName, otherName):
# Using named parameters:
myName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getKeywordParameter("myName").getReturn()
otherName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getKeywordParameter("otherName").getReturn()
# Using numbered parameters:
myName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getParameter(0).getReturn()
otherName() #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getParameter(0).getParameter(1).getReturn()
foo.blob(namedCallback) #$ use=moduleImport("mypkg").getMember("foo").getMember("blob").getReturn()
foo.named(myName = 2) #$ def=moduleImport("mypkg").getMember("foo").getMember("named").getKeywordParameter("myName")
def recusisionCallback(x):
x.recursion() #$ use=moduleImport("mypkg").getMember("foo").getMember("rec").getParameter(0).getMember("callback").getParameter(0).getMember("recursion").getReturn()
x.recursion() #$ use=moduleImport("mypkg").getMember("foo").getMember("rec").getParameter(0).getMember("rec1").getMember("callback").getParameter(0).getMember("recursion").getReturn()
x.recursion() #$ use=moduleImport("mypkg").getMember("foo").getMember("rec").getParameter(0).getMember("rec1").getMember("rec2").getMember("callback").getParameter(0).getMember("recursion").getReturn()
x.recursion() #$ use=moduleImport("mypkg").getMember("foo").getMember("rec").getParameter(0).getMember("rec1").getMember("rec2").getMember("rec1").getMember("callback").getParameter(0).getMember("recursion").getReturn()
recursiveDict = {};
recursiveDict.callback = recusisionCallback;
recursiveDict.rec1 = recursiveDict;
recursiveDict.rec2 = recursiveDict;
foo.rec(recursiveDict); #$ def=moduleImport("mypkg").getMember("foo").getMember("rec").getParameter(0)

View File

@@ -0,0 +1,19 @@
# Subclasses
from flask.views import View #$ use=moduleImport("flask").getMember("views").getMember("View")
class MyView(View): #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass()
myvar = 45 #$ def=moduleImport("flask").getMember("views").getMember("View").getASubclass().getMember("myvar")
def my_method(self): #$ def=moduleImport("flask").getMember("views").getMember("View").getASubclass().getMember("my_method")
return 3 #$ def=moduleImport("flask").getMember("views").getMember("View").getASubclass().getMember("my_method").getReturn()
instance = MyView() #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getReturn()
def internal():
from pflask.views import View #$ use=moduleImport("pflask").getMember("views").getMember("View")
class IntMyView(View): #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass()
my_internal_var = 35 #$ def=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getMember("my_internal_var")
def my_internal_method(self): #$ def=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getMember("my_internal_method")
pass
int_instance = IntMyView() #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getReturn()

View File

@@ -75,27 +75,6 @@ def f():
sink(foo) #$ use=moduleImport("danger").getMember("SOURCE")
# Subclasses
from flask.views import View #$ use=moduleImport("flask").getMember("views").getMember("View")
class MyView(View): #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass()
myvar = 45 #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getMember("myvar")
def my_method(self): #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getMember("my_method")
pass
instance = MyView() #$ use=moduleImport("flask").getMember("views").getMember("View").getASubclass().getReturn()
def internal():
from pflask.views import View #$ use=moduleImport("pflask").getMember("views").getMember("View")
class IntMyView(View): #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass()
my_internal_var = 35 #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getMember("my_internal_var")
def my_internal_method(self): #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getMember("my_internal_method")
pass
int_instance = IntMyView() #$ use=moduleImport("pflask").getMember("views").getMember("View").getASubclass().getReturn()
# Built-ins
def use_of_builtins():

View File

@@ -0,0 +1 @@
import TestUtilities.VerifyApiGraphs

View File

@@ -1,5 +1,4 @@
import python
import semmle.python.objects.ObjectAPI
from int line, ControlFlowNode f, Value v
where

View File

@@ -1,10 +0,0 @@
WARNING: Type CustomPointsToOriginFact has been deprecated and may be removed in future (test.ql:9,27-51)
| 9 | ControlFlowNode for has_type_int | Function has_type_int | builtin-class function |
| 9 | ControlFlowNode for has_type_int() | has_type_int() | builtin-class int |
| 9 | ControlFlowNode for x | has_type_int() | builtin-class int |
| 10 | ControlFlowNode for has_type_float | Function has_type_float | builtin-class function |
| 10 | ControlFlowNode for has_type_float() | has_type_float() | builtin-class float |
| 10 | ControlFlowNode for y | has_type_float() | builtin-class float |
| 11 | ControlFlowNode for Tuple | Tuple | builtin-class tuple |
| 11 | ControlFlowNode for x | has_type_int() | builtin-class int |
| 11 | ControlFlowNode for y | has_type_float() | builtin-class float |

View File

@@ -1,11 +0,0 @@
def has_type_int():
return untaceable()
def has_type_float():
return untaceable2()
def test():
#Ignore before this comment
x = has_type_int()
y = has_type_float()
return x, y

View File

@@ -1,35 +0,0 @@
import python
import semmle.python.types.Extensions
/*
* Customise: Claim any function called has_type_XXX return any class
* whose name matches XXX
*/
class HasTypeFact extends CustomPointsToOriginFact {
HasTypeFact() {
exists(FunctionObject func, string name |
func.getACall() = this and
name = func.getName() and
name.matches("has\\_type\\_%")
)
}
override predicate pointsTo(Object value, ClassObject cls) {
exists(FunctionObject func, string name |
func.getACall() = this and
name = func.getName() and
name.matches("has\\_type\\_%")
|
cls.getName() = name.suffix("has_type_".length())
) and
value = this
}
}
from int line, ControlFlowNode f, Object o, ClassObject c
where
f.getLocation().getStartLine() = line and
exists(Comment ct | ct.getLocation().getStartLine() < line) and
f.refersTo(o, c, _)
select line, f.toString(), o.toString(), c.toString()

View File

@@ -1,23 +0,0 @@
WARNING: Type CustomPointsToAttribute has been deprecated and may be removed in future (Extend.ql:22,34-57)
WARNING: Type CustomPointsToObjectFact has been deprecated and may be removed in future (Extend.ql:38,32-56)
WARNING: Type CustomPointsToOriginFact has been deprecated and may be removed in future (Extend.ql:5,28-52)
WARNING: Type CustomPointsToOriginFact has been deprecated and may be removed in future (Extend.ql:49,38-62)
| test.py:4:1:4:3 | ControlFlowNode for one | int 1 |
| test.py:5:1:5:3 | ControlFlowNode for two | int 2 |
| test.py:8:1:8:1 | ControlFlowNode for IntegerLiteral | int 1 |
| test.py:8:1:8:11 | ControlFlowNode for Tuple | Tuple |
| test.py:8:3:8:3 | ControlFlowNode for IntegerLiteral | int 2 |
| test.py:8:5:8:5 | ControlFlowNode for IntegerLiteral | int 3 |
| test.py:8:7:8:7 | ControlFlowNode for IntegerLiteral | int 4 |
| test.py:8:9:8:9 | ControlFlowNode for IntegerLiteral | int 5 |
| test.py:8:11:8:11 | ControlFlowNode for IntegerLiteral | int 6 |
| test.py:10:1:10:2 | ControlFlowNode for a3 | int 3 |
| test.py:10:6:10:7 | ControlFlowNode for Tuple | Tuple |
| test.py:10:6:10:13 | ControlFlowNode for Attribute | int 3 |
| test.py:11:1:11:2 | ControlFlowNode for a4 | int 4 |
| test.py:11:6:11:10 | ControlFlowNode for False | bool False |
| test.py:11:6:11:15 | ControlFlowNode for Attribute | int 4 |
| test.py:13:1:13:2 | ControlFlowNode for a3 | int 3 |
| test.py:14:1:14:2 | ControlFlowNode for a4 | int 4 |
| test.py:16:1:16:4 | ControlFlowNode for five | int 5 |
| test.py:17:1:17:3 | ControlFlowNode for six | int 6 |

View File

@@ -1,59 +0,0 @@
import python
import semmle.python.pointsto.PointsTo
private import semmle.python.types.Extensions
class CfgExtension extends CustomPointsToOriginFact {
CfgExtension() {
this.(NameNode).getId() = "one"
or
this.(NameNode).getId() = "two"
}
override predicate pointsTo(Object value, ClassObject cls) {
cls = theIntType() and
(
this.(NameNode).getId() = "one" and value.(NumericObject).intValue() = 1
or
this.(NameNode).getId() = "two" and value.(NumericObject).intValue() = 2
)
}
}
class AttributeExtension extends CustomPointsToAttribute {
AttributeExtension() { this = this }
override predicate attributePointsTo(
string name, Object value, ClassObject cls, ControlFlowNode origin
) {
cls = theIntType() and
origin = any(Module m).getEntryNode() and
(
name = "three" and value.(NumericObject).intValue() = 3
or
name = "four" and value.(NumericObject).intValue() = 4
)
}
}
class NoClassExtension extends CustomPointsToObjectFact {
NoClassExtension() { this = this }
override predicate pointsTo(Object value) {
this.(NameNode).getId() = "five" and value.(NumericObject).intValue() = 5
or
this.(NameNode).getId() = "six" and value.(NumericObject).intValue() = 6
}
}
/* Check that we can use old API without causing non-monotonic recursion */
class RecurseIntoOldPointsTo extends CustomPointsToOriginFact {
RecurseIntoOldPointsTo() { PointsTo::points_to(this, _, unknownValue(), _, _) }
override predicate pointsTo(Object value, ClassObject cls) {
value = unknownValue() and cls = theUnknownType()
}
}
from ControlFlowNode f, Object o
where f.getLocation().getFile().getBaseName() = "test.py" and f.refersTo(o)
select f, o.toString()

View File

@@ -1,17 +0,0 @@
#Magical values
one
two
#Make sure values exist in DB
1,2,3,4,5,6
a3 = ().three
a4 = False.four
a3
a4
five
six

View File

@@ -4,7 +4,7 @@ predicate of_interest(ControlFlowNode n, int line) {
exists(Location l, File f | l = n.getLocation() |
line = l.getStartLine() and
f = l.getFile() and
f.getName().matches("%test.py%") and
f.getAbsolutePath().matches("%test.py%") and
exists(Comment c |
c.getLocation().getStartLine() < line and
c.getLocation().getFile() = f

View File

@@ -1,5 +1,4 @@
import python
import python
import semmle.python.pointsto.PointsTo
import semmle.python.pointsto.PointsToContext
import semmle.python.objects.ObjectInternal

View File

@@ -1,5 +1,4 @@
import python
import semmle.python.pointsto.Base
from ClassObject cls, string name
where class_declares_attribute(cls, name)

View File

@@ -2,7 +2,7 @@ import python
private import semmle.python.objects.ObjectInternal
private import semmle.python.pointsto.PointsTo
/** Make unknown type visible */
/** An unknown type. Not usually visible. */
class UnknownType extends UnknownClassInternal {
override string toString() { result = "*UNKNOWN TYPE" }
}

View File

@@ -2,7 +2,7 @@ import python
private import semmle.python.objects.ObjectInternal
private import semmle.python.pointsto.PointsTo
/** Make unknown type visible */
/** An unknown type. Not usually visible. */
class UnknownType extends UnknownClassInternal {
override string toString() { result = "*UNKNOWN TYPE" }
}

View File

@@ -1,7 +1,7 @@
import python
private import semmle.python.objects.ObjectInternal
/** Make unknown type visible */
/** An unknown type. Not usually visible. */
class UnknownType extends UnknownClassInternal {
override string toString() { result = "*UNKNOWN TYPE" }
}

View File

@@ -1,5 +1,4 @@
import python
import semmle.python.types.Descriptors
import Util
from ClassMethodObject cm, CallNode call

View File

@@ -1,7 +1,6 @@
import python
import Util
import semmle.python.pointsto.PointsTo
import semmle.python.objects.ObjectInternal
/* This test should return _no_ results. */
predicate relevant_node(ControlFlowNode n) {

View File

@@ -1,7 +1,6 @@
import python
import Util
import semmle.python.pointsto.PointsTo
import semmle.python.objects.ObjectInternal
from ControlFlowNode f, ControlFlowNode x
where PointsTo::pointsTo(f, _, ObjectInternal::unknown(), x)

View File

@@ -1,4 +1,3 @@
WARNING: Predicate ssa_variable_points_to has been deprecated and may be removed in future (SSA.ql:10,3-35)
| __init__.py:0 | __name___0 = ScopeEntryDefinition | 'code' | builtin-class str |
| __init__.py:0 | __name___0 = ScopeEntryDefinition | 'code.package' | builtin-class str |
| __init__.py:0 | __name___0 = ScopeEntryDefinition | 'code.test_package' | builtin-class str |

View File

@@ -3,10 +3,21 @@ private import semmle.python.pointsto.PointsTo
private import semmle.python.pointsto.PointsToContext
import Util
predicate ssa_variable_points_to(
EssaVariable var, PointsToContext context, Object obj, ClassObject cls, @py_object origin
) {
exists(ObjectInternal value |
PointsToInternal::variablePointsTo(var, context, value, origin) and
cls = value.getClass().getSource()
|
obj = value.getSource()
)
}
from EssaVariable v, EssaDefinition def, Object o, ClassObject cls
where
def = v.getDefinition() and
not v.getSourceVariable() instanceof SpecialSsaSourceVariable and
PointsTo::ssa_variable_points_to(v, _, o, cls, _)
ssa_variable_points_to(v, _, o, cls, _)
select locate(def.getLocation(), "abcdegjqmns_"),
v.getRepresentation() + " = " + def.getRepresentation(), repr(o), repr(cls)

View File

@@ -1,5 +1,4 @@
import python
import semmle.python.SelfAttribute
from SelfAttributeRead sa, int line, string g, string l
where

View File

@@ -1,5 +1,4 @@
import python
import semmle.python.types.Descriptors
int lineof(Object o) { result = o.getOrigin().getLocation().getStartLine() }

View File

@@ -1,5 +1,4 @@
import python
import semmle.python.types.Descriptors
from PropertyValue p, string method_name, FunctionValue method
where

View File

@@ -13,11 +13,12 @@ class SimpleSource extends TaintSource {
class MySimpleSanitizer extends Sanitizer {
MySimpleSanitizer() { this = "MySimpleSanitizer" }
/**
/*
* The test `if is_safe(arg):` sanitizes `arg` on its `true` edge.
*
* Can't handle `if not is_safe(arg):` :\ that's why it's called MySimpleSanitizer
*/
override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) {
taint instanceof ExternalStringKind and
exists(CallNode call | test.getTest() = call and test.getSense() = true |
@@ -30,7 +31,7 @@ class MySimpleSanitizer extends Sanitizer {
class MySanitizerHandlingNot extends Sanitizer {
MySanitizerHandlingNot() { this = "MySanitizerHandlingNot" }
/** The test `if is_safe(arg):` sanitizes `arg` on its `true` edge. */
/** Holds if the test `if is_safe(arg):` sanitizes `arg` on its `true` edge. */
override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) {
taint instanceof ExternalStringKind and
clears_taint_on_true(test.getTest(), test.getSense(), test)

View File

@@ -0,0 +1,16 @@
import aiohttp
import asyncio
s = aiohttp.ClientSession()
resp = s.request("method", "url") # $ clientRequestUrlPart="url"
resp = s.request("method", url="url") # $ clientRequestUrlPart="url"
with aiohttp.ClientSession() as session:
resp = session.get("url") # $ clientRequestUrlPart="url"
resp = session.request(method="GET", url="url") # $ clientRequestUrlPart="url"
# other methods than GET
s = aiohttp.ClientSession()
resp = s.post("url") # $ clientRequestUrlPart="url"
resp = s.patch("url") # $ clientRequestUrlPart="url"
resp = s.options("url") # $ clientRequestUrlPart="url"

View File

@@ -0,0 +1,5 @@
db.sqlite3
# The testapp/migrations/ folder needs to be comitted to git,
# but we don't care to store the actual migrations
testapp/migrations/

View File

@@ -0,0 +1,2 @@
missingAnnotationOnSINK
failures

View File

@@ -0,0 +1,2 @@
import python
import experimental.dataflow.TestUtil.NormalDataflowTest

View File

@@ -0,0 +1,26 @@
The interesting ORM tests files can be found under `testapp/orm_*.py`. These are set up to be executed by the [testapp/tests.py](testapp/tests.py) file.
List of interesting tests files (that might go out of date if it is forgotten :flushed:):
- [testapp/orm_tests.py](testapp/orm_tests.py): which tests flow from source to sink
- [testapp/orm_form_test.py](testapp/orm_form_test.py): shows how forms can be used to save Models to the DB
- [testapp/orm_security_tests.py](testapp/orm_security_tests.py): which highlights some interesting interactions with security queries
- [testapp/orm_inheritance.py](testapp/orm_inheritance.py): which highlights how inheritance of ORM models works
## Setup
```
pip install django pytest pytest-django django-polymorphic
```
## Run server
```
python manage.py makemigrations && python manage.py migrate && python manage.py runserver
```
## Run tests
```
pytest
```

View File

@@ -0,0 +1,101 @@
edges
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute age] | testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute age] |
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute age] | testapp/orm_security_tests.py:51:14:51:53 | ControlFlowNode for Attribute() [Attribute age] |
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute name] | testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute name] |
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute name] | testapp/orm_security_tests.py:47:14:47:53 | ControlFlowNode for Attribute() [Attribute name] |
| testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | testapp/orm_security_tests.py:22:23:22:34 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | testapp/orm_security_tests.py:23:22:23:33 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:22:9:22:14 | [post store] ControlFlowNode for person [Attribute name] | testapp/orm_security_tests.py:23:9:23:14 | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:22:23:22:34 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:22:23:22:42 | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:22:23:22:42 | ControlFlowNode for Subscript | testapp/orm_security_tests.py:22:9:22:14 | [post store] ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:23:9:23:14 | ControlFlowNode for person [Attribute name] | testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:23:9:23:14 | [post store] ControlFlowNode for person [Attribute age] | testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:23:22:23:33 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:23:22:23:40 | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:23:22:23:40 | ControlFlowNode for Subscript | testapp/orm_security_tests.py:23:9:23:14 | [post store] ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute age] | testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute age] |
| testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute name] | testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute name] |
| testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute age] | testapp/orm_security_tests.py:43:62:43:67 | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute name] | testapp/orm_security_tests.py:43:49:43:54 | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute age] | testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute age] |
| testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute name] | testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute name] |
| testapp/orm_security_tests.py:43:49:43:54 | ControlFlowNode for person [Attribute name] | testapp/orm_security_tests.py:43:49:43:59 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:43:49:43:59 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:44:29:44:37 | ControlFlowNode for resp_text |
| testapp/orm_security_tests.py:43:62:43:67 | ControlFlowNode for person [Attribute age] | testapp/orm_security_tests.py:43:62:43:71 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:43:62:43:71 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:44:29:44:37 | ControlFlowNode for resp_text |
| testapp/orm_security_tests.py:47:14:47:53 | ControlFlowNode for Attribute() [Attribute name] | testapp/orm_security_tests.py:48:46:48:51 | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:48:46:48:51 | ControlFlowNode for person [Attribute name] | testapp/orm_security_tests.py:48:46:48:56 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:48:46:48:56 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:48:25:48:57 | ControlFlowNode for Attribute() |
| testapp/orm_security_tests.py:51:14:51:53 | ControlFlowNode for Attribute() [Attribute age] | testapp/orm_security_tests.py:55:45:55:50 | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:55:45:55:50 | ControlFlowNode for person [Attribute age] | testapp/orm_security_tests.py:55:45:55:54 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:55:45:55:54 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:55:25:55:55 | ControlFlowNode for Attribute() |
| testapp/orm_security_tests.py:92:1:92:44 | [orm-model] Class CommentValidatorNotUsed [Attribute text] | testapp/orm_security_tests.py:101:15:101:52 | ControlFlowNode for Attribute() [Attribute text] |
| testapp/orm_security_tests.py:95:37:95:43 | ControlFlowNode for request | testapp/orm_security_tests.py:96:44:96:55 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:96:15:96:64 | ControlFlowNode for CommentValidatorNotUsed() [Attribute text] | testapp/orm_security_tests.py:97:5:97:11 | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:96:44:96:55 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:96:44:96:63 | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:96:44:96:63 | ControlFlowNode for Subscript | testapp/orm_security_tests.py:96:15:96:64 | ControlFlowNode for CommentValidatorNotUsed() [Attribute text] |
| testapp/orm_security_tests.py:97:5:97:11 | ControlFlowNode for comment [Attribute text] | testapp/orm_security_tests.py:92:1:92:44 | [orm-model] Class CommentValidatorNotUsed [Attribute text] |
| testapp/orm_security_tests.py:101:15:101:52 | ControlFlowNode for Attribute() [Attribute text] | testapp/orm_security_tests.py:102:25:102:31 | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:102:25:102:31 | ControlFlowNode for comment [Attribute text] | testapp/orm_security_tests.py:102:25:102:36 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:111:1:111:41 | [orm-model] Class CommentValidatorUsed [Attribute text] | testapp/orm_security_tests.py:120:15:120:49 | ControlFlowNode for Attribute() [Attribute text] |
| testapp/orm_security_tests.py:114:33:114:39 | ControlFlowNode for request | testapp/orm_security_tests.py:115:41:115:52 | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:115:15:115:61 | ControlFlowNode for CommentValidatorUsed() [Attribute text] | testapp/orm_security_tests.py:117:5:117:11 | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:115:41:115:52 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:115:41:115:60 | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:115:41:115:60 | ControlFlowNode for Subscript | testapp/orm_security_tests.py:115:15:115:61 | ControlFlowNode for CommentValidatorUsed() [Attribute text] |
| testapp/orm_security_tests.py:117:5:117:11 | ControlFlowNode for comment [Attribute text] | testapp/orm_security_tests.py:111:1:111:41 | [orm-model] Class CommentValidatorUsed [Attribute text] |
| testapp/orm_security_tests.py:120:15:120:49 | ControlFlowNode for Attribute() [Attribute text] | testapp/orm_security_tests.py:121:25:121:31 | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:121:25:121:31 | ControlFlowNode for comment [Attribute text] | testapp/orm_security_tests.py:121:25:121:36 | ControlFlowNode for Attribute |
nodes
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute age] | semmle.label | [orm-model] Class Person [Attribute age] |
| testapp/orm_security_tests.py:15:1:15:27 | [orm-model] Class Person [Attribute name] | semmle.label | [orm-model] Class Person [Attribute name] |
| testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| testapp/orm_security_tests.py:22:9:22:14 | [post store] ControlFlowNode for person [Attribute name] | semmle.label | [post store] ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:22:23:22:34 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:22:23:22:42 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:23:9:23:14 | ControlFlowNode for person [Attribute name] | semmle.label | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:23:9:23:14 | [post store] ControlFlowNode for person [Attribute age] | semmle.label | [post store] ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:23:22:23:33 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:23:22:23:40 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute age] | semmle.label | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:28:9:28:14 | ControlFlowNode for person [Attribute name] | semmle.label | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute age] | semmle.label | SSA variable person [Attribute age] |
| testapp/orm_security_tests.py:42:13:42:18 | SSA variable person [Attribute name] | semmle.label | SSA variable person [Attribute name] |
| testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute age] | semmle.label | ControlFlowNode for Attribute() [List element, Attribute age] |
| testapp/orm_security_tests.py:42:23:42:42 | ControlFlowNode for Attribute() [List element, Attribute name] | semmle.label | ControlFlowNode for Attribute() [List element, Attribute name] |
| testapp/orm_security_tests.py:43:49:43:54 | ControlFlowNode for person [Attribute name] | semmle.label | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:43:49:43:59 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:43:62:43:67 | ControlFlowNode for person [Attribute age] | semmle.label | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:43:62:43:71 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:44:29:44:37 | ControlFlowNode for resp_text | semmle.label | ControlFlowNode for resp_text |
| testapp/orm_security_tests.py:47:14:47:53 | ControlFlowNode for Attribute() [Attribute name] | semmle.label | ControlFlowNode for Attribute() [Attribute name] |
| testapp/orm_security_tests.py:48:25:48:57 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| testapp/orm_security_tests.py:48:46:48:51 | ControlFlowNode for person [Attribute name] | semmle.label | ControlFlowNode for person [Attribute name] |
| testapp/orm_security_tests.py:48:46:48:56 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:51:14:51:53 | ControlFlowNode for Attribute() [Attribute age] | semmle.label | ControlFlowNode for Attribute() [Attribute age] |
| testapp/orm_security_tests.py:55:25:55:55 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
| testapp/orm_security_tests.py:55:45:55:50 | ControlFlowNode for person [Attribute age] | semmle.label | ControlFlowNode for person [Attribute age] |
| testapp/orm_security_tests.py:55:45:55:54 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:92:1:92:44 | [orm-model] Class CommentValidatorNotUsed [Attribute text] | semmle.label | [orm-model] Class CommentValidatorNotUsed [Attribute text] |
| testapp/orm_security_tests.py:95:37:95:43 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| testapp/orm_security_tests.py:96:15:96:64 | ControlFlowNode for CommentValidatorNotUsed() [Attribute text] | semmle.label | ControlFlowNode for CommentValidatorNotUsed() [Attribute text] |
| testapp/orm_security_tests.py:96:44:96:55 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:96:44:96:63 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:97:5:97:11 | ControlFlowNode for comment [Attribute text] | semmle.label | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:101:15:101:52 | ControlFlowNode for Attribute() [Attribute text] | semmle.label | ControlFlowNode for Attribute() [Attribute text] |
| testapp/orm_security_tests.py:102:25:102:31 | ControlFlowNode for comment [Attribute text] | semmle.label | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:102:25:102:36 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:111:1:111:41 | [orm-model] Class CommentValidatorUsed [Attribute text] | semmle.label | [orm-model] Class CommentValidatorUsed [Attribute text] |
| testapp/orm_security_tests.py:114:33:114:39 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
| testapp/orm_security_tests.py:115:15:115:61 | ControlFlowNode for CommentValidatorUsed() [Attribute text] | semmle.label | ControlFlowNode for CommentValidatorUsed() [Attribute text] |
| testapp/orm_security_tests.py:115:41:115:52 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| testapp/orm_security_tests.py:115:41:115:60 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| testapp/orm_security_tests.py:117:5:117:11 | ControlFlowNode for comment [Attribute text] | semmle.label | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:120:15:120:49 | ControlFlowNode for Attribute() [Attribute text] | semmle.label | ControlFlowNode for Attribute() [Attribute text] |
| testapp/orm_security_tests.py:121:25:121:31 | ControlFlowNode for comment [Attribute text] | semmle.label | ControlFlowNode for comment [Attribute text] |
| testapp/orm_security_tests.py:121:25:121:36 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
subpaths
#select
| testapp/orm_security_tests.py:44:29:44:37 | ControlFlowNode for resp_text | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | testapp/orm_security_tests.py:44:29:44:37 | ControlFlowNode for resp_text | Cross-site scripting vulnerability due to $@. | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | a user-provided value |
| testapp/orm_security_tests.py:48:25:48:57 | ControlFlowNode for Attribute() | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | testapp/orm_security_tests.py:48:25:48:57 | ControlFlowNode for Attribute() | Cross-site scripting vulnerability due to $@. | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | a user-provided value |
| testapp/orm_security_tests.py:55:25:55:55 | ControlFlowNode for Attribute() | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | testapp/orm_security_tests.py:55:25:55:55 | ControlFlowNode for Attribute() | Cross-site scripting vulnerability due to $@. | testapp/orm_security_tests.py:19:12:19:18 | ControlFlowNode for request | a user-provided value |
| testapp/orm_security_tests.py:102:25:102:36 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:95:37:95:43 | ControlFlowNode for request | testapp/orm_security_tests.py:102:25:102:36 | ControlFlowNode for Attribute | Cross-site scripting vulnerability due to $@. | testapp/orm_security_tests.py:95:37:95:43 | ControlFlowNode for request | a user-provided value |
| testapp/orm_security_tests.py:121:25:121:36 | ControlFlowNode for Attribute | testapp/orm_security_tests.py:114:33:114:39 | ControlFlowNode for request | testapp/orm_security_tests.py:121:25:121:36 | ControlFlowNode for Attribute | Cross-site scripting vulnerability due to $@. | testapp/orm_security_tests.py:114:33:114:39 | ControlFlowNode for request | a user-provided value |

View File

@@ -0,0 +1 @@
Security/CWE-079/ReflectedXss.ql

View File

@@ -0,0 +1,18 @@
uniqueEnclosingCallable
uniqueType
uniqueNodeLocation
missingLocation
uniqueNodeToString
missingToString
parameterCallable
localFlowIsLocal
compatibleTypesReflexive
unreachableNodeCCtx
localCallNodes
postIsNotPre
postHasUniquePre
uniquePostUpdate
postIsInSameCallable
reverseRead
argHasPostUpdate
postWithInFlow

View File

@@ -0,0 +1 @@
import semmle.python.dataflow.new.internal.DataFlowImplConsistency::Consistency

View File

@@ -0,0 +1,5 @@
# to force extractor to see files. since we use `--max-import-depth=1`, we use this
# "fake" import that doesn't actually work, but tricks the python extractor to look at
# all the files
from testapp import *

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testproj.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,5 @@
[pytest]
DJANGO_SETTINGS_MODULE = testproj.settings
python_files = tests.py
# don't require that you have manually run `python manage.py makemigrations`
addopts = --no-migrations --ignore-glob=*.testproj/ -v

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class TestappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'testapp'

View File

@@ -0,0 +1,7 @@
from django.db import models
# Create your models here.
from .orm_tests import *
from .orm_security_tests import *
from .orm_form_test import *
from .orm_inheritance import *

View File

@@ -0,0 +1,35 @@
from django.db import models
from django.http.response import HttpResponse
from django.shortcuts import render
from django import forms
class MyModel(models.Model):
text = models.CharField(max_length=256)
class MyModelForm(forms.ModelForm):
# see https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/#django.forms.ModelForm
class Meta:
model = MyModel
fields = ["text"]
# TODO: When we actually start supporting ModelForm, we need to add test-cases for
# limiting what fields are used. See
# https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/#selecting-the-fields-to-use
def add_mymodel_handler(request):
if request.method == "POST":
form = MyModelForm(request.POST, request.FILES)
if form.is_valid():
new_MyMoodel_instance = form.save()
return HttpResponse("ok")
else:
print("not valid", form.errors)
else:
form = MyModelForm(initial=request.GET)
return render(request, "form_example.html", {"form": form})
def show_mymodel_handler(request):
obj = MyModel.objects.last()
return HttpResponse("Last object (id={}) had text: {!r}".format(obj.id, obj.text))

View File

@@ -0,0 +1,194 @@
from django.db import models
SOURCE = "source"
def SINK(arg):
print(arg)
assert arg == SOURCE
def SINK_F(arg):
print(arg)
assert arg != SOURCE
# ==============================================================================
# Inheritance
#
# If base class defines a field, there can be
# 1. flow when field is assigned on subclass construction to lookup of base class
# 2. no flow from field assignment on subclass A to lookup of sibling subclass B
# 3. no flow from field assignment on base class to lookup of subclass
# ==============================================================================
# ------------------------------------------------------------------------------
# Inheritance with vanilla Django
# ------------------------------------------------------------------------------
class Book(models.Model):
title = models.CharField(max_length=256)
class PhysicalBook(Book):
physical_location = models.CharField(max_length=256)
same_name_different_value = models.CharField(max_length=256)
class EBook(Book):
download_link = models.CharField(max_length=256)
same_name_different_value = models.CharField(max_length=256)
def save_base_book():
return Book.objects.create(
title=SOURCE,
)
def fetch_book(id):
book = Book.objects.get(id=id)
try:
# This sink should have 2 sources, from `save_base_book` and
# `save_physical_book`
SINK(book.title) # $ flow="SOURCE, l:-10 -> book.title" flow="SOURCE, l:+21 -> book.title"
# The sink assertion will fail for the EBook, which we handle. The title attribute
# of a Book could be tainted, so we want this to be a sink in general.
except AssertionError:
if book.title == "safe ebook":
pass
else:
raise
assert not isinstance(book, PhysicalBook)
assert not isinstance(book, EBook)
try:
SINK_F(book.physical_location)
raise Exception("This field is not available with vanilla Django")
except AttributeError:
pass
def save_physical_book():
return PhysicalBook.objects.create(
title=SOURCE,
physical_location=SOURCE,
same_name_different_value=SOURCE,
)
def fetch_physical_book(id):
book = PhysicalBook.objects.get(id=id)
# This sink should have only 1 sources, from `save_physical_book`
SINK(book.title) # $ flow="SOURCE, l:-10 -> book.title"
SINK(book.physical_location) # $ flow="SOURCE, l:-10 -> book.physical_location"
SINK(book.same_name_different_value) # $ flow="SOURCE, l:-10 -> book.same_name_different_value"
def save_ebook():
return EBook.objects.create(
title="safe ebook",
download_link="safe",
same_name_different_value="safe",
)
def fetch_ebook(id):
book = EBook.objects.get(id=id)
SINK_F(book.title)
SINK_F(book.download_link)
SINK_F(book.same_name_different_value)
# ------------------------------------------------------------------------------
# Inheritance with `django-polymorphic`, which automatically turns lookups on the
# base class into the right subclass
#
# see https://django-polymorphic.readthedocs.io/en/stable/quickstart.html
# ------------------------------------------------------------------------------
from polymorphic.models import PolymorphicModel
class PolyBook(PolymorphicModel):
title = models.CharField(max_length=256)
class PolyPhysicalBook(PolyBook):
physical_location = models.CharField(max_length=256)
same_name_different_value = models.CharField(max_length=256)
class PolyEBook(PolyBook):
download_link = models.CharField(max_length=256)
same_name_different_value = models.CharField(max_length=256)
def poly_save_base_book():
return PolyBook.objects.create(
title=SOURCE
)
def poly_fetch_book(id, test_for_subclass=True):
book = PolyBook.objects.get(id=id)
try:
# This sink should have 2 sources, from `poly_save_base_book` and
# `poly_save_physical_book`
SINK(book.title) # $ flow="SOURCE, l:-10 -> book.title" flow="SOURCE, l:+24 -> book.title"
# The sink assertion will fail for the PolyEBook, which we handle. The title
# attribute of a PolyBook could be tainted, so we want this to be a sink in general.
except AssertionError:
if book.title == "safe ebook":
pass
else:
raise
if test_for_subclass:
assert isinstance(book, PolyPhysicalBook) or isinstance(book, PolyEBook)
if isinstance(book, PolyPhysicalBook):
SINK(book.title) # $ flow="SOURCE, l:+11 -> book.title" SPURIOUS: flow="SOURCE, l:-23 -> book.title"
SINK(book.physical_location) # $ flow="SOURCE, l:+11 -> book.physical_location"
SINK(book.same_name_different_value) # $ flow="SOURCE, l:+11 -> book.same_name_different_value"
elif isinstance(book, PolyEBook):
SINK_F(book.title) # $ SPURIOUS: flow="SOURCE, l:-27 -> book.title" flow="SOURCE, l:+7 -> book.title"
SINK_F(book.download_link)
SINK_F(book.same_name_different_value) # $ SPURIOUS: flow="SOURCE, l:+7 -> book.same_name_different_value"
def poly_save_physical_book():
return PolyPhysicalBook.objects.create(
title=SOURCE,
physical_location=SOURCE,
same_name_different_value=SOURCE,
)
def poly_fetch_physical_book(id):
book = PolyPhysicalBook.objects.get(id=id)
SINK(book.title) # $ flow="SOURCE, l:-9 -> book.title"
SINK(book.physical_location) # $ flow="SOURCE, l:-9 -> book.physical_location"
SINK(book.same_name_different_value) # $ flow="SOURCE, l:-9 -> book.same_name_different_value"
def poly_save_ebook():
return PolyEBook.objects.create(
title="safe ebook",
download_link="safe",
same_name_different_value="safe",
)
def poly_fetch_ebook(id):
book = PolyEBook.objects.get(id=id)
SINK_F(book.title)
SINK_F(book.download_link)
SINK_F(book.same_name_different_value)

View File

@@ -0,0 +1,126 @@
"""
Handling of ORM steps that are only relevant to real taint-tracking queries, and not core dataflow.
"""
from django.db import models
from django.http.response import HttpResponse
# ------------------------------------------------------------------------------
# Some fields are not relevant for some security queries
# ------------------------------------------------------------------------------
# TODO: We need some way to mark that a certain data-flow node can only contain
# an integer, so it can be excluded from queries.
class Person(models.Model):
name = models.CharField(max_length=256)
age = models.IntegerField()
def person(request):
if request.method == "POST":
person = Person()
person.name = request.POST["name"]
person.age = request.POST["age"]
# at this point, `person.age` is a string, and could contain anything
assert isinstance(person.age, str)
person.save()
# after saving, there will be an error if the string could not be converted to an integer.
# the attribute on the local object is not changed (so still `str`) but after fetching from DB it is
# an `int`
assert isinstance(person.age, str)
# after doing `.full_clean` it also has the proper data-type
person.full_clean()
assert isinstance(person.age, int)
return HttpResponse("ok")
elif request.method == "GET":
resp_text = "<h1>Persons:</h1>"
for person in Person.objects.all():
resp_text += "\n{} (age {})".format(person.name, person.age)
return HttpResponse(resp_text) # NOT OK
def show_name(request):
person = Person.objects.get(id=request.GET["id"])
return HttpResponse("Name is: {}".format(person.name)) # NOT OK
def show_age(request):
person = Person.objects.get(id=request.GET["id"])
assert isinstance(person.age, int)
# Since the age is an integer, there is not actually XSS in the line below
return HttpResponse("Age is: {}".format(person.age)) # OK
# look at the log after doing
"""
http -f 'http://127.0.0.1:8000/person/' name="foo" age=42
http 'http://127.0.0.1:8000/show_age/?id=1'
"""
# ------------------------------------------------------------------------------
# Custom validators on fields
# ------------------------------------------------------------------------------
#
# We currently do not include these in any of our queries. There are two reasons:
#
# 1. We don't have any good way to determine what the validator actually does. So we
# don't have any way to determine if a validator would make data safe for a
# particular query. So we would have to blindly trust that if any validator was
# specified, that would mean data is always safe :| We still want to produce more
# results, so by default, we would want to do it this way, and live live with the FPs
# that arise -- if it turns out that is too troublesome, we can look more into it.
#
# 2. Using a validator on the input data does not make any guarantees on the data that
# is already in the DB. It's better to perform escaping on the data as part of
# outputing/rendering, since you know _all_ data will be escaped, and that the right
# kind of escaping is applied (there is a difference in what needs to be escaped for
# different vulnerabilities, so doing validation that rejects things that would cause
# XSS might still accept things that can do SQL injection)
from django.core.exceptions import ValidationError
import re
def only_az(value):
if not re.match(r"^[a-zA-Z]$", value):
raise ValidationError("only a-zA-Z allowed")
# First example: Validator is set, but not used
class CommentValidatorNotUsed(models.Model):
text = models.CharField(max_length=256, validators=[only_az])
def save_comment_validator_not_used(request): # $requestHandler
comment = CommentValidatorNotUsed(text=request.POST["text"])
comment.save()
return HttpResponse("ok")
def display_comment_validator_not_used(request): # $requestHandler
comment = CommentValidatorNotUsed.objects.last()
return HttpResponse(comment.text) # NOT OK
# To test this
"""
http -f http://127.0.0.1:8000/save_comment_validator_not_used/ text="foo!@#"
http http://127.0.0.1:8000/display_comment_validator_not_used/
"""
# Second example: Validator is set, AND is used
class CommentValidatorUsed(models.Model):
text = models.CharField(max_length=256, validators=[only_az])
def save_comment_validator_used(request): # $requestHandler
comment = CommentValidatorUsed(text=request.POST["text"])
comment.full_clean()
comment.save()
def display_comment_validator_used(request): # $requestHandler
comment = CommentValidatorUsed.objects.last()
return HttpResponse(comment.text) # sort of OK
# Doing the following will raise a ValidationError
"""
http -f http://127.0.0.1:8000/save_comment_validator_used/ text="foo!@#"
"""

View File

@@ -0,0 +1,372 @@
from django.db import models
SOURCE = "source"
def SINK(arg):
print(arg)
assert arg == SOURCE
def SINK_F(arg):
print(arg)
assert arg != SOURCE
# ------------------------------------------------------------------------------
# Different ways to save data to the DB through ORM
#
# These tests are set up with their own individual model, so it's possible to see
# whether the source works or not (although there is quite a bit of boilerplate). The
# problem with using the same model multiple times, is that it won't be obvious if the
# single SINK call actually has flow from all the expected places or not.
# ------------------------------------------------------------------------------
# --------------------------------------
# Constructor: kw arg
# --------------------------------------
class TestSave1(models.Model):
text = models.CharField(max_length=512)
def test_save1_store():
obj = TestSave1(text=SOURCE)
obj.save()
def test_save1_load():
obj = TestSave1.objects.first()
SINK(obj.text) # $ flow="SOURCE, l:-5 -> obj.text"
# --------------------------------------
# Constructor: positional arg
# --------------------------------------
class TestSave2(models.Model):
text = models.CharField(max_length=512)
def test_save2_store():
# first positional argument is `id`, a primary key automatically added
# see https://docs.djangoproject.com/en/4.0/topics/db/models/#automatic-primary-key-fields
obj = TestSave2(None, SOURCE)
obj.save()
def test_save2_load():
obj = TestSave2.objects.first()
SINK(obj.text) # $ MISSING: flow
# --------------------------------------
# Constructor: positional arg, with own primary key
# --------------------------------------
class TestSave3(models.Model):
text = models.CharField(max_length=512, primary_key=True)
def test_save3_store():
# no `id` column added, see https://docs.djangoproject.com/en/4.0/topics/db/models/#automatic-primary-key-fields
obj = TestSave3(SOURCE)
obj.save()
def test_save3_load():
obj = TestSave3.objects.first()
SINK(obj.text) # $ MISSING: flow
# --------------------------------------
# Set attribute on fresh object
# --------------------------------------
class TestSave4(models.Model):
text = models.CharField(max_length=512)
def test_save4_store():
obj = TestSave4()
obj.text = SOURCE
obj.save()
def test_save4_load():
obj = TestSave4.objects.first()
SINK(obj.text) # $ flow="SOURCE, l:-5 -> obj.text"
# --------------------------------------
# Set attribute on existing
# --------------------------------------
class TestSave4b(models.Model):
text = models.CharField(max_length=512)
def test_save4b_init():
obj = TestSave4b()
obj.text = "foo"
obj.save()
def test_save4b_store():
obj = TestSave4b.objects.first()
obj.text = SOURCE
obj.save()
def test_save4b_load():
obj = TestSave4b.objects.first()
SINK(obj.text) # $ flow="SOURCE, l:-5 -> obj.text"
# --------------------------------------
# <Model>.objects.create()
# see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#create
# --------------------------------------
class TestSave5(models.Model):
text = models.CharField(max_length=512)
def test_save5_store():
# note: positional args not possible
obj = TestSave5.objects.create(text=SOURCE)
SINK(obj.text) # $ flow="SOURCE, l:-1 -> obj.text"
def test_save5_load():
obj = TestSave5.objects.first()
SINK(obj.text) # $ flow="SOURCE, l:-5 -> obj.text"
# --------------------------------------
# <Model>.objects.get_or_create()
# see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#get-or-create
# --------------------------------------
class TestSave6(models.Model):
text = models.CharField(max_length=512)
email = models.CharField(max_length=256)
def test_save6_store():
obj, _created = TestSave6.objects.get_or_create(defaults={"text": SOURCE}, email=SOURCE)
SINK(obj.text) # $ MISSING: flow
SINK(obj.email) # $ MISSING: flow
def test_save6_load():
obj = TestSave6.objects.first()
SINK(obj.text) # $ MISSING: flow
SINK(obj.email) # $ flow="SOURCE, l:-7 -> obj.email"
# --------------------------------------
# <Model>.objects.update_or_create()
# see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#update-or-create
# --------------------------------------
class TestSave7(models.Model):
text = models.CharField(max_length=512)
email = models.CharField(max_length=256)
def test_save7_store():
obj, _created = TestSave7.objects.update_or_create(defaults={"text": SOURCE}, email=SOURCE)
SINK(obj.text) # $ MISSING: flow
SINK(obj.email) # $ MISSING: flow
def test_save7_load():
obj = TestSave7.objects.first()
SINK(obj.text) # $ MISSING: flow
SINK(obj.email) # $ flow="SOURCE, l:-7 -> obj.email"
# --------------------------------------
# <Model>.objects.[<QuerySet>].update()
# --------------------------------------
class TestSave8(models.Model):
text = models.CharField(max_length=512)
def test_save8_init():
TestSave8.objects.create(text="foo")
def test_save8_store():
_updated_count = TestSave8.objects.all().update(text=SOURCE)
def test_save8_load():
obj = TestSave8.objects.first()
SINK(obj.text) # $ flow="SOURCE, l:-4 -> obj.text"
# --------------------------------------
# .save() on foreign key field
# --------------------------------------
class TestSave9(models.Model):
text = models.CharField(max_length=512)
class TestSave9WithForeignKey(models.Model):
test = models.ForeignKey(TestSave9, models.deletion.CASCADE)
def test_save9_init():
obj = TestSave9.objects.create(text="foo")
TestSave9WithForeignKey.objects.create(test=obj)
def test_save9_store():
w_fk = TestSave9WithForeignKey.objects.first()
w_fk.test.text = SOURCE
w_fk.test.save()
# note that `w_fk.save()` does NOT save the state of the `TestSave9` object.
def test_save9_load():
obj = TestSave9.objects.first()
SINK(obj.text) # $ MISSING: flow
# --------------------------------------
# foreign key backreference (auto-generated name)
# see https://docs.djangoproject.com/en/4.0/topics/db/queries/#following-relationships-backward
# --------------------------------------
class save10_BlogPost(models.Model):
# dummy content, only has automatic `id` field
pass
class save10_Comment(models.Model):
text = models.CharField(max_length=512)
blog = models.ForeignKey(save10_BlogPost, models.deletion.CASCADE)
def test_save10_init():
blogpost = save10_BlogPost.objects.create()
save10_Comment.objects.create(blog=blogpost, text="foo")
def test_save10_store():
blogpost = save10_BlogPost.objects.first()
for comment in blogpost.save10_comment_set.all():
comment.text = SOURCE
comment.save()
def test_save10_load():
obj = save10_Comment.objects.first()
SINK(obj.text) # $ MISSING: flow
# --------------------------------------
# foreign key backreference, with custom name
# see https://docs.djangoproject.com/en/4.0/topics/db/queries/#following-relationships-backward
# --------------------------------------
class save11_BlogPost(models.Model):
# dummy contet, only has automatic `id` field
pass
class save11_Comment(models.Model):
text = models.CharField(max_length=512)
blog = models.ForeignKey(save11_BlogPost, models.deletion.CASCADE, related_name="comments")
def test_save11_init():
blogpost = save11_BlogPost.objects.create()
save11_Comment.objects.create(blog=blogpost, text="foo")
def test_save11_store():
blogpost = save11_BlogPost.objects.first()
for comment in blogpost.comments.all():
comment.text = SOURCE
comment.save()
def test_save11_load():
obj = save11_Comment.objects.first()
SINK(obj.text) # $ MISSING: flow
# --------------------------------------
# <Model>.objects.bulk_create()
# see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#bulk-create
# --------------------------------------
class TestSave12(models.Model):
text = models.CharField(max_length=512)
def test_save12_store():
objs = TestSave12.objects.bulk_create([
TestSave12(text=SOURCE),
TestSave12(text="foo"),
])
SINK(objs[0].text) # $ MISSING: flow
def test_save12_load():
obj = TestSave12.objects.first()
SINK(obj.text) # $ MISSING: flow
# --------------------------------------
# <Model>.objects.bulk_update()
# see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#bulk-update
# --------------------------------------
class TestSave13(models.Model):
text = models.CharField(max_length=512)
def test_save13_init():
obj = TestSave13(text="foo")
obj.save()
def test_save13_store():
objs = TestSave13.objects.all()
for obj in objs:
obj.text = SOURCE
TestSave13.objects.bulk_update(objs, ["text"])
def test_save13_load():
obj = TestSave13.objects.first()
SINK(obj.text) # $ MISSING: flow
# ------------------------------------------------------------------------------
# Different ways to load data from the DB through the ORM
# ------------------------------------------------------------------------------
class TestLoad(models.Model):
text = models.CharField(max_length=512)
def test_load_init():
for _ in range(10):
obj = TestLoad()
obj.text = SOURCE
obj.save()
def test_load_single():
obj = TestLoad.objects.get(id=1)
SINK(obj.text) # $ flow="SOURCE, l:-5 -> obj.text"
def test_load_many():
objs = TestLoad.objects.all()
for obj in objs:
SINK(obj.text) # $ flow="SOURCE, l:-10 -> obj.text"
SINK(objs[0].text) # $ flow="SOURCE, l:-11 -> objs[0].text"
def test_load_many_skip():
objs = TestLoad.objects.all()[5:]
for obj in objs:
SINK(obj.text) # $ MISSING: flow
SINK(objs[0].text) # $ MISSING: flow
def test_load_qs_chain_single():
obj = TestLoad.objects.all().filter(text__contains="s").exclude(text=None).first()
SINK(obj.text) # $ flow="SOURCE, l:-21 -> obj.text"
def test_load_qs_chain_many():
objs = TestLoad.objects.all().filter(text__contains="s").exclude(text=None)
for obj in objs:
SINK(obj.text) # $ flow="SOURCE, l:-26 -> obj.text"
SINK(objs[0].text) # $ flow="SOURCE, l:-27 -> objs[0].text"
def test_load_values():
# see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#django.db.models.query.QuerySet.values
vals = TestLoad.objects.all().values()
for val in vals:
SINK(val['text']) # $ MISSING: flow
SINK(vals[0]['text']) # $ MISSING: flow
# only selecting some of the fields
vals = TestLoad.objects.all().values("text")
for val in vals:
SINK(val['text']) # $ MISSING: flow
SINK(vals[0]['text']) # $ MISSING: flow
def test_load_values_list():
# see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#django.db.models.query.QuerySet.values_list
vals = TestLoad.objects.all().values_list()
for (_id, text) in vals:
SINK(text) # $ MISSING: flow
SINK(vals[0][1]) # $ MISSING: flow
# only selecting some of the fields
vals = TestLoad.objects.all().values_list("text")
for (text,) in vals:
SINK(text) # $ MISSING: flow
SINK(vals[0][0]) # $ MISSING: flow
# with flat=True, each row will not be a tuple, but just the value
vals = TestLoad.objects.all().values_list("text", flat=True)
for text in vals:
SINK(text) # $ MISSING: flow
SINK(vals[0]) # $ MISSING: flow
def test_load_in_bulk():
# see https://docs.djangoproject.com/en/4.0/ref/models/querysets/#in-bulk
d = TestLoad.objects.in_bulk([1])
for val in d.values():
SINK(val.text) # $ MISSING: flow
SINK(d[1].text) # $ flow="SOURCE, l:-66 -> d[1].text"
# Good resources:
# - https://docs.djangoproject.com/en/4.0/topics/db/queries/#making-queries
# - https://docs.djangoproject.com/en/4.0/ref/models/querysets/
# - https://docs.djangoproject.com/en/4.0/ref/models/instances/

View File

@@ -0,0 +1,5 @@
<form action="/mymodel/add/" method="post">
{% comment %} {% csrf_token %} {% endcomment %}
{{ form }}
<input type="submit" value="Submit">
</form>

View File

@@ -0,0 +1,143 @@
import importlib
import re
import pytest
# Create your tests here.
def discover_save_tests():
mod = importlib.import_module("testapp.orm_tests")
test_names = []
for name in dir(mod):
m = re.match("test_(save.*)_load", name)
if not m:
continue
name = m.group(1)
test_names.append(name)
return test_names
def discover_load_tests():
mod = importlib.import_module("testapp.orm_tests")
test_names = []
for name in dir(mod):
m = re.match("test_(load.*)", name)
if not m:
continue
name = m.group(1)
if name == "load_init":
continue
test_names.append(name)
return test_names
@pytest.mark.django_db
@pytest.mark.parametrize("name", discover_save_tests())
def test_run_save_tests(name):
mod = importlib.import_module("testapp.orm_tests")
init_func = getattr(mod, f"test_{name}_init", None)
store_func = getattr(mod, f"test_{name}_store", None)
load_func = getattr(mod, f"test_{name}_load", None)
if init_func:
init_func()
store_func()
load_func()
has_run_load_init = False
@pytest.fixture
def load_test_init():
from .orm_tests import test_load_init
test_load_init()
@pytest.mark.django_db
@pytest.mark.parametrize("name", discover_load_tests())
def test_run_load_tests(load_test_init, name):
mod = importlib.import_module("testapp.orm_tests")
load_func = getattr(mod, f"test_{name}", None)
load_func()
assert getattr(mod, "TestLoad").objects.count() == 10
@pytest.mark.django_db
def test_mymodel_form_save():
from .orm_form_test import MyModel, MyModelForm
import uuid
text = str(uuid.uuid4())
form = MyModelForm(data={"text": text})
form.save()
obj = MyModel.objects.last()
assert obj.text == text
@pytest.mark.django_db
def test_none_all():
from .orm_form_test import MyModel
MyModel.objects.create(text="foo")
assert len(MyModel.objects.all()) == 1
assert len(MyModel.objects.none().all()) == 0
assert len(MyModel.objects.all().none()) == 0
@pytest.mark.django_db
def test_orm_inheritance():
from .orm_inheritance import (save_physical_book, save_ebook, save_base_book,
fetch_book, fetch_physical_book, fetch_ebook,
PhysicalBook, EBook,
)
base = save_base_book()
physical = save_physical_book()
ebook = save_ebook()
fetch_book(base.id)
fetch_book(physical.id)
fetch_book(ebook.id)
fetch_physical_book(physical.id)
fetch_ebook(ebook.id)
try:
fetch_physical_book(base.id)
except PhysicalBook.DoesNotExist:
pass
try:
fetch_ebook(ebook.id)
except EBook.DoesNotExist:
pass
@pytest.mark.django_db
def test_poly_orm_inheritance():
from .orm_inheritance import (poly_save_physical_book, poly_save_ebook, poly_save_base_book,
poly_fetch_book, poly_fetch_physical_book, poly_fetch_ebook,
PolyPhysicalBook, PolyEBook,
)
base = poly_save_base_book()
physical = poly_save_physical_book()
ebook = poly_save_ebook()
poly_fetch_book(base.id, test_for_subclass=False)
poly_fetch_book(physical.id)
poly_fetch_book(ebook.id)
poly_fetch_physical_book(physical.id)
poly_fetch_ebook(ebook.id)
try:
poly_fetch_physical_book(base.id)
except PolyPhysicalBook.DoesNotExist:
pass
try:
poly_fetch_ebook(ebook.id)
except PolyEBook.DoesNotExist:
pass

View File

@@ -0,0 +1,19 @@
from django.urls import path, re_path
from . import orm_security_tests
from . import orm_form_test
urlpatterns = [
path("person/", orm_security_tests.person),
path("show_name/", orm_security_tests.show_name),
path("show_age/", orm_security_tests.show_age),
path("save_comment_validator_not_used/", orm_security_tests.save_comment_validator_not_used),
path("display_comment_validator_not_used/", orm_security_tests.display_comment_validator_not_used),
path("save_comment_validator_used/", orm_security_tests.save_comment_validator_used),
path("display_comment_validator_used/", orm_security_tests.display_comment_validator_used),
path("mymodel/add/", orm_form_test.add_mymodel_handler),
path("mymodel/show/", orm_form_test.show_mymodel_handler),
]

Some files were not shown because too many files have changed in this diff Show More