--ของ Git ไม่ใช่ตัวจบตัวเลือกแบบทั่วไป แต่ใช้เพื่อ แยกระหว่าง revision กับ pathspec ดังนั้นหากต้องการส่ง revision ที่ไม่น่าเชื่อถืออย่างปลอดภัย จำเป็นต้องใช้--end-of-optionsซึ่งรองรับตั้งแต่ Git 2.24.0- ใน
git log --end-of-options "$rev" -- "$path"เครื่องหมายตัวแรกใช้แยก ตัวเลือกกับ revision ส่วน--ตัวหลังใช้แยก revision กับ path ทั้งสองใช้แทนกันไม่ได้ - แม้จะรัน
argvarray โดยตรงโดยไม่ผ่าน shell หากอินพุตที่ขึ้นต้นด้วยขีดถูกตีความเป็นตัวเลือกอย่าง--upload-pack,core.sshCommand,ProxyCommandก็อาจเกิด CWE-88 argument injection ได้ - จาก package manager 19 ตัวที่สำรวจ มี 17 ตัวที่เรียกใช้ไบนารี Git เป็นวิธีหลักหรือวิธีเดียว แต่มีเครื่องมือที่ใช้
--end-of-optionsเพียงcmd/goของ Go ตัวเดียว - การแก้ที่รากเหตุมีต้นทุนด้านความเข้ากันได้ เพราะต้องยกระดับเวอร์ชันขั้นต่ำของ Git เป็น 2.24.0·2.30.0·2.43.1 ตาม subcommand ส่วน Git library แม้จะตัดขอบเขตที่ทำให้เกิด argument injection ออกไป แต่ต้องติดตามแพตช์ความปลอดภัยของ upstream โดยตรงในเรื่อง checkout
ความต่างระหว่าง -- กับ --end-of-options
- ในเครื่องมือ Unix ทั่วไป
--ใช้ระบุจุดสิ้นสุดของการตีความตัวเลือก ดังนั้นrm -- -fจะปฏิบัติกับ-fเป็นชื่อไฟล์ ไม่ใช่ตัวเลือกบังคับลบ - Git ใช้
--มาตั้งแต่ช่วงแรก ๆ ในฐานะ ตัวคั่นระหว่าง revision กับ pathspecgit log fooกำกวมว่าfooหมายถึง branch หรือไฟล์git log main -- README.mdหมายถึง commit ในmainที่แตะREADME.md
- ด้วยการออกแบบนี้ ตำแหน่ง revision จึงไม่มีเครื่องหมายจบตัวเลือก และใน
git log "$rev"หาก$revขึ้นต้นด้วยขีด Git จะตีความเป็นตัวเลือก - commit ที่นำ
--end-of-optionsเข้ามา ระบุว่าเพราะ--เดิมถูกใช้แยกระหว่าง revision กับ pathspec อยู่แล้ว จึงต้องมีเครื่องหมายแยกอีกตัวสำหรับแยกตัวเลือกกับ revision --end-of-optionsถูกบันทึกไว้ในเอกสาร gitcli(7) และเพิ่มเข้ามาในเดือนพฤศจิกายน 2019 เมื่อ Git 2.24.0 ออก
วิธีใช้ที่ถูกต้องตามคำสั่ง
git clone -- "$url"เป็นไปตามธรรมเนียม POSIX ของcloneดังนั้น--หน้า URL จึงจบการตีความตัวเลือก--ด้านหลังในgit checkout "$ref" --ระบุว่า$refเป็น revision ไม่ใช่ชื่อไฟล์ แต่ไม่สามารถป้องกันไม่ให้$refถูกตีความเป็นตัวเลือกก่อน- หากต้องการส่ง revision และ path ที่ไม่น่าเชื่อถือร่วมกันอย่างปลอดภัย ต้องใช้ เครื่องหมายทั้งสองตัว เช่น
git log --end-of-options "$rev" -- "$path"--end-of-optionsแยกตัวเลือกกับ revision--แยก revision กับ path
- หากถือว่าสองเครื่องหมายนี้ใช้แทนกันได้ จะไม่สามารถบล็อกอินพุตที่ขึ้นต้นด้วยขีดได้
ช่วงเวลารองรับที่ต่างกันในแต่ละ subcommand
- การรองรับ
--end-of-optionsไม่ได้ถูกนำไปใช้กับคำสั่ง Git ทั้งหมดพร้อมกัน แต่ถูก เพิ่มแยกตาม subcommand git rev-parseใช้ parser อาร์กิวเมนต์ของตัวเอง จึงเริ่มรองรับใน Git 2.30.0 ซึ่งช้ากว่าการเริ่มนำมาใช้ครั้งแรก 1 ปีgit checkoutและgit resetตีความ--เอง และ implementation ช่วงแรกปล่อยให้--end-of-optionsเหลืออยู่ในรายการอาร์กิวเมนต์จนคำสั่งปฏิเสธมัน- ปัญหานี้ได้รับการแก้ใน กุมภาพันธ์ 2024 เมื่อ Git 2.43.1 ออก
Argument injection ที่เกิดได้แม้ไม่ใช้ shell
- Git, Mercurial และ SSH มีตัวเลือกที่เป็นฟีเจอร์ทางการสำหรับรันคำสั่งที่ผู้เรียกกำหนด
git clone --upload-pack=<cmd>ระบุไบนารีฝั่งเซิร์ฟเวอร์-c core.sshCommand=<cmd>ในทุกการเรียก Git เปลี่ยนคำสั่งที่ใช้เชื่อมต่อ--config=alias.<subcmd>=!<shell>ของ Mercurial นิยาม subcommand ที่จะรันใหม่เป็น shell script ใดก็ได้-oProxyCommand=<cmd>ของ SSH ระบุคำสั่ง proxy
- หากโปรแกรม wrapper ใส่สตริงที่ไม่น่าเชื่อถือเข้าไปในรายการอาร์กิวเมนต์ ฟีเจอร์เหล่านี้อาจกลายเป็น ช่องทางโจมตี
- รูปแบบความล้มเหลวนี้เข้าข่าย CWE-88 argument injection และต่างจาก shell command injection
- เกิดขึ้นได้แม้โปรแกรมใช้
argvarray กับexecแทนsystem() - array ถูกส่งต่อไปยัง Git โดยไม่เสียหาย แต่ Git ตีความอาร์กิวเมนต์ที่ขึ้นต้นด้วยขีดเป็นตัวเลือก
- เกิดขึ้นได้แม้โปรแกรมใช้
docker buildCVE-2019-13139 เป็นกรณีที่ใช้os/execของ Go และargvarray โดยไม่ผ่าน shell- ส่วน
#ref:dirของ Git context URL ถูกส่งต่อไปยังgit fetch origin <ref>ทำให้<ref>ถูกตีความเป็น--upload-pack=<cmd>
- ส่วน
ช่องโหว่รูปแบบเดิมที่เกิดซ้ำในระบบจัดการเวอร์ชันหลายตัว
- ในวันเดียวกันของเดือนสิงหาคม 2017 มีการเปิดเผยรูปแบบเดียวกันในระบบจัดการเวอร์ชัน 4 ตัว
- CVE-2017-1000117 ของ Git
- CVE-2017-1000116 ของ Mercurial
- CVE-2017-9800 ของ Subversion
- CVE-2017-12836 ของ CVS
- ทั้ง 4 ระบบส่ง hostname ใน URL เป็นอาร์กิวเมนต์ให้ SSH และ hostname ที่ขึ้นต้นด้วย
-oProxyCommand=ถูกปฏิบัติเป็น ตัวเลือก SSH - ตาม postmortem ของ Phabricator ในบรรดาเครื่องมือ 3 ตัวที่ยังมีการดูแลอย่างแข็งขันในตอนนั้น มีเพียง Subversion ที่เพิ่ม
--หน้า hostname- Git และ Mercurial ตรวจสอบรูปแบบ hostname แทน ส่วนหนึ่งเพราะ SSH implementation บางตัวไม่รองรับ
--
- Git และ Mercurial ตรวจสอบรูปแบบ hostname แทน ส่วนหนึ่งเพราะ SSH implementation บางตัวไม่รองรับ
- โค้ดที่ไม่มี
--ก็ดูเหมือนทำงานปกติจนกว่าอาร์กิวเมนต์จะเริ่มด้วยขีด ดังนั้นกลไกนี้จึง ไม่ปลอดภัยโดยพื้นฐาน
เส้นทางที่ package manager ถูกเปิดเผย
- Package manager รับ Git URL หรือ ref จาก manifest, lock file และ metadata ของ transitive dependency แล้วส่งต่อให้ subprocess
gem 'foo', git: '...'ใน Gemfilegithub:user/repo#refในpackage.json- การตั้งค่าที่เทียบเท่าใน
pyproject.toml,Cargo.toml,mix.exs,Package.swift,pubspec.yaml,conanfile.py,go.mod
- จากการสำรวจ ณ HEAD เดือนกรกฎาคม 2026 ใน package manager 19 ตัว มี 17 ตัว ที่เรียกใช้ไบนารี Git เป็นเส้นทางหลักหรือเส้นทางเดียว
- อีก 2 ตัวที่เหลือใช้ library เป็นค่าเริ่มต้น
- Cargo ใช้ libgit2 และจะรัน process ของ Git หากเปิดใช้
net.git-fetch-with-cli - Poetry เปลี่ยนไปใช้ dulwich ตั้งแต่ 1.2.0 และสามารถใช้ Git ของระบบได้ผ่านการตั้งค่า
system-git-client
- Cargo ใช้ libgit2 และจะรัน process ของ Git หากเปิดใช้
- Nix ใช้ libgit2 เมื่ออ่าน repository ในเครื่อง แต่รัน process ของ Git สำหรับ fetch เพราะ libgit2 ไม่รองรับ git-credential helper
- กลุ่มที่สำรวจคือ Bundler, Cargo, CocoaPods, Composer, Conan, Go, Helm, Homebrew, Mix, Nix, npm, pip, pnpm, Poetry, Pub, SwiftPM, uv, vcpkg, Yarn
CVE ที่พบใน package manager
- ช่องโหว่ package manager ที่เปิดเผยในรูปแบบนี้มีตัวอย่างต่อไปนี้
- CVE-2021-43809 ของ Bundler
- CVE-2021-29472, CVE-2022-24828 ของ Composer
- CVE-2022-36069 ของ Poetry
- CVE-2023-5752 ของ pip
- CVE-2022-21223, CVE-2022-24440 ของ CocoaPods
- CVE-2025-68119 ของ Go
- Snyk ซึ่งพบช่องโหว่หลายรายการในปี 2022 ได้เผยแพร่ งานวิจัย argument injection ใน Git และ Mercurial
- Sonar ดูแล รายการตัวเลือกอันตราย แยกตามไบนารี
สถานะการป้องกันจริงและการแก้ของ Go
- ใน package manager 17 ตัวที่เรียกใช้ process ของ Git มีเพียง
cmd/goของ Go ตัวเดียวที่ใช้--end-of-options - Go เพิ่ม
--หน้า URL ของ repository ในเดือนมิถุนายน 2019 เพื่อ harden การป้องกันทั่วไป - ในเดือนมกราคม 2026 พบว่า
--เพียงอย่างเดียวไม่เพียงพอ จึง เพิ่ม--end-of-optionsในวงกว้าง เป็นการแก้ CVE-2025-68119 - การแก้เดียวกันยังรวม
HGPLAIN=+strictflags- การตั้งค่านี้จำกัดการตีความตัวเลือกช่วงแรกของ Mercurial ตั้งแต่ปี 2017 เมื่อ Mercurial 4.4.2 ออก
- commit แก้ของ Go ระบุว่าอาจต้องมีการเปลี่ยนแปลงเชิงโครงสร้างมากขึ้นเพื่อให้ปัญหาเดิมถูกนำกลับมาได้ยาก แต่ในขั้นแรกได้แก้ปัญหาปัจจุบันแล้ว
การป้องกันส่วนใหญ่ถูกเพิ่มหลังเปิดเผยช่องโหว่
- Package manager ที่เหลือ แม้จะปกป้องรายการอาร์กิวเมนต์ ก็มักใช้
--หรือ ปฏิเสธอินพุตที่ขึ้นต้นด้วยขีด --หน้า URL ของgit cloneใน Bundler ถูกเพิ่มใน แพตช์ CVE-2021-43809- การปฏิเสธขีดนำหน้าใน cocoapods-downloader ถูกนำเข้าใน 3 commit ช่วง 10 วันในเดือนมีนาคม 2022 ซึ่งสอดคล้องกับช่วงเปิดเผย CVE-2022-21223
- การป้องกันของ Poetry เพิ่มในเดือนกันยายน 2021 และได้รับ CVE ในอีก 1 ปีถัดมา จากนั้นอีก 6 เดือนจึงเปลี่ยนไปใช้ dulwich
- vcpkg เป็นข้อยกเว้น โดย ใช้
--ตั้งแต่วันแรก ที่เขียนการรองรับ Git registry
ข้อจำกัดด้านความเข้ากันได้จากเวอร์ชัน Git ขั้นต่ำ
- advisory ของ CVE-2022-24828 ของ Composer ระบุว่า
--end-of-optionsคือการแก้ที่ถูกต้อง แต่เพราะต้องรองรับ Git ที่เก่ากว่า จึงเลือกปฏิเสธชื่อ branch ที่ขึ้นต้นด้วยขีดแทน - การผสาน Git ของ vcpkg ระบุเวอร์ชันขั้นต่ำเป็น Git 2.7.4 ส่วน
HOMEBREW_MINIMUM_GIT_VERSIONสำหรับ Linux ของ Homebrew คือ 2.7.0 ที่ตั้งไว้ในปี 2018 - เมื่อ Amazon Linux 2 ที่ให้ Git 2.14.3 สิ้นสุดอายุในเดือนมิถุนายน 2026 distro ที่ขีดขั้นต่ำเหล่านี้ติดตามอยู่จึงเพิ่งหลุดจากขอบเขตรองรับ
- สถานะ LTS ของ Ubuntu ก็ทำให้การเปลี่ยนพร้อมกันทำได้ยาก
- Ubuntu 18.04 ให้ Git 2.17.0 และมี extended support ถึงปี 2028
- Ubuntu 20.04 ให้ Git 2.25.1 และมี extended support ถึงปี 2030
- Git 2.25.1 รับ
--end-of-optionsของgit fetchแต่ปฏิเสธในgit rev-parse
- หากจะพึ่ง
--end-of-optionsต้องกำหนดเวอร์ชันขั้นต่ำเป็น Git 2.24.0 สำหรับ subcommand ส่วนใหญ่, 2.30.0 สำหรับrev-parse, และ 2.43.1 สำหรับcheckoutกับreset - การยกเวอร์ชันขั้นต่ำจะทำให้ไม่สามารถรองรับผู้ใช้ที่ใช้ Git เก่าที่มากับ distro ได้
ใช้ Git library แทนการรัน process
- libgit2, gitoxide, go-git, JGit, dulwich implement Git transport protocol ที่จำเป็นสำหรับ clone และ fetch ภายใน process
- เมื่อไม่มีขอบเขต
argvแยกต่างหาก จึง ไม่มีเป้าหมายให้ฉีดเข้าไปในรายการอาร์กิวเมนต์ตั้งแต่แรก - Jujutsu ใช้ gitoxide สำหรับการเชื่อมกับ Git และไม่มี CVE สาธารณะประเภท argument injection
- advisory 2 รายการจนถึงตอนนี้คือ path traversal และการขาดการตรวจ SHA-1 collision ที่รับมาจาก library
- CVE-2025-21613 ของ go-git จำกัดอยู่ที่ transport
file://- เส้นทางนี้เป็น code path เดียวใน go-git ที่รันไบนารี Git
- หากรวม Git implementation ของตัวเอง ต้องติดตามการแก้ด้านความปลอดภัย checkout ทั้งหมดที่ upstream Git ออกมา และทั้ง libgit2 กับ JGit เคยมีการแก้ในเรื่องนี้ซ้ำ ๆ
- ต้นทุนนี้มีอยู่จริง แต่ปัญหาจะเปลี่ยนจากการต้องจำตรวจอาร์กิวเมนต์ถาวรในทุกจุดเรียก ไปเป็นการตาม กระแสแพตช์ upstream ที่เป็นรูปธรรม
ขอบเขตการเปลี่ยนแปลงที่เสนอให้ Homebrew
- Homebrew PR ยกระดับเวอร์ชัน Git ขั้นต่ำเป็น 2.30.0 และเพิ่ม
--end-of-optionsในตำแหน่งต่อไปนี้- หน้า URL ของ
clone,remote set-url,ls-remote - หน้า ref ของ
rev-parse
- หน้า URL ของ
- ไม่เปลี่ยนการเรียก
checkoutและreset- หากต้องการปกป้องทั้งสองคำสั่งนี้ด้วย ต้องใช้ Git 2.43.1 ที่ออกในเดือนกุมภาพันธ์ 2024
- เวอร์ชันนี้ใหม่กว่า Git ที่ distro หลายตัวซึ่งยังได้รับการรองรับในปัจจุบันจัดให้
1 ความคิดเห็น
ความเห็นจาก Lobste.rs
โดยเฉพาะความรู้ที่ได้จากคำสั่ง
jjหนึ่งคำสั่งสามารถนำไปใช้กับคำสั่งอื่นได้อย่างเป็นธรรมชาติgit logเอกสาร มีแฟล็กของตัวเลือกเอาต์พุตเพิ่มพูนจนล้น แถมยังปนกับ “สัญกรณ์พิเศษ” สำหรับไวยากรณ์ช่วงคอมมิตและแฟล็กกรองเพิ่มเติมอีกด้วย และยังมีความรู้ที่ถ่ายโอนไปยังคำสั่ง Git อื่นได้น้อยตรงกันข้าม
jj logเอกสาร กลับกระชับจนแม้แต่หน้าเดียวก็ยังเหลือที่ว่าง มันแทนที่ความซับซ้อนแบบจับฉ่ายของ Git ด้วย revision sets, file sets, output template DSL สามอย่าง และทั้งหมดนี้ถูกใช้อย่างสม่ำเสมอทั่วทั้ง jj จึงง่ายกว่า นำมาประกอบกันได้มากกว่า และเข้าใจได้ตรงไปตรงมามากกว่า แม้ต้องคำนึงว่า Git สะสมส่วนเกินมานาน 20 ปี แต่ jj ก็ดูได้เปรียบกว่ามากในการหลีกเลี่ยงปัญหานี้--นำหน้าอาร์กิวเมนต์ที่ผู้ใช้ป้อนเอง แต่ถ้าต้องมี กฎการแปลงเฉพาะของตัวเอง เพิ่มเข้ามาด้วย แบบนี้ก็กลายเป็นกับดักอันตรายเกินไป--มาใช้เพื่อแก้ความกำกวมของอาร์กิวเมนต์ไฟล์ แทนที่จะใช้วิธีง่าย ๆ แบบรับไฟล์เป็นอาร์กิวเมนต์ที่ชัดเจน มี การแลกเปลี่ยนเชิงออกแบบ อะไรที่ไม่เห็นจากภายนอกหรือเปล่าถ้าย้อนกลับไปดูข้อถกเถียงระหว่าง Tcl กับ Scheme ในยุค 1990 สำหรับกรณีนี้อาจเป็นไปได้ว่า “ยิ่งแย่ยิ่งดีกว่า” นั้นถูกต้อง แม้ตระกูล S-expression รวมถึง JSON และ XML แทบจะปักหลักในระบบนิเวศ UNIX ไม่ได้เลย ขณะที่ Tcl มีแนวทางที่ค่อนข้างมีหลักการในการซ้อนภาษาย่อยไว้ภายในสตริง