Compare commits

..

1 Commits

Author SHA1 Message Date
Owen Mansel-Chan
7126b95b16 Add test with MISSING alerts 2026-06-11 07:22:17 +02:00
12 changed files with 82 additions and 256 deletions

View File

@@ -1,4 +0,0 @@
---
category: minorAnalysis
---
* Python type tracking now follows values stored in instance attributes such as `self.attr` across instance methods on the same class. As a result, analysis is more likely to recognize user-defined objects that are stored on `self` and used later in other methods, which may produce additional results.

View File

@@ -167,15 +167,11 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
}
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which may depend on the call graph. */
predicate levelStepCall(Node nodeFrom, LocalSourceNode nodeTo) {
instanceFieldStep(nodeFrom, nodeTo)
}
predicate levelStepCall(Node nodeFrom, LocalSourceNode nodeTo) { none() }
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */
predicate levelStepNoCall(Node nodeFrom, LocalSourceNode nodeTo) {
TypeTrackerSummaryFlow::levelStepNoCall(nodeFrom, nodeTo)
or
localFieldStep(nodeFrom, nodeTo)
}
/**
@@ -321,87 +317,6 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> {
)
}
/**
* Holds if `ref` accesses attribute `attr` of `self`, where `self` is the first
* parameter of an instance method of `cls` (i.e. an access of the form `self.attr`).
*
* Static methods and class methods are excluded, since their first parameter is not a
* `self` instance reference.
*/
private predicate selfAttrRef(Class cls, string attr, DataFlowPublic::AttrRef ref) {
exists(Function method, Name selfUse |
method = cls.getAMethod() and
not DataFlowDispatch::isStaticmethod(method) and
not DataFlowDispatch::isClassmethod(method) and
selfUse.getVariable() = method.getArg(0).(Name).getVariable() and
ref.getObject().asCfgNode().getNode() = selfUse and
ref.mayHaveAttributeName(attr)
)
}
/**
* Holds if `read` reads attribute `attr` from an instance of `cls`, where the instance
* is referred to from outside the methods of `cls` (i.e. an access of the form
* `instance.attr`, where `instance` is a reference to an instance of `cls`).
*
* This complements `selfAttrRef`, which only handles `self.attr` accesses inside the
* methods of `cls`. Unlike `selfAttrRef`, this depends on the call graph (via
* `classInstanceTracker`), so steps using it must be reported as `levelStepCall`.
*/
private predicate instanceAttrRead(Class cls, string attr, DataFlowPublic::AttrRead read) {
read.getObject() = DataFlowDispatch::classInstanceTracker(cls) and
read.mayHaveAttributeName(attr)
}
/**
* Holds if `nodeFrom` is written to attribute `self.attr` in some instance method of a
* class, and `nodeTo` reads attribute `self.attr` in some (possibly different) instance
* method of the same class.
*
* This models flow through instance attributes (`self.foo`): a value stored into
* `self.foo` in one method can be read from `self.foo` in another method. Type-tracking
* handles the store and read steps via `AttrWrite`/`AttrRead`, but on its own it cannot
* relate the `self` of the writing method to the `self` of the reading method. Following
* the approach used for Ruby and JavaScript, we model this directly as a level step from
* the written value to the read reference, for any pair of methods on the class (not
* just from `__init__`).
*
* This is an over-approximation: it is instance-insensitive (it does not distinguish
* between different instances of the same class) and order-insensitive (it does not
* require the write to happen before the read), matching the precision of
* instance-attribute handling for Ruby and JavaScript.
*/
private predicate localFieldStep(Node nodeFrom, LocalSourceNode nodeTo) {
exists(Class cls, string attr, DataFlowPublic::AttrWrite write, DataFlowPublic::AttrRead read |
selfAttrRef(cls, attr, write) and
nodeFrom = write.getValue() and
selfAttrRef(cls, attr, read) and
nodeTo = read
)
}
/**
* Holds if `nodeFrom` is written to attribute `self.attr` in some instance method of a
* class, and `nodeTo` reads attribute `attr` from an instance of the same class outside
* its methods (e.g. `instance.attr`).
*
* This is the cross-instance counterpart of `localFieldStep`: it relates a write of
* `self.attr` inside the class to a read of `attr` on a reference to an instance of the
* class. Identifying instances relies on the call graph (via `classInstanceTracker`), so
* this step is reported as `levelStepCall` rather than `levelStepNoCall`.
*
* Like `localFieldStep`, this is an over-approximation: it is both instance-insensitive
* and order-insensitive.
*/
private predicate instanceFieldStep(Node nodeFrom, LocalSourceNode nodeTo) {
exists(Class cls, string attr, DataFlowPublic::AttrWrite write, DataFlowPublic::AttrRead read |
selfAttrRef(cls, attr, write) and
nodeFrom = write.getValue() and
instanceAttrRead(cls, attr, read) and
nodeTo = read
)
}
/**
* Holds if data can flow from `node1` to `node2` in a way that discards call contexts.
*/

