|
| 1 | +/** |
| 2 | + * Provides default sources, sinks and sanitizers for detecting |
| 3 | + * modifications of a parameters default value, as well as extension points for adding your own. |
| 4 | + */ |
| 5 | + |
| 6 | +private import python |
| 7 | +private import semmle.python.dataflow.new.DataFlow |
| 8 | +private import semmle.python.dataflow.new.BarrierGuards |
| 9 | + |
| 10 | +/** |
| 11 | + * Provides default sources, sinks and sanitizers for detecting |
| 12 | + * "command injection" |
| 13 | + * vulnerabilities, as well as extension points for adding your own. |
| 14 | + */ |
| 15 | +module ModificationOfParameterWithDefault { |
| 16 | + /** |
| 17 | + * A data flow source for detecting modifications of a parameters default value, |
| 18 | + * that is a default value for some parameter. |
| 19 | + */ |
| 20 | + abstract class Source extends DataFlow::Node { |
| 21 | + /** Result is true if the default value is non-empty for this source and false if not. */ |
| 22 | + abstract boolean isNonEmpty(); |
| 23 | + } |
| 24 | + |
| 25 | + /** |
| 26 | + * A data flow sink for detecting modifications of a parameters default value, |
| 27 | + * that is a node representing a modification. |
| 28 | + */ |
| 29 | + abstract class Sink extends DataFlow::Node { } |
| 30 | + |
| 31 | + /** |
| 32 | + * A sanitizer for detecting modifications of a parameters default value |
| 33 | + * should determine if the node (which is perhaps about to be modified) |
| 34 | + * can be the default value or not. |
| 35 | + * |
| 36 | + * In this query we do not track the default value exactly, but rather wheter |
| 37 | + * it is empty or not (see `Source`). |
| 38 | + * |
| 39 | + * This is the extension point for determining that a node must be empty and |
| 40 | + * therefor is allowed to be modified if the tracked default value is non-empty. |
| 41 | + */ |
| 42 | + abstract class MustBeEmpty extends DataFlow::Node { } |
| 43 | + |
| 44 | + /** |
| 45 | + * A sanitizer for detecting modifications of a parameters default value |
| 46 | + * should determine if the node (which is perhaps about to be modified) |
| 47 | + * can be the default value or not. |
| 48 | + * |
| 49 | + * In this query we do not track the default value exactly, but rather wheter |
| 50 | + * it is empty or not (see `Source`). |
| 51 | + * |
| 52 | + * This is the extension point for determining that a node must be non-empty |
| 53 | + * and therefor is allowed to be modified if the tracked default value is empty. |
| 54 | + */ |
| 55 | + abstract class MustBeNonEmpty extends DataFlow::Node { } |
| 56 | + |
| 57 | + /** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */ |
| 58 | + private boolean mutableDefaultValue(Parameter p) { |
| 59 | + exists(Dict d | p.getDefault() = d | |
| 60 | + exists(d.getAKey()) and result = true |
| 61 | + or |
| 62 | + not exists(d.getAKey()) and result = false |
| 63 | + ) |
| 64 | + or |
| 65 | + exists(List l | p.getDefault() = l | |
| 66 | + exists(l.getAnElt()) and result = true |
| 67 | + or |
| 68 | + not exists(l.getAnElt()) and result = false |
| 69 | + ) |
| 70 | + } |
| 71 | + |
| 72 | + /** |
| 73 | + * A mutable default value for a parameter, considered as a flow source. |
| 74 | + */ |
| 75 | + class MutableDefaultValue extends Source { |
| 76 | + boolean nonEmpty; |
| 77 | + |
| 78 | + MutableDefaultValue() { nonEmpty = mutableDefaultValue(this.asCfgNode().(NameNode).getNode()) } |
| 79 | + |
| 80 | + override boolean isNonEmpty() { result = nonEmpty } |
| 81 | + } |
| 82 | + |
| 83 | + /** |
| 84 | + * A name of a list function that modifies the list. |
| 85 | + * See https://docs.python.org/3/tutorial/datastructures.html#more-on-lists |
| 86 | + */ |
| 87 | + string list_modifying_method() { |
| 88 | + result in ["append", "extend", "insert", "remove", "pop", "clear", "sort", "reverse"] |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * A name of a dict function that modifies the dict. |
| 93 | + * See https://docs.python.org/3/library/stdtypes.html#dict |
| 94 | + */ |
| 95 | + string dict_modifying_method() { result in ["clear", "pop", "popitem", "setdefault", "update"] } |
| 96 | + |
| 97 | + /** |
| 98 | + * A mutation of the default value is a flow sink. |
| 99 | + * |
| 100 | + * Syntactic constructs that modify a list are: |
| 101 | + * - s[i] = x |
| 102 | + * - s[i:j] = t |
| 103 | + * - del s[i:j] |
| 104 | + * - s[i:j:k] = t |
| 105 | + * - del s[i:j:k] |
| 106 | + * - s += t |
| 107 | + * - s *= n |
| 108 | + * See https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types |
| 109 | + * |
| 110 | + * Syntactic constructs that modify a dictionary are: |
| 111 | + * - d[key] = value |
| 112 | + * - del d[key] |
| 113 | + * - d |= other |
| 114 | + * See https://docs.python.org/3/library/stdtypes.html#dict |
| 115 | + * |
| 116 | + * These are all covered by: |
| 117 | + * - assignment to a subscript (includes slices) |
| 118 | + * - deletion of a subscript |
| 119 | + * - augmented assignment to the value |
| 120 | + */ |
| 121 | + class Mutation extends Sink { |
| 122 | + Mutation() { |
| 123 | + // assignment to a subscript (includes slices) |
| 124 | + exists(DefinitionNode d | d.(SubscriptNode).getObject() = this.asCfgNode()) |
| 125 | + or |
| 126 | + // deletion of a subscript |
| 127 | + exists(DeletionNode d | d.getTarget().(SubscriptNode).getObject() = this.asCfgNode()) |
| 128 | + or |
| 129 | + // augmented assignment to the value |
| 130 | + exists(AugAssign a | a.getTarget().getAFlowNode() = this.asCfgNode()) |
| 131 | + or |
| 132 | + // modifying function call |
| 133 | + exists(DataFlow::CallCfgNode c, DataFlow::AttrRead a | c.getFunction() = a | |
| 134 | + a.getObject() = this and |
| 135 | + a.getAttributeName() in [list_modifying_method(), dict_modifying_method()] |
| 136 | + ) |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + // This to reimplement some of the functionality of the DataFlow::BarrierGuard |
| 141 | + private import semmle.python.essa.SsaCompute |
| 142 | + |
| 143 | + /** |
| 144 | + * A data-flow node that is known to be either truthy or falsey. |
| 145 | + * |
| 146 | + * It handles the cases `if x` and `if not x`. |
| 147 | + * |
| 148 | + * For example, in the following code, `this` will be the `x` that is printed, |
| 149 | + * which we will know is truthy: |
| 150 | + * |
| 151 | + * ```py |
| 152 | + * if x: |
| 153 | + * print(x) |
| 154 | + * ``` |
| 155 | + */ |
| 156 | + private class MustBe extends DataFlow::Node { |
| 157 | + boolean truthy; |
| 158 | + |
| 159 | + MustBe() { |
| 160 | + exists(DataFlow::GuardNode guard, NameNode guarded, boolean branch | |
| 161 | + // case: if x |
| 162 | + guard = guarded and |
| 163 | + branch = truthy |
| 164 | + or |
| 165 | + // case: if not x |
| 166 | + guard.(UnaryExprNode).getNode().getOp() instanceof Not and |
| 167 | + guarded = guard.(UnaryExprNode).getOperand() and |
| 168 | + branch = truthy.booleanNot() |
| 169 | + | |
| 170 | + // guard controls this |
| 171 | + guard.controlsBlock(this.asCfgNode().getBasicBlock(), branch) and |
| 172 | + // there is a definition tying the guarded value to this |
| 173 | + exists(EssaDefinition def | |
| 174 | + AdjacentUses::useOfDef(def, this.asCfgNode()) and |
| 175 | + AdjacentUses::useOfDef(def, guarded) |
| 176 | + ) |
| 177 | + ) |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + /** Simple guard detecting truthy values. */ |
| 182 | + private class MustBeTruthy extends MustBe, MustBeNonEmpty { |
| 183 | + MustBeTruthy() { truthy = true } |
| 184 | + } |
| 185 | + |
| 186 | + /** Simple guard detecting falsey values. */ |
| 187 | + private class MustBeFalsey extends MustBe, MustBeEmpty { |
| 188 | + MustBeFalsey() { truthy = false } |
| 189 | + } |
| 190 | +} |
0 commit comments