From cf9b853a8fba8dac3be1f6d173caf4673edaed2c Mon Sep 17 00:00:00 2001 From: Kylie Stradley <4666485+KyFaSt@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:14:03 -0400 Subject: [PATCH] unversioned immutable actions wip --- ql/lib/codeql/actions/config/Config.qll | 11 ++++++ .../actions/config/ConfigExtensions.qll | 7 ++++ ql/lib/ext/config/immutable_actions.yml | 22 +++++++++++ .../CWE-829/UnversionedImmutableAction.md | 27 +++++++++++++ .../CWE-829/UnversionedImmutableAction.ql | 38 +++++++++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 ql/lib/ext/config/immutable_actions.yml create mode 100644 ql/src/Security/CWE-829/UnversionedImmutableAction.md create mode 100644 ql/src/Security/CWE-829/UnversionedImmutableAction.ql diff --git a/ql/lib/codeql/actions/config/Config.qll b/ql/lib/codeql/actions/config/Config.qll index 82b7a53a9d7..a439f999623 100644 --- a/ql/lib/codeql/actions/config/Config.qll +++ b/ql/lib/codeql/actions/config/Config.qll @@ -119,6 +119,17 @@ predicate vulnerableActionsDataModel( Extensions::vulnerableActionsDataModel(action, vulnerable_version, vulnerable_sha, fixed_version) } +/** + * MaD models for vulnerable actions + * Fields: + * - action: action name + */ +predicate immutableActionsDataModel( + string action +) { + Extensions::immutableActionsDataModel(action) +} + /** * MaD models for untrusted git commands * Fields: diff --git a/ql/lib/codeql/actions/config/ConfigExtensions.qll b/ql/lib/codeql/actions/config/ConfigExtensions.qll index a32e9c445f2..c36ad046a3c 100644 --- a/ql/lib/codeql/actions/config/ConfigExtensions.qll +++ b/ql/lib/codeql/actions/config/ConfigExtensions.qll @@ -58,6 +58,13 @@ extensible predicate vulnerableActionsDataModel( string action, string vulnerable_version, string vulnerable_sha, string fixed_version ); +/** + * Holds for actions that are known to be immutable. + */ +extensible predicate immutableActionsDataModel( + string action +); + /** * Holds for git commands that may introduce untrusted data when called on an attacker controlled branch. */ diff --git a/ql/lib/ext/config/immutable_actions.yml b/ql/lib/ext/config/immutable_actions.yml new file mode 100644 index 00000000000..072e8ed0b09 --- /dev/null +++ b/ql/lib/ext/config/immutable_actions.yml @@ -0,0 +1,22 @@ +extensions: + - addsTo: + pack: github/actions-all + extensible: immutableActionsDataModel + data: + - ["actions/checkout"] + - ["actions/cache"] + - ["actions/setup-node"] + - ["actions/upload-artifact"] + - ["actions/setup-python"] + - ["actions/download-artifact"] + - ["actions/github-script"] + - ["actions/setup-java"] + - ["actions/setup-go"] + - ["actions/upload-pages-artifact"] + - ["actions/deploy-pages"] + - ["actions/setup-dotnet"] + - ["actions/stale"] + - ["actions/labeler"] + - ["actions/create-github-app-token"] + - ["actions/configure-pages"] + - ["octokit/request-action"] diff --git a/ql/src/Security/CWE-829/UnversionedImmutableAction.md b/ql/src/Security/CWE-829/UnversionedImmutableAction.md new file mode 100644 index 00000000000..eab708f8602 --- /dev/null +++ b/ql/src/Security/CWE-829/UnversionedImmutableAction.md @@ -0,0 +1,27 @@ +# Unpinned tag for 3rd party Action in workflow + +## Description + +Using a tag for a 3rd party Action that is not pinned to a commit can lead to executing an untrusted Action through a supply chain attack. + +## Recommendations + +Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload. When selecting a SHA, you should verify it is from the action's repository and not a repository fork. + +## Examples + +### Incorrect Usage + +```yaml +- uses: tj-actions/changed-files@v44 +``` + +### Correct Usage + +```yaml +- uses: tj-actions/changed-files@c65cd883420fd2eb864698a825fc4162dd94482c # v44 +``` + +## References + +- [Using third-party actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions) diff --git a/ql/src/Security/CWE-829/UnversionedImmutableAction.ql b/ql/src/Security/CWE-829/UnversionedImmutableAction.ql new file mode 100644 index 00000000000..d9a1394641f --- /dev/null +++ b/ql/src/Security/CWE-829/UnversionedImmutableAction.ql @@ -0,0 +1,38 @@ +/** + * @name Unversioned Immutable Action + * @description Using an Immutable Action without a semantic version tag opts out of the protections of Immutable Action + * @kind problem + * @security-severity 5.0 + * @problem.severity recommendation + * @precision high + * @id actions/unversioned-immutable-action + * @tags security + * actions + * external/cwe/cwe-829 + */ + +import actions + +bindingset[version] +private predicate isSemanticVersioned(string version) { version.regexpMatch("^v[0-9]+(\\.[0-9]+)*(\\.[xX])?$") } + +bindingset[repo] +private predicate isTrustedOrg(string repo) { + exists(string org | org in ["actions", "github", "advanced-security", "octokit"] | repo.matches(org + "/%")) +} + +from UsesStep uses, string repo, string version, Workflow workflow, string name +where + uses.getCallee() = repo and + uses.getEnclosingWorkflow() = workflow and + ( + workflow.getName() = name + or + not exists(workflow.getName()) and workflow.getLocation().getFile().getBaseName() = name + ) and + uses.getVersion() = version and + not isTrustedOrg(repo) and + not isPinnedCommit(version) +select uses.getCalleeNode(), + "Unpinned 3rd party Action '" + name + "' step $@ uses '" + repo + "' with ref '" + version + + "', not a pinned commit hash", uses, uses.toString()