View File

@@ -1,6 +1,9 @@
testFailures
debug_callableNotUnique
pointsTo_found_typeTracker_notFound
| code/class_attr_assign.py:10:9:10:27 | ControlFlowNode for Attribute() | my_func |
| code/class_attr_assign.py:11:9:11:25 | ControlFlowNode for Attribute() | my_func |
| code/class_attr_assign.py:26:9:26:25 | ControlFlowNode for Attribute() | DummyObject.method |
| code/class_super.py:50:1:50:6 | ControlFlowNode for Attribute() | outside_def |
| code/conditional_in_argument.py:18:5:18:11 | ControlFlowNode for Attribute() | X.bar |
| code/func_defined_outside_class.py:21:1:21:11 | ControlFlowNode for Attribute() | A.foo |

View File

@@ -7,8 +7,8 @@ class Foo(object):
self.direct_ref = my_func
def later(self):
self.indirect_ref() # $ pt=my_func tt=my_func
self.direct_ref() # $ pt=my_func tt=my_func
self.indirect_ref() # $ pt=my_func MISSING: tt=my_func
self.direct_ref() # $ pt=my_func MISSING: tt=my_func
foo = Foo(my_func) # $ tt=Foo.__init__
foo.later() # $ pt,tt=Foo.later
@@ -23,7 +23,7 @@ class Bar(object):
self.obj = DummyObject()
def later(self):
self.obj.method() # $ pt=DummyObject.method tt=DummyObject.method
self.obj.method() # $ pt=DummyObject.method MISSING: tt=DummyObject.method
bar = Bar(my_func) # $ tt=Bar.__init__

View File

@@ -151,10 +151,10 @@ class MyClass2(object):
self.foo = tracked # $ tracked=foo tracked
def print_foo(self): # $ MISSING: tracked=foo
print(self.foo) # $ tracked MISSING: tracked=foo
print(self.foo) # $ MISSING: tracked=foo tracked
def possibly_uncalled_method(self): # $ MISSING: tracked=foo
print(self.foo) # $ tracked MISSING: tracked=foo
print(self.foo) # $ MISSING: tracked=foo tracked
instance = MyClass2()
print(instance.foo) # $ MISSING: tracked=foo tracked

View File

@@ -9,28 +9,47 @@ cursor.executemany("some sql", (42,)) # $ getSql="some sql"
cursor.close()
# Connection stored in a class attribute (`self._conn`) and used in another method.
#
# This is detected because type tracking includes a level step modelling flow through
# instance attributes: a value written to `self._conn` in one method (here `__init__`) can
# be read back from `self._conn` (directly or via a getter) in any other method on the same
# class. This follows the same approach used for instance fields in Ruby and JavaScript.
class Database:
# ---------------------------------------------------------------------------
# Connection stored in a class attribute and accessed via various patterns
# ---------------------------------------------------------------------------
class WrapperA:
def __init__(self):
self._conn = dbapi.connect(address="hostname", port=300, user="username")
self._conn = dbapi.connect(address="hostname", port=300, user="username", pass_arg="testpass")
def get_connection(self):
return self._conn
def run_via_getter(self):
conn = self.get_connection()
cursor = conn.cursor()
cursor.execute("getter sql") # $ getSql="getter sql"
def run_direct(self):
self._conn.execute("direct sql") # $ getSql="direct sql"
# Getter called on a fresh constructor result
conn_a1 = WrapperA().get_connection()
cursor_a1 = conn_a1.cursor()
cursor_a1.execute("some sql", (42,)) # $ MISSING: getSql="some sql"
# Getter called via a stored wrapper instance
wrapper_instance = WrapperA()
conn_a2 = wrapper_instance.get_connection()
cursor_a2 = conn_a2.cursor()
cursor_a2.execute("some sql", (42,)) # $ MISSING: getSql="some sql"
# Direct attribute access on a fresh constructor result
conn_b = WrapperA()._conn
cursor_b = conn_b.cursor()
cursor_b.execute("some sql", (42,)) # $ MISSING: getSql="some sql"
db = Database()
db.run_via_getter()
db.run_direct()
class WrapperB:
"""Stores the connection under a different attribute name."""
def __init__(self):
self._hana = dbapi.connect(address="hostname", port=300, user="username", pass_arg="testpass")
def cursor(self):
return self._hana.cursor()
# Direct attribute access on a stored instance (mirrors hdb_con3 in the issue)
conn_c = WrapperB()._hana
cursor_c = conn_c.cursor()
cursor_c.execute("some sql", (42,)) # $ MISSING: getSql="some sql"

