Skip to content

Commit 1d582a4

Browse files
author
Alvaro Muñoz
committed
feat(model-generation): Add more model generation queries
Add new queries for finding reusable workflows that behave as summaries, sources or sinks. Add new query for finding composite actions that behave as sinks. Add `github.event.inputs` context to the regular expression matching input var accesses.
1 parent 334fda1 commit 1d582a4

7 files changed

+177
-3
lines changed

ql/lib/codeql/actions/Ast.qll

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,10 @@ private string jobsCtxRegex() {
369369

370370
private string envCtxRegex() { result = "\\benv\\.([A-Za-z0-9_-]+)\\b" }
371371

372-
private string inputsCtxRegex() { result = "\\binputs\\.([A-Za-z0-9_-]+)\\b" }
372+
private string inputsCtxRegex() {
373+
result = "\\binputs\\.([A-Za-z0-9_-]+)\\b" or
374+
result = "\\bgithub\\.event\\.inputs\\.([A-Za-z0-9_-]+)\\b"
375+
}
373376

374377
/**
375378
* Holds for an expression accesing the `steps` context.

ql/src/Security/CWE-020/CompositeActionSummaries.ql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,7 @@ module MyFlow = TaintTracking::Global<MyConfig>;
3131
import MyFlow::PathGraph
3232

3333
from MyFlow::PathNode source, MyFlow::PathNode sink
34-
where MyFlow::flowPath(source, sink)
34+
where
35+
MyFlow::flowPath(source, sink) and
36+
source.getNode().getLocation().getFile() = sink.getNode().getLocation().getFile()
3537
select sink.getNode(), source, sink, "Summary"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @name Composite Action Sinks
3+
* @description Actions passing input variables to expression injection sinks.
4+
* @kind path-problem
5+
* @problem.severity warning
6+
* @security-severity 9.3
7+
* @precision high
8+
* @id actions/composite-action-sinks
9+
* @tags actions
10+
* model-generator
11+
* external/cwe/cwe-020
12+
*/
13+
14+
import actions
15+
import codeql.actions.TaintTracking
16+
import codeql.actions.dataflow.FlowSources
17+
import codeql.actions.dataflow.ExternalFlow
18+
19+
private class ExpressionInjectionSink extends DataFlow::Node {
20+
ExpressionInjectionSink() {
21+
exists(RunExpr e | e.getScriptExpr() = this.asExpr()) or
22+
externallyDefinedSink(this, "expression-injection")
23+
}
24+
}
25+
26+
private module MyConfig implements DataFlow::ConfigSig {
27+
predicate isSource(DataFlow::Node source) {
28+
exists(CompositeActionStmt c | c.getInputsStmt().getInputExpr(_) = source.asExpr())
29+
}
30+
31+
predicate isSink(DataFlow::Node sink) { sink instanceof ExpressionInjectionSink }
32+
}
33+
34+
module MyFlow = TaintTracking::Global<MyConfig>;
35+
36+
import MyFlow::PathGraph
37+
38+
from MyFlow::PathNode source, MyFlow::PathNode sink
39+
where
40+
MyFlow::flowPath(source, sink) and
41+
source.getNode().getLocation().getFile() = sink.getNode().getLocation().getFile()
42+
select sink.getNode(), source, sink, "Sink"

ql/src/Security/CWE-020/CompositeActionsSources.ql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,7 @@ module MyFlow = TaintTracking::Global<MyConfig>;
4040
import MyFlow::PathGraph
4141

4242
from MyFlow::PathNode source, MyFlow::PathNode sink
43-
where MyFlow::flowPath(source, sink)
43+
where
44+
MyFlow::flowPath(source, sink) and
45+
source.getNode().getLocation().getFile() = sink.getNode().getLocation().getFile()
4446
select sink.getNode(), source, sink, "Source"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @name Reusable Workflow Sinks
3+
* @description Reusable Workflows passing parameters to an expression injection sink.
4+
* @kind path-problem
5+
* @problem.severity warning
6+
* @security-severity 9.3
7+
* @precision high
8+
* @id actions/reusable-wokflow-sinks
9+
* @tags actions
10+
* model-generator
11+
* external/cwe/cwe-020
12+
*/
13+
14+
import actions
15+
import codeql.actions.TaintTracking
16+
import codeql.actions.dataflow.FlowSources
17+
import codeql.actions.dataflow.ExternalFlow
18+
19+
private class ExpressionInjectionSink extends DataFlow::Node {
20+
ExpressionInjectionSink() {
21+
exists(RunExpr e | e.getScriptExpr() = this.asExpr()) or
22+
externallyDefinedSink(this, "expression-injection")
23+
}
24+
}
25+
26+
private module MyConfig implements DataFlow::ConfigSig {
27+
predicate isSource(DataFlow::Node source) {
28+
exists(ReusableWorkflowStmt w | w.getInputsStmt().getInputExpr(_) = source.asExpr())
29+
}
30+
31+
predicate isSink(DataFlow::Node sink) { sink instanceof ExpressionInjectionSink }
32+
}
33+
34+
module MyFlow = TaintTracking::Global<MyConfig>;
35+
36+
import MyFlow::PathGraph
37+
38+
from MyFlow::PathNode source, MyFlow::PathNode sink
39+
where
40+
MyFlow::flowPath(source, sink) and
41+
source.getNode().getLocation().getFile() = sink.getNode().getLocation().getFile()
42+
select sink.getNode(), source, sink, "Sink"
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @name Reusable Workflow Sources
3+
* @description Reusable Workflow that pass user-controlled data to their output variables.
4+
* @kind path-problem
5+
* @problem.severity warning
6+
* @security-severity 9.3
7+
* @precision high
8+
* @id actions/reusable-workflow-sources
9+
* @tags actions
10+
* model-generator
11+
* external/cwe/cwe-020
12+
*/
13+
14+
import actions
15+
import codeql.actions.TaintTracking
16+
import codeql.actions.dataflow.FlowSources
17+
import codeql.actions.dataflow.ExternalFlow
18+
19+
private module MyConfig implements DataFlow::ConfigSig {
20+
predicate isSource(DataFlow::Node source) {
21+
source instanceof RemoteFlowSource and
22+
not source instanceof DataFlow::ParameterNode and
23+
exists(ReusableWorkflowStmt w | w.getAChildNode*() = source.asExpr())
24+
}
25+
26+
predicate isSink(DataFlow::Node sink) {
27+
exists(ReusableWorkflowStmt w | w.getOutputsStmt().getOutputExpr(_) = sink.asExpr())
28+
}
29+
30+
predicate allowImplicitRead(DataFlow::Node node, DataFlow::ContentSet set) {
31+
allowImplicitRead(node, set)
32+
or
33+
isSink(node) and
34+
set instanceof DataFlow::FieldContent
35+
}
36+
}
37+
38+
module MyFlow = TaintTracking::Global<MyConfig>;
39+
40+
import MyFlow::PathGraph
41+
42+
from MyFlow::PathNode source, MyFlow::PathNode sink
43+
where
44+
MyFlow::flowPath(source, sink) and
45+
source.getNode().getLocation().getFile() = sink.getNode().getLocation().getFile()
46+
select sink.getNode(), source, sink, "Source"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @name Reusable Workflows Summaries
3+
* @description Reusable workflow that pass user-controlled data to their output variables.
4+
* @kind path-problem
5+
* @problem.severity warning
6+
* @security-severity 9.3
7+
* @precision high
8+
* @id actions/reusable-workflow-summaries
9+
* @tags actions
10+
* model-generator
11+
* external/cwe/cwe-020
12+
*/
13+
14+
import actions
15+
import codeql.actions.TaintTracking
16+
import codeql.actions.dataflow.FlowSources
17+
import codeql.actions.dataflow.ExternalFlow
18+
19+
private module MyConfig implements DataFlow::ConfigSig {
20+
predicate isSource(DataFlow::Node source) {
21+
exists(ReusableWorkflowStmt w | w.getInputsStmt().getInputExpr(_) = source.asExpr())
22+
}
23+
24+
predicate isSink(DataFlow::Node sink) {
25+
exists(ReusableWorkflowStmt w | w.getOutputsStmt().getOutputExpr(_) = sink.asExpr())
26+
}
27+
}
28+
29+
module MyFlow = TaintTracking::Global<MyConfig>;
30+
31+
import MyFlow::PathGraph
32+
33+
from MyFlow::PathNode source, MyFlow::PathNode sink
34+
where
35+
MyFlow::flowPath(source, sink) and
36+
source.getNode().getLocation().getFile() = sink.getNode().getLocation().getFile()
37+
select sink.getNode(), source, sink, "Summary"

0 commit comments

Comments
 (0)