Research/ats-esi-unix-recvport
0-dayNo CVEHighPublic

ATS 10.2.0 ESI FetchSM port 0 matches http+unix remap

Apache Traffic Server 10.2.0 ESI FetchSM PluginVC reports dest port 0, so an http include matches map_with_recv_port http+unix rules meant only for UDS listeners. TCP /secret stays TCP. CONTROL without that map has no witness. Labbed. No CVE yet.

Name
ATS 10.2.0 ESI FetchSM port 0 matches http+unix remap
Type
0-day analysis
CVE
n/a
CVE Risk
high
Disclosure Status
public
Vendor
Apache Traffic Server (ASF)
Affected
Apache Traffic Server 10.2.0 (b1df7334) ESI plus map_with_recv_port http+unix. Typical esi.so plus one map does not hit this.
Published
13 Oct 2026
Updated
14 Oct 2026
Tags
0-day, trafficserver, esi, ssrf, remote, cwe-918

Port 0 meant unix. The include was still http.

The proof of concept is on GitHub: abraxas/ats-esi-unix-recvport (loopback client; @abraxas_null). The lab stack is lab/: Dockerfile, docker-compose.yml, run.sh, poc.py, origin.py, etc-poc/remap.config, etc-control/remap.config. Authorized lab only. It talks to loopback.

This is Apache Traffic Server 10.2.0 (b1df7334b39a878971c376da849e8da4984918b6). No CVE yet. CWE-918. 8.6 High (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N). Remote SSRF. ESI plugin plus map_with_recv_port http+unix. Not typical esi.so plus one map.

ESI FetchSM PluginVC reports dest port 0. Remap treats port 0 as UDS and rewrites the include scheme to http+unix so it matches map_with_recv_port http+unix:// rules meant only for UDS listeners. A public-looking esi:include of http://www.example.com/secret then hits the unix-socket origin.

The July 2026 validator rejects include scheme http+unix as BAD_SCHEME. It does not reject an http:// include whose internal remap hop became unix because dest port was 0. TCP GET /secret stays on the TCP map. CONTROL without the unix recv-port line inlines the TCP origin. No witness.

Why ESI after regex_remap

regex_remap noslash $n is one SSRF. ESI is the other plugin people enable because the origin already emits esi:include. CVE-2026-58178 already taught this plugin a lesson: private literals, file://, nested comments, depth cap. I wanted to know what the validator still trusts once those are closed.

I only wanted Critical or High. Hostname denylist without DNS is documented and dead on remap_required: 1. Location follow needs number_of_redirections > 0 (default 0). Typical esi.so plus one map is not this leftover. CONTROL exists so I cannot pretend it is.

Pinning 10.2.0

Same peel: tag 10.2.0, commit b1df7334b39a878971c376da849e8da4984918b6, image ats-10.2.0-lab:local. number_of_redirections stayed 0. remap_required stayed 1. Include scheme http+unix stayed BAD_SCHEME. The leftover is recv-port 0, not those.

The skip list, for this plugin

  • CVE-2026-58178 ESI unbounded recursion / SSRF. Validator on every production EsiProcessor. file://, http+unix as an include scheme, IPv4-mapped / NAT64, decimal/octal/hex IPv4, zone ids, localhost, 169.254 literals: rejected. Nested <!--esi --> raw-byte reject. Depth cap 3/9.
  • CVE-2025-49763 ESI memory exhaustion. Skip.
  • CVE-2026-58189 plugin redirect-counter. Closed. Default follow is still 0.
  • Hostname denylist without DNS. Documented. Unmapped host 404s when remap_required=1.

FetchSM dest port is zero

Includes are not a raw socket to src. HttpDataFetcherImpl::addFetchRequest builds GET <url> HTTP/1.0 and calls TSFetchUrl. FetchSM::httpConnect uses PluginHttpConnectInternal. That is an internal ATS transaction: remap, cache, origin. ESI does not set TS_FETCH_FLAGS_SKIP_REMAP.

PluginVCCore zeros passive_addr_struct (PluginVC.h:274-278). PluginHttpConnectInternal sets only active_addr (the original client). FetchSM's HttpSM runs on the passive PluginVC. attach_client_session copies netvc->get_local_addr() into client_info.dst_addr. Passive local addr is the zeroed struct. host_order_port() is 0.

Remap lookup (RemapProcessor.cc:87-114):

