From 89b4cac818d9b2957caf26bef71c3c24b983ddd9 Mon Sep 17 00:00:00 2001 From: Translator Date: Mon, 6 Jul 2026 15:30:23 +0000 Subject: [PATCH] Translated ['', 'src/pentesting-ci-cd/github-security/abusing-github-act --- .../gh-actions-context-script-injections.md | 98 ++++++++++++++----- 1 file changed, 71 insertions(+), 27 deletions(-) diff --git a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md index 6b600f45b..766318d94 100644 --- a/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md +++ b/src/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-context-script-injections.md @@ -2,20 +2,20 @@ {{#include ../../../banners/hacktricks-training.md}} -## 위험 이해 +## Understanding the risk -GitHub Actions는 단계가 실행되기 전에 ${{ ... }} 표현식을 렌더링합니다. 렌더된 값은 해당 단계의 프로그램(예: run 단계의 경우 셸 스크립트)에 붙여넣어집니다. run: 안에 신뢰할 수 없는 입력을 직접 인터폴레이션하면 공격자가 셸 프로그램의 일부를 제어하여 임의의 명령을 실행할 수 있습니다. +GitHub Actions는 step이 실행되기 전에 expressions ${{ ... }}를 렌더링합니다. 렌더링된 값은 step의 program에 그대로 붙여넣어집니다(run steps의 경우 shell script). 신뢰할 수 없는 input을 run: 안에 직접 interpolate하면, attacker가 shell program의 일부를 제어하게 되어 arbitrary commands를 실행할 수 있습니다. Docs: https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions and contexts/functions: https://docs.github.com/en/actions/learn-github-actions/contexts -핵심 요점: -- 렌더링은 실행 전에 발생합니다. 모든 표현식이 해석된 상태로 run 스크립트가 생성된 다음 셸에서 실행됩니다. -- 많은 contexts는 트리거 이벤트(issues, PRs, comments, discussions, forks, stars 등)에 따라 사용자 제어 필드를 포함합니다. 신뢰할 수 없는 입력에 대한 참고는 다음을 보세요: https://securitylab.github.com/resources/github-actions-untrusted-input/ -- run: 내부의 셸 따옴표는 신뢰할 수 있는 방어책이 아닙니다. 인젝션은 템플릿 렌더링 단계에서 발생하기 때문입니다. 공격자는 조작된 입력을 통해 따옴표를 탈출하거나 연산자를 주입할 수 있습니다. +Key points: +- Rendering happens before execution. run script는 모든 expressions가 resolve된 상태로 생성된 뒤, shell에 의해 실행됩니다. +- 많은 contexts에는 triggering event에 따라 user-controlled fields가 포함됩니다(issues, PRs, comments, discussions, forks, stars, etc.). untrusted input reference를 보세요: https://securitylab.github.com/resources/github-actions-untrusted-input/ +- run: 안에서의 shell quoting은 reliable defense가 아닙니다. injection이 template rendering stage에서 발생하기 때문입니다. attacker는 crafted input으로 quotes를 빠져나가거나 operators를 inject할 수 있습니다. -## 취약한 패턴 → RCE on runner +## Vulnerable pattern → RCE on runner -취약한 workflow (누군가 새 이슈를 열 때 트리거됨): +Vulnerable workflow (triggered when someone opens a new issue): ```yaml name: New Issue Created on: @@ -36,20 +36,56 @@ with: github_token: ${{ secrets.GITHUB_TOKEN }} labels: new ``` -공격자가 제목이 $(id)인 이슈를 열면, 렌더된 단계는 다음과 같이 됩니다: +공격자가 $(id)라는 제목의 issue를 열면, 렌더링된 step은 다음과 같아집니다: ```sh echo "New issue $(id) created" ``` -command substitution은 runner에서 id를 실행합니다. 예시 출력: +command substitution은 runner에서 id를 실행한다. 예시 출력: ``` New issue uid=1001(runner) gid=118(docker) groups=118(docker),4(adm),100(users),999(systemd-journal) created ``` -따옴표로 감싸는 것으로는 안전해지지 않는 이유: -- 표현식은 먼저 렌더링된 다음, 그 결과 스크립트가 실행됩니다. 만약 신뢰할 수 없는 값에 $(...), `;`, `"`/`'`, 또는 개행이 포함되어 있다면, 따옴표로 감싸더라도 프로그램 구조를 변경할 수 있습니다. +인용이 당신을 지켜주지 못하는 이유: +- Expressions는 먼저 렌더링되고, 그다음 생성된 script가 실행됩니다. 신뢰할 수 없는 값에 $(...), `;`, `"`/`'`, 또는 줄바꿈이 포함되어 있으면, 인용을 했더라도 program structure를 바꿀 수 있습니다. -## 안전한 패턴 (shell variables via env) +## Comment-state confusion: spoofed bot comments → shell injection -올바른 완화 방법: 신뢰할 수 없는 입력을 환경 변수에 복사한 다음, run 스크립트에서 네이티브 shell 확장($VAR)을 사용하세요. 명령 내부에 ${{ ... }}로 다시 포함시키지 마세요. +위험한 변형은 workflow가 **comments를 search한 뒤 반환된 comment를 trusted automation state로 취급할 때** 나타납니다. 예를 들어, `peter-evans/find-comment`는 `body-includes`로 search하고 일치하는 `comment-body`를 step output으로 노출할 수 있습니다. workflow가 `comment-author`도 함께 제한하지 않으면, comment를 남길 수 있는 누구나 bot에서 기대하는 marker text를 spoof할 수 있습니다. +```yaml +- uses: peter-evans/find-comment@v4 +id: fc +with: +issue-number: ${{ github.event.issue.number }} +body-includes: "Opened a new issue in org/repo:" +``` +그 출력이 나중에 shell syntax에 포함되면, 원래 소스가 "just a comment"였더라도 workflow는 exploitable해집니다: +```yaml +- run: | +if [ '${{ steps.fc.outputs.comment-body }}' = '' ]; then +echo "new issue needed" +fi +``` +공격자는 다음을 모두 만족하는 comment를 게시할 수 있습니다: +- 검색된 marker string과 일치하고, +- `' ]; ; if [ 'x` 같은 shell-breaking content를 포함함 + +GitHub가 `${{ ... }}`를 렌더링한 뒤, Bash는 attacker-controlled syntax를 data가 아니라 받게 됩니다. 이는 **two-stage exploit**를 만듭니다: +1. **Provenance confusion**: workflow가 attacker comments를 bot state로 잘못 인식함. +2. **Script injection**: 반환된 `comment-body`가 `run:`에 붙여넣어져 실행됨. + +### bot comments를 상대로 한 TOCTOU race + +정상적인 bot comment가 어떤 이전 단계 이후에만 생성된다면, attacker는 spoofed comment를 먼저 게시해 이를 race할 수 있습니다. search action이 real bot comment가 존재하기 전(또는 선택되기 전)에 attacker's comment를 반환하면, 낮은 권한의 public commenter도 `issue_comment`/issue workflow를 privileged runner execution으로 바꿀 수 있습니다. + +### comment-driven automation을 위한 더 안전한 패턴 + +- `find-comment`를 사용할 때는 **content와 provenance**(`comment-author`, repository/App identity, 또는 다른 강한 binding)를 모두 요구하세요. +- label, artifact, issue field, 또는 external datastore가 같은 state를 더 안전하게 저장할 수 있다면 comment를 state로 사용하지 마세요. +- `comment-body`, issue titles, labels, 또는 그로부터 파생된 workflow output을 `run:`에 직접 붙여넣지 마세요. +- comment text를 꼭 소비해야 한다면, `env:`나 file로 전달하고 data로만 처리하세요. + +## Safe pattern (shell variables via env) + +올바른 mitigation: untrusted input을 environment variable로 복사한 다음, run script에서 native shell expansion ($VAR)을 사용하세요. command 안에서 ${{ ... }}로 다시 삽입하지 마세요. ```yaml # safe jobs: @@ -63,31 +99,39 @@ run: | echo "New issue $TITLE created" ``` Notes: -- run: 안에서 ${{ env.TITLE }} 사용을 피하세요. 이는 명령에 템플릿 렌더링을 다시 도입하여 동일한 injection 위험을 초래합니다. -- untrusted inputs는 env: 매핑을 통해 전달하고 run:에서 $VAR로 참조하는 것이 바람직합니다. +- `run:` 안에서 `${{ env.TITLE }}`를 사용하지 마세요. 그러면 template rendering이 command 안으로 다시 들어가고, 같은 injection risk가 생깁니다. +- 신뢰할 수 없는 입력은 `env:` mapping으로 전달하고, `run:`에서는 `$VAR`로 참조하는 것을 권장합니다. ## Reader-triggerable surfaces (treat as untrusted) -읽는 사용자가 트리거할 수 있는 이벤트는 많습니다. public repositories에 대해 read 권한만 있는 계정도 여러 이벤트를 트리거할 수 있습니다. 이러한 이벤트로부터 유래한 contexts의 모든 필드는 달리 입증되지 않는 한 공격자에 의해 조작될 수 있다고 간주해야 합니다. 예시: +public repositories에 대해 read permission만 있는 계정도 여전히 많은 event를 trigger할 수 있습니다. 이 events에서 파생된 contexts의 어떤 field든, 달리 증명되지 않는 한 attacker-controlled로 간주해야 합니다. 예: - issues, issue_comment -- discussion, discussion_comment (orgs는 discussions를 제한할 수 있음) +- discussion, discussion_comment (orgs can restrict discussions) - pull_request, pull_request_review, pull_request_review_comment -- pull_request_target (오용 시 위험함, base repo 컨텍스트에서 실행됨) -- fork (누구나 public repos를 fork할 수 있음) -- watch (리포지토리에 star를 누르는 행위) -- workflow_run/workflow_call 체인을 통한 간접적 경로 +- pull_request_target (dangerous if misused, runs in base repo context) +- fork (anyone can fork public repos) +- watch (starring a repo) +- Indirectly via workflow_run/workflow_call chains -어떤 특정 필드가 공격자 제어인지 여부는 이벤트별로 다릅니다. GitHub Security Lab의 untrusted input 가이드를 참조하세요: https://securitylab.github.com/resources/github-actions-untrusted-input/ +어떤 specific fields가 attacker-controlled인지 여부는 event-specific입니다. GitHub Security Lab의 untrusted input guide를 참고하세요: https://securitylab.github.com/resources/github-actions-untrusted-input/ + +## Local validation without touching the target repo + +[`act`](https://github.com/nektos/act)를 사용하면 많은 GitHub Actions script injections를 안전하게 재현할 수 있습니다: synthetic event JSON을 생성하고, 취약한 workflow를 로컬에서 실행한 뒤, external action output을 controlled value로 교체합니다(예: mocked `comment-body`). 이는 payload structure를 디버깅하고, injected text가 여전히 valid Bash syntax를 유지하는지 확인하며, 실제 test 전에 harmless canary exfiltration을 검증하는 데 유용합니다. ## Practical tips -- run: 안에서 expressions 사용을 최소화하세요. env: 매핑 + $VAR를 선호하세요. -- 입력을 변환해야 한다면 shell에서 안전한 도구(printf %q, jq -r 등)를 사용해 변환하되, 항상 shell 변수에서 시작하세요. -- 스크립트, 명령행 플래그, 파일 경로에 branch names, PR titles, usernames, labels, discussion titles, PR head refs 등을 보간할 때 각별히 주의하세요. -- reusable workflows와 composite actions에도 동일한 패턴을 적용하세요: env로 매핑한 다음 $VAR로 참조합니다. +- `run:` 안에서 expression 사용을 최소화하세요. `env:` mapping + `$VAR`를 선호하세요. +- 입력을 변환해야 한다면, shell 안에서 안전한 tools(printf %q, jq -r, etc.)를 사용하되, 시작점은 여전히 shell variable이어야 합니다. +- branch names, PR titles, usernames, labels, discussion titles, PR head refs를 scripts, command-line flags, file paths에 삽입할 때는 특히 주의하세요. +- reusable workflows와 composite actions에도 같은 패턴을 적용하세요: env로 매핑한 다음 `$VAR`를 참조하세요. ## References +- [Find Comment, Get Shell: Command Injection in dbt’s GitHub Actions](https://landh.tech/blog/20260701-find-comment-get-shell) +- [peter-evans/find-comment](https://github.com/peter-evans/find-comment) +- [GHSL-2023-109: GitHub Actions command injection in a TDesign Vue Next workflow](https://securitylab.github.com/advisories/GHSL-2023-109_TDesign_Vue_Next/) +- [nektos/act](https://github.com/nektos/act) - [GitHub Actions: A Cloudy Day for Security - Part 1](https://binarysecurity.no/posts/2025/08/securing-gh-actions-part1) - [GitHub workflow syntax](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions) - [Contexts and expression syntax](https://docs.github.com/en/actions/learn-github-actions/contexts)