Full Report
The author of this post found an unintended way to solve a CTF challenge by exploiting a new cross-site leak (XSLeaks) technique. So, they made this into a standalone challenge for this CTF. The challenge had a single solve. The setup is a note-taking app where GET / returns the notes, with a search parameter in query, and a note can be created via POST /new, which is vulnerable to CSRF. One of the notes on the bot contains the flag, and it's your job to steal it from another JavaScript tab. The timeout is 60s but there's no HTML injection, no sorting, no CSS and no other loaded resources. The ETag header is an HTTP response header that acts as a unique identifier for a specific version of a web resource. It's useful for caching data more effectively. Mozilla docs. The application sets the tag via jshttp/etag, which formats the content size in hex as a prefix. The ETag length can differ by 1 depending on the response size and is controlled because of the CSRF bug. This is the beginning of the primitive. What can you do with this? If a response includes the ETag header, subsequent requests will use the same URL with the If-None-Match header containing the ETag. Many web servers have a maximum size for request headers and will output a 431 Request Header Fields Too Large error if exceeded. By padding the URL so that the overall header size is right at the threshold, the extra If-None-Match byte can be the difference between a 200 Ok and a 431. Using the search, this can be abused to check whether the searched bytes match or not, cross-origin. But, can you see this? Cross-origin status codes are opaque! Chrome has a behaviour where the browser may or may not push an entry to the page history. If the same URL is accessed twice in a row but the second navigation fails, only one history event is added. If they both succeed, then two events are added. By looking at the number of entries in the page history, we can determine whether the navigation succeeded or failed. Putting this altogether, the exploit has a few steps: Use the CSRF creation of notes to fine-tune the number of bytes on a page to be on the boundary. URL pad the SECOND request near the Node's header-size threshold. Measure the history.length of the frame to see whether the second navigation occurred or not. Repeat character by character until the flag is leaked. In the unintended solution of another challenge, they used the presence of an ETag header to cause the same issue. Overall, a great post on a new XS-Leaks technique! These are always really complicated and really subtle, so I appreciated the new write-up for it.
Analysis Summary
# Vulnerability: Cross-Site ETag Length Leak (XS-Leak)
## CVE Details
- **CVE ID**: N/A (Newly discovered technique/CTF exploit)
- **CVSS Score**: N/A (Research discovery)
- **CWE**: CWE-200: Exposure of Sensitive Information to an Unauthorized Actor; CWE-345: Insufficient Verification of Data Authenticity (Side Channel)
## Affected Systems
- **Products**: Modern web browsers (specifically Chromium-based) and web servers/frameworks using the `jshttp/etag` library or similar deterministic ETag generation.
- **Versions**: Node.js `jshttp/etag` v1.8.1 and below; Chromium-based browsers (Chrome, Edge) demonstrating specific History API navigation behaviors.
- **Configurations**: Applications that generate ETags based on response size, contain a search feature with reflected results, and are vulnerable to Cross-Site Request Forgery (CSRF).
## Vulnerability Description
This is a sophisticated **Cross-Site Leak (XS-Leak)** that allows an attacker to exfiltrate sensitive data (e.g., a flag or private notes) from another origin. The attack leverages three core components:
1. **ETag Length Variance**: The `jshttp/etag` library formats the ETag using the hex representation of the content size. When the content size crosses a hex boundary (e.g., from `0xfff` to `0x1000`), the ETag header increases in length by one byte.
2. **HTTP 431 Request Header Fields Too Large**: If an ETag is present, subsequent requests include it in the `If-None-Match` header. By purposefully over-padding a URL, an attacker can ensure the total header size is exactly at the server's limit. A 1-byte increase in the ETag/If-None-Match header will trigger a 431 error instead of a 200 OK.
3. **Chromium History Navigation Oracle**: Because cross-origin status codes are opaque, the attacker uses the History API as a side channel. In Chromium, if a navigation to a URL succeeds, a history entry is added. If it fails (e.g., 431 error) on a duplicate navigation attempt, the history length does not increment.
## Exploitation
- **Status**: PoC available (developed for SECCON CTF 14).
- **Complexity**: High (Requires fine-tuning response sizes via CSRF and precise URL padding).
- **Attack Vector**: Network (Web-based via a malicious site visited by the victim).
## Impact
- **Confidentiality**: High (Allows extraction of private data char-by-char).
- **Integrity**: None.
- **Availability**: None.
## Remediation
### Patches
- No specific patches for the ETag library are listed, as the behavior is technically "by design." However, updates to browsers to normalize History API behavior or prevent cross-origin history length inspection may mitigate this in the future.
### Workarounds
- **SameSite Cookies**: Implementing `SameSite=Lax` or `Strict` prevents the CSRF portion of the attack used to pad the notes.
- **Randomized ETags**: Use non-deterministic or fixed-length ETags (e.g., SHA-256 hashes) instead of size-based ETags.
- **Disable Search Reflection**: Avoid reflecting sensitive data in a way that changes the page size based on search matches.
## Detection
- **Indicators of Compromise**: High volumes of requests to a single endpoint with varying, extremely long URL parameters (padding) and frequent 431 "Request Header Fields Too Large" errors in server logs.
- **Detection Methods**: Monitor for rapid CSRF-style POST requests followed by automated HEAD or GET requests with identical paths but slightly different lengths.
## References
- Original Write-up: hxxps://blog.arkark[.]dev/2025/12/26/etag-length-leak
- ETag Library Source: hxxps://github[.]com/jshttp/etag
- SECCON CTF 14 Challenges: hxxps://github[.]com/arkark/my-ctf-challenges/tree/main/challenges/202512_SECCON_CTF_14_Quals/web/impossible-leak