# GitHub 원격 코드 실행 취약점: CVE-2026-3854 분석

> Clean Markdown view of GeekNews topic #29017. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=29017](https://news.hada.io/topic?id=29017)
- GeekNews Markdown: [https://news.hada.io/topic/29017.md](https://news.hada.io/topic/29017.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-04-29T21:42:33+09:00
- Updated: 2026-04-29T21:42:33+09:00
- Original source: [wiz.io](https://www.wiz.io/blog/github-rce-vulnerability-cve-2026-3854)
- Points: 1
- Comments: 1

## Topic Body

- **git push 경로의 내부 프로토콜 결함**만으로 백엔드에서 원격 코드 실행이 가능했고, GitHub.com은 이미 완화됐지만 **GHES**는 패치 적용이 필요함
- 사용자 제어 입력인 **push option**이 `X-Stat` 헤더에 그대로 들어가면서 세미콜론 하나로 새 필드를 주입할 수 있었고, 같은 키의 나중 값이 앞값을 덮는 **last-write-wins** 동작이 악용됨
- 주입 가능한 필드 중 **`rails_env`**, **`custom_hooks_dir`**, **`repo_pre_receive_hooks`** 를 조합하면 샌드박스를 우회하고 공격자가 지정한 경로의 훅을 `git` 사용자 권한으로 실행할 수 있었음
- 같은 메커니즘으로 GitHub.com의 **enterprise mode 플래그**까지 주입되면서 shared storage node에서 코드 실행이 확인됐고, 해당 노드의 다른 사용자와 조직 저장소까지 읽을 수 있는 상태로 이어짐
- 서로 다른 서비스가 공유 포맷을 신뢰하는 **멀티서비스 아키텍처**에서는 입력 정리 부재, 비생산 실행 경로, 경로 검증 누락이 결합해 큰 취약점으로 커질 수 있음을 보여줌

---

### 즉시 대응과 영향 범위
- **GitHub.com**에서는 이 이슈가 이미 완화되어 추가 조치가 필요하지 않음
- **GitHub Enterprise Server**는 즉시 대응이 필요하며, `CVE-2026-3854` 수정이 포함된 **GHES 3.19.3 이상**으로 업그레이드해야 함
- 취약 버전 범위는 **GHES 3.19.1 이하**이며, 수정 버전으로는 `3.14.24`, `3.15.19`, `3.16.15`, `3.17.12`, `3.18.6`, `3.19.3`가 제시됨
- 작성 시점 기준으로 **GHES 인스턴스 88%** 가 아직 취약한 상태였음
- GitHub의 추가 기술 정보와 복구 절차는 [GitHub 보안 블로그](https://github.blog/security/securing-the-git-push-pipeline-responding-to-a-critical-remote-code-execution-vulnerability/)에서 확인 가능함
- Wiz 고객은 [Wiz Threat Center의 사전 구축 쿼리](https://app.wiz.io/boards/threat-center/wiz-adv-2026-026)로 취약한 **GHES 인스턴스**를 식별할 수 있음

### 조사 배경과 접근 방식
- GitHub의 내부 git 인프라는 모든 `git push`를 처리하는 경로로, 여러 내부 서비스가 서로 다른 프로그래밍 언어로 작성돼 있음
- 이런 **멀티서비스 구조**에서는 각 컴포넌트가 공유 데이터를 파싱하고 신뢰하는 방식의 차이가 취약점으로 이어질 수 있음
- 기존에는 이 파이프라인을 구성하는 대량의 **컴파일된 블랙박스 바이너리**를 추출하고 감사하는 데 과도한 시간과 수작업이 들었음
- **AI 보강 도구**와 `IDA MCP` 기반 자동 리버스 엔지니어링으로 컴파일된 바이너리를 빠르게 분석하고 내부 프로토콜을 재구성할 수 있었음
- 그 과정에서 사용자 입력이 전체 파이프라인에서 서버 동작에 영향을 주는 지점을 체계적으로 추적했고, 입력 흐름 전반의 **근본적 결함**을 찾아냄

### 내부 아키텍처와 신뢰 경계
- `git push`가 SSH로 들어오면 요청은 `babeld`, `gitauth`, `gitrpcd`, 그리고 **pre-receive hook** 순으로 흐름
- `babeld`는 모든 git 작업의 진입점으로 SSH 연결을 받고, 인증은 `gitauth`로 전달함
- `gitauth`는 사용자 자격 증명과 저장소 push 권한을 확인하고, 파일 크기 제한이나 브랜치 이름 규칙 같은 보안 정책을 반환함
- `babeld`는 이 응답을 바탕으로 **보안 메타데이터**를 담은 내부 `X-Stat` 헤더를 구성함
- `gitrpcd`는 `X-Stat` 헤더를 받아 후속 프로세스 환경을 설정하며, 자체 인증 없이 `babeld`를 전적으로 신뢰함
- **pre-receive hook**은 push 수락 전에 파일 크기 제한, 브랜치 이름 규칙, LFS 무결성, 관리자 정의 커스텀 훅을 검사함
- 핵심 연결 고리는 `;`로 구분된 `key=value` 쌍을 담는 **X-Stat 헤더**였음
- 내부 서비스는 `X-Stat`를 `;` 기준으로 나눠 맵으로 채우며, 같은 키가 두 번 나오면 나중 값이 앞선 값을 덮어쓰는 **last-write-wins** 규칙을 따름
- `babeld`는 `git push -o`로 전달된 **push options**를 `push_option_0`, `push_option_1`, `push_option_count` 같은 필드로 `X-Stat`에 함께 넣음

### 취약점 원인: X-Stat 필드 주입
- `babeld`는 사용자 제어 입력인 **push option 값**을 `X-Stat` 헤더에 복사하면서 세미콜론을 정리하지 않음
- `;`는 `X-Stat` 필드 구분자이기 때문에, push option 안의 세미콜론 하나로 원래 필드를 벗어나 **공격자 제어 필드**를 새로 만들 수 있었음
- 예시로 `push_option_0` 내부에 `large_blob_rejection_enabled=bool:false`를 주입하면, 앞서 설정된 `bool:true`를 나중 값이 덮어쓰게 됨
- 이런 동작은 바이너리 분석과 실제 GHES 인스턴스의 **패킷 캡처**에서 모두 확인됨
- 리버스 엔지니어링과 wire-level 분석을 결합해 주입 가능한 `X-Stat` 필드를 매핑함
- 특히 보안상 중요한 필드로 `rails_env`, `custom_hooks_dir`, `repo_pre_receive_hooks`, `large_blob_rejection_enabled`, `reject_sha_like_refs`, `user_operator_mode`가 확인됨
- 이 중 **`rails_env`**, **`custom_hooks_dir`**, **`repo_pre_receive_hooks`** 세 필드가 원격 코드 실행으로 이어지는 핵심이었음

### GHES에서 RCE로 이어지는 경로
- GHES는 push 수락 전에 실행되는 **custom pre-receive hooks**를 지원함
- pre-receive 바이너리에는 `X-Stat`의 `rails_env` 값으로만 갈리는 **두 개의 실행 경로**가 있었음
- `production` 값이면 훅을 샌드박스 안에서 실행하고, 그 외 값이면 샌드박스나 격리 없이 `git` 서비스 사용자 권한으로 직접 실행함
- 따라서 `rails_env`를 비생산 값으로 주입하면 **샌드박스 우회**가 가능해짐
- 이어서 `custom_hooks_dir`를 주입하면 훅 스크립트를 찾는 기본 디렉터리를 공격자가 통제할 수 있게 됨
- 마지막으로 `repo_pre_receive_hooks`에 경로 순회가 들어간 훅 정의를 주입하면, 바이너리의 경로 해석이 공격자 제어 디렉터리와 순회 페이로드를 결합해 **파일시스템 임의 경로**를 가리키게 됨
- 비생산 실행 경로는 이렇게 해석된 경로를 인자 없이, 샌드박스 없이, `git` 서비스 사용자로 직접 실행함
- 실제 검증에서는 `git push` 한 번으로 `uid=500(git)` 출력이 반환돼 **`git` 사용자 권한 RCE**가 확인됨
- 이 권한으로 GHES 인스턴스의 파일시스템 읽기·쓰기와 내부 서비스 설정 가시성을 포함한 **전체 제어권**을 확보할 수 있었음

### GitHub.com으로의 확장과 교차 테넌트 노출
- 같은 익스플로잇 체인을 GitHub.com 저장소에 적용했을 때 처음에는 push는 성공했지만 **custom hooks**는 실행되지 않았음
- `user_operator_mode=bool:true`를 주입해 양쪽 플랫폼의 디버그 출력을 비교한 결과, GitHub.com에서는 custom hooks 코드 경로에 도달하지 않음이 드러남
- 추가 리버스 엔지니어링으로 서버의 **enterprise mode** 동작 여부를 제어하는 불리언 플래그가 `X-Stat` 헤더에 존재함을 확인함
- GHES에서는 이 플래그가 기본적으로 true라 custom hooks 경로가 항상 활성화되고, GitHub.com에서는 기본값이 false라 정상 상태에서는 그 경로에 도달하지 않음
- 이 플래그도 같은 메커니즘으로 주입할 수 있었기 때문에, 필드 하나를 더 주입해 GitHub.com에서도 전체 **익스플로잇 체인**이 작동함
- 이후 GitHub.com 인프라 내부에서 `hostname` 실행 결과가 반환돼 **GitHub.com RCE**가 확인됨
- GitHub.com은 **멀티테넌트 플랫폼**이어서 여러 사용자와 조직의 저장소가 공유 백엔드 인프라에 저장됨
- 코드 실행이 일어난 위치는 shared storage node였고, 여기서 `git` 사용자는 해당 노드의 모든 저장소 작업을 처리하도록 폭넓은 파일시스템 권한을 가짐
- 이 사용자가 손상되면 노드에 있는 다른 조직과 사용자의 저장소도 소유자와 무관하게 읽을 수 있게 됨
- 손상된 두 개 노드에서 접근 가능한 저장소 인덱스 엔트리를 열거한 결과, 각 노드마다 **수백만 개 엔트리**가 있었고 다른 사용자와 조직의 저장소가 포함돼 있었음
- 다른 테넌트 저장소의 실제 내용에는 접근하지 않았고, 자체 테스트 계정만 사용해 `git` 사용자의 파일시스템 권한이 **노드 내 모든 저장소 읽기**를 허용함을 검증함

### 핵심 교훈과 공개 일정
- `git push` 한 번만으로도 내부 프로토콜 결함을 악용해 백엔드 인프라에서 **원격 코드 실행**이 가능했음
- 서로 다른 언어로 작성된 여러 서비스가 공유 내부 프로토콜로 데이터를 주고받을 때, 각 서비스의 **신뢰 가정** 자체가 공격 표면이 됨
- 이번 체인에서는 한 서비스가 push option 값을 그대로 삽입했고, 다른 서비스는 `X-Stat`의 모든 필드를 신뢰했으며, pre-receive hook은 운영 환경에서 `rails_env`가 `production`일 것이라고 가정했음
- 운영 바이너리 안의 **비생산 코드 경로**, 훅 스크립트의 경로 순회 검증 부재, 구분자 기반 프로토콜에서의 입력 정리 부재는 다른 코드베이스에도 나타날 수 있는 패턴임
- 멀티서비스 아키텍처를 운영하는 팀은 특히 보안 민감 설정이 공유 데이터 포맷에서 파생될 때, 사용자 제어 입력이 **내부 프로토콜**을 따라 어떻게 흐르는지 점검할 필요가 있음
- 이번 연구에서는 `IDA MCP`를 포함한 **AI 보강 리버스 엔지니어링** 도구가 컴파일된 바이너리 분석과 내부 프로토콜 재구성을 빠르게 가능하게 했음
- 이런 도구가 성숙할수록 깊은 **교차 컴포넌트 분석**이 필요한 취약점 군을 찾는 데 더 중요한 역할을 하게 될 것으로 보임
- 공개 일정상 `2026-03-04`에 X-Stat push option 주입 취약점을 발견했고, 같은 날 `GHES 3.19.1`에서 RCE를 확인하고 GitHub에 보고했으며 GitHub.com 수정도 같은 날 배포됨
- `2026-03-10`에는 **CVE-2026-3854**와 `CVSS 8.7`이 부여됐고, GHES 패치가 공개됨
- `2026-04-28`에 **공개**됨

## Comments



### Comment 56562

- Author: neo
- Created: 2026-04-29T21:42:34+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=47936479) 
- 내부 **인증 서비스**가 설정하는 보안 핵심 헤더에,  
  최종 사용자가 `git push -o`로 넣는 **임의 문자열**도 함께 들어가게 만든 셈임  
  지나고 나서 말하긴 쉽다지만, 그래도 이건 너무 황당함

- **AI 보강 리버싱** 방식이 지금 LLM 에이전트의 강점을 잘 보여줌  
  코드로 많이 학습된 모델이라 복잡한 시스템 내부를 이해하는 속도를 엄청 끌어올릴 수 있음  
  보안 연구는 보통 1) 복잡한 내부 동작을 파악하는 일과 2) 그 안에서 취약점을 찾는 일이 겹쳐 있는데,  
  종종 진짜 내부 메커니즘만 드러나면 취약점 자체는 의외로 쉽게 보이기도 함  
  **CVE-2026-3854**는 내부를 알아도 바로 obvious한 유형은 아니었지만,  
  더 전통적이거나 접근 쉬운 공격 표면에 노출됐으면 이 커맨드 인젝션은 금방 발견됐을 거라고 봄
  - 원래는 AI가 **C++ 리버스 엔지니어링**이나 C++를 단순한 C로 대량 포팅하는 데 강점이 있다는 신호가 있었음  
    그런데 요즘은 그 흐름이 좀 흐트러졌거나, C++ 문법 복잡성으로 생기는 dev/vendor lock-in을 지키려는 쪽 때문에 일부러 방해받는 느낌도 듦

- **Wiz**에서 일하는 사람 있나 싶을 정도로 결과물이 꽤 괜찮아 보임  
  제품도 극단적인 성장과 기능 비대화를 겪고도 아직 꽤 잘 버티고,  
  보안팀도 정말 흥미로운 걸 자주 찾아냄
  - **Unit 8200** 출신이 많음
  - 너무 **노이즈**가 많아서, 우리는 critical만 보려고 `osv-scanner`와 `trivy`를 돌리는 커스텀 파이프라인만 씀
  - 거기 있진 않지만 우리 회사에서 써보면 완전히 무해한 행동에도 자꾸 경고를 띄움  
    그런데 정작 CLI로 DC 조회하고 자격 증명 리셋하는 식의 좀 수상한 작업엔 조용해서 허탈함

- `babeld`가 push 요청을 전달할 때 내부 요청의 **X-Stat 헤더**에 push options를 넣고,  
  그 값은 사용자가 `git push -o`로 넣는 **임의 문자열**임  
  그런데 세미콜론을 sanitize하지 않은 채 값을 그대로 복사해서,  
  `;`가 X-Stat 필드 구분자이기 때문에 원래 필드를 탈출해 공격자가 새 필드를 만들 수 있게 됨  
  정말 가장 단순한 실수를 그대로 해버린 수준이라, 과일이 너무 낮게 달려서 땅속에 묻힌 수준처럼 보임
  - 아, **Bobby Tables** 엄마는 정말 영리했지

- 이미 악용되기 전에 잡아낸 취약점인데도  
  **BREAKING**, **unauthorized access**, **millions of repositories** 같은 표현으로 괜한 공포를 키울 필요가 있나 싶음  
  [https://x.com/wiz_io/status/2049153209982140718](https://x.com/wiz_io/status/2049153209982140718)
  - 그렇다고 그 표현들이 부정확한 것도 아님  
    GitHub는 국가 지원 공격자가 아니라 **Wiz의 퍼징**에 걸린 쪽이라 운이 좋았던 셈임

- **88%의 GHES 인스턴스**가 7주 전에 나온 치명적 보안 패치를 아직도 적용하지 않았다는 건 꽤 심각해 보임  
  [https://docs.github.com/en/enterprise-server@3.19/admin/release-notes#3.19.3](https://docs.github.com/en/enterprise-server@3.19/admin/release-notes#3.19.3)
  - **GHES**는 사실상 수년째 거의 방치 상태나 다름없음  
    패치 레벨 릴리스 하나 적용하는 데도 몇 시간 다운타임이 필요하고,  
    지원되는 HA 업그레이드 방식도 없어서 성실한 고객조차 최신 버전을 바로 따라가기 어려움  
    불만을 제기하면 다들 GitHub Enterprise Cloud로 옮기라고 하지만,  
    요즘 같은 때에 그걸 선뜻 택할 사람이 얼마나 될지도 의문임  
    그래도 GHES는 github.com의 일일 장애 중엔 멈추지 않는다는 점은 있음
  - 온프렘 고객 상당수는 **VPN 뒤**에 GHES를 두고 있고,  
    운영 영향이 적은 날짜를 잡아서 업그레이드하려는 듯함  
    다만 퍼블릭 인스턴스라면 즉시 업데이트해야 함  
    기사에 나온 정보와 공개된 GitHub Enterprise 소스만으로도 재현법을 짜내는 건 어렵지 않아 보임
  - 엔터프라이즈에선 정기 일정 밖에서 업데이트했다가 다 터뜨리고 책임질 수도 있고,  
    그냥 일정대로 가면서 별일 없길 바랄 수도 있는데 대개 후자가 선택됨
  - **보안을 매우 진지하게 본다**는 이유로 github.com을 못 쓰는 환경에선,  
    온프렘 제품이 1년에 한 번 업데이트돼도 이상하지 않음
  - 큰 설치 환경에선 업그레이드 과정이 얼마나 **취약한지**가 핵심임  
    데이터 규모가 큰 엔터프라이즈 소프트웨어는 사소한 것 하나로 설치가 깨지고 운영팀이 롤백하는 일이 흔했음  
    예전 SharePoint 업그레이드는 거의 주사위 굴리는 느낌이었음

- 이번 건도 **Wiz의 대형 성과**이고,  
  AI 도구가 RE와 침해 경로 발견을 얼마나 밀어주는지 보여주는 분기점처럼 느껴짐
  - 그래서 **소스를 공개하지 말아야 AI가 덜 뚫는다**는 주장에도 금이 감  
    보안은 결국 **security through obscurity**에 기대면 안 된다는 데이터 포인트가 하나 더 쌓인 셈임

- 다들 GitHub를 대체하자고 하지만, 그럼 뭘 쓰겠냐는 고민이 남음  
  GitHub 정도 되는 곳도 이제 와서 **RCE**가 나오는데 다른 대안이 더 낫다고 쉽게 장담하긴 어려움
  - 걱정 말라며 Thomas Dohmke가 새 프로젝트로 다 해결해준다는 농담도 가능함  
    [https://news.ycombinator.com/item?id=46961345](https://news.ycombinator.com/item?id=46961345)  
    [https://news.ycombinator.com/item?id=47712656](https://news.ycombinator.com/item?id=47712656)
  - 그나마 현실적인 답은 **self-hosted Forgejo**를 원본 포지로 두고,  
    GitHub는 무료 CI를 쓰는 동안만 미러로 활용하는 방식 같음  
    시크릿은 별도 secret-hosting provider에 맡기면 됨
  - 우리는 6개월 전에 GitHub에서 **Forgejo** self-hosted로 옮겼는데 아주 잘 돌아감  
    Forgejo가 이렇게 반응 빠르고 GitHub가 이렇게 느려졌다는 게 아직도 믿기지 않음
  - 내부용 프로젝트와 대외 공개용 프로젝트를 이제 확실히 분리함  
    내부용은 private **Forgejo 인스턴스**에 두고, 공개용은 GitHub에 올리되 Forgejo로 미러링함  
    Forgejo가 사실상 단일 바이너리에 설정도 쉬워서 놀랐고,  
    내부 서비스가 전부 Forgejo를 바라보게 해둬서 GitHub를 떠나야 해도 마찰이 적음
  - **VPN 뒤 self-hosted GitLab**도 충분히 괜찮음  
    올인원 Docker 이미지와 GitLab runner 몇 개면 소규모~중간 규모 팀엔 충분하고,  
    정말 필요하지 않으면 Kubernetes 버전까지 복잡하게 갈 이유는 없음

- 소스 코드에서 **AI가 취약점**을 찾는 것도 인상적이었는데,  
  바이너리 실행 파일에서까지 해내는 건 정말 놀라움  
  좋은 쪽으로도 나쁜 쪽으로도 잠재력이 아주 큼  
  그리고 또 한 번, 데이터를 명령으로 취급하면 안 된다는 교훈이 드러남  
  사용자 입력은 전부 sanitize해야 함
  - **Transformer**는 원래 번역을 위해 설계된 구조임  
    그래서 source-to-source나 text-to-source에 강한 건 익숙한 얘기였고,  
    asm 버전을 이해하는 데도 잘 맞는 건 아주 놀랍기만 한 일은 아닐 수 있음  
    그래도 인상적인 건 여전함

- 이게 실제로 **악용됐는지** 판별할 수 있을지 궁금함
  - 내가 보기엔 이 취약점은 **익명 사용자**도 악용 가능해 보임  
    HTTP/git protocol 로그로 악용 여부 자체는 어느 정도 확인할 수 있겠지만,  
    실제로 무엇에 접근했고 누가 했는지까지는 남기지 못했을 가능성이 큼  
    익스플로잇이 git 서버에서 독립 실행될 수 있었다면, 정의상 로깅을 피해갈 수 있기 때문임
