A few ways to hack stuff on GitHub
Last modified 1710971818000 ago by Blake Burkhart
5 min read
pull_request
vs pull_request_target
AKA “pwn request”
Insecure example: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/#:~:text=INSECURE
https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
# Potentially insecure
on:
pull_request_target:
types: [assigned, opened, synchronize, reopened]
Forks can attack self-hosted runners via modified pull_request
workflows
https://adnanthekhan.com/2023/12/20/one-supply-chain-attack-to-rule-them-all/
Compromised https://github.com/actions/runner-images which builds GitHub.com hosted runner images
-
Submit a benign typo fix PR to a project to get past “Require approval for first-time contributors”
-
Send a second malicious PR that runs without manual approval
A PR can actually modify the workflow. Because the workflow users pull_request
it still only gets read only tokens. But they can modify runs_on
and select a self-hosted runner’s tag. And they can attempt to break out of an insecure/non-ephemeral runner.
pull_request_target
instead uses the workflow from the base branch instead of the PR’s branch. But it’s very risky as mentioned previously as it may read/write access to the forked repo.
Fix: use “Require approval for all outside collaborators”
Alternate hard to use fix: Use worfkflow restrictions
We recommend that you only use self-hosted runners with private repositories. This is because forks of your public repository can potentially run dangerous code on your self-hosted runner machine by creating a pull request that executes the code in a workflow.
Command injection through git branch name
-
https://securitylab.github.com/research/github-actions-untrusted-input/
-
Also, XSS via branch name! https://github.com/bburky/xss
-
Reusable GitHub Actions should be safe from command injection, but they can have vulnerabilities…
Fix: use an intermediate environment variable
# INSECURE
- name: echo PR title insecurely
run: |
echo "${{ github.event.pull_request.title }}"
# Secured with an intermediate environment variable
- name: echo PR title securely
env:
TITLE: ${{ github.event.pull_request.title }}
run: |
echo "${TITLE}"
Also, this issue can show up with to Zarf variables or Helm values substituted into YAML. But the input is usually not considered untrusted, so no security risk. Just a risk of things breaking because they have quotes.
Write access → steal repository secrets
DEMO: https://github.com/praetorian-inc/gato/wiki/Usage
Attacker can create unprotected branch and run actions, can delete logs of attack
gh auth login
export GH_TOKEN=$(gh auth token)
gato enumerate --target octocat
gato attack --target octocat/gato-demo-repo --secrets
Fix: use GitHub environment secrets
Note: over privileged PATs (such as classic PATs) can be stolen. This gives the attacker access to a user’s identity on GitHub and gives them access to any repos accessible by the user, including personal repos.
Recommendation: Use a GitHub App with actions/create-github-app-token
to get a PAT-like token instead.
Best fix: Avoid secrets entirely, use id-token
JWT. However… see next section for risks.
Write access → id-token
JWT → access AWS
permissions:
id-token: write
Any user with write access can create unprotected branches and run workflows. They can create a JWT id-token
in their workflow and attempt to access external systems like AWS.
Fix: AssumeRoleWithWebIdentity IAM policy should check JWT claims to ensure it’s being run from a protected branch (check environment name or ref)
Except… AWS only supports inspecting standard claims like sub
, not custom claims.
AWS Fix: Use GitHub feature to customize the sub
claim if needed:
https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-subject-claims-for-an-organization-or-repository
TODO: sub
may contain environment
by default?
Write access → edit releases and packages
https://github.com/orgs/community/discussions/51588
Write access allows users to modify releases. Packages have the same issue, but can be restricted with manual hard to use config.
No easy fix: drop users below write access, require fork-and-PR
GitHub Runner as C2
A fun trick that shows up in this attack: https://adnanthekhan.com/2023/12/20/one-supply-chain-attack-to-rule-them-all/
Any firewall around a self hosted runner must allow internet access to GitHub.com. An attacker can register their own runner on their own org and use this as a C2 channel.
Bonus: “Google OAuth is Broken (Sort Of)”
https://trufflesecurity.com/blog/google-oauth-is-broken-sort-of
You can register a personal non-gmail Google account on Workspace domains using a + email.
Fix: Use SAML with Google Workspace
Related issue: https://www.descope.com/blog/post/noauth
Similarly, beware of the security of the email
JWT claim when integrating Keycloak. Many apps (such as GitLab) expect it to never change: a user claiming another’s email address can lead to account takeover.
Bonus: Alternate authenticators (e.g. PATs) work after disabling SSO
Some apps like GitLab support alternate authentication methods PATs and SSH keys. When you deactivate a user’s access by only disabling their Keycloak account, this only blocks browser login. They can still access GitLab APIs via PAT.
This is true for many apps other than GitLab if they have any kind of “API key” feature. Also, check if “API keys” are associated to a user or not, they may not even be deleted if they’re not associated to a user.
Fix: deactivate user accounts inside each app, not just at Keycloak. SCIM can automate deprovisioning, but Keycloak doesn’t support it natively.
Fix for custom code: consider using offline
OIDC tokens which can query the IdP to check if the token is still valid.