- เมื่อ clone repository ที่ไม่น่าเชื่อถือบนระบบตระกูล Unix ด้วย
git clone --recursive อาจเปิดทางให้รันโค้ดจากระยะไกลผ่าน CVE-2025-48384 ได้ จึงจำเป็นต้องอัปเดต Git และซอฟต์แวร์ที่ฝัง Git ไว้
- สาเหตุอยู่ที่วิธีจัดการ Carriage Return (
\r) แตกต่างกันระหว่างกระบวนการอ่านและเขียนการตั้งค่าของ Git ทำให้ค่าที่ผ่านการตรวจสอบแล้วไม่ตรงกับค่าที่ถูกใช้งานจริง
- หากใส่
\r ไว้ท้าย path ของ submodule ใน .gitmodules เส้นทาง checkout อาจเปลี่ยนเป็น เส้นทางอื่นหลังผ่านการตรวจสอบแล้ว
- Windows ไม่ได้รับผลกระทบโดยตรง เพราะไม่อนุญาตให้ใช้ control character ในชื่อไฟล์ แต่ macOS มีช่องโหว่ทั้ง CVE-2024-32002 และ CVE-2025-48384
- แพตช์เปลี่ยนให้ค่าการตั้งค่าที่มี
\r ถูกครอบด้วยเครื่องหมายคำพูด เพื่อปิดเส้นทางโจมตี เช่น การเขียนไฟล์ภายใน .git และการสร้าง Git hook
ภาพรวมช่องโหว่และสิ่งที่ควรทำทันที
- CVE-2025-48384 เป็นช่องโหว่ของ Git ที่อาจนำไปสู่การรันโค้ดจากระยะไกล เมื่อ clone repository ที่ไม่น่าเชื่อถือบนแพลตฟอร์มตระกูล Unix ด้วย
git clone --recursive
- ควรอัปเดตเป็น Git เวอร์ชันที่แก้ไขแล้ว และอัปเดตซอฟต์แวร์ที่ฝัง Git ไว้ด้วย รวมถึง GitHub Desktop
- หากรันเพียง
git clone จาก command line ระบบจะไม่ clone submodule โดยอัตโนมัติ
- วิธีลดความเสี่ยงคือรัน
git clone โดยไม่ใช้ --recursive ก่อน จากนั้นตรวจสอบว่า .gitmodules ปลอดภัย แล้วจึง initialize submodule
- GitHub Desktop โดยค่าเริ่มต้นจะ clone แบบ recursive จึงอาจได้รับผลกระทบจากพฤติกรรมดังกล่าว
Carriage Return และไฟล์ตั้งค่าของ Git
- Carriage Return คืออักขระ ASCII หมายเลข 13 และในสตริงภาษา C แสดงเป็น
\r
- Unix ตั้งใจใช้เฉพาะ LF หรือ
\n เพื่อแบ่งบรรทัด แต่ Windows และหลายโปรโตคอลบนอินเทอร์เน็ตใช้ CR+LF หรือ \r\n
- รูปแบบการตั้งค่าแบบ
.ini ของ Git ใช้ทั้งกับไฟล์ตั้งค่าผู้ใช้ และไฟล์ .gitmodules ที่อยู่ใน repository
- parser การตั้งค่าของ Git รองรับ line ending แบบ 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 ที่ไม่น่าเชื่อถือ จะทำให้การจัดการเส้นทาง submodule สั่นคลอน
ความสับสนของเส้นทาง submodule และขอบเขตผลกระทบ
- บนระบบ Unix-based สามารถใส่ control character ในชื่อไฟล์ได้ จึงสามารถใส่ค่าที่มี CR ใน
path ของ .gitmodules ได้
- ตัวอย่างมีรูปแบบดังนี้
[submodule "foo"]
path = "foo^M"
- เมื่อค่านี้ถูกบันทึกลงใน
.git/modules/foo/config โดยโค้ดตั้งค่าของ Git อาจกลายเป็นรูปแบบดังนี้
[core]
workdir = ../../../foo^M
- การตรวจสอบเส้นทางที่ไม่น่าเชื่อถือซึ่งอ่านจาก
.gitmodules ได้เสร็จสิ้นไปแล้ว
- ต่อมา เมื่ออ่านการตั้งค่ากลับอีกครั้ง หาก
\r ตัวสุดท้ายถูกลบออก Git ก็จะใช้เส้นทางที่แตกต่างจากเส้นทางที่ผ่านการตรวจสอบ
- ผลคือระหว่าง clone submodule ตำแหน่งที่อ่านจาก
path = ... กับตำแหน่งที่ถูกบันทึกและใช้งานจริงอาจแตกต่างกัน
- หลักการนี้คล้ายกับ CVE-2024-32002
- CVE-2024-32002 ใช้ประโยชน์จากการแยกแยะตัวพิมพ์เล็ก-ใหญ่ใน submodule เพื่อทำให้ Git สับสน
- CVE-2025-48384 ต้องใช้ระบบไฟล์ที่อนุญาตให้มี control character ในชื่อไฟล์
- Windows ไม่อนุญาตให้มี control character ในชื่อไฟล์ จึงไม่ได้เปราะบางโดยตรงต่อบั๊กนี้โดยเฉพาะ
- 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 = "\"";
- ด้วยความสับสนของเส้นทาง สามารถวางไฟล์อันตรายของ submodule ไว้เกือบทุกตำแหน่งในระบบไฟล์ได้ และเนื่องจากอยู่ในสภาพที่เลี่ยงการตรวจสอบแล้ว จึงสามารถตาม symbolic link ที่อยู่นอก repository ได้ด้วย
- วิธีโจมตีที่ตรงที่สุดคือเขียนไฟล์เข้าไปในไดเรกทอรี
.git แล้วสร้าง สคริปต์ Git hook เพื่อให้โค้ดที่ผู้โจมตีควบคุมถูกรันเมื่อ Git เรียกใช้ hook
- ความเป็นไปได้อื่นคือการเขียนทับ
.git/config
- ยังไม่มีการเผยแพร่ PoC แต่ระบุไว้ว่าสามารถแก้ไข exploit ของ CVE-2024-32002 ได้แทบเล็กน้อยเท่านั้น
- การทดสอบ ใน commit แก้ไขอาจให้เบาะแสสำคัญเกี่ยวกับวิธีโจมตี
ปัญหา CR ที่เกิดซ้ำและบทเรียน
- นี่ไม่ใช่ครั้งแรกที่ Carriage Return ก่อปัญหาให้ Git
- ในเดือนมกราคม RyotaK ค้นพบ ปัญหาในโปรโตคอล credential helper ที่สามารถถูกหลอกด้วย Carriage Return
- ในปี 2023 André Baptista และ Vítor Pinho ค้นพบ CVE-2023-29007 ซึ่งเป็นข้อผิดพลาดเชิงตรรกะในการ parse การตั้งค่า
- ช่องโหว่นี้ไม่ใช่ปัญหาของภาษา C เอง แต่ใกล้เคียงกับ ข้อผิดพลาดเชิงตรรกะ ที่เกิดขึ้นได้ในแทบทุกภาษา
- จุดร่วมคือเกิดขึ้นในการ สื่อสารระหว่างโปรเซส ไม่ว่าจะระหว่างองค์ประกอบภายในของ Git หรือระหว่าง Git กับโปรเซสภายนอก
- โครงสร้างยังคล้ายกับ CRLF injection, request smuggling และ SMTP smuggling ใน HTTP
- ในอดีต อินเทอร์เน็ตพึ่งพาหลักความแข็งแกร่งของ Postel ที่ว่า “จงอนุรักษนิยมเมื่อส่ง และจงเปิดกว้างเมื่อรับ” แต่ปัจจุบันคำแนะนำนั้นอาจไม่ใช่แนวทางที่สมเหตุสมผลที่สุดแล้ว
- หัวข้อนี้มีการอธิบายเพิ่มเติมใน RFC 9413
คำขอบคุณและการแก้ไขที่เกี่ยวข้อง
- ช่องโหว่นี้ถูกค้นพบระหว่างกระบวนการ audit Git
- ใน release เดียวกัน ยังมีการแก้บั๊กอื่น ๆ ที่มีระดับความรุนแรงหลากหลายด้วย
- G-Research Open Source ทำให้งานนี้เกิดขึ้นได้
ยังไม่มีความคิดเห็น