--của Git không phải là dấu kết thúc tùy chọn thông thường mà dùng để phân tách revision và pathspec, vì vậy để truyền một revision không đáng tin cậy một cách an toàn thì cần--end-of-options, được hỗ trợ từ Git 2.24.0- Trong
git log --end-of-options "$rev" -- "$path", dấu ở phía trước phân tách tùy chọn và revision, còn--ở phía sau phân tách revision và đường dẫn, và hai dấu này không thể thay thế cho nhau - Ngay cả khi chạy trực tiếp mảng
argvmà không qua shell, nếu đầu vào bắt đầu bằng dấu gạch ngang bị diễn giải thành các tùy chọn như--upload-pack,core.sshCommand,ProxyCommandthì vẫn có thể xảy ra CWE-88 argument injection - Trong 19 trình quản lý gói được khảo sát, 17 trình chạy nhị phân Git theo cách mặc định hoặc là cách duy nhất, nhưng công cụ dùng
--end-of-optionsthì chỉ cócmd/gocủa Go - Biện pháp xử lý tận gốc có chi phí tương thích là phải nâng phiên bản Git tối thiểu lên 2.24.0, 2.30.0 hoặc 2.43.1 tùy subcommand; còn thư viện Git thì loại bỏ ranh giới argument injection nhưng đổi lại phải tự bám theo các bản vá an toàn checkout từ upstream
Khác biệt giữa -- và --end-of-options
- Trong các công cụ Unix thông thường,
--đánh dấu kết thúc việc phân tích tùy chọn, nênrm -- -fsẽ coi-flà tên tệp thay vì tùy chọn xóa bắt buộc - Git từ rất sớm đã dùng
--làm dấu phân cách giữa revision và pathspecgit log foolà mơ hồ, vì không rõfoolà nhánh hay tệpgit log main -- README.mdnghĩa là các commit trongmaincó thay đổiREADME.md
- Vì thiết kế này nên ở vị trí revision không có dấu kết thúc tùy chọn, và trong
git log "$rev", nếu$revbắt đầu bằng dấu gạch ngang thì Git sẽ diễn giải nó như một tùy chọn - Commit đưa vào
--end-of-optionsgiải thích rằng vì--hiện có đã dùng để ngăn cách revision và pathspec, nên cần một dấu riêng để phân tách tùy chọn và revision --end-of-optionsđược ghi trong tài liệu gitcli(7) và được thêm vào tháng 11/2019, khi Git 2.24.0 phát hành
Cách dùng đúng theo từng lệnh
git clone -- "$url"hoạt động vìclonetuân theo quy ước POSIX, nên--trước URL sẽ kết thúc việc phân tích tùy chọngit checkout "$ref" --thì--phía sau dùng để chỉ ra$reflà revision chứ không phải tên tệp, nhưng không ngăn được việc$refbị diễn giải là tùy chọn ngay từ đầu- Để truyền an toàn đồng thời revision và đường dẫn không đáng tin cậy, cần dùng cả hai dấu như trong
git log --end-of-options "$rev" -- "$path"--end-of-optionsphân tách tùy chọn và revision--phân tách revision và đường dẫn
- Nếu coi hai dấu này có thể thay thế cho nhau thì sẽ không chặn được đầu vào bắt đầu bằng dấu gạch ngang
Mốc hỗ trợ khác nhau theo từng subcommand
- Hỗ trợ
--end-of-optionskhông được áp dụng cho mọi lệnh Git cùng lúc mà được bổ sung riêng theo từng subcommand git rev-parsedùng bộ phân tích argument riêng, nên bắt đầu hỗ trợ ở Git 2.30.0, muộn hơn 1 năm so với lần giới thiệu ban đầugit checkoutvàgit resettự diễn giải--, và vì cách triển khai ban đầu giữ lại--end-of-optionstrong danh sách argument nên đã từ chối nó- Vấn đề này được sửa vào tháng 2/2024, khi Git 2.43.1 phát hành
Argument injection vẫn xảy ra dù không dùng shell
- Git, Mercurial và SSH đều có sẵn các tùy chọn cho phép chạy lệnh do bên gọi chỉ định
git clone --upload-pack=<cmd>chỉ định nhị phân phía máy chủ-c core.sshCommand=<cmd>trong mọi lệnh Git sẽ thay đổi lệnh kết nối--config=alias.<subcmd>=!<shell>của Mercurial ghi đè subcommand sẽ chạy bằng một shell script tùy ý-oProxyCommand=<cmd>của SSH chỉ định lệnh proxy
- Nếu chương trình bao bọc đưa chuỗi không đáng tin cậy vào danh sách argument, các tính năng này có thể biến thành công cụ tấn công
- Kiểu thất bại này thuộc CWE-88 là argument injection, khác với shell command injection
- Nó vẫn xảy ra dù chương trình dùng mảng
argvvàexecthay vìsystem() - Mảng được truyền nguyên vẹn sang Git, nhưng Git lại diễn giải argument bắt đầu bằng dấu gạch ngang như tùy chọn
- Nó vẫn xảy ra dù chương trình dùng mảng
- CVE-2019-13139 của
docker buildlà một trường hợp dùngos/execcủa Go và mảngargvmà không đi qua shell- Phần
#ref:dircủa Git context URL được truyền vàogit fetch origin <ref>, khiến<ref>bị diễn giải thành--upload-pack=<cmd>
- Phần
Lỗ hổng lặp lại trên nhiều hệ thống quản lý phiên bản
- Cùng một ngày trong tháng 8/2017, mô hình giống nhau này được công bố trên bốn hệ thống quản lý phiên bản
- CVE-2017-1000117 của Git
- CVE-2017-1000116 của Mercurial
- CVE-2017-9800 của Subversion
- CVE-2017-12836 của CVS
- Cả bốn hệ thống đều truyền hostname trong URL làm argument cho SSH, và hostname bắt đầu bằng
-oProxyCommand=sẽ bị xử lý như một tùy chọn SSH - Theo phân tích hậu sự cố của Phabricator, trong ba công cụ còn được duy trì tích cực khi đó, chỉ Subversion thêm
--trước hostname- Git và Mercurial thì xác thực định dạng hostname, một phần vì không phải mọi triển khai SSH đều hỗ trợ
--
- Git và Mercurial thì xác thực định dạng hostname, một phần vì không phải mọi triển khai SSH đều hỗ trợ
- Ngay cả mã thiếu
--cũng trông và hoạt động bình thường cho đến khi argument bắt đầu bằng dấu gạch ngang, nên cơ chế này về bản chất là không an toàn mặc định
Con đường phơi lộ qua trình quản lý gói
- Trình quản lý gói nhận Git URL hoặc ref từ manifest, lockfile và metadata của dependency bắc cầu rồi truyền chúng cho tiến trình con
gem 'foo', git: '...'trong Gemfilegithub:user/repo#reftrongpackage.json- Các cấu hình tương đương trong
pyproject.toml,Cargo.toml,mix.exs,Package.swift,pubspec.yaml,conanfile.py,go.mod
- Tính đến HEAD tháng 7/2026, trong 19 trình quản lý gói được khảo sát thì có 17 trình chạy nhị phân Git theo đường mặc định hoặc là đường duy nhất
- Hai trình còn lại mặc định dùng thư viện
- Cargo dùng libgit2 và sẽ chạy tiến trình Git nếu bật
net.git-fetch-with-cli - Poetry chuyển sang dulwich từ 1.2.0 và có thể dùng Git hệ thống qua cấu hình
system-git-client
- Cargo dùng libgit2 và sẽ chạy tiến trình Git nếu bật
- Nix dùng libgit2 khi đọc kho cục bộ, nhưng khi fetch thì chạy tiến trình Git vì libgit2 không hỗ trợ helper
git-credential - Danh sách khảo sát gồm Bundler, Cargo, CocoaPods, Composer, Conan, Go, Helm, Homebrew, Mix, Nix, npm, pip, pnpm, Poetry, Pub, SwiftPM, uv, vcpkg, Yarn
Các CVE đã ghi nhận ở trình quản lý gói
- Các lỗ hổng đã công bố thuộc loại này trong trình quản lý gói gồm có
- CVE-2021-43809 của Bundler
- CVE-2021-29472, CVE-2022-24828 của Composer
- CVE-2022-36069 của Poetry
- CVE-2023-5752 của pip
- CVE-2022-21223, CVE-2022-24440 của CocoaPods
- CVE-2025-68119 của Go
- Snyk, đơn vị phát hiện nhiều lỗ hổng năm 2022, đã công bố nghiên cứu về argument injection trong Git và Mercurial
- Sonar duy trì danh sách các tùy chọn nguy hiểm theo từng nhị phân
Tình hình phòng vệ thực tế và bản vá của Go
- Trong 17 trình quản lý gói chạy tiến trình Git, công cụ dùng
--end-of-optionschỉ cócmd/gocủa Go - Tháng 6/2019, Go thêm
--trước URL kho chứa như một biện pháp tăng cường phòng thủ chung - Đến tháng 1/2026, người ta nhận ra chỉ
--là chưa đủ, nên bản vá cho CVE-2025-68119 đã bổ sung--end-of-optionsmột cách rộng khắp - Cùng bản vá đó còn có
HGPLAIN=+strictflags- Thiết lập này đã hạn chế việc diễn giải tùy chọn giai đoạn đầu của Mercurial từ khi Mercurial 4.4.2 phát hành năm 2017
- Commit vá của Go lưu ý rằng có thể cần thay đổi mang tính cấu trúc hơn để tránh tái đưa lại cùng vấn đề, nhưng trước mắt nó giải quyết được lỗi hiện tại
Hầu hết cơ chế phòng vệ chỉ được thêm sau khi có lỗ hổng công khai
- Các trình quản lý gói còn lại, dù có bảo vệ danh sách argument, chủ yếu vẫn dùng
--hoặc từ chối dấu gạch ngang ở đầu đầu vào --trước URL tronggit clonecủa Bundler được thêm vào bởi bản vá CVE-2021-43809- Việc cocoapods-downloader từ chối dấu gạch ngang đầu vào được triển khai qua ba commit trong 10 ngày tháng 3/2022, trùng thời điểm công bố CVE-2022-21223
- Cơ chế phòng vệ của Poetry được thêm vào tháng 9/2021, được gán CVE sau đó 1 năm, và 6 tháng sau thì chuyển sang dulwich
- vcpkg là ngoại lệ khi dùng
--ngay từ ngày đầu viết hỗ trợ Git registry
Ràng buộc tương thích do phiên bản Git tối thiểu
- Khuyến cáo về CVE-2022-24828 của Composer chỉ ra
--end-of-optionslà cách sửa đúng, nhưng vì phải hỗ trợ cả Git cũ hơn nên họ chọn cách từ chối tên nhánh bắt đầu bằng dấu gạch ngang - Tích hợp Git của vcpkg ghi rõ phiên bản tối thiểu là Git 2.7.4, còn
HOMEBREW_MINIMUM_GIT_VERSIONtrên Linux của Homebrew là 2.7.0 được đặt từ năm 2018 - Amazon Linux 2, bản phân phối từng cung cấp Git 2.14.3, hết vòng đời vào tháng 6/2026, nên các bản phân phối mà các ngưỡng tối thiểu này từng phải bám theo hiện mới bắt đầu rời khỏi phạm vi hỗ trợ
- Trạng thái hỗ trợ dài hạn của Ubuntu cũng khiến việc chuyển đổi đồng loạt trở nên khó khăn
- Ubuntu 18.04 cung cấp Git 2.17.0 và được hỗ trợ mở rộng đến 2028
- Ubuntu 20.04 cung cấp Git 2.25.1 và được hỗ trợ mở rộng đến 2030
- Git 2.25.1 chấp nhận
--end-of-optionsvớigit fetch, nhưng từ chối nó vớigit rev-parse
- Muốn dựa vào
--end-of-optionsthì phần lớn subcommand cần tối thiểu Git 2.24.0,rev-parsecần 2.30.0, còncheckoutvàresetcần 2.43.1 - Nâng phiên bản tối thiểu đồng nghĩa không còn hỗ trợ người dùng đang dùng Git cũ đi kèm trong các bản phân phối
Dùng thư viện Git thay vì chạy tiến trình
- libgit2, gitoxide, go-git, JGit, dulwich đều triển khai giao thức truyền tải Git cần cho clone và fetch ngay trong tiến trình
- Không có ranh giới
argvriêng, nên không tồn tại đối tượng để chèn vào danh sách argument - Jujutsu dùng gitoxide để tích hợp Git và chưa có CVE công khai nào thuộc kiểu argument injection
- Hai khuyến cáo hiện có chỉ liên quan đến path traversal và việc thư viện kế thừa thiếu kiểm tra va chạm SHA-1
- CVE-2025-21613 của go-git chỉ giới hạn ở truyền tải
file://- Đây là đường mã duy nhất trong go-git có chạy nhị phân Git
- Khi đóng gói một triển khai Git riêng, bạn phải tự theo dõi mọi bản vá an toàn checkout mà upstream Git phát hành; cả libgit2 lẫn JGit đều từng lặp lại các bản sửa liên quan lĩnh vực này
- Chi phí đó là có thật, nhưng đổi lại vấn đề chuyển từ việc phải nhớ kiểm tra argument vĩnh viễn ở từng điểm gọi sang việc áp dụng luồng bản vá upstream cụ thể
Phạm vi thay đổi được đề xuất cho Homebrew
- PR của Homebrew nâng phiên bản Git tối thiểu lên 2.30.0 và thêm
--end-of-optionstại các vị trí sau- trước URL của
clone,remote set-url,ls-remote - trước ref của
rev-parse
- trước URL của
- Các lời gọi
checkoutvàresetkhông được thay đổi- Muốn bảo vệ cả hai lệnh này thì cần Git 2.43.1, phát hành tháng 2/2024
- Phiên bản này mới hơn Git mà nhiều bản phân phối hiện còn hỗ trợ đang cung cấp
1 bình luận
Ý kiến trên Lobste.rs
Đặc biệt, kiến thức học được từ một lệnh
jjcó thể tự nhiên áp dụng sang các lệnh khác. Tài liệugit logthì số lượng cờ theo từng tùy chọn đầu ra bùng nổ, lại còn trộn lẫn “ký pháp đặc biệt” cho cú pháp phạm vi commit với các cờ lọc bổ sung, và cũng có rất ít kiến thức chuyển sang được các lệnh Git khácTrong khi đó, tài liệu
jj logngắn gọn đến mức một trang còn dư chỗ. Nó thay thế kiểu phức tạp chắp vá của Git bằng ba thứ là tập revision, tập file và DSL mẫu đầu ra, và chúng được dùng nhất quán trên toàn bộ jj nên đơn giản hơn nhiều, dễ kết hợp hơn và trực quan hơn. Tất nhiên cũng phải tính đến chuyện Git đã tích lũy đủ thứ rườm rà suốt 20 năm, nhưng jj có vẻ ở vị thế thuận lợi hơn nhiều để tránh điều đó--trước các đối số do người dùng cung cấp, nhưng nếu còn đòi hỏi thêm cả quy tắc biến thể riêng ở đây thì đúng là một cái bẫy nguy hiểm quá mức--để giải quyết sự mơ hồ của đối số file. Không rõ có đánh đổi thiết kế nào khó nhìn thấy từ bên ngoài khiến họ chọn cách này thay vì kiểu đơn giản là nhận đối số file một cách tường minh hay khôngNhìn lại tranh luận giữa Tcl và Scheme trong thập niên 1990, có lẽ trong trường hợp này “càng tệ càng tốt” lại đúng. Trong khi các hệ S-biểu thức, bao gồm cả JSON và XML, hầu như không bám rễ được trong hệ sinh thái UNIX, thì Tcl lại có một cách tiếp cận tương đối có nguyên tắc: xếp chồng các ngôn ngữ con bên trong chuỗi ký tự