GitHub Actions 취약점 보고를 통한 첫 CVE(CVE-2026-42298) 취득 수기
요약
오픈 소스 프로젝트인 postiz-app에서 GitHub Actions 설정 오류로 인한 치명적인 취약점(CVE-2026-42298)을 발견하여 CVE를 취득한 사례를 공유합니다. 악의적인 Pull Request를 통해 CI/CD 파이프라인을 탈취하고 리포지토리 권한을 완전히 장악할 수 있는 'Pwn Request' 공격 기법의 위험성을 다룹니다.
핵심 포인트
- 신뢰할 수 없는 사용자의 파일(Dockerfile.dev)을 사용하여 빌드를 수행할 때 발생하는 보안 리스크
- GITHUB_TOKEN에 과도한 권한(write-all)이 부여될 경우 발생할 수 있는 임의 코드 실행 및 토큰 유출 위험
- GitHub Search를 활용한 정적 분석을 통해 취약한 워크플로 패턴을 탐지하는 방법
- Pwn Request 공격을 방지하기 위한 GitHub Actions 설정의 중요성
서론
안녕하세요. AI Security Lab의 미오야(見尾谷)입니다.
이번에 인생 처음으로 CVE(Common Vulnerabilities and Exposures, 공통 취약점 식별자)를 취득할 수 있게 되어, 그 경위와 기술적인 상세 내용에 대해 공유하고자 합니다.
취득한 CVE는 CVE-2026-42298입니다.
대상은 GitHub에서 3만 개 이상의 Star를 보유한 OSS(Open Source Software, 오픈 소스 소프트웨어)인 「postiz-app」입니다. 취약점의 심각도를 나타내는 CVSS(Common Vulnerability Scoring System, 공통 취약점 점수 시스템) 점수는 10.0 (Critical) 이었으며, 원격에서의 임의 코드 실행 및 토큰 유출이 가능한 상태였습니다.
본 기사에서는 CVE 취득까지의 흐름과, GitHub Actions의 설정 오류가 초래하는 치명적인 리스크를 소개합니다.
어떤 취약점이었는가?
이번에 발견한 것은 악의적인 Pull Request (PR)를 통해 CI/CD 파이프라인을 탈취하는, 이른바 「Pwn Request」라고 불리는 공격 기법이 성립하는 취약점이었습니다.
Pwn Request에 대해서는 아래 기사를 참고하여 학습했습니다.
근본적인 원인은 .github/workflows/pr-docker-build.yml (PR 생성 시 Docker 이미지를 빌드하는 워크플로)와 포크(Fork)된 PR에서 제공되는 Dockerfile.dev 사이의 관계, 그리고 권한 설정의 미흡함에 있었습니다.
문제의 워크플로는 신뢰할 수 없는 사용자(포크 원본)로부터 전달된 PR 내의 Dockerfile.dev를 사용하여 빌드 처리를 실행하는 구성이었습니다.
여기에 더해, 이 워크플로에는 GITHUB_TOKEN에 대해 write-all에 상당하는 권한이 부여되어 있어, 리포지토리(Repository)에 대한 쓰기 작업이 가능한 상태였습니다.
【공격자가 악용했을 경우의 임팩트】
만약 공격자가 포크한 리포지토리의 Dockerfile.dev 안에 악의적인 명령(예: 환경 변수의 외부 전송 명령 등)을 심고, 원본 리포지토리로 PR을 생성했다고 가정해 봅시다.
그러면 GitHub Actions가 해당 악의적인 Dockerfile을 빌드하게 되어 코드가 실행됩니다.
이때 실행 환경에 write-all에 상당하는 권한을 가진 토큰이 존재한다면, 공격자는 다음과 같은 일을 할 수 있게 됩니다.
- 리포지토리에 임의의 코드 커밋
- 릴리스(Release) 생성 및 변조
- PR 및 Issue의 자유로운 조작
- 리포지토리의 완전한 읽기/쓰기 권한 탈취
어떻게 취약점을 발견했는가
본 취약점은 GitHub Actions의 안티 패턴(Anti-pattern)을 노린 **정적 분석 (Static Analysis)**과 안전한 환경에서의 로컬 검증이라는 단계를 거쳐 발견되었습니다.
정적 분석 (GitHub Search를 활용한 조사)
GitHub Search를 사용하면 Public 리포지토리에 있는 코드를 검색할 수 있습니다. 여기서 Pwn Request에 해당하는 워크플로[1][2]를 찾기 위해, pull_request_target 이벤트와 github.event.pull_request.head.sha를 조합하고 있는 코드를 검색했습니다.
path:.github/workflows "pull_request_target" "github.event.pull_request.head.sha"
그 검색 결과 중에서 보고 대상 프로젝트인 postiz-app이 검색되었습니다.
해당 .github/workflows/pr-docker-build.yml의 소스 코드를 읽어보니, 다음과 같은 위험한 조합이 존재한다는 것을 깨달았습니다.
- 신뢰할 수 없는 사용자(포크 원본)로부터 전달된
Dockerfile.dev를 그대로docker build하고 있음. - 해당 워크플로에 연결된
GITHUB_TOKEN의 권한이 과도하게 넓게 설정되어 있음.
로컬 검증 (PoC를 통한 확증)
소스 코드의 정적 분석만으로는 "취약점일지도 모른다"라는 추측에 불과합니다. 실제로 악용 가능한지를 증명하고 보고의 정확도를 높이기 위해, Proof of Concept (PoC: 개념 증명) 작성을 수행했습니다.
로컬 테스트 환경에 대상 프로젝트와 동일한 구성의 테스트 리포지토리를 생성하고, 실제로 "악의적인 Dockerfile.dev...
를 포함하는 Pull Request」를 재현해 보았습니다. 구체적으로는 포크(Fork)한 리포지토리의 Dockerfile.dev에 다음과 같은 한 줄을 추가하여 PR을 생성했습니다.
RUN echo $(whoami)
그러자 원래 리포지토리 측의 워크플로우(Workflow)가 트리거되었고, docker build 실행 로그에 root라고 표시되었습니다. 포크 원본의 악의적인 코드가 원래 리포지토리의 CI 환경상에서 root 사용자로 임의의 명령어를 실행할 수 있는 상태임이 실증되었습니다.
이 검증을 통해 확신을 얻었고, 메인테이너(Maintainer)에게 보고하기로 결심했습니다.
Tips: 취약점 보고 시 「AI에 의한 자동 보고」로 오해받지 않기 위해
최근 OSS 커뮤니티에서는 생성형 AI를 이용한 근거 없는 취약점 보고(스팸)가 급증하고 있어, 메인테이너 측에서도 "이것도 AI의 오검출(Hallucination)이 아닌가?"라며 경계하는 경우가 적지 않습니다.
스팸 취급을 받지 않고 메인테이너가 진지하게 다루게 만들기 위해, 보고 시 제가 강력하게 의식했던 포인트들을 소개합니다.
반드시 직접 검증(PoC 작성)을 수행할 것
단순히 "소스 코드의 이 부분이 위험해 보입니다"라고 지적하는 것만으로는 AI가 출력한 텍스트와 구별할 수 없습니다. 앞서 언급한 바와 같이, 반드시 자신의 테스트 환경에서 취약점 여부를 확인합니다. 취약점이 있다는 확신이 있다면, 상대방으로부터 기술적인 질문이 오더라도 당당하게 답변할 수 있습니다.
검증 결과는 반드시 「스크린샷」으로 제시할 것
자신의 검증 환경에서 취약점이 트리거된 증거를 반드시 스크린샷으로 첨부하여 보고합니다.
「텍스트 설명」에 더해 「명확한 에비던스(Evidence)」를 제시함으로써, AI에 의한 자동 생성 리포트가 아닌 진짜 보고라는 신뢰를 얻기 쉬워집니다.
보고부터 CVE 취득까지의 타임라인
자신의 환경에서 취약점을 검증한 후, GitHub의 기능(Private vulnerability reporting)을 사용하여 메인테이너에게 보고를 진행했습니다.
다음은 실제 타임라인입니다.
- 2026/04/22 06:31 UTC — 보고 (기점)
- 2026/04/22 08:58 UTC — 메인테이너가 보고를 수락 (+약 2.5시간)
- 2026/04/22 14:22 UTC — 수정 및 공개 (+약 5.5시간)
- 2026/04/26 13:09 UTC — GitHub로부터 CVE 발행 (+약 4일)
특기할 점은 메인테이너의 압도적인 대응 속도입니다.
보고 후 불과 몇 시간 만에 수락부터 수정, 그리고 어드바이저리(Advisory) 공개까지 완료되었습니다. 인기 있는 OSS를 지탱하는 팀의 보안에 대한 높은 의식과 대응력의 속도에는 정말 놀랐습니다.
GitHub Actions에 대한 인젝션(Injection)을 방지하기 위한 방어책
이번 취약점 보고와 수정 과정을 통해, CI/CD 환경을 인젝션 공격으로부터 보호하기 위한 구체적인 베스트 프랙티스(Best Practice)를 깊이 있게 배울 수 있었습니다.
자신의 리포지토리에서도 즉시 실천할 수 있는 강력한 방어책을 몇 가지 소개합니다.
1. permissions를 contents: read 등 최소한으로 제한하기
GitHub Actions에서는 기본 GITHUB_TOKEN의 권한이 너무 넓은(write-all로 되어 있는) 경우가 있습니다. 워크플로우 파일 내에서 명시적으로 permissions를 정의하여 최소 권한의 원칙을 지키는 것이 가장 중요합니다.
# 나쁜 예: 권한을 명시하지 않았거나, write-all로 되어 있음
# 좋은 예: 필요한 권한만 명시함
permissions:
...
2. OWNER나 MAINTAINER로 제한하기
누구나 PR을 생성하여 CI를 돌릴 수 있는 상태는 위험합니다. 필요에 따라 워크플로우의 실행 조건을 리포지토리의 오너(Owner)나 메인테이너, 또는 특정 콜라보레이터(Collaborator)로 한정함으로써, 알 수 없는 공격자의 실행을 방지합니다.
jobs:
build:
# 실행 사용자의 권한을 체크하는 조건식을 추가
...
3. pull_request_target 대신 pull_request나 workflow_dispatch를 사용하기
pull_request_target을 사용할 필요가 정말 있는지 먼저 재검토하는 것이 가장 효과적인 방어책입니다.
포크로부터의 PR을 트리거로 하는 경우, pull_request
이벤트는 기본적으로 GITHUB_TOKEN이 읽기 전용 (read-only)으로 제한되며, 리포지토리의 시크릿 (secrets)에도 접근할 수 없는 사양으로 되어 있습니다. [3]
따라서 시크릿에 대한 접근이나 리포지토리에 대한 쓰기 작업이 필요하지 않다면, pull_request_target 대신 pull_request로 전환하는 것만으로도 이번과 같은 공격 리스크를 근본적으로 배제할 수 있습니다.
또한, 외부 PR을 자동으로 트리거하지 않고, 메인테이너 (maintainer)가 내용을 확인한 후 수동으로 실행할 수 있는 workflow_dispatch를 활용하는 것도 유효합니다. CI의 자동 트리거를 의도적으로 제거하고 인간의 판단을 개입시킴으로써, 악의적인 코드가 자동으로 빌드되는 리스크를 제로로 만들 수 있습니다.
github.repository로 리포지토리를 제한하기
pull_request_target은 포크 (fork) 원본의 PR을 트리거하더라도 베이스 브랜치 (base branch)의 컨텍스트에서 워크플로우 (workflow)가 동작합니다. 따라서github.repository를 통해 실행 리포지토리를 명시적으로 제한함으로써, 포크 측에서 워크플로우가 의도치 않게 실행되는 문제를 방지할 수 있습니다.
jobs:
build:
# 원본 리포지토리에서만 실행되도록 설정
...
5. Action의 버전을 '태그'가 아닌 '커밋 해시'로 지정하기
actions/checkout@v4와 같은 태그 지정은 편리하지만, 만약 서드파티 액션 (third-party action)의 개발 원천이 탈취되어 v4 태그의 내용이 악의적인 코드로 교체될 경우, CI를 탈취당하게 됩니다 (공급망 공격 (supply chain attack)).
이를 방지하기 위해, 실행되는 코드가 절대 변하지 않는 '커밋의 SHA (해시값)'로 지정하는 것이 가장 안전한 방법입니다.
steps:
# 태그 지정 (편리하지만 교체될 리스크가 있음)
# - uses: actions/checkout@v4
...
인생 첫 CVE를 통해 얻은 것과 배움
이 경험을 통해 개인적으로 큰 깨달음을 얻었습니다. 지금까지 OSS 코드를 읽으며 '혹시 버그인가?'라고 생각해도, '내 착각일지도 몰라'라거나 '영어로 보고하는 것이 부담스러워'라며 주저했던 적이 있었습니다.
하지만 용기를 내어 보고해 봄으로써 프로젝트의 안전에 기여할 수 있었을 뿐만 아니라, 메인테이너와의 커뮤니케이션도 경험할 수 있었습니다.
만약 이 글을 읽고 보안에 흥미를 느끼신 분이 있다면, 꼭 주변의 OSS 코드나 CI/CD 설정을 살펴보시기 바랍니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기