View File

@@ -1,36 +1,4 @@
#select
| app.py:23:20:23:24 | ControlFlowNode for query | app.py:20:18:20:21 | ControlFlowNode for name | app.py:23:20:23:24 | ControlFlowNode for query | This SQL query depends on a $@. | app.py:20:18:20:21 | ControlFlowNode for name | user-provided value |
| app.py:30:20:30:24 | ControlFlowNode for query | app.py:27:19:27:22 | ControlFlowNode for name | app.py:30:20:30:24 | ControlFlowNode for query | This SQL query depends on a $@. | app.py:27:19:27:22 | ControlFlowNode for name | user-provided value |
| app.py:37:20:37:24 | ControlFlowNode for query | app.py:34:19:34:22 | ControlFlowNode for name | app.py:37:20:37:24 | ControlFlowNode for query | This SQL query depends on a $@. | app.py:34:19:34:22 | ControlFlowNode for name | user-provided value |
| app.py:44:20:44:24 | ControlFlowNode for query | app.py:41:19:41:22 | ControlFlowNode for name | app.py:44:20:44:24 | ControlFlowNode for query | This SQL query depends on a $@. | app.py:41:19:41:22 | ControlFlowNode for name | user-provided value |
| app.py:51:20:51:24 | ControlFlowNode for query | app.py:48:19:48:22 | ControlFlowNode for name | app.py:51:20:51:24 | ControlFlowNode for query | This SQL query depends on a $@. | app.py:48:19:48:22 | ControlFlowNode for name | user-provided value |
| sql_injection.py:21:24:21:77 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:21:24:21:77 | ControlFlowNode for BinaryExpr | This SQL query depends on a $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | user-provided value |
| sql_injection.py:24:38:24:95 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:24:38:24:95 | ControlFlowNode for BinaryExpr | This SQL query depends on a $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | user-provided value |
| sql_injection.py:25:26:25:83 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:25:26:25:83 | ControlFlowNode for BinaryExpr | This SQL query depends on a $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | user-provided value |
| sql_injection.py:26:28:26:85 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:26:28:26:85 | ControlFlowNode for BinaryExpr | This SQL query depends on a $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:27:28:27:87 | ControlFlowNode for Attribute() | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:27:28:27:87 | ControlFlowNode for Attribute() | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:31:50:31:72 | ControlFlowNode for Attribute() | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:31:50:31:72 | ControlFlowNode for Attribute() | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:41:26:41:33 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:41:26:41:33 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:42:31:42:38 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:42:31:42:38 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:43:30:43:37 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:43:30:43:37 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:44:35:44:42 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:44:35:44:42 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:45:41:45:48 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:45:41:45:48 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:46:46:46:53 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:46:46:46:53 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:47:47:47:54 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:47:47:47:54 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:48:52:48:59 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:48:52:48:59 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:50:18:50:25 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:50:18:50:25 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:51:24:51:31 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:51:24:51:31 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
edges
| app.py:20:18:20:21 | ControlFlowNode for name | app.py:21:5:21:9 | ControlFlowNode for query | provenance | |
| app.py:21:5:21:9 | ControlFlowNode for query | app.py:23:20:23:24 | ControlFlowNode for query | provenance | |
| app.py:27:19:27:22 | ControlFlowNode for name | app.py:28:5:28:9 | ControlFlowNode for query | provenance | |
| app.py:28:5:28:9 | ControlFlowNode for query | app.py:30:20:30:24 | ControlFlowNode for query | provenance | |
| app.py:34:19:34:22 | ControlFlowNode for name | app.py:35:5:35:9 | ControlFlowNode for query | provenance | |
| app.py:35:5:35:9 | ControlFlowNode for query | app.py:37:20:37:24 | ControlFlowNode for query | provenance | |
| app.py:41:19:41:22 | ControlFlowNode for name | app.py:42:5:42:9 | ControlFlowNode for query | provenance | |
| app.py:42:5:42:9 | ControlFlowNode for query | app.py:44:20:44:24 | ControlFlowNode for query | provenance | |
| app.py:48:19:48:22 | ControlFlowNode for name | app.py:49:5:49:9 | ControlFlowNode for query | provenance | |
| app.py:49:5:49:9 | ControlFlowNode for query | app.py:51:20:51:24 | ControlFlowNode for query | provenance | |
| sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:21:24:21:77 | ControlFlowNode for BinaryExpr | provenance | |
| sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:24:38:24:95 | ControlFlowNode for BinaryExpr | provenance | |
| sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:25:26:25:83 | ControlFlowNode for BinaryExpr | provenance | |
@@ -48,21 +16,6 @@ edges
| sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:50:18:50:25 | ControlFlowNode for username | provenance | |
| sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:51:24:51:31 | ControlFlowNode for username | provenance | |
nodes
| app.py:20:18:20:21 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
| app.py:21:5:21:9 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| app.py:23:20:23:24 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| app.py:27:19:27:22 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
| app.py:28:5:28:9 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| app.py:30:20:30:24 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| app.py:34:19:34:22 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
| app.py:35:5:35:9 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| app.py:37:20:37:24 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| app.py:41:19:41:22 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
| app.py:42:5:42:9 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| app.py:44:20:44:24 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| app.py:48:19:48:22 | ControlFlowNode for name | semmle.label | ControlFlowNode for name |
| app.py:49:5:49:9 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| app.py:51:20:51:24 | ControlFlowNode for query | semmle.label | ControlFlowNode for query |
| sql_injection.py:14:15:14:22 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
| sql_injection.py:21:24:21:77 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
| sql_injection.py:24:38:24:95 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
@@ -82,3 +35,20 @@ nodes
| sqlalchemy_textclause.py:50:18:50:25 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
| sqlalchemy_textclause.py:51:24:51:31 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
subpaths
#select
| sql_injection.py:21:24:21:77 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:21:24:21:77 | ControlFlowNode for BinaryExpr | This SQL query depends on a $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | user-provided value |
| sql_injection.py:24:38:24:95 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:24:38:24:95 | ControlFlowNode for BinaryExpr | This SQL query depends on a $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | user-provided value |
| sql_injection.py:25:26:25:83 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:25:26:25:83 | ControlFlowNode for BinaryExpr | This SQL query depends on a $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | user-provided value |
| sql_injection.py:26:28:26:85 | ControlFlowNode for BinaryExpr | sql_injection.py:14:15:14:22 | ControlFlowNode for username | sql_injection.py:26:28:26:85 | ControlFlowNode for BinaryExpr | This SQL query depends on a $@. | sql_injection.py:14:15:14:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:27:28:27:87 | ControlFlowNode for Attribute() | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:27:28:27:87 | ControlFlowNode for Attribute() | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:31:50:31:72 | ControlFlowNode for Attribute() | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:31:50:31:72 | ControlFlowNode for Attribute() | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:41:26:41:33 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:41:26:41:33 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:42:31:42:38 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:42:31:42:38 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:43:30:43:37 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:43:30:43:37 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:44:35:44:42 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:44:35:44:42 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:45:41:45:48 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:45:41:45:48 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:46:46:46:53 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:46:46:46:53 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:47:47:47:54 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:47:47:47:54 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:48:52:48:59 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:48:52:48:59 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:50:18:50:25 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:50:18:50:25 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |
| sqlalchemy_textclause.py:51:24:51:31 | ControlFlowNode for username | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | sqlalchemy_textclause.py:51:24:51:31 | ControlFlowNode for username | This SQL query depends on a $@. | sqlalchemy_textclause.py:23:15:23:22 | ControlFlowNode for username | user-provided value |

