A reverse proxy that can't agree with its backend on where one HTTP request ends and the next begins
SAP Approuter is the Node.js-based reverse proxy fronting apps deployed on SAP BTP (Business Technology Platform) — it terminates external traffic, handles OAuth/UAA login flow, and forwards requests to backing microservices. CVE-2026-27690 is a classic CL.TE / TE.CL desync (CWE-444) where Approuter and its upstream disagree on request boundaries. An unauthenticated attacker sends a crafted request with conflicting Content-Length / Transfer-Encoding framing; the smuggled payload gets prepended to the *next* legitimate user's request on the same keep-alive connection, so the attacker can steal that user's response (session cookies, JWTs, personal data) or poison a shared cache. Affected: Approuter versions < 20.10.0; fix is 20.10.0.
The vendor 9.1 (AV:N/AC:L/PR:N/UI:N/C:H/I:N/A:H) is defensible but slightly overheated. Smuggling here does not give RCE, does not give write-side integrity impact (I:N is honest), and the confidentiality impact is bounded by whatever passes through Approuter — session tokens and app responses, not the SAP database directly. It's a genuine unauth internet-reachable primitive against a component that is *deliberately* internet-exposed, which is why we hold it at HIGH rather than downgrading to MEDIUM.
4 steps from start to impact.
Enumerate Approuter endpoint and fingerprint version
*.cfapps.<region>.hana.ondemand.com or a custom domain CNAMEd to it. Response headers (x-vcap-request-id, x-powered-by), the /login/callback OAuth flow, and error pages fingerprint Approuter. Version pinning via timing/behaviour differences on chunked requests confirms < 20.10.0.- Approuter instance reachable from the internet
- Version < 20.10.0
- Many BTP tenants restrict Approuter to corporate IP ranges or SAP Cloud Connector overlays
- WAF in front (SAP Web Dispatcher, CloudFront, F5) may normalize framing
manifest.yml and buildpack metadata.Probe for CL.TE / TE.CL desync
smuggler.py (defparam), Burp Repeater, or http-request-smuggler (Burp BApp) to send probe requests with conflicting Content-Length and Transfer-Encoding: chunked headers. Differential response timing (500ms hang vs immediate reply) confirms one endpoint honours CL and the other TE. Node.js HTTP parser quirks (obs-fold, CR-only line terminators, Transfer-Encoding: chunked\r\nX) are the usual triggers.- HTTP/1.1 keep-alive between Approuter and upstream
- No strict framing normalizer between attacker and Approuter
- HTTP/2 downgrade to HTTP/1.1 upstream is required — pure HTTP/2 end-to-end kills the class
- Front-end CDN often rewrites framing
Weaponize smuggled request against a victim session
/attacker-collector or reflected in a stored resource). Tools: HTTP Request Smuggler for auto-poisoning, Turbo Intruder for concurrent smuggling. Alternatively poison Approuter's response cache to serve attacker JS to the next N users.- Shared upstream connection pool
- Active concurrent traffic (victim users)
- Approuter's default per-tenant isolation limits poison blast radius
- Session-bound routing (sticky sessions) narrows victim pool
x-vcap-request-id.Replay captured session or pivot into BTP app
- Token not bound to source IP or TLS channel
- Victim had privileged role
- xsuaa token lifetime is short (default 12h)
- Some tenants enforce mTLS or IP allowlisting on tokens
@sap/approuter to 20.10.0 and redeploy affected BTP apps — The only complete fix. cf push or Kyma redeploy after bumping package.json. Deploy within 30 days per noisgate HIGH mitigation SLA; complete fleet rollout within 180 days per remediation SLA.routing.http.drop_invalid_header_fields.enabled=true in front. Reject requests containing both Content-Length and Transfer-Encoding, or with obs-fold / CR-only line endings. Deploy within 30 days.token-validity to ≤ 3600s and enable x-forwarded-for-bound token issuance in xsuaa config.x-vcap-request-id. Push to your SIEM this week.- WAF signature rules alone — request smuggling probes are legal HTTP; only *strict-normalization* modes catch them, not typical OWASP CRS signatures.
- MFA on the app — MFA happens *inside* the session that gets hijacked; smuggling steals the post-auth cookie/JWT, so MFA is already satisfied.
- Rate limiting — a single probe is enough to confirm the desync; volumetric controls don't help.
- Network segmentation of the SAP backend — Approuter is the *front door*, deliberately reachable. Segmenting HANA or S/4 does not protect the sessions passing through the proxy.
The supporting signals.
| In-the-wild exploitation | None observed as of 2026-07-14 disclosure; no vendor or Onapsis IOC advisory. |
|---|---|
| Public PoC | No CVE-specific PoC published yet. Generic tooling (smuggler.py by defparam, Burp HTTP Request Smuggler by Albinowax) trivially adapts. |
| EPSS | Not yet scored (published today); expect <1% initial, rising if PoC lands. |
| KEV status | Not listed by CISA KEV. |
| CVSS vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H — network, no auth, no UI, high confidentiality + availability, no integrity impact (bounded to response theft / DoS). |
| Affected versions | SAP Approuter < 20.10.0 (Node.js @sap/approuter npm package). |
| Fixed version | 20.10.0 — install via npm install @sap/approuter@20.10.0 and redeploy BTP app. |
| Exposure population | SAP BTP hosts tens of thousands of Approuter frontends; most are internet-facing by design. Shodan/Censys BTP fingerprint counts run in the low six figures. |
| Disclosure | 2026-07-14 — SAP Security Patch Day July 2026, one of 16 notes + 1 GHSA. |
| Reported by | Credited via SAP Security Note; researcher not yet public in advisory text. |
Sources.
- SAP Security Patch Day — July 2026
- SecurityBridge Cloud — SAP Security Notes 2026
- Cryptika — SAP Security Update July 2026
- entplexit — SAP Security News 07/2026
- Onapsis — HTTP Request Smuggling in SAP Web Dispatcher (class background)
- PortSwigger — HTTP Request Smuggling research
- SAP Approuter npm package
- CWE-444 — Inconsistent Interpretation of HTTP Requests
Why this verdict
- Role multiplier — Approuter IS the identity broker. For BTP-hosted apps, Approuter terminates external TLS, runs the OAuth handshake with xsuaa, and injects the JWT into upstream calls. Smuggling here = session theft on the auth boundary itself. Chain succeeds → tenant-wide session hijack. Floor: HIGH.
- Unauth, network-reachable, low complexity — no prerequisite compromise stage. Attacker needs only a TCP path to the Approuter, and BTP apps are internet-facing by product design (unlike, say, an internal ESXi). Baseline severity is preserved, not downgraded, for exposure.
- Integrity is bounded (I:N). No RCE, no code write, no privilege escalation into SAP kernel. Blast radius is limited to sessions and cached responses that flow through the specific vulnerable Approuter instance — not fleet-scale SAP compromise. This is why we sit at HIGH, not CRITICAL.
- Modern mitigations partially blunt the class. BTP tenants fronted by SAP Web Dispatcher, CloudFront, or Cloudflare with strict HTTP/1.1 normalization will drop conflicting-framing requests before they reach Approuter. HTTP/2 end-to-end (rare but growing) eliminates the vector.
- No KEV, no in-the-wild, no PoC at disclosure — pulls us off CRITICAL to HIGH pending exploitation evidence. If a working PoC lands (expect within 30 days for a class this well-tooled), re-rate to 9.0+.
Why not higher?
CRITICAL requires either integrity/RCE impact or active exploitation of a canonical fleet-owning component. This is C:H/I:N — the attacker reads and disrupts but does not write or execute. No KEV listing, no public PoC on day one, and blast radius is scoped to the individual Approuter's connection pool, not the SAP application server or HANA database behind it.
Why not lower?
MEDIUM is off the table: unauthenticated, network-reachable, low-complexity attack against an internet-facing identity broker that handles OAuth tokens for BTP customer tenants. The role multiplier (Approuter = auth edge) enforces a HIGH floor even without in-the-wild evidence.
Crowdsourced verification payload.
Run on any host that has the BTP CF CLI installed and is logged into the target org/space. Invoke as ./check-approuter.sh <app-name> — needs read (SpaceDeveloper or higher) on the target space. Emits VULNERABLE, PATCHED, or UNKNOWN.
#!/usr/bin/env bash
# noisgate: CVE-2026-27690 SAP Approuter version check
# Usage: ./check-approuter.sh <cf-app-name>
set -u
APP="${1:-}"
FIXED="20.10.0"
if [[ -z "$APP" ]]; then
echo "usage: $0 <cf-app-name>" >&2
exit 2
fi
if ! command -v cf >/dev/null 2>&1; then
echo "UNKNOWN: cf CLI not installed" >&2
exit 3
fi
# Pull the running package.json from the container
VER=$(cf ssh "$APP" -c 'cat /home/vcap/app/package.json 2>/dev/null | \
grep -oE "\"@sap/approuter\"\s*:\s*\"[^\"]+\"" | \
grep -oE "[0-9]+\.[0-9]+\.[0-9]+"' 2>/dev/null | head -n1)
if [[ -z "$VER" ]]; then
# Fallback: check node_modules
VER=$(cf ssh "$APP" -c 'cat /home/vcap/app/node_modules/@sap/approuter/package.json 2>/dev/null | \
grep -oE "\"version\"\s*:\s*\"[^\"]+\"" | \
grep -oE "[0-9]+\.[0-9]+\.[0-9]+"' 2>/dev/null | head -n1)
fi
if [[ -z "$VER" ]]; then
echo "UNKNOWN: could not read @sap/approuter version from $APP"
exit 3
fi
# semver compare
lowest=$(printf '%s\n%s\n' "$VER" "$FIXED" | sort -V | head -n1)
if [[ "$VER" == "$FIXED" || "$lowest" == "$FIXED" ]]; then
echo "PATCHED: $APP running @sap/approuter $VER (>= $FIXED)"
exit 0
else
echo "VULNERABLE: $APP running @sap/approuter $VER (< $FIXED)"
exit 1
fi