본문 바로가기
CVE 취약점

CVE-2026-6951: simple-git --config 플래그 우회를 통한 원격 코드 실행(RCE) 분석

by WhiteGuidance 2026. 10. 7.
반응형


항목 내용
CVE ID CVE-2026-6951
대상 패키지 simple-git (npm)
영향받는 버전 < 3.36.0
패치 버전 3.36.0
CWE CWE-94 (Improper Control of Generation of Code), CWE-88 (Argument Injection)
CVSS 3.1 9.8 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

 

 

개요

simple-git은 Node.js 애플리케이션에서 git 명령을 프로그래밍 방식으로 실행할 수 있게 해주는 라이브러리로, 주간 다운로드 870만 건, 의존 패키지 7,789개에 달하는 JavaScript 생태계의 핵심 빌드/배포 도구 중 하나입니다.

 

CVE-2026-6951은 2022년에 패치되었던 CVE-2022-25912(옵션 인젝션을 통한 RCE)에 대한 불완전한 수정(incomplete fix)이 원인입니다. 2022년 패치는 -c 옵션(짧은 형식)만 차단했을 뿐, git이 완전히 동일하게 해석하는 --config 옵션(긴 형식)은 차단하지 않았습니다. 그 결과 공격자는 단순히 플래그 표기만 바꿔서 4년 전에 막혔던 공격 경로를 그대로 재사용할 수 있었습니다.


취약점의 뿌리: CVE-2022-25912부터 이해하기

이 취약점을 제대로 이해하려면 먼저 2022년에 무엇이 패치되었는지 알아야 합니다.

 

git에는 ext:: 라는 특수한 원격 저장소 프로토콜이 있습니다. 이 프로토콜은 원래 "외부 헬퍼 프로그램을 통해 원격 저장소에 접근"하는 기능인데, 문제는 이 헬퍼 프로그램 자리에 임의의 셸 명령어를 넣을 수 있다는 점입니다.

# ext:: 프로토콜 사용 예시 — 콜론 뒤에 오는 문자열이 그대로 실행됨
git clone "ext::sh -c touch% /tmp/pwned% >&2" /tmp/dest
 

단, 이 기능은 기본적으로 비활성화되어 있고, protocol.ext.allow=always 라는 git 설정값을 켜야만 동작합니다. 그리고 이 설정값은 -c 또는 --config 플래그로 명령줄에서 즉석으로 주입할 수 있습니다.

git clone -c protocol.ext.allow=always "ext::sh -c '...'" /tmp/dest
 

 

즉 공격이 성립하려면 두 가지 조건이 동시에 필요합니다.

  1. protocol.ext.allow=always 설정을 주입할 수 있어야 함
  2. ext:: 로 시작하는 악성 clone URL을 주입할 수 있어야 함

simple-git은 사용자가 clone(), fetch() 같은 메서드에 전달하는 options(또는 customArgs) 배열을 거의 그대로 git 바이너리에 넘겨줍니다. 애플리케이션이 이 배열에 신뢰할 수 없는 입력(사용자 입력, 외부 API 응답 등)을 그대로 섞어 넣는다면, 공격자는 1번과 2번 조건을 모두 주입할 수 있게 됩니다.

 

2022년 패치는 바로 이 1번 조건, 즉 -c protocol...allow=... 형태의 인자를 블랙리스트 정규식으로 탐지해서 차단하는 방식이었습니다.


2022년 패치의 구조와 한계

패치가 적용된 파일은 simple-git/src/lib/plugins/block-unsafe-operations-plugin.ts 이며, 핵심은 preventProtocolOverride라는 함수입니다. 동작 방식을 간단한 의사코드로 표현하면 다음과 같습니다.

 

이 코드를 한 줄씩 쉽게 풀어보면:

  • ① args[i] === '-c': 전달된 인자 배열을 순회하면서, -c 라는 토큰(짧은 옵션 플래그)만 정확히 찾습니다. 문자열 비교이기 때문에 -c가 아닌 다른 표기는 전혀 감지하지 못합니다.
  • ② 정규식 검사: -c 바로 다음에 오는 값이 protocol.allow, protocol.ext.allow 같은 패턴과 일치하면 예외를 던져 실행을 중단시킵니다.

이 로직은 git에게 설정을 전달하는 방법이 -c 하나뿐이라는 잘못된 전제 위에 설계되어 있습니다. 그러나 실제 git CLI는 동일한 기능을 수행하는 두 가지 플래그 표기를 제공합니다.