View File

@@ -1,2 +1 @@
query: Security/CWE-089/SqlInjection.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql
Security/CWE-089/SqlInjection.ql

View File

@@ -1,52 +0,0 @@
from fastapi import FastAPI
from hdbcli import dbapi
from db_connection import get_conn
from db_connection import hdb_con
from db_connection import hdb_con2
from db_connection import hdb_con3
app = FastAPI()
class DatabaseConnection:
def __init__(self):
self._conn = dbapi.connect(address='localhost', port=30015, user='system', password='Password123')
def get_conn(self):
return self._conn
db_connection = DatabaseConnection()
@app.get("/unsafe1/")
async def unsafe(name: str): # $ Source
query = "select * from users where name=" + name
cursor = hdb_con.cursor()
cursor.execute(query) # $ Alert
cursor.close()
@app.get("/unsafe2/")
async def unsafe2(name: str): # $ Source
query = "select * from users where name=" + name
cursor = hdb_con2.cursor()
cursor.execute(query) # $ Alert
cursor.close()
@app.get("/unsafe3/")
async def unsafe3(name: str): # $ Source
query = "select * from users where name=" + name
cursor = hdb_con3.cursor()
cursor.execute(query) # $ Alert
cursor.close()
@app.get("/unsafe4/")
async def unsafe4(name: str): # $ Source
query = "select * from users where name=" + name
cursor = get_conn().cursor()
cursor.execute(query) # $ Alert
cursor.close()
@app.get("/unsafe5/")
async def unsafe5(name: str): # $ Source
query = "select * from users where name=" + name
cursor = db_connection.get_conn().cursor()
cursor.execute(query) # $ Alert
cursor.close()

