Send TCS polling secret in X-Tsunami-TCS-Secret header (CWE-598 client-side half)#163
Open
OffByQuant wants to merge 1 commit into
Open
Send TCS polling secret in X-Tsunami-TCS-Secret header (CWE-598 client-side half)#163OffByQuant wants to merge 1 commit into
OffByQuant wants to merge 1 commit into
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
The Tsunami Callback Server (TCS) polling protocol used to carry the
per-scan correlation secret in the polling URL query string
(`/?secret=<raw_secret>`). URL query strings are routinely captured
by:
* web-server access logs (the project documents nginx-fronted
deployments as a supported topology)
* cloud load balancer request logs (AWS ALB, GCP HTTP(S) LB log
full URLs by default)
* outbound forward proxies on the scanner side
* browser history / Referer when a human opens the polling URL
to debug a callback
* `ps`-output of curl/wget invocations against the polling
endpoint
* CDN request-sampling analytics
— none of which are appropriate channels for a secret. The raw
secret was supposed to stay private to the scanner; only its SHA3
hash (the cbid) was the public projection. Once the raw secret is
captured, a network-adjacent attacker can forge an out-of-band
callback for a probe that never actually triggered, injecting a
false-positive vulnerability finding into the operator's scan
report (CWE-598).
This is the **client-side half** of the coordinated fix.
Both the Java orchestrator's TcsClient and the Python plugin
server's tcs_client now send the secret in an `X-Tsunami-TCS-Secret`
request header. Header values are not logged by default in any of
the channels listed above. The polling URL becomes a clean
'<base>/' with no query string.
Coordinated with the matching server-side change in
google/tsunami-security-scanner-callback-server, which teaches the
polling handler to read the secret from the new header (preferring
header when present, falling back to the legacy query parameter
during a deprecation window). The server-side patch must merge
and ship before this client-side switch — otherwise new scanner +
old TCS server will fail polling because the old server only
knows about the query parameter.
Tests:
* TcsClientTest.isVulnerable_sendsValidPollingRequest now asserts
that the recorded request has path "/" with no query string and
carries the secret in the X-Tsunami-TCS-Secret header.
* tcs_client_test.test_has_oob_log_sends_polling_request asserts
the same on the Python side using requests_mock's last_request
inspection (qs is empty, header value matches).
* Other has_oob_log tests are updated to register the new
header-only URI.
References:
* CWE-598: Use of GET Request Method With Sensitive Query
Strings
* RFC 9110 §17.9: Sensitive Information in URIs
c5c93d9 to
1ba6d37
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The Tsunami Callback Server (TCS) polling protocol used to carry the per-scan correlation secret in the polling URL query string (
/?secret=<raw_secret>), which is logged by every standard intermediary on the path:Refererwhen a human opens the polling URL to debug a callbackps-output ofcurl/wgetinvocations against the polling endpointNone of these are appropriate channels for a secret. The raw secret was supposed to stay private to the scanner; only its SHA3-224 hash (the cbid) was the public projection. An attacker who reads the secret out of one of those log surfaces during the scan window can forge an out-of-band callback for a probe that never actually triggered, injecting a false-positive vulnerability finding into the operator's scan report (CWE-598 — Use of GET Request Method With Sensitive Query Strings).
This is the client-side half of the coordinated fix. The matching server-side PR is referenced below.
Changes
Two TCS client implementations now send the secret in an
X-Tsunami-TCS-Secretrequest header instead of the URL query string. The polling URL becomes a clean<base>/with no query string.plugin/src/main/java/com/google/tsunami/plugin/TcsClient.java— adds theSECRET_HEADER_NAMEconstant;sendPollingRequestbuilds the request with the header.plugin_server/py/plugin/tcs_client.py— adds the_SECRET_HEADER_NAMEmodule-level constant;_build_polling_requestattaches the header.Header values are not logged by default in any of the channels listed above.
Coordination
Companion server-side PR (must merge and ship first):
That PR teaches
InteractionPollingHandlerto accept the secret via the new header, with a fallback to the legacy?secret=query parameter so older Tsunami client releases keep polling correctly through the deprecation window. The fallback log is rate-limited to once per minute to surface upgrade guidance without log-flood.Recommended merge order:
Tests
TcsClientTest.isVulnerable_sendsValidPollingRequestupdated: now asserts the recorded request path is"/"(no query string) AND that the request carries the secret in theX-Tsunami-TCS-Secretheader.tcs_client_test.test_has_oob_log_sends_polling_requestasserts the same on the Python side usingrequests_mock.last_request(.qs == {}, header value matches).has_oob_log_*Python tests are updated to registermock.register_uri('GET', '<URL>/', ...)(header-form URIs) instead of'<URL>/?secret=<SECRET>'.Test plan
./gradlew testagainst the patched Java sources. (Couldn't run gradle locally — no wrapper checked in, no system gradle; relying on this PR's CI.)tcs_client_test.py. (requests-mock+absl-pynot in my local env; relying on CI.)GET /(no?secret=...) and theX-Tsunami-TCS-Secretheader is set.References