XFCC URI SAN Injection via Unquoted Semicolons
Affected commit: c33d01624d
Sink: source/common/http/conn_manager_utility.cc:636
Summary
When forwarding URI SANs in text XFCC headers, Envoy emits URI=value without quoting, while using ; as the XFCC attribute delimiter. A certificate whose URI SAN contains a semicolon (e.g. spiffe://x.com/a;Subject="admin") creates a forged XFCC attribute. Downstream services that parse XFCC identity data would authorize a low-privilege mTLS client as a privileged user.
Detail
// source/common/http/conn_manager_utility.cc:636
absl::StrCat("URI=", uri) // no quoting
// Compare with:
// line 629: Subject is quoted
Line 659 joins all details with ;. The XFCC specification requires values containing delimiters to be quoted, but URI= values are emitted bare.
Reproduce
#!/bin/bash
set -e
# Clone and identify HEAD
git clone --depth 1 https://github.com/envoyproxy/envoy.git /tmp/envoy-repro
cd /tmp/envoy-repro
echo "HEAD: $(git rev-parse HEAD)"
# Verify URI= is emitted without quoting while Subject= is quoted
echo "--- URI emitted bare (no quotes) ---"
sed -n '631,637p' source/common/http/conn_manager_utility.cc
echo "--- Subject emitted with quotes ---"
sed -n '626,630p' source/common/http/conn_manager_utility.cc
echo "--- Joined with semicolon delimiter ---"
grep -n 'StrJoin.*client_cert_details.*";"' source/common/http/conn_manager_utility.cc
The vulnerable code at conn_manager_utility.cc:636:
case ClientCertDetailsType::URI: {
const absl::Span<const std::string> sans = connection.ssl()->uriSanPeerCertificate();
if (!sans.empty()) {
for (const std::string& uri : sans) {
client_cert_details.push_back(absl::StrCat("URI=", uri)); // no quoting
}
}
Compare with Subject at line 629 which uses quotes: absl::StrCat("Subject=\"", ..., "\"").
All details are joined with ; at line 659: client_cert_details_str = absl::StrJoin(client_cert_details, ";").
A client presenting a certificate with URI SAN spiffe://cluster.local/svc;Subject="admin" produces the XFCC header:
x-forwarded-client-cert: URI=spiffe://cluster.local/svc;Subject="admin"
A downstream service parsing this XFCC header sees two attributes: URI=spiffe://cluster.local/svc and Subject="admin". The attacker-injected Subject attribute can impersonate a privileged identity.
Expected output:
HEAD: <resolved-commit>
--- URI emitted bare (no quotes) ---
case ClientCertDetailsType::URI: {
// The "URI" key still exists even if the URI is empty.
const absl::Span<const std::string> sans = connection.ssl()->uriSanPeerCertificate();
if (!sans.empty()) {
for (const std::string& uri : sans) {
client_cert_details.push_back(absl::StrCat("URI=", uri));
}
--- Subject emitted with quotes ---
case ClientCertDetailsType::Subject:
// The "Subject" key still exists even if the subject is empty.
client_cert_details.push_back(
absl::StrCat("Subject=\"", connection.ssl()->subjectPeerCertificate(), "\""));
break;
--- Joined with semicolon delimiter ---
659: client_cert_details_str = absl::StrJoin(client_cert_details, ";");
Credit
Zheng Yu @ Depthfirst