mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge pull request #20162 from Napalys/python/global_variable_tracking
Python: Add jump steps for global variable nested field access
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Data flow tracking through global variables now supports nested field access patterns such as `global_var.obj.field`. This improves the precision of taint tracking analysis when data flows through complex global variable structures.
|
||||
@@ -64,6 +64,15 @@ abstract class AttrRef extends Node {
|
||||
abstract class AttrWrite extends AttrRef {
|
||||
/** Gets the data flow node corresponding to the value that is written to the attribute. */
|
||||
abstract Node getValue();
|
||||
|
||||
/**
|
||||
* Holds if this attribute write writes the attribute named `attrName` on object `object` with
|
||||
* value `value`.
|
||||
*/
|
||||
predicate writes(Node object, string attrName, Node value) {
|
||||
this.accesses(object, attrName) and
|
||||
this.getValue() = value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,7 +234,10 @@ private class ClassDefinitionAsAttrWrite extends AttrWrite, CfgNode {
|
||||
* - Dynamic attribute reads using `getattr`: `getattr(object, attr)`
|
||||
* - Qualified imports: `from module import attr as name`
|
||||
*/
|
||||
abstract class AttrRead extends AttrRef, Node, LocalSourceNode { }
|
||||
abstract class AttrRead extends AttrRef, Node, LocalSourceNode {
|
||||
/** Holds if this attribute read reads the attribute named `attrName` on the object `object`. */
|
||||
predicate reads(Node object, string attrName) { this.accesses(object, attrName) }
|
||||
}
|
||||
|
||||
/** A simple attribute read, e.g. `object.attr` */
|
||||
private class AttributeReadAsAttrRead extends AttrRead, CfgNode {
|
||||
|
||||
@@ -556,6 +556,75 @@ predicate runtimeJumpStep(Node nodeFrom, Node nodeTo) {
|
||||
nodeFrom.asCfgNode() = param.getDefault() and
|
||||
nodeTo.asCfgNode() = param.getDefiningNode()
|
||||
)
|
||||
or
|
||||
// Enhanced global variable field access tracking
|
||||
globalVariableNestedFieldJumpStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/** Helper predicate for `globalVariableNestedFieldJumpStep`. */
|
||||
pragma[nomagic]
|
||||
private predicate globalVariableAttrPathRead(
|
||||
ModuleVariableNode globalVar, string accessPath, AttrRead r, string attrName
|
||||
) {
|
||||
globalVariableAttrPathAtDepth(globalVar, accessPath, r.getObject(), _) and
|
||||
attrName = r.getAttributeName()
|
||||
}
|
||||
|
||||
/** Helper predicate for `globalVariableNestedFieldJumpStep`. */
|
||||
pragma[nomagic]
|
||||
private predicate globalVariableAttrPathWrite(
|
||||
ModuleVariableNode globalVar, string accessPath, AttrWrite w, string attrName
|
||||
) {
|
||||
globalVariableAttrPathAtDepth(globalVar, accessPath, w.getObject(), _) and
|
||||
attrName = w.getAttributeName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is a jump step from `nodeFrom` to `nodeTo` through global variable field access.
|
||||
* This supports tracking nested object field access through global variables like `app.obj.foo`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate globalVariableNestedFieldJumpStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(ModuleVariableNode globalVar, AttrWrite write, AttrRead read |
|
||||
// Match writes and reads on the same global variable attribute path
|
||||
exists(string accessPath, string attrName |
|
||||
globalVariableAttrPathRead(globalVar, accessPath, read, attrName) and
|
||||
globalVariableAttrPathWrite(globalVar, accessPath, write, attrName)
|
||||
) and
|
||||
nodeFrom = write.getValue() and
|
||||
nodeTo = read
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum depth for global variable nested attribute access.
|
||||
* Depth 1 = globalVar.foo, depth 2 = globalVar.foo.bar, depth 3 = globalVar.foo.bar.baz, etc.
|
||||
*/
|
||||
private int getMaxGlobalVariableDepth() { result = 2 }
|
||||
|
||||
/**
|
||||
* Holds if `node` is an attribute access path starting from global variable `globalVar` at specific `depth`.
|
||||
*/
|
||||
private predicate globalVariableAttrPathAtDepth(
|
||||
ModuleVariableNode globalVar, string accessPath, Node node, int depth
|
||||
) {
|
||||
// Base case: Direct global variable access (depth 0)
|
||||
depth = 0 and
|
||||
// We use `globalVar` instead of `globalVar.getAWrite()` due to some weirdness with how
|
||||
// attribute writes are handled in the global scope (see `GlobalAttributeAssignmentAsAttrWrite`).
|
||||
node in [globalVar.getARead(), globalVar] and
|
||||
accessPath = ""
|
||||
or
|
||||
exists(Node obj, string attrName, string parentAccessPath, int parentDepth |
|
||||
node.(AttrRead).reads(obj, attrName)
|
||||
or
|
||||
any(AttrWrite aw).writes(obj, attrName, node)
|
||||
|
|
||||
globalVariableAttrPathAtDepth(globalVar, parentAccessPath, obj, parentDepth) and
|
||||
accessPath = parentAccessPath + "." + attrName and
|
||||
depth = parentDepth + 1 and
|
||||
depth <= getMaxGlobalVariableDepth()
|
||||
)
|
||||
}
|
||||
|
||||
//--------
|
||||
|
||||
@@ -301,12 +301,12 @@ def set_to_source():
|
||||
@expects(4) # $ unresolved_call=expects(..) unresolved_call=expects(..)(..)
|
||||
def test_global_flow_to_class_attribute():
|
||||
inst = WithTuple2()
|
||||
SINK_F(WithTuple2.my_tuple[0])
|
||||
SINK_F(WithTuple2.my_tuple[0]) # $ SPURIOUS: flow="SOURCE, l:-5 -> WithTuple2.my_tuple[0]"
|
||||
SINK_F(inst.my_tuple[0])
|
||||
|
||||
set_to_source()
|
||||
|
||||
SINK(WithTuple2.my_tuple[0]) # $ MISSING: flow="SOURCE, l:-10 -> WithTuple2.my_tuple[0]"
|
||||
SINK(WithTuple2.my_tuple[0]) # $ flow="SOURCE, l:-10 -> WithTuple2.my_tuple[0]"
|
||||
SINK(inst.my_tuple[0]) # $ MISSING: flow="SOURCE, l:-11 -> inst.my_tuple[0]"
|
||||
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ SINK(fields_with_local_flow(SOURCE)) # $ flow="SOURCE -> fields_with_local_flow(
|
||||
class NestedObj(object):
|
||||
def __init__(self):
|
||||
self.obj = MyObj("OK")
|
||||
self.obj.foo = "default"
|
||||
|
||||
def getObj(self):
|
||||
return self.obj
|
||||
@@ -163,17 +164,31 @@ x2 = SOURCE
|
||||
a2 = NestedObj()
|
||||
a2.getObj().foo = x2
|
||||
SINK(a2.obj.foo) # $ flow="SOURCE, l:-3 -> a2.obj.foo"
|
||||
|
||||
# Global variable
|
||||
app = NestedObj()
|
||||
|
||||
def init_global():
|
||||
app.obj.foo = SOURCE
|
||||
|
||||
def read_global():
|
||||
return app.obj.foo
|
||||
|
||||
def test_global_nested_attributes():
|
||||
init_global()
|
||||
result = read_global()
|
||||
SINK(result) # $ flow="SOURCE, l:-8 -> result"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Global scope interaction
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
def func_defined_before():
|
||||
SINK(global_obj.foo) # $ MISSING: flow="SOURCE, l:+3 -> global_obj.foo"
|
||||
SINK(global_obj.foo) # $ flow="SOURCE, l:+3 -> global_obj.foo"
|
||||
|
||||
global_obj = MyObj(NONSOURCE)
|
||||
global_obj.foo = SOURCE
|
||||
SINK(global_obj.foo) # $ flow="SOURCE, l:-1 -> global_obj.foo"
|
||||
|
||||
def func_defined_after():
|
||||
SINK(global_obj.foo) # $ MISSING: flow="SOURCE, l:-4 -> global_obj.foo"
|
||||
SINK(global_obj.foo) # $ flow="SOURCE, l:-4 -> global_obj.foo"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
missingAnnotationOnSink
|
||||
testFailures
|
||||
@@ -1,4 +0,0 @@
|
||||
import python
|
||||
import utils.test.dataflow.DataflowQueryTest
|
||||
import semmle.python.security.dataflow.PathInjectionQuery
|
||||
import FromTaintTrackingStateConfig<PathInjectionConfig>
|
||||
@@ -1,4 +1,38 @@
|
||||
#select
|
||||
| fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | fastapi_path_injection.py:17:21:17:24 | ControlFlowNode for path | fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | This path depends on a $@. | fastapi_path_injection.py:17:21:17:24 | ControlFlowNode for path | user-provided value |
|
||||
| fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | fastapi_path_injection.py:26:21:26:24 | ControlFlowNode for path | fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | This path depends on a $@. | fastapi_path_injection.py:26:21:26:24 | ControlFlowNode for path | user-provided value |
|
||||
| fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | fastapi_path_injection.py:31:21:31:24 | ControlFlowNode for path | fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | This path depends on a $@. | fastapi_path_injection.py:31:21:31:24 | ControlFlowNode for path | user-provided value |
|
||||
| fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | fastapi_path_injection.py:48:21:48:24 | ControlFlowNode for path | fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | This path depends on a $@. | fastapi_path_injection.py:48:21:48:24 | ControlFlowNode for path | user-provided value |
|
||||
| flask_path_injection.py:21:32:21:38 | ControlFlowNode for dirname | flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_path_injection.py:21:32:21:38 | ControlFlowNode for dirname | This path depends on a $@. | flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:13:14:13:47 | ControlFlowNode for Attribute() | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:13:14:13:47 | ControlFlowNode for Attribute() | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:21:14:21:18 | ControlFlowNode for npath | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:21:14:21:18 | ControlFlowNode for npath | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:31:14:31:18 | ControlFlowNode for npath | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:31:14:31:18 | ControlFlowNode for npath | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:48:14:48:18 | ControlFlowNode for npath | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:48:14:48:18 | ControlFlowNode for npath | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:65:14:65:18 | ControlFlowNode for npath | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:65:14:65:18 | ControlFlowNode for npath | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:87:18:87:37 | ControlFlowNode for possibly_unsafe_path | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:87:18:87:37 | ControlFlowNode for possibly_unsafe_path | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:94:14:94:17 | ControlFlowNode for path | path_injection.py:91:20:91:25 | ControlFlowNode for foo_id | path_injection.py:94:14:94:17 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:91:20:91:25 | ControlFlowNode for foo_id | user-provided value |
|
||||
| path_injection.py:102:14:102:17 | ControlFlowNode for path | path_injection.py:98:20:98:22 | ControlFlowNode for foo | path_injection.py:102:14:102:17 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:98:20:98:22 | ControlFlowNode for foo | user-provided value |
|
||||
| path_injection.py:113:14:113:17 | ControlFlowNode for path | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:113:14:113:17 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:124:14:124:17 | ControlFlowNode for path | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:124:14:124:17 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:132:14:132:22 | ControlFlowNode for sanitized | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:132:14:132:22 | ControlFlowNode for sanitized | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:142:14:142:17 | ControlFlowNode for path | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:142:14:142:17 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:152:18:152:21 | ControlFlowNode for path | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:152:18:152:21 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| pathlib_use.py:14:5:14:5 | ControlFlowNode for p | pathlib_use.py:3:26:3:32 | ControlFlowNode for ImportMember | pathlib_use.py:14:5:14:5 | ControlFlowNode for p | This path depends on a $@. | pathlib_use.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| pathlib_use.py:17:5:17:6 | ControlFlowNode for p2 | pathlib_use.py:3:26:3:32 | ControlFlowNode for ImportMember | pathlib_use.py:17:5:17:6 | ControlFlowNode for p2 | This path depends on a $@. | pathlib_use.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:19:10:19:10 | ControlFlowNode for x | test.py:3:26:3:32 | ControlFlowNode for ImportMember | test.py:19:10:19:10 | ControlFlowNode for x | This path depends on a $@. | test.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:26:10:26:10 | ControlFlowNode for y | test.py:3:26:3:32 | ControlFlowNode for ImportMember | test.py:26:10:26:10 | ControlFlowNode for y | This path depends on a $@. | test.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:33:14:33:14 | ControlFlowNode for x | test.py:3:26:3:32 | ControlFlowNode for ImportMember | test.py:33:14:33:14 | ControlFlowNode for x | This path depends on a $@. | test.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:49:14:49:14 | ControlFlowNode for y | test.py:3:26:3:32 | ControlFlowNode for ImportMember | test.py:49:14:49:14 | ControlFlowNode for y | This path depends on a $@. | test.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
edges
|
||||
| fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | provenance | |
|
||||
| fastapi_path_injection.py:17:21:17:24 | ControlFlowNode for path | fastapi_path_injection.py:20:34:20:37 | ControlFlowNode for path | provenance | |
|
||||
| fastapi_path_injection.py:20:34:20:37 | ControlFlowNode for path | fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | provenance | |
|
||||
| fastapi_path_injection.py:26:21:26:24 | ControlFlowNode for path | fastapi_path_injection.py:27:34:27:37 | ControlFlowNode for path | provenance | |
|
||||
| fastapi_path_injection.py:27:34:27:37 | ControlFlowNode for path | fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | provenance | |
|
||||
| fastapi_path_injection.py:31:21:31:24 | ControlFlowNode for path | fastapi_path_injection.py:32:34:32:37 | ControlFlowNode for path | provenance | |
|
||||
| fastapi_path_injection.py:32:34:32:37 | ControlFlowNode for path | fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | provenance | |
|
||||
| fastapi_path_injection.py:48:21:48:24 | ControlFlowNode for path | fastapi_path_injection.py:49:45:49:48 | ControlFlowNode for path | provenance | |
|
||||
| fastapi_path_injection.py:49:45:49:48 | ControlFlowNode for path | fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | provenance | |
|
||||
| flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_path_injection.py:1:26:1:32 | ControlFlowNode for request | provenance | |
|
||||
| flask_path_injection.py:1:26:1:32 | ControlFlowNode for request | flask_path_injection.py:19:15:19:21 | ControlFlowNode for request | provenance | |
|
||||
| flask_path_injection.py:19:5:19:11 | ControlFlowNode for dirname | flask_path_injection.py:21:32:21:38 | ControlFlowNode for dirname | provenance | |
|
||||
@@ -122,6 +156,16 @@ edges
|
||||
| test.py:48:23:48:23 | ControlFlowNode for x | test.py:12:15:12:15 | ControlFlowNode for x | provenance | |
|
||||
| test.py:48:23:48:23 | ControlFlowNode for x | test.py:48:13:48:24 | ControlFlowNode for normalize() | provenance | Config |
|
||||
nodes
|
||||
| fastapi_path_injection.py:6:24:6:31 | ControlFlowNode for filepath | semmle.label | ControlFlowNode for filepath |
|
||||
| fastapi_path_injection.py:7:19:7:26 | ControlFlowNode for filepath | semmle.label | ControlFlowNode for filepath |
|
||||
| fastapi_path_injection.py:17:21:17:24 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
|
||||
| fastapi_path_injection.py:20:34:20:37 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
|
||||
| fastapi_path_injection.py:26:21:26:24 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
|
||||
| fastapi_path_injection.py:27:34:27:37 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
|
||||
| fastapi_path_injection.py:31:21:31:24 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
|
||||
| fastapi_path_injection.py:32:34:32:37 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
|
||||
| fastapi_path_injection.py:48:21:48:24 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
|
||||
| fastapi_path_injection.py:49:45:49:48 | ControlFlowNode for path | semmle.label | ControlFlowNode for path |
|
||||
| flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember |
|
||||
| flask_path_injection.py:1:26:1:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| flask_path_injection.py:19:5:19:11 | ControlFlowNode for dirname | semmle.label | ControlFlowNode for dirname |
|
||||
@@ -252,24 +296,3 @@ nodes
|
||||
subpaths
|
||||
| test.py:25:19:25:19 | ControlFlowNode for x | test.py:12:15:12:15 | ControlFlowNode for x | test.py:13:12:13:30 | ControlFlowNode for Attribute() | test.py:25:9:25:20 | ControlFlowNode for normalize() |
|
||||
| test.py:48:23:48:23 | ControlFlowNode for x | test.py:12:15:12:15 | ControlFlowNode for x | test.py:13:12:13:30 | ControlFlowNode for Attribute() | test.py:48:13:48:24 | ControlFlowNode for normalize() |
|
||||
#select
|
||||
| flask_path_injection.py:21:32:21:38 | ControlFlowNode for dirname | flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | flask_path_injection.py:21:32:21:38 | ControlFlowNode for dirname | This path depends on a $@. | flask_path_injection.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:13:14:13:47 | ControlFlowNode for Attribute() | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:13:14:13:47 | ControlFlowNode for Attribute() | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:21:14:21:18 | ControlFlowNode for npath | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:21:14:21:18 | ControlFlowNode for npath | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:31:14:31:18 | ControlFlowNode for npath | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:31:14:31:18 | ControlFlowNode for npath | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:48:14:48:18 | ControlFlowNode for npath | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:48:14:48:18 | ControlFlowNode for npath | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:65:14:65:18 | ControlFlowNode for npath | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:65:14:65:18 | ControlFlowNode for npath | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:87:18:87:37 | ControlFlowNode for possibly_unsafe_path | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:87:18:87:37 | ControlFlowNode for possibly_unsafe_path | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:94:14:94:17 | ControlFlowNode for path | path_injection.py:91:20:91:25 | ControlFlowNode for foo_id | path_injection.py:94:14:94:17 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:91:20:91:25 | ControlFlowNode for foo_id | user-provided value |
|
||||
| path_injection.py:102:14:102:17 | ControlFlowNode for path | path_injection.py:98:20:98:22 | ControlFlowNode for foo | path_injection.py:102:14:102:17 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:98:20:98:22 | ControlFlowNode for foo | user-provided value |
|
||||
| path_injection.py:113:14:113:17 | ControlFlowNode for path | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:113:14:113:17 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:124:14:124:17 | ControlFlowNode for path | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:124:14:124:17 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:132:14:132:22 | ControlFlowNode for sanitized | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:132:14:132:22 | ControlFlowNode for sanitized | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:142:14:142:17 | ControlFlowNode for path | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:142:14:142:17 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| path_injection.py:152:18:152:21 | ControlFlowNode for path | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | path_injection.py:152:18:152:21 | ControlFlowNode for path | This path depends on a $@. | path_injection.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| pathlib_use.py:14:5:14:5 | ControlFlowNode for p | pathlib_use.py:3:26:3:32 | ControlFlowNode for ImportMember | pathlib_use.py:14:5:14:5 | ControlFlowNode for p | This path depends on a $@. | pathlib_use.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| pathlib_use.py:17:5:17:6 | ControlFlowNode for p2 | pathlib_use.py:3:26:3:32 | ControlFlowNode for ImportMember | pathlib_use.py:17:5:17:6 | ControlFlowNode for p2 | This path depends on a $@. | pathlib_use.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:19:10:19:10 | ControlFlowNode for x | test.py:3:26:3:32 | ControlFlowNode for ImportMember | test.py:19:10:19:10 | ControlFlowNode for x | This path depends on a $@. | test.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:26:10:26:10 | ControlFlowNode for y | test.py:3:26:3:32 | ControlFlowNode for ImportMember | test.py:26:10:26:10 | ControlFlowNode for y | This path depends on a $@. | test.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:33:14:33:14 | ControlFlowNode for x | test.py:3:26:3:32 | ControlFlowNode for ImportMember | test.py:33:14:33:14 | ControlFlowNode for x | This path depends on a $@. | test.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
| test.py:49:14:49:14 | ControlFlowNode for y | test.py:3:26:3:32 | ControlFlowNode for ImportMember | test.py:49:14:49:14 | ControlFlowNode for y | This path depends on a $@. | test.py:3:26:3:32 | ControlFlowNode for ImportMember | user-provided value |
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
Security/CWE-022/PathInjection.ql
|
||||
query: Security/CWE-022/PathInjection.ql
|
||||
postprocess: utils/test/InlineExpectationsTestQuery.ql
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
from fastapi import FastAPI, Depends
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
class FileHandler:
|
||||
def get_data(self, filepath: str):
|
||||
with open(filepath, "r") as f: # $ Alert
|
||||
return f.readline()
|
||||
|
||||
file_handler = None
|
||||
|
||||
def init_file_handler():
|
||||
global file_handler
|
||||
file_handler = FileHandler()
|
||||
|
||||
@app.get("/file/")
|
||||
async def read_item(path: str): # $ Source
|
||||
if file_handler is None:
|
||||
init_file_handler()
|
||||
return file_handler.get_data(path)
|
||||
|
||||
def init_file_handler():
|
||||
return FileHandler()
|
||||
|
||||
@app.get("/file2/", dependencies=[Depends(init_file_handler)])
|
||||
async def read_item(path: str, file_handler: FileHandler = Depends()): # $ Source
|
||||
return file_handler.get_data(path)
|
||||
|
||||
|
||||
@app.get("/file3/", dependencies=[Depends(init_file_handler)])
|
||||
async def read_item(path: str): # $ Source
|
||||
return file_handler.get_data(path)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
def init_file_handler():
|
||||
app.state.file_handler1 = FileHandler()
|
||||
app.state.file_handler2 = FileHandler()
|
||||
|
||||
def get_data_source():
|
||||
return app.state.file_handler1
|
||||
|
||||
@app.get("/file4/")
|
||||
async def read_item(path: str, data_source=Depends(get_data_source)): # $ MISSING: Source
|
||||
return data_source.get_data(path)
|
||||
|
||||
@app.get("/file5/", dependencies=[Depends(init_file_handler)])
|
||||
async def read_item(path: str): # $ Source
|
||||
return app.state.file_handler2.get_data(path)
|
||||
@@ -1,4 +1,4 @@
|
||||
from flask import Flask, request, send_from_directory
|
||||
from flask import Flask, request, send_from_directory # $Source
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@@ -18,4 +18,4 @@ def download_file():
|
||||
def download_file():
|
||||
dirname = request.args.get('dirname', '')
|
||||
filename = request.args.get('filename', '')
|
||||
return send_from_directory(dirname, filename) # $result=BAD result=OK(filename)
|
||||
return send_from_directory(dirname, filename) # $ Alert
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
|
||||
from flask import Flask, request
|
||||
from flask import Flask, request # $ Source
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ STATIC_DIR = "/server/static/"
|
||||
@app.route("/path1")
|
||||
def path_injection():
|
||||
filename = request.args.get('filename', '')
|
||||
f = open(os.path.join(STATIC_DIR, filename)) # $result=BAD
|
||||
f = open(os.path.join(STATIC_DIR, filename)) # $ Alert
|
||||
|
||||
|
||||
@app.route("/path2")
|
||||
@@ -18,7 +18,7 @@ def path_injection():
|
||||
# Normalized, but not checked
|
||||
filename = request.args.get('filename', '')
|
||||
npath = os.path.normpath(os.path.join(STATIC_DIR, filename))
|
||||
f = open(npath) # $result=BAD
|
||||
f = open(npath) # $ Alert
|
||||
|
||||
|
||||
@app.route("/path3")
|
||||
@@ -28,7 +28,7 @@ def unsafe_path_normpath():
|
||||
npath = os.path.normpath(os.path.join(STATIC_DIR, filename))
|
||||
if npath.startswith(STATIC_DIR):
|
||||
pass
|
||||
f = open(npath) # $result=BAD
|
||||
f = open(npath) # $ Alert
|
||||
|
||||
|
||||
@app.route("/path4")
|
||||
@@ -45,7 +45,7 @@ def unsafe_path_realpath():
|
||||
# Normalized (by `realpath` that also follows symlinks), but not checked properly
|
||||
filename = request.args.get('filename', '')
|
||||
npath = os.path.realpath(os.path.join(STATIC_DIR, filename))
|
||||
f = open(npath) # $result=BAD
|
||||
f = open(npath) # $ Alert
|
||||
|
||||
|
||||
@app.route("/path6")
|
||||
@@ -62,7 +62,7 @@ def unsafe_path_abspath():
|
||||
# Normalized (by `abspath`), but not checked properly
|
||||
filename = request.args.get('filename', '')
|
||||
npath = os.path.abspath(os.path.join(STATIC_DIR, filename))
|
||||
f = open(npath) # $result=BAD
|
||||
f = open(npath) # $ Alert
|
||||
|
||||
|
||||
@app.route("/path7")
|
||||
@@ -84,22 +84,22 @@ def safe_path_abspath_tricky():
|
||||
filename = request.args.get('filename', '')
|
||||
possibly_unsafe_path = os.path.join(STATIC_DIR, filename)
|
||||
if os.path.abspath(possibly_unsafe_path).startswith(STATIC_DIR):
|
||||
f = open(possibly_unsafe_path) # $SPURIOUS: result=BAD
|
||||
f = open(possibly_unsafe_path) # $ SPURIOUS: Alert
|
||||
|
||||
|
||||
@app.route("/int-only/<int:foo_id>")
|
||||
def flask_int_only(foo_id):
|
||||
def flask_int_only(foo_id): # $ SPURIOUS: Source
|
||||
# This is OK, since the flask routing ensures that `foo_id` MUST be an integer.
|
||||
path = os.path.join(STATIC_DIR, foo_id)
|
||||
f = open(path) # $spurious: result=BAD
|
||||
f = open(path) # $ SPURIOUS: Alert
|
||||
|
||||
|
||||
@app.route("/not-path/<foo>")
|
||||
def flask_not_path(foo):
|
||||
def flask_not_path(foo): # $ Source
|
||||
# On UNIX systems, this is OK, since without being marked as `<path:foo>`, flask
|
||||
# routing ensures that `foo` cannot contain forward slashes (not by using %2F either).
|
||||
path = os.path.join(STATIC_DIR, foo)
|
||||
f = open(path) # $result=BAD // OK if only running on UNIX systems, NOT OK if could be running on windows
|
||||
f = open(path) # $ Alert // OK if only running on UNIX systems, NOT OK if could be running on windows
|
||||
|
||||
|
||||
@app.route("/no-dot-dot")
|
||||
@@ -110,7 +110,7 @@ def no_dot_dot():
|
||||
# handle if `filename` is an absolute path
|
||||
if '../' in path:
|
||||
return "not this time"
|
||||
f = open(path) # $result=BAD
|
||||
f = open(path) # $ Alert
|
||||
|
||||
|
||||
@app.route("/no-dot-dot-with-prefix")
|
||||
@@ -121,7 +121,7 @@ def no_dot_dot_with_prefix():
|
||||
# Therefore, for UNIX-only programs, the `../` check is enough to stop path injections.
|
||||
if '../' in path:
|
||||
return "not this time"
|
||||
f = open(path) # $result=BAD // OK if only running on UNIX systems, NOT OK if could be running on windows
|
||||
f = open(path) # $ Alert // OK if only running on UNIX systems, NOT OK if could be running on windows
|
||||
|
||||
|
||||
@app.route("/replace-slash")
|
||||
@@ -129,7 +129,7 @@ def replace_slash():
|
||||
filename = request.args.get('filename', '')
|
||||
path = os.path.join(STATIC_DIR, filename)
|
||||
sanitized = path.replace("/", "_")
|
||||
f = open(sanitized) # $result=BAD // OK if only running on UNIX systems, NOT OK if could be running on windows
|
||||
f = open(sanitized) # $ Alert // OK if only running on UNIX systems, NOT OK if could be running on windows
|
||||
|
||||
|
||||
@app.route("/stackoverflow-solution")
|
||||
@@ -139,7 +139,7 @@ def stackoverflow_solution():
|
||||
path = os.path.join(STATIC_DIR, filename)
|
||||
if os.path.commonprefix((os.path.realpath(path), STATIC_DIR)) != STATIC_DIR:
|
||||
return "not this time"
|
||||
f = open(path) # $SPURIOUS: result=BAD
|
||||
f = open(path) # $ SPURIOUS: Alert
|
||||
|
||||
|
||||
SAFE_FILES = ['foo', 'bar', 'baz']
|
||||
@@ -149,4 +149,4 @@ def safe_set_of_files():
|
||||
filename = request.args.get('filename', '')
|
||||
if filename in SAFE_FILES:
|
||||
path = os.path.join(STATIC_DIR, filename)
|
||||
f = open(path) # $SPURIOUS: result=BAD
|
||||
f = open(path) # $ SPURIOUS: Alert
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pathlib
|
||||
|
||||
from flask import Flask, request
|
||||
from flask import Flask, request # $ Source
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ STATIC_DIR = pathlib.Path("/server/static/")
|
||||
def path_injection():
|
||||
filename = request.args.get('filename', '')
|
||||
p = STATIC_DIR / filename
|
||||
p.open() # $ result=BAD
|
||||
p.open() # $ Alert
|
||||
|
||||
p2 = pathlib.Path(STATIC_DIR, filename)
|
||||
p2.open() # $ result=BAD
|
||||
p2.open() # $ Alert
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import os.path
|
||||
|
||||
from flask import Flask, request
|
||||
from flask import Flask, request # $ Source
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@@ -16,21 +16,21 @@ def normalize(x):
|
||||
@app.route("/path")
|
||||
def simple():
|
||||
x = source()
|
||||
open(x) # $result=BAD
|
||||
open(x) # $ Alert
|
||||
|
||||
|
||||
@app.route("/path")
|
||||
def normalization():
|
||||
x = source()
|
||||
y = normalize(x)
|
||||
open(y) # $result=BAD
|
||||
open(y) # $ Alert
|
||||
|
||||
|
||||
@app.route("/path")
|
||||
def check():
|
||||
x = source()
|
||||
if x.startswith("subfolder/"):
|
||||
open(x) # $result=BAD
|
||||
open(x) # $ Alert
|
||||
|
||||
|
||||
@app.route("/path")
|
||||
@@ -46,4 +46,4 @@ def check_then_normalize():
|
||||
x = source()
|
||||
if x.startswith("subfolder/"):
|
||||
y = normalize(x)
|
||||
open(y) # $result=BAD
|
||||
open(y) # $ Alert
|
||||
|
||||
Reference in New Issue
Block a user