Skip to content

Commit 3ce3cf4

Browse files
author
Alvaro Muñoz
committed
refactor common code to identify untrusted checkouts
1 parent 064c983 commit 3ce3cf4

File tree

4 files changed

+37
-71
lines changed

4 files changed

+37
-71
lines changed

ql/lib/codeql/actions/dataflow/FlowSources.qll

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -92,28 +92,7 @@ class GitCommandSource extends RemoteFlowSource, CommandSource {
9292

9393
GitCommandSource() {
9494
exists(Step checkout, string cmd_regex |
95-
// This should be:
96-
// source instanceof PRHeadCheckoutStep
97-
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
98-
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
99-
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
100-
(
101-
exists(Uses uses |
102-
checkout = uses and
103-
uses.getCallee() = "actions/checkout" and
104-
exists(uses.getArgument("ref")) and
105-
not uses.getArgument("ref").matches("%base%") and
106-
uses.getATriggerEvent().getName() = checkoutTriggers()
107-
)
108-
or
109-
checkout instanceof GitMutableRefCheckout
110-
or
111-
checkout instanceof GitSHACheckout
112-
or
113-
checkout instanceof GhMutableRefCheckout
114-
or
115-
checkout instanceof GhSHACheckout
116-
) and
95+
checkout instanceof SimplePRHeadCheckoutStep and
11796
this.asExpr() = run.getScript() and
11897
checkout.getAFollowingStep() = run and
11998
run.getScript().getAStmt() = cmd and
@@ -255,29 +234,7 @@ class ArtifactSource extends RemoteFlowSource, FileSource {
255234
private class CheckoutSource extends RemoteFlowSource, FileSource {
256235
Event event;
257236

258-
CheckoutSource() {
259-
// This should be:
260-
// source instanceof PRHeadCheckoutStep
261-
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
262-
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
263-
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
264-
exists(Uses uses |
265-
this.asExpr() = uses and
266-
uses.getCallee() = "actions/checkout" and
267-
exists(uses.getArgument("ref")) and
268-
not uses.getArgument("ref").matches("%base%") and
269-
event = uses.getATriggerEvent() and
270-
event.getName() = checkoutTriggers()
271-
)
272-
or
273-
this.asExpr() instanceof GitMutableRefCheckout
274-
or
275-
this.asExpr() instanceof GitSHACheckout
276-
or
277-
this.asExpr() instanceof GhMutableRefCheckout
278-
or
279-
this.asExpr() instanceof GhSHACheckout
280-
}
237+
CheckoutSource() { this.asExpr() instanceof SimplePRHeadCheckoutStep }
281238

282239
override string getSourceType() { result = "artifact" }
283240

ql/lib/codeql/actions/security/ArtifactPoisoningQuery.qll

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ private import codeql.actions.TaintTracking
33
import codeql.actions.DataFlow
44
import codeql.actions.dataflow.FlowSources
55
import codeql.actions.security.PoisonableSteps
6+
import codeql.actions.security.UntrustedCheckoutQuery
67

78
string unzipRegexp() { result = "(unzip|tar)\\s+.*" }
89

@@ -22,11 +23,10 @@ class GitHubDownloadArtifactActionStep extends UntrustedArtifactDownloadStep, Us
2223
exists(this.getArgument("github-token"))
2324
or
2425
// There is an artifact upload step in the same workflow which can be influenced by an attacker on a checkout step
25-
exists(LocalJob job, UsesStep checkout, UsesStep upload |
26+
exists(LocalJob job, SimplePRHeadCheckoutStep checkout, UsesStep upload |
2627
this.getEnclosingWorkflow().getAJob() = job and
2728
job.getAStep() = checkout and
28-
job.getATriggerEvent().getName() = "pull_request_target" and
29-
checkout.getCallee() = "actions/checkout" and
29+
checkout.getATriggerEvent().getName() = "pull_request_target" and
3030
checkout.getAFollowingStep() = upload and
3131
upload.getCallee() = "actions/upload-artifact"
3232
)
@@ -55,8 +55,10 @@ class DownloadArtifactActionStep extends UntrustedArtifactDownloadStep, UsesStep
5555
"ma-ve/action-download-artifact-with-retry"
5656
] and
5757
(
58-
not exists(this.getArgument(["branch", "branch_name"])) or
59-
not this.getArgument(["branch", "branch_name"]) = ["main", "master"]
58+
not exists(this.getArgument(["branch", "branch_name"]))
59+
or
60+
exists(this.getArgument(["branch", "branch_name"])) and
61+
this.getArgument("allow_forks") = "true"
6062
) and
6163
(
6264
not exists(this.getArgument(["commit", "commitHash", "commit_sha"])) or
@@ -74,7 +76,8 @@ class DownloadArtifactActionStep extends UntrustedArtifactDownloadStep, UsesStep
7476
) and
7577
(
7678
not exists(this.getArgument("pr")) or
77-
not this.getArgument("pr").matches("%github.event.pull_request.number%")
79+
not this.getArgument("pr")
80+
.matches(["%github.event.pull_request.number%", "%github.event.number%"])
7881
)
7982
}
8083

ql/lib/codeql/actions/security/OutputClobberingQuery.qll

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,7 @@ class OutputClobberingFromFileReadSink extends OutputClobberingSink {
2020
(
2121
step instanceof UntrustedArtifactDownloadStep
2222
or
23-
// This should be:
24-
// artifact instanceof PRHeadCheckoutStep
25-
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
26-
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
27-
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
28-
exists(Uses uses |
29-
step = uses and
30-
uses.getCallee() = "actions/checkout" and
31-
exists(uses.getArgument("ref")) and
32-
not uses.getArgument("ref").matches("%base%") and
33-
uses.getATriggerEvent().getName() = checkoutTriggers()
34-
)
35-
or
36-
step instanceof GitMutableRefCheckout
37-
or
38-
step instanceof GitSHACheckout
39-
or
40-
step instanceof GhMutableRefCheckout
41-
or
42-
step instanceof GhSHACheckout
23+
step instanceof SimplePRHeadCheckoutStep
4324
) and
4425
step.getAFollowingStep() = run and
4526
this.asExpr() = run.getScript() and

ql/lib/codeql/actions/security/UntrustedCheckoutQuery.qll

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,31 @@ predicate containsHeadRef(string s) {
193193
)
194194
}
195195

196+
class SimplePRHeadCheckoutStep extends Step {
197+
SimplePRHeadCheckoutStep() {
198+
// This should be:
199+
// artifact instanceof PRHeadCheckoutStep
200+
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
201+
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
202+
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
203+
exists(Uses uses |
204+
this = uses and
205+
uses.getCallee() = "actions/checkout" and
206+
exists(uses.getArgument("ref")) and
207+
not uses.getArgument("ref").matches("%base%") and
208+
uses.getATriggerEvent().getName() = checkoutTriggers()
209+
)
210+
or
211+
this instanceof GitMutableRefCheckout
212+
or
213+
this instanceof GitSHACheckout
214+
or
215+
this instanceof GhMutableRefCheckout
216+
or
217+
this instanceof GhSHACheckout
218+
}
219+
}
220+
196221
/** Checkout of a Pull Request HEAD */
197222
abstract class PRHeadCheckoutStep extends Step {
198223
abstract string getPath();

0 commit comments

Comments
 (0)