mirror of
https://github.com/github/codeql.git
synced 2026-05-01 11:45:14 +02:00
Merge branch 'main' into python/simple-csrf
This commit is contained in:
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
135
python/ql/test/TestUtilities/VerifyApiGraphs.qll
Normal file
135
python/ql/test/TestUtilities/VerifyApiGraphs.qll
Normal 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() }
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
foo = 42
|
||||
@@ -1 +0,0 @@
|
||||
pass
|
||||
@@ -1 +0,0 @@
|
||||
pass
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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!
|
||||
@@ -0,0 +1 @@
|
||||
ERROR: Could not resolve type DataFlow::Add (Test.ql:7,6-19)
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
1+1
|
||||
@@ -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
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-022/ZipSlip.ql
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
| xmlrpc_server.py:7:10:7:48 | ControlFlowNode for SimpleXMLRPCServer() | SimpleXMLRPCServer is vulnerable to XML bombs |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-611/SimpleXmlRpcServer.ql
|
||||
@@ -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>'
|
||||
@@ -1 +0,0 @@
|
||||
experimental/Security/CWE-643/XpathInjection.ql
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
import TestUtilities.VerifyApiGraphs
|
||||
61
python/ql/test/library-tests/ApiGraphs/py3/deftest1.py
Normal file
61
python/ql/test/library-tests/ApiGraphs/py3/deftest1.py
Normal 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)
|
||||
19
python/ql/test/library-tests/ApiGraphs/py3/deftest2.py
Normal file
19
python/ql/test/library-tests/ApiGraphs/py3/deftest2.py
Normal 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()
|
||||
@@ -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():
|
||||
@@ -0,0 +1 @@
|
||||
import TestUtilities.VerifyApiGraphs
|
||||
@@ -1,5 +1,4 @@
|
||||
import python
|
||||
import semmle.python.objects.ObjectAPI
|
||||
|
||||
from int line, ControlFlowNode f, Value v
|
||||
where
|
||||
|
||||
@@ -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 |
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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 |
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import python
|
||||
import python
|
||||
import semmle.python.pointsto.PointsTo
|
||||
import semmle.python.pointsto.PointsToContext
|
||||
import semmle.python.objects.ObjectInternal
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import python
|
||||
import semmle.python.pointsto.Base
|
||||
|
||||
from ClassObject cls, string name
|
||||
where class_declares_attribute(cls, name)
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import python
|
||||
import semmle.python.types.Descriptors
|
||||
import Util
|
||||
|
||||
from ClassMethodObject cm, CallNode call
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import python
|
||||
import semmle.python.SelfAttribute
|
||||
|
||||
from SelfAttributeRead sa, int line, string g, string l
|
||||
where
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import python
|
||||
import semmle.python.types.Descriptors
|
||||
|
||||
int lineof(Object o) { result = o.getOrigin().getLocation().getStartLine() }
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import python
|
||||
import semmle.python.types.Descriptors
|
||||
|
||||
from PropertyValue p, string method_name, FunctionValue method
|
||||
where
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
5
python/ql/test/library-tests/frameworks/django-orm/.gitignore
vendored
Normal file
5
python/ql/test/library-tests/frameworks/django-orm/.gitignore
vendored
Normal 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/
|
||||
@@ -0,0 +1,2 @@
|
||||
missingAnnotationOnSINK
|
||||
failures
|
||||
@@ -0,0 +1,2 @@
|
||||
import python
|
||||
import experimental.dataflow.TestUtil.NormalDataflowTest
|
||||
26
python/ql/test/library-tests/frameworks/django-orm/README.md
Normal file
26
python/ql/test/library-tests/frameworks/django-orm/README.md
Normal 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
|
||||
```
|
||||
@@ -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 |
|
||||
@@ -0,0 +1 @@
|
||||
Security/CWE-079/ReflectedXss.ql
|
||||
@@ -0,0 +1,18 @@
|
||||
uniqueEnclosingCallable
|
||||
uniqueType
|
||||
uniqueNodeLocation
|
||||
missingLocation
|
||||
uniqueNodeToString
|
||||
missingToString
|
||||
parameterCallable
|
||||
localFlowIsLocal
|
||||
compatibleTypesReflexive
|
||||
unreachableNodeCCtx
|
||||
localCallNodes
|
||||
postIsNotPre
|
||||
postHasUniquePre
|
||||
uniquePostUpdate
|
||||
postIsInSameCallable
|
||||
reverseRead
|
||||
argHasPostUpdate
|
||||
postWithInFlow
|
||||
@@ -0,0 +1 @@
|
||||
import semmle.python.dataflow.new.internal.DataFlowImplConsistency::Consistency
|
||||
@@ -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 *
|
||||
22
python/ql/test/library-tests/frameworks/django-orm/manage.py
Executable file
22
python/ql/test/library-tests/frameworks/django-orm/manage.py
Executable 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()
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TestappConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'testapp'
|
||||
@@ -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 *
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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!@#"
|
||||
"""
|
||||
@@ -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/
|
||||
@@ -0,0 +1,5 @@
|
||||
<form action="/mymodel/add/" method="post">
|
||||
{% comment %} {% csrf_token %} {% endcomment %}
|
||||
{{ form }}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user