Skip to content

Commit 3aa4f7f

Browse files
author
Alvaro Muñoz
committed
feat(triggers): Add getEnclosingWorkflowStmt to Statement class
1 parent 4b9cec7 commit 3aa4f7f

File tree

4 files changed

+64
-12
lines changed

4 files changed

+64
-12
lines changed

ql/lib/codeql/actions/Ast.qll

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ class AstNode instanceof YamlNode {
2020
* A statement is a group of expressions and/or statements that you design to carry out a task or an action.
2121
* Any statement that can return a value is automatically qualified to be used as an expression.
2222
*/
23-
class Statement extends AstNode { }
23+
class Statement extends AstNode {
24+
/** Gets the workflow that this job is a part of. */
25+
WorkflowStmt getEnclosingWorkflowStmt() { exists(WorkflowStmt w | w.getAChildNode*() = result) }
26+
}
2427

2528
/**
2629
* An expression is any word or group of words or symbols that is a value. In programming, an expression is a value, or anything that executes and ends up being a value.
@@ -53,6 +56,14 @@ class WorkflowStmt extends Statement instanceof Actions::Workflow {
5356
JobStmt getAJobStmt() { result = super.getJob(_) }
5457

5558
JobStmt getJobStmt(string id) { result = super.getJob(id) }
59+
60+
predicate hasTriggerEvent(string trigger) {
61+
exists(YamlNode n | n = super.getOn().(YamlMappingLikeNode).getNode(trigger))
62+
}
63+
64+
string getATriggerEvent() {
65+
exists(YamlNode n | n = super.getOn().(YamlMappingLikeNode).getNode(result))
66+
}
5667
}
5768

5869
class ReusableWorkflowStmt extends WorkflowStmt {
@@ -122,9 +133,6 @@ class JobStmt extends Statement instanceof Actions::Job {
122133
*/
123134
string getId() { result = super.getId() }
124135

125-
/** Gets the workflow that this job is a part of. */
126-
WorkflowStmt getWorkflowStmt() { result = super.getWorkflow() }
127-
128136
/** Gets the step at the given index within this job. */
129137
StepStmt getStepStmt(int index) { result = super.getStep(index) }
130138

@@ -222,7 +230,7 @@ class StepUsesExpr extends StepStmt, UsesExpr {
222230
)
223231
or
224232
exists(Actions::WorkflowEnv env |
225-
env.getWorkflow() = this.getJobStmt().getWorkflowStmt() and
233+
env.getWorkflow() = this.getJobStmt().getEnclosingWorkflowStmt() and
226234
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
227235
)
228236
}
@@ -287,7 +295,7 @@ class JobUsesExpr extends UsesExpr instanceof YamlMapping {
287295
)
288296
or
289297
exists(Actions::WorkflowEnv env |
290-
env.getWorkflow() = this.getJobStmt().getWorkflowStmt() and
298+
env.getWorkflow() = this.getJobStmt().getEnclosingWorkflowStmt() and
291299
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
292300
)
293301
}
@@ -320,7 +328,7 @@ class RunExpr extends StepStmt, Expression {
320328
)
321329
or
322330
exists(Actions::WorkflowEnv env |
323-
env.getWorkflow() = this.getJobStmt().getWorkflowStmt() and
331+
env.getWorkflow() = this.getJobStmt().getEnclosingWorkflowStmt() and
324332
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
325333
)
326334
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @name Expression injection in Actions
3+
* @description Using user-controlled GitHub Actions contexts like `run:` or `script:` may allow a malicious
4+
* user to inject code into the GitHub action.
5+
* @kind path-problem
6+
* @problem.severity error
7+
* @security-severity 9
8+
* @precision high
9+
* @id actions/critical-expression-injection
10+
* @tags actions
11+
* security
12+
* external/cwe/cwe-094
13+
*/
14+
15+
import actions
16+
import codeql.actions.TaintTracking
17+
import codeql.actions.dataflow.FlowSources
18+
import codeql.actions.dataflow.ExternalFlow
19+
20+
private class ExpressionInjectionSink extends DataFlow::Node {
21+
ExpressionInjectionSink() {
22+
exists(RunExpr e | e.getScriptExpr() = this.asExpr()) or
23+
externallyDefinedSink(this, "expression-injection")
24+
}
25+
}
26+
27+
private module MyConfig implements DataFlow::ConfigSig {
28+
predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
29+
30+
predicate isSink(DataFlow::Node sink) { sink instanceof ExpressionInjectionSink }
31+
}
32+
33+
module MyFlow = TaintTracking::Global<MyConfig>;
34+
35+
import MyFlow::PathGraph
36+
37+
from MyFlow::PathNode source, MyFlow::PathNode sink
38+
where
39+
MyFlow::flowPath(source, sink) and
40+
source
41+
.getNode()
42+
.asExpr()
43+
.(Statement)
44+
.getEnclosingWorkflowStmt()
45+
.hasTriggerEvent("pull_request_target")
46+
select sink.getNode(), source, sink,
47+
"Potential expression injection, which may be controlled by an external user."

ql/src/Security/CWE-094/ExpressionInjection.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* user to inject code into the GitHub action.
55
* @kind path-problem
66
* @problem.severity warning
7-
* @security-severity 9.3
7+
* @security-severity 5.0
88
* @precision high
99
* @id actions/expression-injection
1010
* @tags actions

ql/src/test/.github/workflows/simple2.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
name: CI
22

3-
on:
4-
pull_request:
5-
branches:
6-
- main
3+
on: [pull_request_target, pull_request]
74

85
jobs:
96
changed_files:

0 commit comments

Comments
 (0)