C++
if (s->client_info.dst_addr.host_order_port() == 0) {
 // Port number 0 means that UDS is used.
 // Adjust the scheme so that we can find rules with +unix suffix.
...
 if (auto scheme = adjusted_url.scheme_get_wksidx(); scheme == URL_WKSIDX_HTTP) {
 adjusted_url.scheme_set("http+unix");
 }

Unit test test_RemapRules.cc:237-240 documents that. 10.2 added http+unix recv-port maps for UDS listeners. FetchSM was already port 0. Those two facts meet.

Poc remap:

Plain text
map http://www.example.com/ http://origin-tcp:8080/
map_with_recv_port http+unix://www.example.com/ http://origin-uds:8080/

Control remap is the first line only.

The lab

Four containers: origin-tcp, origin-uds, ats-control (127.0.0.1:18751), ats-poc (127.0.0.1:18750). Compose project ats-esi-unix-recvport. Image ats-10.2.0-lab:local. Shared UDS volume. esi.so on both ATS instances.

TCP origin /page is an ESI document: esi:include src="http://www.example.com/secret" plus X-Esi: 1. TCP /secret is TCP-ORIGIN-OK. UDS /secret is ATS-ESI-UNIX-RECVPORT-WITNESS.

CONTROL GET /page: 200, inlines TCP-ORIGIN-OK, no witness. POC GET /secret as a TCP client: 200, TCP-ORIGIN-OK, no witness. POC GET /page: 200, inlined witness, no TCP body.

Last line:

Plain text
SUCCESS ats-esi-unix-recvport control-no-witness=yes poc-page-witness=yes poc-tcp-secret-no-witness=yes ATS-ESI-UNIX-RECVPORT-WITNESS

Replay against loopback. cd lab &&./run.sh.

Wrong turns, in the order I made them

Treating typical esi.so + one map as this leftover. CONTROL is that config. No witness. The unix recv-port line is the gate.

Filing hostname denylist without DNS. Documented. Dead on remap_required=1. Not this post.

Claiming file:// still works. Validator BAD_SCHEME / MALFORMED. CVE-2026-58178.

Expecting TCP /secret to follow the unix map. Recv-port match is FetchSM dest port 0. A real TCP client has a real dest port. Stayed on the TCP map. That is why the SUCCESS line has three clauses.

Include scheme http+unix. Validator rejects it. The leftover is an http:// include whose internal hop was rewritten.

A reverse shell. Theatre. The oracle is an inlined witness from the UDS origin, with TCP /secret still TCP.

What an attacker can do

Unauth HTTP to an ESI vhost whose remap has map_with_recv_port http+unix://. Include src the attacker can influence (compromised origin, stored ESI, or $(HTTP_HEADER{...})). The lab include is a static http://www.example.com/secret on /page.

Method Sink Privilege
Unauth GET /page ESI include http://www.example.com/secret FetchSM dest port 0 matches map_with_recv_port http+unix://www.example.com/ (RemapProcessor.cc:87-114) Recon / SSRF (lab-proven): client body inlined ATS-ESI-UNIX-RECVPORT-WITNESS from the UDS origin.
Same first-order write / shell Integrity: none. No first-order write. No shell.
CONTROL GET /page without unix recv-port map TCP origin include Closed (lab-proven): inlines TCP-ORIGIN-OK, no witness.
TCP client GET /secret on the poc vhost TCP map Closed (lab-proven): TCP-ORIGIN-OK, no witness.

Lab-proven vs inferred: control no witness, poc page witness, poc TCP /secret no witness: lab. Header-expansion of src is the documented ESI trust model, not re-proven here.

Requires: ESI plugin plus map_with_recv_port http+unix. Default remap_required: 1. number_of_redirections: 0.

You get ATS as a client to a UDS origin that was supposed to be reachable only from a unix listener, not a TCP /secret bypass, not file://, not a typical esi.so enable. Scope changes because the include left the TCP origin.

Adjacent surface that stays closed

Surface Lab
CONTROL without unix recv-port map control-no-witness=yes
TCP client GET /secret poc-tcp-secret-no-witness=yes
Include scheme http+unix validator BAD_SCHEME
file://, private literals, nested comments, depth cap CVE-2026-58178 closed
Hostname denylist without DNS documented; dead on remap_required: 1
number_of_redirections: 0 Location follow off

The fix

Do not treat FetchSM PluginVC dest port 0 as a UDS recv-port match. Keep map_with_recv_port http+unix:// for real UDS listeners. ESI includes of http://host/ should stay on the TCP map even when an http+unix recv-port rule exists for that host.

Until then, a 10.2 unix recv-port rule next to ESI is an include trampoline onto that UDS origin.

I am not going to print an ESI include you can paste at someone else's vhost. The port-0 rewrite is the useful part. If you own the proxy, run the proof of concept against loopback.

References

ATS 10.2.0 ESI FetchSM port 0 matches http+unix remap · Abraxas Labs