플래그 의미 탐지
-c key=value 짧은 형식 ✅ 탐지됨
--config key=value 긴 형식, -c와 완전히 동일한 동작 ❌ 탐지 안 됨

 

git 공식 문서 기준으로 -c와 --config는 완전한 동의어이며, git 바이너리 내부에서 동일한 파싱 경로를 거칩니다. 하지만 simple-git의 블랙리스트는 문자열 '-c' 만을 정확히 매칭하는 방식이었기 때문에, --config를 사용하면 검사 로직 자체가 실행되지 않고 그대로 통과합니다.

 

이는 전형적인 "블랙리스트 기반 인자 검증의 구조적 한계"를 보여주는 사례입니다. 완전한 금지 목록을 만들려면 "같은 의미를 가질 수 있는 모든 표기 방식"을 다 나열해야 하는데, CLI 도구는 보통 하나의 기능에 여러 표기(단축형/전체형, 대소문자, = 결합형 등)를 허용하기 때문에 이런 방식은 구조적으로 깨지기 쉽습니다. 실제로 같은 플러그인에서 발생한 CVE-2026-28292(대문자 PROTOCOL.ALLOW=always로 대소문자 구분 정규식을 우회)도 동일한 근본 원인에서 나온 자매 취약점입니다.


공격 흐름 (Attack Flow)

위 흐름에서 핵심은 2단계입니다. 공격자가 특별히 정교한 기술을 쓰는 것이 아니라, 단지 플래그 표기 방식을 바꾸는 것만으로 2022년의 보안 수정을 무력화시킨다는 점이 이 취약점의 본질입니다.

 

 


실제 동작하는 PoC (공개된 개념 증명)

공개된 개념 증명 코드는 다음과 같은 형태입니다(원문은 참고 문헌 없이 핵심 로직만 재구성).

코드를 하나씩 설명하면:

  • ① clone()의 첫 번째 인자는 원래 "clone할 저장소 주소"가 들어가야 할 자리입니다. 여기에 ext::sh -c touch% /tmp/pwned% >&2 라는 문자열을 넣으면, git은 이것을 "ext 프로토콜로 clone하되, sh -c touch /tmp/pwned 를 헬퍼 명령으로 실행하라"는 뜻으로 해석합니다. (%는 git이 URL 내부의 공백을 구분하기 위해 사용하는 인코딩 문자입니다.)
  • ② 두 번째 인자는 정상적인 clone 대상 경로로, 공격과는 무관합니다.
  • ③ 세 번째 인자가 핵심입니다. --config와 protocol.ext.allow=always를 별도의 두 배열 요소로 전달합니다. 이는 -c protocol.ext.allow=always와 git 입장에서 완전히 동일한 효과를 내지만, simple-git의 블랙리스트는 '-c' 문자열만 찾기 때문에 '--config' 토큰은 그냥 지나칩니다.

결과적으로 simple-git은 내부적으로 다음과 같은 명령을 그대로 실행합니다.

git clone --config protocol.ext.allow=always "ext::sh -c touch% /tmp/pwned% >&2" /tmp/example-new-repo
 

 

이 명령이 실행되면 git이 clone 실패(존재하지 않는 저장소이므로)를 리턴하기 이전에 ext:: 헬퍼 프로세스가 먼저 실행되어 /tmp/pwned 파일이 생성됩니다. 즉 최종 clone이 실패하더라도 명령 실행 자체는 이미 성공한 뒤라는 점이 중요합니다 — 애플리케이션이 clone 실패를 정상적으로 캐치하더라도 공격은 이미 끝나 있습니다.

 

실제 애플리케이션 시나리오로 바꿔보면 더 위험성이 와닿습니다.


패치 분석 (simple-git 3.36.0)

패치는 단순히 --config를 블랙리스트에 한 줄 추가하는 수준이 아니라, 아키텍처 자체를 상당히 확장했습니다. 커밋명은 "Environment Parsing"이며 29개 파일, 1,000줄 이상이 변경되었습니다.

 

6-1. 설정 키 블랙리스트 확장

