Python: straight port of query

The old query uses `pointsTo` to limit the sinks
to methods on lists and dictionaries.
That constraint is omitted here which could hurt performance.
This commit is contained in:
Rasmus Lerchedahl Petersen
2021-08-24 16:34:12 +02:00
parent e3765ced78
commit e865a290de
5 changed files with 170 additions and 111 deletions

View File

@@ -12,88 +12,12 @@
*/
import python
import semmle.python.security.Paths
import semmle.python.functions.ModificationOfParameterWithDefault
import DataFlow::PathGraph
predicate safe_method(string name) {
name = "count" or
name = "index" or
name = "copy" or
name = "get" or
name = "has_key" or
name = "items" or
name = "keys" or
name = "values" or
name = "iteritems" or
name = "iterkeys" or
name = "itervalues" or
name = "__contains__" or
name = "__getitem__" or
name = "__getattribute__"
}
/** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */
private boolean mutableDefaultValue(Parameter p) {
exists(Dict d | p.getDefault() = d |
exists(d.getAKey()) and result = true
or
not exists(d.getAKey()) and result = false
)
or
exists(List l | p.getDefault() = l |
exists(l.getAnElt()) and result = true
or
not exists(l.getAnElt()) and result = false
)
}
class NonEmptyMutableValue extends TaintKind {
NonEmptyMutableValue() { this = "non-empty mutable value" }
}
class EmptyMutableValue extends TaintKind {
EmptyMutableValue() { this = "empty mutable value" }
override boolean booleanValue() { result = false }
}
class MutableDefaultValue extends TaintSource {
boolean nonEmpty;
MutableDefaultValue() { nonEmpty = mutableDefaultValue(this.(NameNode).getNode()) }
override string toString() { result = "mutable default value" }
override predicate isSourceOf(TaintKind kind) {
nonEmpty = false and kind instanceof EmptyMutableValue
or
nonEmpty = true and kind instanceof NonEmptyMutableValue
}
}
private ClassValue mutable_class() {
result = Value::named("list") or
result = Value::named("dict")
}
class Mutation extends TaintSink {
Mutation() {
exists(AugAssign a | a.getTarget().getAFlowNode() = this)
or
exists(Call c, Attribute a | c.getFunc() = a |
a.getObject().getAFlowNode() = this and
not safe_method(a.getName()) and
this.(ControlFlowNode).pointsTo().getClass() = mutable_class()
)
}
override predicate sinks(TaintKind kind) {
kind instanceof EmptyMutableValue
or
kind instanceof NonEmptyMutableValue
}
}
from TaintedPathSource src, TaintedPathSink sink
where src.flowsTo(sink)
select sink.getSink(), src, sink, "$@ flows to here and is mutated.", src.getSource(),
from
ModificationOfParameterWithDefault::Configuration config, DataFlow::PathNode source,
DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ flows to here and is mutated.", source.getNode(),
"Default value"

View File

@@ -0,0 +1,34 @@
/**
* Provides a data-flow configuration for detecting modifications of a parameters default value.
*
* Note, for performance reasons: only import this file if
* `ModificationOfParameterWithDefault::Configuration` is needed, otherwise
* `ModificationOfParameterWithDefaultCustomizations` should be imported instead.
*/
private import python
import semmle.python.dataflow.new.DataFlow
/**
* Provides a data-flow configuration for detecting modifications of a parameters default value.
*/
module ModificationOfParameterWithDefault {
import ModificationOfParameterWithDefaultCustomizations::ModificationOfParameterWithDefault
/**
* A data-flow configuration for detecting modifications of a parameters default value.
*/
class Configuration extends DataFlow::Configuration {
Configuration() { this = "ModificationOfParameterWithDefault" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
guard instanceof BarrierGuard
}
}
}

View File

