diff --git a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md index 89a0f54c91..75acaaf0d0 100644 --- a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md +++ b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md @@ -165,6 +165,29 @@ Trusted publishing/OIDC removes long-lived static npm tokens, but it does not ma Provenance answers **which workflow built this artifact**, not **whether the workflow, source tree, cache, or build steps were clean**.[[1]](#references)[[2]](#references)[[13]](#references)[[15]](#references) +### Build-to-publish privilege crossing + +A release job that installs dependencies and publishes in the same job gives install-time code the job's identity. In this abbreviated workflow, a malicious `postinstall` script reached by `npm ci` inherits the workflow-level `NODE_AUTH_TOKEN` and can request an OIDC token because `id-token: write` applies to the whole job, not just the publish step.[[19]](#references)[[21]](#references) + +```yaml +env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} +jobs: + release: + permissions: + contents: read + id-token: write + steps: + - run: npm ci + - run: npm publish +``` + +`npm ci` requires an existing `package-lock.json` or `npm-shrinkwrap.json`; without one, this example fails rather than resolving a fresh malicious version. The poisoned version must be selected by the lockfile for this exact install path to run it.[[20]](#references) + +Moving `NODE_AUTH_TOKEN` to the `npm publish` step removes it from the install step, but **does not protect the publish command** if the attacker controls the package's publish-time scripts.[[19]](#references) npm runs `prepublishOnly`, `prepack`, `prepare`, `postpack`, `publish`, and `postpublish` around `npm publish`, so a token-bearing publish step remains an execution surface.[[6]](#references) Step-scoped environment variables also do not narrow `id-token: write`, which GitHub supports at workflow or job scope, not step scope.[[21]](#references) + +When assessing a release workflow, trace whether dependency installation, tests, artifact creation, and publication share a job with `id-token: write` or registry secrets.[[19]](#references) A separate build job with no publisher identity removes those credentials and OIDC request access from install-time code in that job; the privileged publish job can consume its output via an artifact.[[19]](#references) The artifact is still untrusted input: a malicious package or publish-time script in it can execute with the publisher identity. See [Artifact Poisoning](./#artifact-poisoning) for the cross-job trust boundary and [AWS - Federation Abuse](../../../pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md#oidc---github-actions-abuse) for role-ARN discovery when the trusted provider is AWS.[[14]](#references)[[19]](#references) + ChainDrop implemented a repository-gated path that injected a Git-pinned `@opensearch/setup` dependency imitating the legitimate `@opensearch-project` scope. Review dependency diffs even when lifecycle scripts do not change.[[13]](#references) High-signal review points: @@ -221,5 +244,8 @@ Useful behavior-level signals from the same chain include:[[18]](#reference - [16] [Malicious key-derivation telemetry commit in InjectiveLabs/injective-ts](https://github.com/InjectiveLabs/injective-ts/commit/01219285b16ce85c70cdf47a71a551ff5e41f1ed) - [17] [Not-So-Anonymous Telemetry: The @injectivelabs/sdk-ts Backdoor](https://securitylabs.datadoghq.com/articles/not-so-anonymous-telemetry-injectivelabs-sdk-ts-backdoor) - [18] [NPM Worm Compromises Hundreds of Popular npm Packages](https://securitylabs.datadoghq.com/articles/npm-worm-compromises-popular-npm-packages) +- [19] [CI/CD Protected Build Jobs: Isolating Build and Publish Phases](https://labs.sra.io/posts/cicd_protected_build_jobs) +- [20] [npm-ci | npm Docs](https://docs.npmjs.com/cli/v11/commands/npm-ci/) +- [21] [Workflow syntax for GitHub Actions | GitHub Docs](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax) {{#include ../../../banners/hacktricks-training.md}} diff --git a/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md b/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md index 76e23c7759..5cd6edefc3 100644 --- a/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md +++ b/src/pentesting-cloud/aws-security/aws-basic-information/aws-federation-abuse.md @@ -42,6 +42,7 @@ GitHub Actions can exchange a short-lived OIDC token for temporary AWS credentia ``` The `sub` values limit the role to the selected repository, pull-request runs, and the `main` branch ref. The role's permissions policy should independently enforce least privilege.[[1]](#references)[[4]](#references)[[6]](#references) 6. The role ARN is the identifier consumed by the action; the example stores it in the `READ_ROLE` secret. An ARN is not itself a credential, but an environment-scoped secret can be used when the workflow also uses protected environment rules.[[1]](#references)[[6]](#references) + - A compromised job with `id-token: write` still needs the target role ARN to exchange its GitHub OIDC token for AWS credentials.[[4]](#references)[[13]](#references) If the repository is checked out, a literal `role-to-assume` value may be visible in `.github/workflows/`; the ARN may also be in a job environment variable.[[4]](#references)[[13]](#references) A workflow reference to a secret reveals only the secret name, not its value.[[14]](#references) The ARN is not a security boundary: the IAM trust policy decides whether that workflow identity can assume the role.[[4]](#references)[[13]](#references) See [Build-to-publish privilege crossing](../../../pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-npm-supply-chain-abuse.md#build-to-publish-privilege-crossing) for the related npm job boundary. 7. Use `aws-actions/configure-aws-credentials` to exchange the GitHub OIDC token for temporary AWS credentials.[[4]](#references)[[7]](#references) The following workflow is an example: ```yaml @@ -141,5 +142,7 @@ An explicit `sub` condition prevents other service accounts in the cluster or na - [10] [IAM Roles for Service Accounts - eksctl](https://docs.aws.amazon.com/eks/latest/eksctl/iamserviceaccounts.html) - [11] [Assign IAM roles to Kubernetes service accounts](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html) - [12] [Get started with Amazon EKS - eksctl](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html) +- [13] [CI/CD Protected Build Jobs: Isolating Build and Publish Phases](https://labs.sra.io/posts/cicd_protected_build_jobs) +- [14] [Secrets - GitHub Docs](https://docs.github.com/en/actions/concepts/security/secrets) {{#include ../../../banners/hacktricks-training.md}}