기존 detect-config-writes.ts는 protocol.allow, core.sshCommand, core.fsmonitor, core.gitProxy, core.hooksPath, diff.external 단 6개 키만 차단했습니다. 새 detect-vulnerable-config-writes.ts는 정규식 확장을 통해 credential.https://example.com.helper 같은 스코프 지정 변형까지 매칭하는 preventExpandedConfigBuilder 헬퍼를 도입했고, 아래와 같은 키들을 추가로 차단합니다.

  • alias.* — 임의 명령을 git 별칭으로 등록
  • core.askPass, core.editor, core.pager — 바이너리 치환
  • credential.helper — 자격 증명 가로채기
  • diff.textconv, filter.clean, filter.smudge — 파일 내용 가로채기(콘텐츠 필터 체인)
  • gpg.program 계열 — 서명 바이너리 치환
  • merge.driver, mergetool.cmd — 머지 시점 코드 실행
  • sequence.editor — rebase 시점 바이너리 치환

6-2. 플래그 탐지 확장

기존에는 --upload-pack, --receive-pack 류만 탐지하던 detect-upload-pack.ts를 대체해 detect-vulnerable-flags.ts가 --template 플래그(템플릿 디렉터리를 통한 훅 심기 공격)까지 추가로 차단합니다.

 

6-3. 환경 변수 스캐닝 — 완전히 새로운 방어선

이번 패치의 가장 중요한 변화입니다. git은 명령줄 -c/--config뿐 아니라 GIT_CONFIG_COUNT, GIT_CONFIG_KEY_n, GIT_CONFIG_VALUE_n 같은 환경 변수를 통해서도 동일한 설정 주입이 가능합니다. 즉 명령줄 인자만 필터링해서는 근본적인 방어가 될 수 없다는 뜻입니다. 새로 추가된 parseEnv() 함수는 GIT_ASKPASS, GIT_SSH_COMMAND, GIT_CONFIG_COUNT, GIT_EXTERNAL_DIFF, GIT_PROXY_COMMAND 등 위험 환경 변수를 매핑하고, GIT_CONFIG_COUNT가 설정되어 있으면 대응하는 GIT_CONFIG_KEY_n/VALUE_n 쌍을 추출해 동일한 블랙리스트 검사를 적용합니다.

 

이 변경을 쉽게 풀면, 기존에는 "명령줄에 뭐가 들어왔는지"만 보던 문지기가, 이제는 "환경 변수에 숨겨서 같은 설정을 몰래 넣는 것"까지 함께 확인하도록 역할이 확장된 것입니다. 공격자가 명령줄 인자 검사를 피하더라도 환경 변수 경로로 똑같은 공격을 시도하면 이번에는 vulnerabilityCheck() 단계에서 걸러집니다.

 

6-4. opt-in 모델 세분화

allowUnsafeAlias, allowUnsafeCredentialHelper, allowUnsafeTemplateDir 등 차단 카테고리별로 명시적 허용 플래그를 제공해, 정말로 해당 기능이 필요한 개발자는 신뢰할 수 있는 입력에 한해 선택적으로 허용할 수 있도록 세분화했습니다. 기존 7개였던 VulnerabilityCategoryFlags가 20개 이상으로 확장되었습니다.


왜 이런 우회가 반복되는가 — 구조적 원인

simple-git은 이번이 세 번째 관련 취약점입니다.

  • CVE-2022-25912: -c 플래그를 통한 최초 RCE
  • CVE-2026-28292: 대문자 PROTOCOL.ALLOW=always로 대소문자 구분 정규식 우회
  • CVE-2026-6951: --config라는 동의어 플래그로 블랙리스트 자체를 우회

세 건 모두 근본 원인은 동일합니다. "위험한 패턴 하나를 정확히 매칭하는" 블랙리스트 방식은 같은 의미를 가진 다른 표기(단축형 vs 전체형, 대소문자, 환경 변수 경로 등)가 하나라도 존재하면 뚫립니다. 이번 3.36.0 패치가 명령줄 인자뿐 아니라 환경 변수까지 함께 검사하도록 방어선을 넓힌 것은, 단일 지점 블랙리스트의 한계를 인정하고 다층 방어(defense in depth) 쪽으로 방향을 튼 것으로 볼 수 있습니다.


대응 방안

방법 설명
버전 업그레이드 npm install simple-git@latest (3.36.0 이상)
부분 업그레이드 주의 @simple-git/argv-parser 등 하위 패키지 버전이 불일치하면 CI에서 TypeError 발생 가능 — yarn.lock/package-lock.json 전체 재생성 권장
입력 검증 사용자 입력을 options/customArgs에 직접 전달하지 말고, 허용된 값만 화이트리스트로 매핑해서 전달
최소 권한 simple-git을 호출하는 프로세스 자체를 샌드박스/제한된 권한 계정으로 격리

 

반응형