View File

@@ -1,24 +0,0 @@
from hdbcli import dbapi
from typing import Optional
hdb_con = dbapi.connect(address='localhost', port=30015, user='system', password='Password123')
class DatabaseConnection:
def __init__(self):
self._conn = dbapi.connect(address='localhost', port=30015, user='system', password='Password123')
def get_conn(self):
return self._conn
hdb_con2 = DatabaseConnection().get_conn()
hdb_con3 = DatabaseConnection()._conn
_hana_connection: Optional[DatabaseConnection] = None
def get_conn():
global _hana_connection
if _hana_connection is None:
_hana_connection = DatabaseConnection()
return _hana_connection.get_conn()

View File

@@ -11,19 +11,19 @@ class User(models.Model):
pass
@app.route("/users/<username>")
def show_user(username): # $ Source
def show_user(username):
with connection.cursor() as cursor:
# GOOD -- Using parameters
cursor.execute("SELECT * FROM users WHERE username = %s", username)
User.objects.raw("SELECT * FROM users WHERE username = %s", (username,))
# BAD -- Using string formatting
cursor.execute("SELECT * FROM users WHERE username = '%s'" % username) # $ Alert
cursor.execute("SELECT * FROM users WHERE username = '%s'" % username)
# BAD -- other ways of executing raw SQL code with string interpolation
User.objects.annotate(RawSQL("insert into names_file ('name') values ('%s')" % username)) # $ Alert
User.objects.raw("insert into names_file ('name') values ('%s')" % username) # $ Alert
User.objects.extra("insert into names_file ('name') values ('%s')" % username) # $ Alert
User.objects.annotate(RawSQL("insert into names_file ('name') values ('%s')" % username))
User.objects.raw("insert into names_file ('name') values ('%s')" % username)
User.objects.extra("insert into names_file ('name') values ('%s')" % username)
# BAD (but currently no custom query to find this)
#

View File

@@ -20,15 +20,15 @@ class User(Base):
@app.route("/users/<username>")
def show_user(username): # $ Source
def show_user(username):
session = sqlalchemy.orm.Session(engine)
# BAD, normal SQL injection
stmt = sqlalchemy.text("SELECT * FROM users WHERE username = '{}'".format(username)) # $ Alert
stmt = sqlalchemy.text("SELECT * FROM users WHERE username = '{}'".format(username))
results = session.execute(stmt).fetchall()
# BAD, allows SQL injection
username_formatted_for_sql = sqlalchemy.text("'{}'".format(username)) # $ Alert
username_formatted_for_sql = sqlalchemy.text("'{}'".format(username))
stmt = sqlalchemy.select(User).where(User.username == username_formatted_for_sql)
results = session.execute(stmt).scalars().all()
@@ -38,14 +38,14 @@ def show_user(username): # $ Source
# All of these should be flagged by query
t1 = sqlalchemy.text(username) # $ Alert
t2 = sqlalchemy.text(text=username) # $ Alert
t3 = sqlalchemy.sql.text(username) # $ Alert
t4 = sqlalchemy.sql.text(text=username) # $ Alert
t5 = sqlalchemy.sql.expression.text(username) # $ Alert
t6 = sqlalchemy.sql.expression.text(text=username) # $ Alert
t7 = sqlalchemy.sql.expression.TextClause(username) # $ Alert
t8 = sqlalchemy.sql.expression.TextClause(text=username) # $ Alert
t1 = sqlalchemy.text(username)
t2 = sqlalchemy.text(text=username)
t3 = sqlalchemy.sql.text(username)
t4 = sqlalchemy.sql.text(text=username)
t5 = sqlalchemy.sql.expression.text(username)
t6 = sqlalchemy.sql.expression.text(text=username)
t7 = sqlalchemy.sql.expression.TextClause(username)
t8 = sqlalchemy.sql.expression.TextClause(text=username)
t9 = db.text(username) # $ Alert
t10 = db.text(text=username) # $ Alert
t9 = db.text(username)
t10 = db.text(text=username)