Summary
gix-transport builds the git-daemon connect request line by writing the URL path and host with no control-character filtering. Because the URL parser validates the still-percent-encoded input and the subsequent percent-decode accepts NUL/CR/LF (all valid UTF-8), an attacker who controls part of a git:// URL can inject raw NUL/CR/LF bytes into the daemon request — smuggling extra NUL-delimited protocol fields (e.g. an attacker-chosen host= virtual-host) into the git pack-protocol connect line.
Root Cause
message::connect (gix-transport/src/client/git/mod.rs:28-49) writes path and host into the git-daemon request git-upload-pack <path>\0host=<host>\0… using extend_from_slice/push_str with zero control-char filtering. Both derive from a parsed git:// URL:
ParsedUrl::parse (gix-url/src/parse/simple_url.rs:107) performs its only control-char guard (is_whitespace / has_valid_percent_encoding) on the still-ENCODED string, so %00/%0A/%0D pass (valid percent-encoding, no whitespace).
percent_decode (simple_url.rs:53-58) only errors on invalid UTF-8; NUL/CR/LF are valid UTF-8 and survive into the decoded path.
connect.rs:53-56 hands the decoded url.path to the daemon connect(), which flows verbatim into message::connect.
Impact
The confirmed primitive is PATH-based injection. Injected content lands in the git pack-protocol's documented NUL-delimited extra-parameters region (host=…), enabling git-daemon virtual-host spoofing (daemon serves an attacker-chosen vhost/repo) and raw newline injection into the request/logs. The HOST sub-vector is self-defeating (a NUL in host breaks (host, port).to_socket_addrs() before the request is built), so only the path vector is live.
Proof of Concept
git://real.example/repo%00host=evil.example%00 — via a malicious .gitmodules submodule URL, a hosting layer that concatenates a trusted host with an attacker-controlled path, or a MITM. ParsedUrl::parse accepts it; the path decodes to /repo\0host=evil.example\0; message::connect emits git-upload-pack /repo\0host=evil.example\0\0host=real.example\0, so the injected host=evil.example field precedes the legitimate one.
Attack Chain
- Entry: victim uses
git://real.example/repo%00host=evil.example%00 (malicious submodule URL / hosting concatenation / MITM). Guard: ParsedUrl::parse whitespace/percent check (simple_url.rs:107). Bypass proof: the check sees literal ASCII %00 (valid percent-encoding, no whitespace) → passes.
- Processing:
percent_decode_path decodes to /repo\0host=evil.example\0 (simple_url.rs:142). Guard: post-decode control-char rejection. Bypass proof: percent_decode (simple_url.rs:53-58) only errors on invalid UTF-8; NUL/CR/LF are valid UTF-8 → survive.
- Sink:
message::connect emits git-upload-pack /repo\0host=evil.example\0\0host=real.example\0 (git/mod.rs:37-47). Guard: control-char validation in the writer (the same guard the credential-helper CR/LF fix, GHSA-6wr8-3w4h-j9wj, added for its own context). Bypass proof: git/mod.rs:37-44 calls extend_from_slice/push_str with no filtering.
- Impact: git-daemon parses the extra NUL-delimited
host= field → virtual-host spoofing; %0A/%0D inject raw newlines into the request/logs.
Bypass Evidence
git show gix-transport-v0.59.1:gix-transport/src/client/git/mod.rs confirms message::connect writes path/host with no filtering on the latest tag.
simple_url.rs:107 runs the guard on encoded input; simple_url.rs:53-58 percent_decode returns Ok for \0/\r/\n.
- The credential-helper CR/LF fix (commit 3a99e25) is confined to
gix-credentials/.../serde.rs::validate — it does not cover this serializer. Same accepted class as GHSA-6wr8-3w4h-j9wj (gitoxide credential helper bare-CR) and existing gix-transport URL-component injection advisories.
Affected Versions
gix-transport <= 0.59.1 (latest release tag gix-transport-v0.59.1; code present on HEAD).
Suggested Fix
Reject control characters (NUL/CR/LF) in the path and host before writing the git-daemon connect request, and/or reject decoded control characters during URL parsing.
Reported by zx (Jace) — GitHub: @manus-use
Summary
gix-transportbuilds the git-daemon connect request line by writing the URL path and host with no control-character filtering. Because the URL parser validates the still-percent-encoded input and the subsequent percent-decode accepts NUL/CR/LF (all valid UTF-8), an attacker who controls part of agit://URL can inject raw NUL/CR/LF bytes into the daemon request — smuggling extra NUL-delimited protocol fields (e.g. an attacker-chosenhost=virtual-host) into the git pack-protocol connect line.Root Cause
message::connect(gix-transport/src/client/git/mod.rs:28-49) writespathandhostinto the git-daemon requestgit-upload-pack <path>\0host=<host>\0…usingextend_from_slice/push_strwith zero control-char filtering. Both derive from a parsedgit://URL:ParsedUrl::parse(gix-url/src/parse/simple_url.rs:107) performs its only control-char guard (is_whitespace/has_valid_percent_encoding) on the still-ENCODED string, so%00/%0A/%0Dpass (valid percent-encoding, no whitespace).percent_decode(simple_url.rs:53-58) only errors on invalid UTF-8; NUL/CR/LF are valid UTF-8 and survive into the decoded path.connect.rs:53-56hands the decodedurl.pathto the daemonconnect(), which flows verbatim intomessage::connect.Impact
The confirmed primitive is PATH-based injection. Injected content lands in the git pack-protocol's documented NUL-delimited extra-parameters region (
host=…), enabling git-daemon virtual-host spoofing (daemon serves an attacker-chosen vhost/repo) and raw newline injection into the request/logs. The HOST sub-vector is self-defeating (a NUL in host breaks(host, port).to_socket_addrs()before the request is built), so only the path vector is live.Proof of Concept
git://real.example/repo%00host=evil.example%00— via a malicious.gitmodulessubmodule URL, a hosting layer that concatenates a trusted host with an attacker-controlled path, or a MITM.ParsedUrl::parseaccepts it; the path decodes to/repo\0host=evil.example\0;message::connectemitsgit-upload-pack /repo\0host=evil.example\0\0host=real.example\0, so the injectedhost=evil.examplefield precedes the legitimate one.Attack Chain
git://real.example/repo%00host=evil.example%00(malicious submodule URL / hosting concatenation / MITM). Guard:ParsedUrl::parsewhitespace/percent check (simple_url.rs:107). Bypass proof: the check sees literal ASCII%00(valid percent-encoding, no whitespace) → passes.percent_decode_pathdecodes to/repo\0host=evil.example\0(simple_url.rs:142). Guard: post-decode control-char rejection. Bypass proof:percent_decode(simple_url.rs:53-58) only errors on invalid UTF-8; NUL/CR/LF are valid UTF-8 → survive.message::connectemitsgit-upload-pack /repo\0host=evil.example\0\0host=real.example\0(git/mod.rs:37-47). Guard: control-char validation in the writer (the same guard the credential-helper CR/LF fix, GHSA-6wr8-3w4h-j9wj, added for its own context). Bypass proof: git/mod.rs:37-44 callsextend_from_slice/push_strwith no filtering.host=field → virtual-host spoofing;%0A/%0Dinject raw newlines into the request/logs.Bypass Evidence
git show gix-transport-v0.59.1:gix-transport/src/client/git/mod.rsconfirmsmessage::connectwrites path/host with no filtering on the latest tag.simple_url.rs:107runs the guard on encoded input;simple_url.rs:53-58percent_decodereturns Ok for\0/\r/\n.gix-credentials/.../serde.rs::validate— it does not cover this serializer. Same accepted class as GHSA-6wr8-3w4h-j9wj (gitoxide credential helper bare-CR) and existing gix-transport URL-component injection advisories.Affected Versions
gix-transport <= 0.59.1(latest release tag gix-transport-v0.59.1; code present on HEAD).Suggested Fix
Reject control characters (NUL/CR/LF) in the path and host before writing the git-daemon connect request, and/or reject decoded control characters during URL parsing.
Reported by zx (Jace) — GitHub: @manus-use