|
3 | 3 | * @description Using user-supplied data in an OS command, without |
4 | 4 | * neutralizing special elements, can make code vulnerable |
5 | 5 | * to command injection. |
6 | | - * @kind problem |
| 6 | + * @kind path-problem |
7 | 7 | * @problem.severity error |
8 | 8 | * @security-severity 9.8 |
9 | 9 | * @precision low |
|
16 | 16 | import cpp |
17 | 17 | import semmle.code.cpp.security.CommandExecution |
18 | 18 | import semmle.code.cpp.security.Security |
19 | | -import semmle.code.cpp.security.TaintTracking |
| 19 | +import semmle.code.cpp.ir.dataflow.TaintTracking |
| 20 | +import semmle.code.cpp.ir.dataflow.TaintTracking2 |
| 21 | +import semmle.code.cpp.ir.IR |
| 22 | +import semmle.code.cpp.security.FlowSources |
| 23 | +import semmle.code.cpp.models.implementations.Strcat |
20 | 24 |
|
21 | | -from Expr taintedArg, Expr taintSource, string taintCause, string callChain |
| 25 | +Expr sinkAsArgumentIndirection(DataFlow::Node sink) { |
| 26 | + result = |
| 27 | + sink.asOperand() |
| 28 | + .(SideEffectOperand) |
| 29 | + .getAddressOperand() |
| 30 | + .getAnyDef() |
| 31 | + .getUnconvertedResultExpression() |
| 32 | +} |
| 33 | + |
| 34 | +predicate interestingConcatenation(DataFlow::Node fst, DataFlow::Node snd) { |
| 35 | + exists(FormattingFunctionCall call, int index, FormatLiteral literal | |
| 36 | + sinkAsArgumentIndirection(fst) = call.getConversionArgument(index) and |
| 37 | + snd.asDefiningArgument() = call.getOutputArgument(false) and |
| 38 | + literal = call.getFormat() and |
| 39 | + not literal.getConvSpecOffset(index) = 0 and |
| 40 | + ( |
| 41 | + literal.getConversionType(index) instanceof CharPointerType |
| 42 | + or |
| 43 | + literal.getConversionType(index).(PointerType).getBaseType() instanceof Wchar_t |
| 44 | + ) |
| 45 | + ) |
| 46 | + or |
| 47 | + // strcat and friends |
| 48 | + exists(StrcatFunction strcatFunc, CallInstruction call, ReadSideEffectInstruction rse | |
| 49 | + call.getStaticCallTarget() = strcatFunc and |
| 50 | + rse.getArgumentDef() = call.getArgument(strcatFunc.getParamSrc()) and |
| 51 | + fst.asOperand() = rse.getSideEffectOperand() and |
| 52 | + snd.asInstruction().(WriteSideEffectInstruction).getDestinationAddress() = |
| 53 | + call.getArgument(strcatFunc.getParamDest()) |
| 54 | + ) |
| 55 | + or |
| 56 | + exists(CallInstruction call, Operator op, ReadSideEffectInstruction rse | |
| 57 | + call.getStaticCallTarget() = op and |
| 58 | + op.hasQualifiedName("std", "operator+") and |
| 59 | + op.getType().(UserType).hasQualifiedName("std", "basic_string") and |
| 60 | + call.getArgument(1) = rse.getArgumentOperand().getAnyDef() and // left operand |
| 61 | + fst.asOperand() = rse.getSideEffectOperand() and |
| 62 | + call = |
| 63 | + snd.asInstruction() |
| 64 | + ) |
| 65 | +} |
| 66 | + |
| 67 | +// TODO: maybe we can drop this? |
| 68 | +class TaintToConcatenationConfiguration extends TaintTracking::Configuration { |
| 69 | + TaintToConcatenationConfiguration() { this = "TaintToConcatenationConfiguration" } |
| 70 | + |
| 71 | + override predicate isSource(DataFlow::Node source) { |
| 72 | + source instanceof FlowSource |
| 73 | + } |
| 74 | + |
| 75 | + override predicate isSink(DataFlow::Node sink) { |
| 76 | + interestingConcatenation(sink, _) |
| 77 | + } |
| 78 | + |
| 79 | + override int explorationLimit() { |
| 80 | + result = 10 |
| 81 | + } |
| 82 | +} |
| 83 | + |
| 84 | +class ExecTaintConfiguration extends TaintTracking::Configuration { |
| 85 | + ExecTaintConfiguration() { this = "ExecTaintConfiguration" } |
| 86 | + |
| 87 | + override predicate isSource(DataFlow::Node source) { |
| 88 | + interestingConcatenation(_, source) |
| 89 | + } |
| 90 | + |
| 91 | + override predicate isSink(DataFlow::Node sink) { |
| 92 | + shellCommand(sinkAsArgumentIndirection(sink), _) |
| 93 | + } |
| 94 | + |
| 95 | + override predicate isSanitizerOut(DataFlow::Node node) { |
| 96 | + isSink(node) // Prevent duplicates along a call chain, since `shellCommand` will include wrappers |
| 97 | + } |
| 98 | +} |
| 99 | + |
| 100 | +query predicate nodes = DataFlow::PathGraph::nodes/3; |
| 101 | + |
| 102 | +query predicate edges(DataFlow::PathNode a, DataFlow::PathNode b) { |
| 103 | + DataFlow::PathGraph::edges(a, b) or |
| 104 | + interestingConcatenation(a.getNode(), b.getNode()) and |
| 105 | + a.getConfiguration() instanceof TaintToConcatenationConfiguration and |
| 106 | + b.getConfiguration() instanceof ExecTaintConfiguration |
| 107 | +} |
| 108 | + |
| 109 | +query predicate pathExplore(DataFlow::PartialPathNode source, DataFlow::PartialPathNode node, int dist) { |
| 110 | + any(TaintToConcatenationConfiguration cfg).hasPartialFlow(source, node, dist) |
| 111 | +} |
| 112 | + |
| 113 | +query predicate pathExploreRev(DataFlow::PartialPathNode node, DataFlow::PartialPathNode sink, int dist) { |
| 114 | + any(TaintToConcatenationConfiguration cfg).hasPartialFlowRev(node, sink, dist) |
| 115 | +} |
| 116 | + |
| 117 | +from |
| 118 | + DataFlow::PathNode sourceNode, DataFlow::PathNode concatSink, DataFlow::PathNode concatSource, DataFlow::PathNode sinkNode, string taintCause, string callChain, |
| 119 | + TaintToConcatenationConfiguration conf1, ExecTaintConfiguration conf2 |
22 | 120 | where |
23 | | - shellCommand(taintedArg, callChain) and |
24 | | - tainted(taintSource, taintedArg) and |
25 | | - isUserInput(taintSource, taintCause) |
26 | | -select taintedArg, |
27 | | - "This argument to an OS command is derived from $@ and then passed to " + callChain, taintSource, |
28 | | - "user input (" + taintCause + ")" |
| 121 | + taintCause = sourceNode.getNode().(FlowSource).getSourceType() and |
| 122 | + conf1.hasFlowPath(sourceNode, concatSink) and // TODO: can we link these better? |
| 123 | + interestingConcatenation(concatSink.getNode(), concatSource.getNode()) and |
| 124 | + conf2.hasFlowPath(concatSource, sinkNode) and |
| 125 | + shellCommand(sinkAsArgumentIndirection(sinkNode.getNode()), callChain) |
| 126 | +select sinkAsArgumentIndirection(sinkNode.getNode()), sourceNode, sinkNode, |
| 127 | + "This argument to an OS command is derived from $@, dangerously concatenated into $@, and then passed to " + callChain, sourceNode, |
| 128 | + "user input (" + taintCause + ")", concatSource, concatSource.toString() |
0 commit comments