@@ -0,0 +1,90 @@
/**
* Provides default sources, sinks and sanitizers for detecting
* modifications of a parameters default value, as well as extension points for adding your own.
*/
private import python
private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.BarrierGuards
/**
* Provides default sources, sinks and sanitizers for detecting
* "command injection"
* vulnerabilities, as well as extension points for adding your own.
*/
module ModificationOfParameterWithDefault {
/**
* A data flow source for detecting modifications of a parameters default value.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for detecting modifications of a parameters default value.
*/
abstract class Sink extends DataFlow::Node { }
/**
* A sanitizer for detecting modifications of a parameters default value.
*/
abstract class Barrier extends DataFlow::Node { }
/**
* A sanitizer guard for detecting modifications of a parameters default value.
*/
abstract class BarrierGuard extends DataFlow::BarrierGuard { }
/** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */
private boolean mutableDefaultValue(Parameter p) {
exists(Dict d | p.getDefault() = d |
exists(d.getAKey()) and result = true
or
not exists(d.getAKey()) and result = false
)
or
exists(List l | p.getDefault() = l |
exists(l.getAnElt()) and result = true
or
not exists(l.getAnElt()) and result = false
)
}
/**
* A source of remote user input, considered as a flow source.
*/
class MutableDefaultValue extends Source {
boolean nonEmpty;
MutableDefaultValue() { nonEmpty = mutableDefaultValue(this.asCfgNode().(NameNode).getNode()) }
}
predicate safe_method(string name) {
name = "count" or
name = "index" or
name = "copy" or
name = "get" or
name = "has_key" or
name = "items" or
name = "keys" or
name = "values" or
name = "iteritems" or
name = "iterkeys" or
name = "itervalues" or
name = "__contains__" or
name = "__getitem__" or
name = "__getattribute__"
}
/**
* A mutation is considered a flow sink.
*/
class Mutation extends Sink {
Mutation() {
exists(AugAssign a | a.getTarget().getAFlowNode() = this.asCfgNode())
or
exists(Call c, Attribute a | c.getFunc() = a |
a.getObject().getAFlowNode() = this.asCfgNode() and
not safe_method(a.getName())
)
}
}
}

View File

