refactor common code to identify untrusted checkouts

This commit is contained in:
Alvaro Muñoz
2024-11-19 11:31:35 +01:00
parent 064c983b47
commit 3ce3cf43be
4 changed files with 37 additions and 71 deletions

View File

@@ -92,28 +92,7 @@ class GitCommandSource extends RemoteFlowSource, CommandSource {
GitCommandSource() { GitCommandSource() {
exists(Step checkout, string cmd_regex | exists(Step checkout, string cmd_regex |
// This should be: checkout instanceof SimplePRHeadCheckoutStep and
// source instanceof PRHeadCheckoutStep
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
(
exists(Uses uses |
checkout = uses and
uses.getCallee() = "actions/checkout" and
exists(uses.getArgument("ref")) and
not uses.getArgument("ref").matches("%base%") and
uses.getATriggerEvent().getName() = checkoutTriggers()
)
or
checkout instanceof GitMutableRefCheckout
or
checkout instanceof GitSHACheckout
or
checkout instanceof GhMutableRefCheckout
or
checkout instanceof GhSHACheckout
) and
this.asExpr() = run.getScript() and this.asExpr() = run.getScript() and
checkout.getAFollowingStep() = run and checkout.getAFollowingStep() = run and
run.getScript().getAStmt() = cmd and run.getScript().getAStmt() = cmd and
@@ -255,29 +234,7 @@ class ArtifactSource extends RemoteFlowSource, FileSource {
private class CheckoutSource extends RemoteFlowSource, FileSource { private class CheckoutSource extends RemoteFlowSource, FileSource {
Event event; Event event;
CheckoutSource() { CheckoutSource() { this.asExpr() instanceof SimplePRHeadCheckoutStep }
// This should be:
// source instanceof PRHeadCheckoutStep
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
exists(Uses uses |
this.asExpr() = uses and
uses.getCallee() = "actions/checkout" and
exists(uses.getArgument("ref")) and
not uses.getArgument("ref").matches("%base%") and
event = uses.getATriggerEvent() and
event.getName() = checkoutTriggers()
)
or
this.asExpr() instanceof GitMutableRefCheckout
or
this.asExpr() instanceof GitSHACheckout
or
this.asExpr() instanceof GhMutableRefCheckout
or
this.asExpr() instanceof GhSHACheckout
}
override string getSourceType() { result = "artifact" } override string getSourceType() { result = "artifact" }

View File

@@ -3,6 +3,7 @@ private import codeql.actions.TaintTracking
import codeql.actions.DataFlow import codeql.actions.DataFlow
import codeql.actions.dataflow.FlowSources import codeql.actions.dataflow.FlowSources
import codeql.actions.security.PoisonableSteps import codeql.actions.security.PoisonableSteps
import codeql.actions.security.UntrustedCheckoutQuery
string unzipRegexp() { result = "(unzip|tar)\\s+.*" } string unzipRegexp() { result = "(unzip|tar)\\s+.*" }
@@ -22,11 +23,10 @@ class GitHubDownloadArtifactActionStep extends UntrustedArtifactDownloadStep, Us
exists(this.getArgument("github-token")) exists(this.getArgument("github-token"))
or or
// There is an artifact upload step in the same workflow which can be influenced by an attacker on a checkout step // There is an artifact upload step in the same workflow which can be influenced by an attacker on a checkout step
exists(LocalJob job, UsesStep checkout, UsesStep upload | exists(LocalJob job, SimplePRHeadCheckoutStep checkout, UsesStep upload |
this.getEnclosingWorkflow().getAJob() = job and this.getEnclosingWorkflow().getAJob() = job and
job.getAStep() = checkout and job.getAStep() = checkout and
job.getATriggerEvent().getName() = "pull_request_target" and checkout.getATriggerEvent().getName() = "pull_request_target" and
checkout.getCallee() = "actions/checkout" and
checkout.getAFollowingStep() = upload and checkout.getAFollowingStep() = upload and
upload.getCallee() = "actions/upload-artifact" upload.getCallee() = "actions/upload-artifact"
) )
@@ -55,8 +55,10 @@ class DownloadArtifactActionStep extends UntrustedArtifactDownloadStep, UsesStep
"ma-ve/action-download-artifact-with-retry" "ma-ve/action-download-artifact-with-retry"
] and ] and
( (
not exists(this.getArgument(["branch", "branch_name"])) or not exists(this.getArgument(["branch", "branch_name"]))
not this.getArgument(["branch", "branch_name"]) = ["main", "master"] or
exists(this.getArgument(["branch", "branch_name"])) and
this.getArgument("allow_forks") = "true"
) and ) and
( (
not exists(this.getArgument(["commit", "commitHash", "commit_sha"])) or not exists(this.getArgument(["commit", "commitHash", "commit_sha"])) or
@@ -74,7 +76,8 @@ class DownloadArtifactActionStep extends UntrustedArtifactDownloadStep, UsesStep
) and ) and
( (
not exists(this.getArgument("pr")) or not exists(this.getArgument("pr")) or
not this.getArgument("pr").matches("%github.event.pull_request.number%") not this.getArgument("pr")
.matches(["%github.event.pull_request.number%", "%github.event.number%"])
) )
} }

View File

@@ -20,26 +20,7 @@ class OutputClobberingFromFileReadSink extends OutputClobberingSink {
( (
step instanceof UntrustedArtifactDownloadStep step instanceof UntrustedArtifactDownloadStep
or or
// This should be: step instanceof SimplePRHeadCheckoutStep
// artifact instanceof PRHeadCheckoutStep
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
exists(Uses uses |
step = uses and
uses.getCallee() = "actions/checkout" and
exists(uses.getArgument("ref")) and
not uses.getArgument("ref").matches("%base%") and
uses.getATriggerEvent().getName() = checkoutTriggers()
)
or
step instanceof GitMutableRefCheckout
or
step instanceof GitSHACheckout
or
step instanceof GhMutableRefCheckout
or
step instanceof GhSHACheckout
) and ) and
step.getAFollowingStep() = run and step.getAFollowingStep() = run and
this.asExpr() = run.getScript() and this.asExpr() = run.getScript() and

View File

@@ -193,6 +193,31 @@ predicate containsHeadRef(string s) {
) )
} }
class SimplePRHeadCheckoutStep extends Step {
SimplePRHeadCheckoutStep() {
// This should be:
// artifact instanceof PRHeadCheckoutStep
// but PRHeadCheckoutStep uses Taint Tracking anc causes a non-Monolitic Recursion error
// so we list all the subclasses of PRHeadCheckoutStep here and use actions/checkout as a workaround
// instead of using ActionsMutableRefCheckout and ActionsSHACheckout
exists(Uses uses |
this = uses and
uses.getCallee() = "actions/checkout" and
exists(uses.getArgument("ref")) and
not uses.getArgument("ref").matches("%base%") and
uses.getATriggerEvent().getName() = checkoutTriggers()
)
or
this instanceof GitMutableRefCheckout
or
this instanceof GitSHACheckout
or
this instanceof GhMutableRefCheckout
or
this instanceof GhSHACheckout
}
}
/** Checkout of a Pull Request HEAD */ /** Checkout of a Pull Request HEAD */
abstract class PRHeadCheckoutStep extends Step { abstract class PRHeadCheckoutStep extends Step {
abstract string getPath(); abstract string getPath();