Skip to content

XHTTP: make the session ID on-wire format obfuscatable (canonical UUID is a static fingerprint; blocked by some TLS-terminating L7 CDN WAFs) #6446

Description

@m-a-prod

Type: Feature request / enhancement

Integrity requirements

  • I have read the documentation and understand the configuration items I reference below.
  • I searched existing issues/PRs/discussions and did not find a duplicate.
  • I am not piling up options; the request is a single, minimal capability.
  • This is a feature/enhancement request, not a support question.

Summary

XHTTP correlates the uplink and downlink of the split modes (packet-up, stream-up) with a session ID that is emitted on the wire as a canonical lowercase UUIDv4 (8-4-4-4-12 hex with hyphens). Its textual format is fixed in the client and cannot be changed via configuration.

That makes the session ID a static protocol fingerprint. Some commercial L7 CDNs / reverse-proxies that terminate TLS and run a WAF match this exact pattern and return 403, which breaks XHTTP through them while leaving no config-level remedy.

Request: allow the session ID (and the seq counter) to be emitted in a non-canonical textual form, analogous to the existing xPaddingObfsMode. Because both peers are Xray, the encoding is implicit and needs no negotiation, and it can be delivered to stock clients through the share-link extra.

Environment

  • Xray-core
  • Topology: stock clients → L7 CDN (TLS termination + WAF) → origin (Caddy) → Xray node
  • Transport: VLESS + XHTTP, mode: packet-up, security: tls

Problem detail

In the split modes the session ID must appear in an inspectable HTTP channel:

  • path → /<base>/<uuid>
  • query → ?<key>=<uuid>
  • cookie → Cookie: <key>=<uuid>
  • header → <key>: <uuid>

(sessionPlacement: "path" is currently rejected at config build — see #5631 — leaving query / cookie / header.)

The downlink is a long-lived GET with no body, so the session ID cannot be moved into the request body (the one channel such WAFs typically don't inspect). Consequently, in every configuration the canonical UUID is visible on the wire.

Observed against a commercial TLS-terminating L7 CDN: a request containing a canonical UUIDv4 in the value of any of path / query / cookie / header is answered 403. Trivial textual variations pass the same WAF (verified): _ instead of -; a file extension appended to the token; hyphen-less hex; percent-encoded separators. In other words the block keys on the canonical textual shape, not on entropy or length as such.

Why existing options don't solve it

  • sessionPlacement / sessionKey, seqPlacement / seqKey — change the channel and the key name, not the value's textual format. The value stays a canonical UUID → still matched.
  • xPaddingObfsMode / xPaddingKey / xPaddingMethod — obfuscate padding only, not the session/seq values.
  • downloadSettings (asymmetric transport) — both halves still share the same visible session ID to correlate; the half that traverses the WAF is still blocked.
  • Session ID in headers (per XHTTP transport: New options for bypassing CDN's potential detection #5414) — the value is still a canonical UUID; no help against WAFs that inspect header values.
  • finalmask — operates below/around TLS on the byte stream; it does not (and cannot, through a TLS-terminating L7 proxy) rewrite the plaintext HTTP session ID.

The only remedy available today is a source patch to session-ID generation, which is not deployable to a subscriber base running stock clients.

Reproduction

  1. Server: VLESS + XHTTP inbound, mode: packet-up.

  2. Put any L7 reverse-proxy in front that returns 403 when a canonical UUIDv4 appears in the request line / query / cookie / header value. Minimal repro with nginx:

    if ($request_uri ~ "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}") {
        return 403;
    }
  3. Connect with a stock client:

    • packet-up — uplink/downlink carry the UUID → 403.
    • stream-one — avoids the session ID, but the uplink is a single long-lived POST body, which such proxies buffer to completion before forwarding → deadlock/timeout.
    • No sessionPlacement / sessionKey / padding combination passes.

Proposed solution (minimal, follows existing precedent)

Add an extra option to obfuscate the session/seq wire representation, mirroring xPaddingObfsMode:

"extra": {
  "sessionObfs": true
}

When enabled, the client encodes the 128-bit session value (and the seq counter) in a textual form that is not a canonical UUID — e.g. base64url, base62, or hyphen-less hex — and the server decodes it accordingly.

  • Correlation semantics are unchanged (still a 128-bit token); only the on-wire spelling differs.
  • Both peers are Xray, so the encoding is implicit — no negotiation needed.
  • Deliverable to stock clients via the share-link extra, consistent with how XHTTP already distributes low-frequency transport knobs.

Optionally an enum instead of a bool, if multiple encodings are wanted:

"extra": {
  "sessionObfs": "base64url"   // "base62" | "hex" | ...
}

Rationale beyond this one CDN

A fixed canonical-UUID token in the request URL is a static distinguisher regardless of this specific WAF. Making it obfuscatable improves XHTTP's generic middlebox / DPI resilience — the transport's stated purpose. Padding already has an obfuscation switch; the session/seq tokens are the remaining fixed textual fingerprint.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions