Git의 캐리지 리턴 및 클론 RCE 취약점
(dgl.cx)- Unix 계열에서 신뢰할 수 없는 저장소를
git clone --recursive로 복제하면 CVE-2025-48384로 원격 코드 실행이 가능할 수 있어, Git과 Git 내장 소프트웨어 업데이트가 필요함 - 원인은 Git 설정을 읽고 다시 쓰는 과정에서 캐리지 리턴(
\r) 처리 방식이 달라져, 검증된 값과 실제 사용되는 값이 어긋나는 데 있음 .gitmodules의 서브모듈path끝에\r을 넣으면 체크아웃 경로가 검증 이후 다른 경로로 바뀔 수 있음- Windows는 파일명에 제어 문자를 허용하지 않아 직접 취약하지 않지만, macOS는 CVE-2024-32002와 CVE-2025-48384 모두에 취약함
- 패치는
\r이 포함된 설정 값을 따옴표로 감싸도록 바꿔,.git내부 파일 쓰기와 Git 훅 생성 같은 공격 경로를 막음
취약점 개요와 즉시 조치
- CVE-2025-48384는 Unix 계열 플랫폼에서 신뢰할 수 없는 저장소를
git clone --recursive로 복제할 때 원격 코드 실행으로 이어질 수 있는 Git 취약점임 - 수정된 Git 버전으로 업데이트하고, GitHub Desktop을 포함해 Git을 내장한 소프트웨어도 함께 업데이트해야 함
- 명령줄에서
git clone만 실행하면 서브모듈은 자동으로 복제되지 않음- 완화하려면 먼저
git clone을--recursive없이 실행하고,.gitmodules가 안전한지 확인한 뒤 서브모듈을 초기화하는 방식이 가능함
- 완화하려면 먼저
- GitHub Desktop은 기본적으로 재귀 옵션으로 복제하므로 해당 동작에서 영향을 받을 수 있음
캐리지 리턴과 Git 설정 파일
- 캐리지 리턴은 ASCII 문자 번호 13인 Carriage Return이며, C 문자열에서는
\r로 표현됨 - Unix는 줄 구분에 LF, 즉
\n만 쓰려 했지만 Windows와 여러 인터넷 프로토콜은 CR+LF, 즉\r\n을 사용함 - Git의
.ini스타일 설정 형식은 사용자 설정 파일뿐 아니라 저장소에 포함되는.gitmodules파일에도 쓰임 - Git 설정 파서는 DOS 줄 끝을 지원하기 위해 문자를 읽을 때 다음처럼 동작함
- 현재 문자가
\r이면 다음 문자를 확인함 - 다음 문자가
\n이면\r을 버리고 줄바꿈으로 처리함 - 다음 문자가
\n이 아니면 다음 문자를 되돌려 놓고\r자체를 반환함
- 현재 문자가
- 이 처리는 줄 단위로 적용되므로, 어떤 줄이 CR로 끝나면 파일 전체 형식과 무관하게 해당 CR이 제거될 수 있음
읽기와 쓰기의 불일치
- Git은 설정 파일을 읽는 것뿐 아니라
git config처럼 설정 값을 쓸 때도 같은 형식으로key = value쌍을 기록함 - 기존 코드는 설정 값을 다시 쓸 때 다음 경우에만 값을 큰따옴표로 감쌌음
- 값이 공백으로 시작하는 경우
- 값 안에
;또는#이 있는 경우 - 값이 공백으로 끝나는 경우
- 반면 설정 읽기 코드는 큰따옴표 문자열을 지원해,
write_pair가 쓴 값을 나중에 다시 읽을 때 마지막\r이 떨어져 나갈 수 있음 - 예를 들어 설정 파일에
key = "foo^M"이 있으면 다시 쓰인 뒤key = foo^M형태가 될 수 있음- 여기서
^M은 리터럴 CR 문자임
- 여기서
- 이 동작이 신뢰할 수 없는
.gitmodules값과 결합되면 서브모듈 경로 처리가 흔들림
서브모듈 경로 혼동과 영향 범위
- Unix 기반 시스템에서는 파일명에 제어 문자를 넣을 수 있어
.gitmodules의path에 CR을 포함한 값을 넣을 수 있음 - 예시는 다음과 같은 형태임
[submodule "foo"]
path = "foo^M"
- 이 값이 Git 설정 코드에 의해
.git/modules/foo/config에 기록되면 다음과 같은 형태가 될 수 있음
[core]
workdir = ../../../foo^M
- 검증은
.gitmodules에서 읽은 신뢰할 수 없는 경로에 대해 이미 끝난 상태임 - 이후 설정을 다시 읽는 과정에서 마지막
\r이 제거되면, Git은 검증된 경로와 다른 경로를 사용하게 됨 - 결과적으로 서브모듈 복제 중
path = ...에서 읽은 위치와 실제로 기록·사용되는 위치가 달라질 수 있음 - 이 원리는 CVE-2024-32002와 유사함
- CVE-2024-32002는 서브모듈에서 대소문자 구분 여부를 이용해 Git을 혼동시킴
- CVE-2025-48384는 제어 문자를 파일명에 허용하는 파일 시스템이 필요함
- Windows는 파일명에 제어 문자를 허용하지 않으므로 이 특정 버그에 직접 취약하지 않음
- macOS는 CVE-2024-32002와 CVE-2025-48384 모두에 취약함
패치와 공격 가능성
- 패치는
write_pair에서 값 안에\r이 있으면 문자열을 따옴표로 감싸도록 변경함 - 변경은
;또는#만 검사하던 조건에'\r'을 추가하는 단순한 형태임
for (i = 0; value[i]; i++)
- if (value[i] == ';' || value[i] == '#')
+ if (value[i] == ';' || value[i] == '#' || value[i] == '\r')
quote = "\"";
- 경로 혼동으로 서브모듈의 악성 파일을 파일 시스템의 거의 임의 위치에 둘 수 있으며, 검증을 우회한 상태라 저장소 밖의 심볼릭 링크도 따라갈 수 있음
- 가장 직접적인 악용 방식은
.git디렉터리 안에 파일을 쓰고 Git hook script를 만들어, Git이 훅을 실행할 때 공격자 제어 코드가 실행되게 하는 것임 - 다른 가능성으로는
.git/config덮어쓰기가 있음 - PoC는 아직 공개되지 않았지만, CVE-2024-32002 익스플로잇을 거의 사소하게 수정하면 된다고 밝힘
- 수정 커밋의 테스트는 공격 방식에 큰 힌트를 줄 수 있음
반복되는 CR 문제와 교훈
- 캐리지 리턴이 Git 문제를 일으킨 사례는 이번이 처음이 아님
- 1월에는 RyotaK가 캐리지 리턴으로 속일 수 있는 credential helper 프로토콜 문제를 발견함
- 2023년에는 André Baptista와 Vítor Pinho가 설정 파싱의 논리 오류인 CVE-2023-29007을 발견함
- 이 취약점은 C 언어 자체의 문제가 아니라, 거의 모든 언어에서 생길 수 있는 논리 오류에 가까움
- 공통점은 Git 내부 구성요소 사이 또는 Git과 외부 프로세스 사이의 프로세스 간 통신에서 발생한다는 점임
- HTTP의 CRLF injection, request smuggling, SMTP smuggling과도 비슷한 구조를 볼 수 있음
- 과거 인터넷은 Postel의 견고성 원칙인 “보낼 때는 보수적으로, 받을 때는 관대하게”에 의존했지만, 지금은 가장 합리적인 조언이 아닐 수 있음
- 이 주제는 RFC 9413에서 더 자세히 다룸
감사와 관련 수정
- 이 취약점은 Git 감사 과정에서 발견됨
- 같은 릴리스에서 심각도가 다양한 다른 버그들도 함께 수정됨
- Git 비교 링크: 첫 번째 수정 범위
- Git 비교 링크: 두 번째 수정 범위
- 릴리스 공지: Git security vulnerabilities announced
- G-Research Open Source가 해당 작업을 가능하게 했음
댓글과 토론
Hacker News 의견들
-
서브모듈 클론에서
path = ...에서 읽은 위치와^M으로 끝나지 않는 다른 위치에 쓰는 일이 어떻게 글에서 말한 원격 코드 실행으로 이어지는지 궁금함. 보안 관점에서 얼마나 심각한지도 의문이었음
CVE-2024-32002를 보면, 서브모듈이 있는 저장소를 조작해 Git이 파일을 서브모듈 작업 트리가 아니라.git/디렉터리에 쓰도록 속일 수 있고, 그러면 클론 도중 실행되는 훅을 심을 수 있음
즉 저장소가 악성 Git 훅을 포함할 수 있고, 원래git clone은 훅을 설치하지 않지만 이 취약점은 설치를 가능하게 하며 그 훅이 클론 중 실행될 수 있음- 새 CVE에 대한 추가 정보는 여기 있음: https://github.blog/open-source/git/git-security-vulnerabili...
Git은 설정값을 읽을 때 끝의 캐리지 리턴(CR)과 줄바꿈(LF)을 제거하지만, 설정값을 쓸 때는 끝의 CR을 인용 처리하지 않아서 나중에 읽을 때 사라짐
경로 끝에 CR이 붙은 서브모듈을 초기화하면 제거된 경로가 사용되어 서브모듈이 잘못된 위치에 체크아웃되고, 그 제거된 경로와 서브모듈 훅 디렉터리 사이에 심볼릭 링크가 있으면 공격자가post-checkout훅으로 임의 코드 실행을 할 수 있음
함께 공개된 다른 Git CVE들도 살펴볼 만함 - 맞음, 안타깝게도 꽤 사소하게 악용 가능함. 임의 파일 쓰기가 가능하면 보통 원격 코드 실행도 가능해짐
- 파일 쓰기가 어떻게 코드 실행으로 이어지는지 더 분명하게 보이도록 해당 문단을 수정했음
- 새 CVE에 대한 추가 정보는 여기 있음: https://github.blog/open-source/git/git-security-vulnerabili...
-
설정에 임시 DSL을 쓰면 이런 큰 문제가 생김. 문법에 대한 공식 명세가 없는 경우가 많아서, 파싱의 기준이 직접 만든 직렬화 구현과 직접 만든 역직렬화 구현 사이에 흩어짐
둘이 어긋나면, 예를 들어 파서에는 새 문법을 추가했지만 writer 갱신을 잊는 식으로 파서 차이가 생기고 시한폭탄이 작동하기 시작함
교훈은 기준을 하나로 두고, 그 기준에 의존하는 모든 것을 거기서 생성하라는 것임- 여기 문제는 파서가 갱신됐다는 데 있지 않음. 파서와 writer는 역사적으로는 타당한 이유로 그렇게 동작했지만, 서브모듈 시스템이 기대한 동작은 아니었음
서브모듈 시스템은 Git 설계에 약간 덧붙여진 구조에 가깝고, 그 추상화가 깨진 게 이번이 처음도 아님
충분히 많은 플랫폼과 프로토콜에서 쓰면 모든 파일 형식은 어떤 식으로든 명세가 부족해짐. 형식 자체가 참조 구현이 아닌 한 그렇고, 참조 구현도 이미 충분히 문제를 겪었음
IETF가 보통 독립 구현 2개를 요구하는 데는 이유가 있음
비슷한 문제는 대소문자 구분 없는 파일 시스템이나, UTF-8 정규화에 영향을 주는 서로 다른 로캘 사이에서 데이터를 옮길 때도 생김. 거의 같은 CVE가 불과 1년 전에 나온 것도 놀랍지 않음
바라는 것을 조심해야 함. 설정 형식에 ini 대신 YAML을 썼다면 지난 세월 보안 이슈가 줄기는커녕 더 많았을 가능성이 큼 - 사소한 딴지지만, 여기서 DSL인 “ini 파일 형식”은 임시적이라고 볼 수는 있어도 매우 흔하고 잘 이해되어 있으며, 충분히 단순해서 관습적 명세로도 실무에서는 잘 돌아감
이 버그는 형식 때문이 아니라, C 프로그램 한가운데 폭탄처럼 들어앉은 손으로 짠 파서[1] 때문임. 이런 건 수십 년 전에 사라졌어야 함
현대에도 C에서도 영리하게 직접 짠 코드가 필요한 곳은 있지만, 데이터 교환은 거의 절대 그런 곳이 아님. 그냥 하지 말아야 함
.ini가 필요하면 TOML을 쓰고, 아니면 JSON을 쓰면 됨. YAML도 괜찮음. 고통을 즐기는 사람에게는 XML도 있음. 형식이 반드시 바이너리여야 한다고 스스로 설득했다면 보통 틀렸고, 그래도 필요하면 protobuf가 있음
단, 직함이 “프로그래밍 언어 작성자”가 아니라면 파서를 직접 쓰지 말아야 함. 다른 데서는 라이브러리를 안 쓰더라도 여기서는 라이브러리를 써야 함
[1] 알겠음, 정확히는 lexer임. 어차피 딴지니까 - 이 버그는 기준을 하나로 두는 문제와 별개임. Unix에 표준 설정 파일 라이브러리가 있었다면 거기에도 존재할 수 있었던 순수 논리 버그임
그런 버그가 표준 시스템 라이브러리에 있었다면 결과는 훨씬 더 심각했을 것임. 여기서는 그래도 대체로 업데이트되는 개발자 머신으로 범위가 제한됨
- 여기 문제는 파서가 갱신됐다는 데 있지 않음. 파서와 writer는 역사적으로는 타당한 이유로 그렇게 동작했지만, 서브모듈 시스템이 기대한 동작은 아니었음
-
잠깐 해보니 재현됐음: https://github.com/acheong08/CVE-2025-48384
바로 Git 버전을 올리러 갔지만 Arch에는 아직 올라오지 않았음. 당분간 아무것도 pull하지 않을 생각이고, 대부분이 업그레이드하기까지는 꽤 걸릴 듯함
어느 정도 인기 있는 저장소에 넣어두고 자동 pull이 있는 환경을 노리면 흥미로운 일이 생길 수 있음- 이게 패치 전에 공개된 건가? “이렇게 하면 당신 머신을 장악할 수 있다”는 식의 글들이 대부분 이미 몇 달 지난 뒤였어서, 이런 블로그 글은 배포판들이 오래전에 패치한 뒤에 올라오는 줄 알았음
제목에서 “지금 중요함”인지 “걱정하지 않아도 됨, 아마 이미 패치됐음”인지 분명히 해주면, 단순 호기심으로 읽는 게 아닌 사람들의 시간을 아낄 수 있을 듯함 - 오늘 아침
pacman에 2.50.1이 올라왔음
- 이게 패치 전에 공개된 건가? “이렇게 하면 당신 머신을 장악할 수 있다”는 식의 글들이 대부분 이미 몇 달 지난 뒤였어서, 이런 블로그 글은 배포판들이 오래전에 패치한 뒤에 올라오는 줄 알았음
-
CR+LF 맥락에서 누군가 Jon Postel을 인용하는 걸 보니 옛 기억이 떠오름
https://jdebp.uk/FGA/qmail-myths-dispelled.html#MythAboutBar...
M. Leadbeater는 지금 “이제는 가장 현명한 조언이 아닐 수 있다”고 하지만, 우리는 2003년에 훨씬 더 단호하게 그렇게 말하고 있었음
당시 Mark Crispin이 말했듯 사람들이 그 문구를 해석하는 방식은 M. Postel이 동의했을 만한 게 아니었음
1990년대 후반 Daniel J. Bernstein은 사람이 읽는 형식과 기계가 읽는 형식 사이를 변환할 때의 파싱과 인용 처리가 문제의 원천이라는 유명한 분석을 했음
그런데 25년도 더 지난 지금, CR을 인용 처리하지 않는 quoter가 있고, 수정 뒤에도 모든 공백 문자를 검사하지는 않음
흥미롭게도git blame을 보면 문제 코드는 19년 전에 작성됐고, 그 무렵 Daniel J. Bernstein은 파싱과 인용에 관한 격언을 10년 회고하고 있었음
https://github.com/git/git/commit/cdd4fb15cf06ec1de588bee457...
https://cr.yp.to/qmail/qmailsec-20071101.pdf
20세기에 어렵게 배운 교훈이 21세기에도 여전히 적용되니 계속 반복해서 말해야 할 듯함- 특히 “보수적으로 행동하라”는 부분을 지켰다면 이 버그를 막을 수 있었을 것임
-
Homebrew 자체도 여기서 문제가 될 수 있나? 재귀 클론을 하는지 궁금함
저장소를 대충 보면 그럴 수도 있어 보임: https://github.com/Homebrew/brew/blob/700d67a85e0129ab8a893f...- 안 한다면 오히려 이상함. 다만 Homebrew의 목적은 저장소 안의 코드를 실행하는 것임
여기서 원격 코드 실행이 문제가 되는 유일한 상황은 실행하고 싶지 않은 데이터가 들어 있는 GitHub 저장소를 클론할 때인데, 그건 꽤 드문 편임
- 안 한다면 오히려 이상함. 다만 Homebrew의 목적은 저장소 안의 코드를 실행하는 것임
-
“기존 익스플로잇의 사소한 수정”이라니…
Git은 왜 Landlock을 쓰지 않는지 모르겠음. Linux 전용인 건 알지만, 그래도 왜 안 쓰는지 의문임
git clone은 설정 디렉터리에 읽기 전용, 클론 디렉터리에 읽기/쓰기 권한만 가져야 하고, 하위 프로세스도 없어야 함
익스플로잇 데모마다 “네, 네모난 구멍으로 들어가서 계산기를 실행합니다”가 반복됨- “하위 프로세스 없음”이면 기능으로 제공되는
post-checkout을 포함한 모든 Git 훅을 깨도 괜찮다는 뜻인가 봄
그런 기능들을 쓰지 않는다면 언제든seccomp같은 걸 걸어 컨테이너 안에서 Git 작업을 실행할 수 있음 ssh로 Git을 써본 적이 없는 건가?- 내가 이해하기로 Landlock의 문제는
exec를 거쳐 상속된다는 점임. OpenBSD의pledge와unveil은 기본적으로exec에서 초기화되도록 의도적으로 설계됐는데, “더 안전한” 동작이 다른 프로그램을 호출해야 하는 기존 코드에 추가하기 어렵거나 불가능하게 만들기 때문임
이론적으로는 잠그기 전에 helper 프로세스를 fork하는 식으로 할 수 있지만, 그 자체로 큰 리팩터링이 필요해서 결국 사람들이seccomp나 Landlock을 안 쓰게 됨
반면 OpenBSD 핵심 유틸리티들은 빠르게pledge와unveil을 어느 정도 쓰도록 수정됐고, 시간이 지나며 더 조여졌음. 이런 점진적 배포를 쉽게 허용하고 촉진하도록 설계됐기 때문임
Landlock이나unveil이 여기서 얼마나 도움이 됐을지는 모르겠음. 클론 도중 훅 실행만 막고, 이후 신뢰된 훅이 실행될 때는 못 막았을 수도 있음
그래도 Git이 하위 프로세스를 호출하는 방식에도 불구하고, 또는 오히려 그 덕분에 Git에unveil지원을 추가하는 쪽이 더 쉬울 것 같음
- “하위 프로세스 없음”이면 기능으로 제공되는
-
타임라인이 헷갈림. 2.50.1 태그는 2025-06-16로 되어 있음(https://github.com/git/git/tags)
https://git-scm.com의 2.50.1 날짜도 2025-06-16임
그런데 거의 어떤 배포판도 아직 패치하지 않은 것처럼 보임
Debian 예시: https://security-tracker.debian.org/tracker/CVE-2025-48386
보안 권고는 어제 올라왔음: https://github.com/git/git/security/advisories/GHSA-4v56-3xv...
Git이 릴리스를 소급해서 날짜를 붙인 건가?- 그건 커밋이 작성된 시간이지, 태그가 공개된 시간이 아님
-
대개 가장 눈에 띄지 않는 것들이 매우 원치 않는 결과로 이어짐
-
Homebrew가 아직 취약한 버전을 제공하는 것 같고, Debian Bookworm도 마찬가지로 보임
- 지금 보는 사람들을 위해 적자면, Homebrew에 2.50.1이 올라왔음