# REPRO-2026-00231: HTTP/2 request smuggling in Vinyl Cache and Varnish Cache (VSV00019) ## Summary Status: published Severity: low Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00231 CVE: CVE-2026-50052 ## Package Name: varnish/varnish Ecosystem: github Affected: Unknown Fixed: Unknown ## Root Cause # RCA Report: CVE-2026-50052 / VSV00019 – HTTP/2 Request Smuggling in Varnish/Vinyl Cache ## Summary CVE-2026-50052 (VSV00019) is an HTTP/2 request-smuggling vulnerability in Varnish/Vinyl Cache. When HTTP/2 support is enabled (`feature=+http2`), an attacker can send a malformed HTTP/2 request whose pseudo-header name `:a` is accepted as a prefix of `:authority`. The HPACK-to-HTTP/1 serialization in `bin/vinyld/http2/cache_http2_hpack.c` rewrites the header in-place, turning it into a zero-length header on the backend wire. This injects a bare `\r\n` into the HTTP/1 request header block, prematurely terminating the headers. By placing a `content-length` before the truncated header and then sending a DATA frame containing a valid HTTP/1 request, the attacker desynchronizes the frontend and backend, causing the backend to parse the DATA frame as a second request on a reused connection. ## Impact - **Product / component:** Varnish Cache / Vinyl Cache HTTP/2 frontend (`bin/vinyld/http2/cache_http2_hpack.c`) and HTTP/1 backend serialization. - **Affected versions:** Varnish/Vinyl Cache 9.0.2 (and earlier affected lines 7.6.0–8.0.1, 6.0 LTS 6.0.14–6.0.17, Vinyl 9.0.0). This run reproduced the issue on `varnish-9.0.2` (`dc27a2dce662015cf79bbda5ff193b6bb74ba2d1`). - **Risk level / consequences:** High. The attacker can desync a reused backend connection, leading to HTTP request smuggling, cache poisoning, and potential authentication bypass or information disclosure, as described in VSV00019. ## Impact Parity - **Disclosed/claimed maximum impact:** HTTP request smuggling (backend request desync) via malformed HTTP/2 requests, with follow-on effects such as cache poisoning and authentication bypass. - **Reproduced impact from this run:** We demonstrated a real backend request desync: the vulnerable Varnish forwarded the attacker's crafted HTTP/2 request to a backend HTTP/1 server, and the backend received a second, attacker-controlled HTTP/1 request (`GET /smuggled`) on the same pooled connection. The same payload against the fixed version (`varnish-9.0.3`) was rejected with an HTTP/2 `RST_STREAM`/`PROTOCOL_ERROR`. - **Parity:** `partial` – the exact network-level desync was reproduced, but the current harness stops short of demonstrating the downstream cache-poisoning / auth-bypass consequences. Those are the natural result of the desync primitive and are claimed by the advisory. - **Not demonstrated:** Full cache poisoning or credential theft. The PoC only proves the smuggling primitive that enables those attacks. ## Root Cause The bug is in the pseudo-header matching logic in `bin/vinyld/http2/cache_http2_hpack.c`, specifically in `h2h_addhdr()`. The vulnerable code used a macro `Tstrcmp()` defined in `include/vdef.h` as: ```c #define Tstrcmp(t, s) (strncmp((t).b, (s), Tlen(t))) ``` This compares only the first `Tlen(t)` bytes of the target string. As a result, any header name that is a prefix of `:authority` (e.g., `:a`, `:au`, `:autho`) is treated as `:authority` and enters the `:authority` branch. That branch writes `"host"` over the last four bytes of the header name and advances the start pointer by 6, leaving a zero-length header in the internal header array. When the request is serialized to HTTP/1 for the backend, `HTTP1_Write()` emits a bare `\r\n` for that zero-length header, ending the header block early. The remaining headers (and any DATA frame) become the request body, letting a second HTTP/1 request follow on the same persistent backend connection. The official fix is the `Tstreq()` macro that checks both length and content, and the migration of `h2h_addhdr()` to use exact equality for all four pseudo-header names. The fixed version was reproduced from `varnish-9.0.3` (`0a625649cd40af4b6c10be5e58a2e89a5e275baa`). Relevant upstream references: - VSV00019 advisory: https://www.varnish.org/docs/security/vsv00019/ - Blog post with detailed exploit mechanics: https://blog.calif.io/p/mad-bugs-my-cousin-vinyl-cve-2026 - Fixed release tag: `varnish-9.0.3` - Vulnerable reproduction tag: `varnish-9.0.2` ## Reproduction Steps All reproduction is driven by `bundle/repro/reproduction_steps.sh`. The script performs the following steps: 1. Reads `bundle/project_cache_context.json` to find the durable project cache, or falls back to `bundle/artifacts/varnish`. 2. Clones `https://github.com/varnish/varnish.git` into the cache if needed and checks out the vulnerable tag `varnish-9.0.2`. 3. Creates a Git worktree at `repo-fixed` with the fixed tag `varnish-9.0.3`. 4. Installs build dependencies (`autoconf`, `automake`, `libpcre2-dev`, `libev-dev`, `libedit-dev`, `python3-docutils`, `python3-sphinx`, etc.). 5. Builds both the vulnerable and the fixed source trees with autotools. 6. Writes the custom `bin/vinyltest/tests/x_vsv00019.vtc` test to both trees. The test: - Starts an HTTP/1 backend server that expects two requests. - Starts `varnishd` with `feature=+http2` and a VCL that strips `X-Varnish`, `Via`, `X-Forwarded-For`, and sets `Host` empty so the byte layout is predictable. - Sends an HTTP/2 HEADERS frame with `-nostrend` containing the pseudo-headers `:method`, `:path`, `:scheme`, a regular `content-length: 35`, the malicious pseudo-header `:a: xx`, and a padding header `x-pad: aaaaaaaaaaaaaaaa`. - Sends a DATA frame whose 35-byte payload is the smuggled HTTP/1 request `GET /smuggled HTTP/1.1\r\nHost: x\r\n\r\n`. 7. Runs the test against the vulnerable build and captures the output to `bundle/logs/vulnsmuggle.log`. 8. Runs the same test against the fixed build and captures the output to `bundle/logs/fixedsmuggle.log`. 9. Verifies that the vulnerable run passes and that the backend receives `GET /smuggled`, while the fixed run returns `RST_STREAM` with `PROTOCOL_ERROR`. 10. Writes `bundle/repro/runtime_manifest.json` and a summary to `bundle/artifacts/summary.txt`. **Expected evidence of reproduction:** - In `vulnsmuggle.log`: lines showing the backend received the first request (`POST /attack`), then a second request whose URL is `/smuggled`, and the test exits with `x_vsv00019.vtc passed`. - In `fixedsmuggle.log`: the server never sees a second request; instead, Varnish responds with `RST_STREAM` and `rst->err: PROTOCOL_ERROR`. ## Evidence - `bundle/logs/vulnsmuggle.log` – full `varnishtest -v` output for the vulnerable run. - `bundle/logs/fixedsmuggle.log` – full `varnishtest -v` output for the fixed run. - `bundle/artifacts/summary.txt` – parsed verdict summary (commit SHAs, boolean flags). - `bundle/repro/runtime_manifest.json` – structured runtime evidence manifest. Key excerpts from the vulnerable run (`bundle/logs/vulnsmuggle.log`): ``` **** s1 rxhdr|POST /attack HTTP/1.1\r **** s1 rxhdr|scheme: http\r **** s1 rxhdr|content-length: 35\r **** s1 rxhdr|\r **** s1 rxhdrlen = 59 ... **** s1 c-l|x-pad: aaaaaaaaaaaaaaaa\r **** s1 c-l|Host: \r **** s1 bodylen = 35 ... **** s1 rxhdr|GET /smuggled HTTP/1.1\r ... **** s1 EXPECT req.url (/smuggled) == "/smuggled" match ... # top TEST ... x_vsv00019.vtc passed (1.913) ``` Key excerpt from the fixed run (`bundle/logs/fixedsmuggle.log`): ``` *** c1 rx: stream: 1, type: RST_STREAM (3), flags: 0x00, size: 4 **** c1.1 rst->err: PROTOCOL_ERROR (1) ---- c1.1 Frame #1 for rxresp was of type RST_STREAM (3) instead of HEADERS (1) ... # top TEST ... x_vsv00019.vtc FAILED (0.601) signal=6 ``` Environment details captured: - Vulnerable source SHA: `dc27a2dce662015cf79bbda5ff193b6bb74ba2d1` (tag `varnish-9.0.2`) - Fixed source SHA: `0a625649cd40af4b6c10be5e58a2e89a5e275baa` (tag `varnish-9.0.3`) - Build configured with `--disable-dependency-tracking`, built with `gcc` and GNU make. ## Recommendations / Next Steps - **Fix approach:** Apply the upstream fix that replaces `Tstrcmp()` with `Tstreq()` and enforces exact pseudo-header name matching in `h2h_addhdr()`. No other code should rely on prefix-only matching. - **Upgrade guidance:** Upgrade to a non-affected version: Varnish/Vinyl Cache 9.0.3, 8.0.2, or 6.0 LTS 6.0.18, or disable HTTP/2 by removing `+http2` from the `feature` parameter until patched. - **Testing recommendations:** Add a regression test that sends an HTTP/2 request with the `:a` pseudo-header and verifies either a `RST_STREAM`/`PROTOCOL_ERROR` response or the absence of a smuggled request on the backend. The upstream `f00019.vtc` test already covers this for the fixed branches. ## Additional Notes - **Idempotency:** `reproduction_steps.sh` is safe to run multiple times. It checks for existing clones and built binaries and skips expensive build steps when they are already present. Two consecutive runs in this session both confirmed the vulnerability. - **Edge cases / limitations:** The PoC uses a short `:a` value (`xx`) and a carefully sized padding header so that the zero-length header injects the exact `\r\n` needed to terminate the backend header block. If Varnish added extra backend headers that could not be controlled, the byte offsets would need to be recalculated; the VCL used in the test removes those variables to make the reproduction deterministic. The PoC demonstrates the desync primitive but does not exercise the full cache-poisoning chain described in the advisory. - **Entrypoint kind:** This reproduction uses the real network protocol path: `vinyltest/varnishtest` drives a TCP connection to the real `varnishd`, performs the HTTP/2 SETTINGS handshake, sends the crafted HEADERS/DATA frames, and observes the backend HTTP/1 conversation. The runtime manifest records `entrypoint_kind: tcp_peer`. ## Reproduction Details Reproduced: 2026-07-06T08:16:33.881Z Duration: 1685 seconds Tool calls: 186 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00231 pruva-verify CVE-2026-50052 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00231&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00231/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-50052 - Source: https://github.com/varnish/varnish.git ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 6785 bytes) - bundle/repro/rca_report.md (analysis, 9634 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 5253 bytes) - bundle/vuln_variant/rca_report.md (analysis, 9107 bytes) - bundle/artifact_promotion_manifest.json (other, 6639 bytes) - bundle/vuln_variant/source_identity.json (other, 734 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 2051 bytes) - bundle/repro/validation_verdict.json (other, 745 bytes) - bundle/repro/runtime_manifest.json (other, 691 bytes) - bundle/logs/variant_summary.txt (other, 707 bytes) - bundle/logs/variant_ca_vuln.log (log, 21703 bytes) - bundle/logs/variant_ca_fixed.log (log, 10319 bytes) - bundle/logs/variant_f00019_fixed.log (log, 17000 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 5528 bytes) - bundle/vuln_variant/variant_manifest.json (other, 2613 bytes) - bundle/vuln_variant/validation_verdict.json (other, 864 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 849 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00231 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00231/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00231 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev