Skip to content

Commit bfb10a2

Browse files
committed
PS: Add flow through string subexpressions and accept test changes.
1 parent a1bd3a5 commit bfb10a2

File tree

4 files changed

+55
-11
lines changed

4 files changed

+55
-11
lines changed

powershell/ql/lib/semmle/code/powershell/controlflow/CfgNodes.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,7 @@ module ExprNodes {
965965

966966
override ExpandableSubExpr getExpr() { result = e }
967967

968-
ExprCfgNode getSubExpr() { e.hasCfgChild(e.getExpr(), this, result) }
968+
StmtNodes::StmtBlockCfgNode getSubExpr() { e.hasCfgChild(e.getExpr(), this, result) }
969969
}
970970

971971
private class UsingExprChildMapping extends ExprChildMapping, UsingExpr {

powershell/ql/lib/semmle/code/powershell/dataflow/internal/TaintTrackingPrivate.qll

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ private import TaintTrackingPublic
44
private import semmle.code.powershell.Cfg
55
private import semmle.code.powershell.dataflow.DataFlow
66
private import FlowSummaryImpl as FlowSummaryImpl
7+
private import PipelineReturns as PipelineReturns
78

89
/**
910
* Holds if `node` should be a sanitizer in all global taint flow configurations
@@ -21,6 +22,44 @@ predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::ContentSet c)
2122
c.isAnyPositional()
2223
}
2324

25+
private module ExpandableSubExprInput implements PipelineReturns::InputSig {
26+
additional predicate isSourceImpl(
27+
CfgNodes::ExprNodes::ExpandableSubExprCfgNode expandableSubExpr, CfgNodes::AstCfgNode source
28+
) {
29+
source = expandableSubExpr.getSubExpr()
30+
}
31+
32+
predicate isSource(CfgNodes::AstCfgNode source) { isSourceImpl(_, source) }
33+
}
34+
35+
/**
36+
* Holds if `nodeFrom` flows to `nodeTo` via string interpolation.
37+
*/
38+
predicate stringInterpolationTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
39+
// flow from $x to "Hello $x"
40+
exists(CfgNodes::ExprNodes::ExpandableStringExprCfgNode es |
41+
nodeFrom.asExpr() = es.getAnExpr() and
42+
nodeTo.asExpr() = es
43+
)
44+
or
45+
// Flow from $x to $($x)
46+
exists(
47+
CfgNodes::ExprNodes::ExpandableSubExprCfgNode es,
48+
CfgNodes::StmtNodes::StmtBlockCfgNode blockStmt
49+
|
50+
ExpandableSubExprInput::isSourceImpl(es, blockStmt) and
51+
nodeFrom.asExpr() = PipelineReturns::Make<ExpandableSubExprInput>::getAReturn(blockStmt) and
52+
nodeTo.asExpr() = es
53+
)
54+
}
55+
56+
predicate operationTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
57+
exists(CfgNodes::ExprNodes::OperationCfgNode op |
58+
op = nodeTo.asExpr() and
59+
op.getAnOperand() = nodeFrom.asExpr()
60+
)
61+
}
62+
2463
cached
2564
private module Cached {
2665
private import semmle.code.powershell.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon
@@ -36,16 +75,10 @@ private module Cached {
3675
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo, string model) {
3776
(
3877
// Flow from an operand to an operation
39-
exists(CfgNodes::ExprNodes::OperationCfgNode op |
40-
op = nodeTo.asExpr() and
41-
op.getAnOperand() = nodeFrom.asExpr()
42-
)
78+
operationTaintStep(nodeFrom, nodeTo)
4379
or
4480
// Flow through string interpolation
45-
exists(CfgNodes::ExprNodes::ExpandableStringExprCfgNode es |
46-
nodeFrom.asExpr() = es.getAnExpr() and
47-
nodeTo.asExpr() = es
48-
)
81+
stringInterpolationTaintStep(nodeFrom, nodeTo)
4982
or
5083
// Although flow through collections is modeled precisely using stores/reads, we still
5184
// allow flow out of a _tainted_ collection. This is needed in order to support taint-

powershell/ql/lib/semmle/code/powershell/dataflow/internal/TaintTrackingPublic.qll

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
private import powershell
2-
private import TaintTrackingPrivate
2+
private import TaintTrackingPrivate as Private
33
private import semmle.code.powershell.Cfg
44
private import semmle.code.powershell.dataflow.DataFlow
55

@@ -19,4 +19,8 @@ predicate localExprTaint(CfgNodes::ExprCfgNode e1, CfgNodes::ExprCfgNode e2) {
1919
localTaintStep*(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
2020
}
2121

22-
predicate localTaintStep = localTaintStepCached/2;
22+
predicate stringInterpolationTaintStep = Private::stringInterpolationTaintStep/2;
23+
24+
predicate operationTaintStep = Private::operationTaintStep/2;
25+
26+
predicate localTaintStep = Private::localTaintStepCached/2;

powershell/ql/test/query-tests/security/cwe-089/SqlInjection.expected

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ edges
44
| test.ps1:1:1:1:10 | userinput | test.ps1:17:24:17:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | provenance | |
55
| test.ps1:1:1:1:10 | userinput | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | provenance | |
66
| test.ps1:1:1:1:10 | userinput | test.ps1:78:13:78:59 | SELECT * FROM Customers WHERE id = $userinput | provenance | |
7+
| test.ps1:1:1:1:10 | userinput | test.ps1:128:28:128:37 | userinput | provenance | |
78
| test.ps1:1:14:1:45 | Call to read-host | test.ps1:1:1:1:10 | userinput | provenance | Src:MaD:0 |
89
| test.ps1:4:1:4:6 | query | test.ps1:5:72:5:77 | query | provenance | |
910
| test.ps1:8:1:8:6 | query | test.ps1:9:72:9:77 | query | provenance | |
1011
| test.ps1:72:1:72:11 | QueryConn2 [element Query] | test.ps1:81:15:81:25 | QueryConn2 | provenance | |
1112
| test.ps1:72:15:79:1 | ${...} [element Query] | test.ps1:72:1:72:11 | QueryConn2 [element Query] | provenance | |
1213
| test.ps1:78:13:78:59 | SELECT * FROM Customers WHERE id = $userinput | test.ps1:72:15:79:1 | ${...} [element Query] | provenance | |
14+
| test.ps1:121:9:121:56 | unvalidated | test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | provenance | |
15+
| test.ps1:128:28:128:37 | userinput | test.ps1:121:9:121:56 | unvalidated | provenance | |
1316
nodes
1417
| test.ps1:1:1:1:10 | userinput | semmle.label | userinput |
1518
| test.ps1:1:14:1:45 | Call to read-host | semmle.label | Call to read-host |
@@ -23,10 +26,14 @@ nodes
2326
| test.ps1:72:15:79:1 | ${...} [element Query] | semmle.label | ${...} [element Query] |
2427
| test.ps1:78:13:78:59 | SELECT * FROM Customers WHERE id = $userinput | semmle.label | SELECT * FROM Customers WHERE id = $userinput |
2528
| test.ps1:81:15:81:25 | QueryConn2 | semmle.label | QueryConn2 |
29+
| test.ps1:121:9:121:56 | unvalidated | semmle.label | unvalidated |
30+
| test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | semmle.label | SELECT * FROM Customers where id = $($unvalidated) |
31+
| test.ps1:128:28:128:37 | userinput | semmle.label | userinput |
2632
subpaths
2733
#select
2834
| test.ps1:5:72:5:77 | query | test.ps1:1:14:1:45 | Call to read-host | test.ps1:5:72:5:77 | query | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin |
2935
| test.ps1:9:72:9:77 | query | test.ps1:1:14:1:45 | Call to read-host | test.ps1:9:72:9:77 | query | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin |
3036
| test.ps1:17:24:17:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | test.ps1:1:14:1:45 | Call to read-host | test.ps1:17:24:17:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin |
3137
| test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | test.ps1:1:14:1:45 | Call to read-host | test.ps1:28:24:28:76 | SELECT * FROM MyTable WHERE MyColumn = '$userinput' | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin |
3238
| test.ps1:81:15:81:25 | QueryConn2 | test.ps1:1:14:1:45 | Call to read-host | test.ps1:81:15:81:25 | QueryConn2 | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin |
39+
| test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | test.ps1:1:14:1:45 | Call to read-host | test.ps1:125:92:125:143 | SELECT * FROM Customers where id = $($unvalidated) | This SQL query depends on a $@. | test.ps1:1:14:1:45 | Call to read-host | read from stdin |

0 commit comments

Comments
 (0)