@@ -1,28 +1,39 @@
edges
| test.py:2:12:2:12 | empty mutable value | test.py:3:5:3:5 | empty mutable value |
| test.py:7:14:7:14 | empty mutable value | test.py:9:14:9:14 | empty mutable value |
| test.py:9:5:9:15 | empty mutable value | test.py:10:5:10:5 | empty mutable value |
| test.py:9:14:9:14 | empty mutable value | test.py:9:5:9:15 | empty mutable value |
| test.py:13:13:13:13 | empty mutable value | test.py:14:5:14:5 | empty mutable value |
| test.py:18:14:18:14 | empty mutable value | test.py:19:13:19:13 | empty mutable value |
| test.py:19:13:19:13 | empty mutable value | test.py:13:13:13:13 | empty mutable value |
| test.py:23:14:23:14 | non-empty mutable value | test.py:24:5:24:5 | non-empty mutable value |
| test.py:52:17:52:17 | empty mutable value | test.py:53:5:53:5 | empty mutable value |
| test.py:57:26:57:26 | non-empty mutable value | test.py:58:5:58:5 | non-empty mutable value |
| test.py:62:35:62:35 | non-empty mutable value | test.py:63:5:63:5 | non-empty mutable value |
| test.py:66:21:66:21 | empty mutable value | test.py:67:5:67:5 | empty mutable value |
| test.py:71:26:71:26 | empty mutable value | test.py:72:21:72:21 | empty mutable value |
| test.py:72:21:72:21 | empty mutable value | test.py:66:21:66:21 | empty mutable value |
| test.py:76:19:76:19 | empty mutable value | test.py:78:14:78:14 | empty mutable value |
| test.py:78:5:78:15 | empty mutable value | test.py:79:5:79:5 | empty mutable value |
| test.py:78:14:78:14 | empty mutable value | test.py:78:5:78:15 | empty mutable value |
| test.py:2:12:2:12 | ControlFlowNode for l | test.py:3:5:3:5 | ControlFlowNode for l |
| test.py:13:13:13:13 | ControlFlowNode for l | test.py:14:5:14:5 | ControlFlowNode for l |
| test.py:18:14:18:14 | ControlFlowNode for l | test.py:19:13:19:13 | ControlFlowNode for l |
| test.py:19:13:19:13 | ControlFlowNode for l | test.py:13:13:13:13 | ControlFlowNode for l |
| test.py:23:14:23:14 | ControlFlowNode for l | test.py:24:5:24:5 | ControlFlowNode for l |
| test.py:52:17:52:17 | ControlFlowNode for d | test.py:53:5:53:5 | ControlFlowNode for d |
| test.py:57:26:57:26 | ControlFlowNode for d | test.py:58:5:58:5 | ControlFlowNode for d |
| test.py:62:35:62:35 | ControlFlowNode for d | test.py:63:5:63:5 | ControlFlowNode for d |
| test.py:66:21:66:21 | ControlFlowNode for d | test.py:67:5:67:5 | ControlFlowNode for d |
| test.py:71:26:71:26 | ControlFlowNode for d | test.py:72:21:72:21 | ControlFlowNode for d |
| test.py:72:21:72:21 | ControlFlowNode for d | test.py:66:21:66:21 | ControlFlowNode for d |
nodes
| test.py:2:12:2:12 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:3:5:3:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:13:13:13:13 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:14:5:14:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:18:14:18:14 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:19:13:19:13 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:23:14:23:14 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:24:5:24:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:52:17:52:17 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:53:5:53:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:57:26:57:26 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:58:5:58:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:62:35:62:35 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:63:5:63:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:66:21:66:21 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:67:5:67:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:71:26:71:26 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:72:21:72:21 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
#select
| test.py:3:5:3:5 | l | test.py:2:12:2:12 | empty mutable value | test.py:3:5:3:5 | empty mutable value | $@ flows to here and is mutated. | test.py:2:12:2:12 | l | Default value |
| test.py:10:5:10:5 | x | test.py:7:14:7:14 | empty mutable value | test.py:10:5:10:5 | empty mutable value | $@ flows to here and is mutated. | test.py:7:14:7:14 | l | Default value |
| test.py:14:5:14:5 | l | test.py:18:14:18:14 | empty mutable value | test.py:14:5:14:5 | empty mutable value | $@ flows to here and is mutated. | test.py:18:14:18:14 | l | Default value |
| test.py:24:5:24:5 | l | test.py:23:14:23:14 | non-empty mutable value | test.py:24:5:24:5 | non-empty mutable value | $@ flows to here and is mutated. | test.py:23:14:23:14 | l | Default value |
| test.py:53:5:53:5 | d | test.py:52:17:52:17 | empty mutable value | test.py:53:5:53:5 | empty mutable value | $@ flows to here and is mutated. | test.py:52:17:52:17 | d | Default value |
| test.py:58:5:58:5 | d | test.py:57:26:57:26 | non-empty mutable value | test.py:58:5:58:5 | non-empty mutable value | $@ flows to here and is mutated. | test.py:57:26:57:26 | d | Default value |
| test.py:63:5:63:5 | d | test.py:62:35:62:35 | non-empty mutable value | test.py:63:5:63:5 | non-empty mutable value | $@ flows to here and is mutated. | test.py:62:35:62:35 | d | Default value |
| test.py:67:5:67:5 | d | test.py:71:26:71:26 | empty mutable value | test.py:67:5:67:5 | empty mutable value | $@ flows to here and is mutated. | test.py:71:26:71:26 | d | Default value |
| test.py:79:5:79:5 | x | test.py:76:19:76:19 | empty mutable value | test.py:79:5:79:5 | empty mutable value | $@ flows to here and is mutated. | test.py:76:19:76:19 | d | Default value |
| test.py:3:5:3:5 | ControlFlowNode for l | test.py:2:12:2:12 | ControlFlowNode for l | test.py:3:5:3:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:2:12:2:12 | ControlFlowNode for l | Default value |
| test.py:14:5:14:5 | ControlFlowNode for l | test.py:18:14:18:14 | ControlFlowNode for l | test.py:14:5:14:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:18:14:18:14 | ControlFlowNode for l | Default value |
| test.py:24:5:24:5 | ControlFlowNode for l | test.py:23:14:23:14 | ControlFlowNode for l | test.py:24:5:24:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:23:14:23:14 | ControlFlowNode for l | Default value |
| test.py:53:5:53:5 | ControlFlowNode for d | test.py:52:17:52:17 | ControlFlowNode for d | test.py:53:5:53:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:52:17:52:17 | ControlFlowNode for d | Default value |
| test.py:58:5:58:5 | ControlFlowNode for d | test.py:57:26:57:26 | ControlFlowNode for d | test.py:58:5:58:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:57:26:57:26 | ControlFlowNode for d | Default value |
| test.py:63:5:63:5 | ControlFlowNode for d | test.py:62:35:62:35 | ControlFlowNode for d | test.py:63:5:63:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:62:35:62:35 | ControlFlowNode for d | Default value |
| test.py:67:5:67:5 | ControlFlowNode for d | test.py:71:26:71:26 | ControlFlowNode for d | test.py:67:5:67:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:71:26:71:26 | ControlFlowNode for d | Default value |

View File

@@ -7,7 +7,7 @@ def simple(l = []):
def includes(l = []):
x = [0]
x.extend(l)
x.extend([1]) # FP
x.extend([1])
return x
def extends(l):
@@ -76,5 +76,5 @@ def dict_deferred_method(d = {}):
def dict_includes(d = {}):
x = {}
x.update(d)
x.update({'a': 1}) # FP
x.update({'a': 1})
return x