
# GitHub Actions 워크플로 디스패치의 명령 주입을 시연하는 개념 증명 랩 (CVE-2026-39866) 교육용 분석을 위해 취약한 버전과 패치된 버전을 나란히 실행합니다.
| 필드 | 값 |
|---|---|
| CVE ID | CVE-2026-39866 |
| GHSA | GHSA-9prc-pp2c-3427 |
| CWE | CWE-78 (OS 명령에 사용되는 특수 요소의 부적절한 중화) |
| 제품 | LawnchairLauncher/lawnchair |
| 영향 범위 | fcba413 미만의 모든 버전 |
| 패치 버전 | fcba413 |
| 발견자 | Abhay Kumar (@abhayclasher) |
이 취약점은 release_update.yml 워크플로 파일에 존재합니다. 해당 액션은 artifactName 매개변수를 받아 따옴표 없이 bash 명령에 직접 전달합니다.
- name: Rename artifact
run: mv ${{ inputs.artifactName }}.zip lawnchair.zip
GitHub Actions는 셸이 실행하기 전에 변수 값을 문자열에 대체합니다. ${{ inputs.artifactName }} 주위에 따옴표가 없으면 셸은 공백과 세미콜론을 명령 구분자로 해석합니다.
공격자가 artifactName으로 file.zip; touch pwned.txt #를 제공하면:
GitHub Actions가 이를 파싱하는 방식:
${{ inputs.artifactName }} → file.zip; touch pwned.txt #
셸이 이를 실행하는 방식:
mv file.zip; touch pwned.txt #.zip lawnchair.zip
셸은 mv file.zip을 실행한 다음 touch pwned.txt를 실행하고 나머지는 주석으로 무시합니다.
GITHUB_TOKEN 또는 릴리스 서명 키) 탈취.git clone https://github.com/abhayclasher/CVE-2026-39866.git
cd CVE-2026-39866/app
npm install
node server.js
git clone https://github.com/abhayclasher/CVE-2026-39866.git
cd CVE-2026-39866
docker compose up -d
서버가 시작된 후 다음 URL을 방문하세요:
| URL | 설명 |
|---|---|
http://localhost:3000/workflow/vulnerable | 취약한 워크플로 — 제출 시 명령 주입 실행 |
http://localhost:3001/workflow/patched | fcba413 패치가 적용된 동일한 워크플로 |
취약한 버전은 주입된 명령을 러너에서 실행합니다. 패치된 버전은 입력을 리터럴 문자열로 처리합니다.
독립형 PoC도 실행할 수 있습니다:
cd poc
node exploit.js
이 명령은 포트 3000에서 최소 서버를 실행하여 전체 애플리케이션 컨텍스트 없이 동일한 버그를 시연합니다.
1. 공격자가 artifactName 입력 매개변수를 제어
2. GitHub Actions가 ${{ inputs.artifactName }}을 YAML로 확장
3. run 단계가 이를 bash로 전송
4. Bash가 따옴표 없는 특수 문자를 보고 명령 구문으로 처리
5. 명령이 러너 환경 권한으로 실행
┌────────────────────────────────────────────────────┐
│ 1. 악성 입력: │
│ │
│ artifactName = "file.zip; id #" │
│ │
│ 2. GitHub Actions가 YAML로 대체: │
│ │
│ run: mv file.zip; id #.zip lawnchair.zip │
│ │
│ 3. Bash가 인식하는 것: │
│ - mv file.zip │
│ - id <-- 임의 명령 │
│ - #.zip lawnchair.zip (무시됨) │
│ │
│ 4. 셸이 임의 명령 실행 │
└────────────────────────────────────────────────────┘
fcba413 버전은 보간된 변수 주위에 따옴표를 도입합니다:
- name: Rename artifact
run: mv "${{ inputs.artifactName }}.zip" lawnchair.zip
따옴표는 셸이 특수 문자를 파싱하지 못하게 합니다. 페이로드는 리터럴 파일 이름 문자열로 처리됩니다.
모범 사례는 환경 변수를 사용하여 사용자 입력을 완전히 격리하는 것입니다:
- name: Rename artifact
env:
ARTIFACT_NAME: ${{ inputs.artifactName }}
run: mv "$ARTIFACT_NAME.zip" lawnchair.zip
전체 커밋 보기: fcba413
CVE-2026-39866/
├── README.md # 이 파일
├── docker-compose.yml # Docker 랩 구성
├── app/
│ ├── Dockerfile # 컨테이너 빌드 구성
│ ├── package.json # Node.js 의존성
│ └── server.js # 취약 + 패치된 워크플로 러너 시뮬레이션
└── poc/
└── exploit.js # 독립형 최소 개념 증명
Abhay Kumar이(가) 보고했습니다. 이 저장소는 로컬 교육용으로만 사용하도록 제작되었습니다. 취약한 서버를 공용 네트워크에 배포하지 마세요.
교육 목적 전용 — 격리된 환경에서만 사용하세요