Research/ats-cripts-rri-uaf
0-dayNo CVEHighPublic

ATS Cripts From/To URL UAF after DoRemap

Apache Traffic Server 10.2.0 Cripts Remap::From/To URL still re-reads the stack TSRemapRequestInfo after TSRemapDoRemap returns. Client::URL was patched not to. Unauth GET through a post-remap cript SIGABRTs traffic_server. Labbed RelWithDebInfo exit 134. ENABLE_CRIPTS default off.

Name
ATS Cripts From/To URL UAF after DoRemap
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) Cripts remap that touches From/To URL after do_remap. ENABLE_CRIPTS default OFF. Not default.
Published
13 Oct 2026
Updated
14 Oct 2026
Tags
0-day, trafficserver, cripts, crash, uaf, cwe-416

Lua cleared rri. Cripts From/To kept a stack pointer.

I am @abraxas_null. The proof of concept is on GitHub: abraxas/ats-cripts-rri-uaf (loopback client). The lab stack is lab/: Dockerfile, docker-compose.yml, run.sh, poc.py, poc.cript, control.cript. Authorized lab only. It talks to loopback.

This is Apache Traffic Server 10.2.0 (b1df7334b39a878971c376da849e8da4984918b6). No CVE yet. CWE-416. 7.5 High (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). Remote Crash. Availability High. Not RCE.

CVE-2026-58177 already named Cripts OOB / path traversal / UAF. On this pin the instance array write, Rebase ../ jail, Geo undersized sockaddr, query-key abort, global Client::URL MLoc, and DoRemap-after-Release UAF are closed. Client::URL was patched not to re-init from RRI. Remap::From / To were not.

RemapPlugins::run_plugin stack-allocates TSRemapRequestInfo rri and passes &rri into TSRemapDoRemap. The struct is dead when that function returns. Cripts stores the pointer on the per-txn Context. When any hook bit >= DO_POST_REMAP is set, it keeps the Context for later HTTP hooks. reset() drops the unique_ptrs. The next Get() allocates a fresh object and _initialize() calls rriValid() then reads rri->mapFromUrl / mapToUrl. rriValid() is rri && rri->requestBufp &&... - a dereference of the dead stack struct, not a null check of the pointer alone.

Fatal: sdk_sanity_check_mbuffer. SIGABRT. traffic_server dies. Unauth GET Host ats-poc is enough if the remap cript touches From/To on do_send_response.

Why Traffic Server

Cripts is ATS's compiled remap language. Admin writes C++-shaped hooks, compiler emits a .so, remap loads it. ENABLE_CRIPTS defaults OFF. Stock remap has no @plugin cripts. That is not a skip for a leftover of a named UAF CVE on the same objects.

I only wanted Critical or High. Crash of traffic_server on unauth HTTP is High availability on a cache. RIP control is not claimed. Class stays Crash.

Lua already cleared rri after remap. That contrast is the whole hunt: the TS API leftover was fixed in one plugin family and not the other.

Pinning 10.2.0

Tag 10.2.0, peel b1df7334b39a878971c376da849e8da4984918b6. Lab is RelWithDebInfo so Fatal aborts instead of getting optimized into a shrug. ASan is optional; the SUCCESS line on this pin is SIGABRT exit 134 without needing ASan to name the frame.

The skip list is half the hunt

CVE-2026-58177 as disclosed:

  • Issue 31 instance std::array write: _initialize stops at data.size(). Closed.
  • Issue 32 File::Path::Rebase: weakly_canonical plus prefix check. Closed for HTTP. A cript that copies request bytes into File::Path and then Rebase() on an already-existing regular file is operator C++. Admin compiles cripts. Not this post.
  • Geo: IPEndpoint holds v4/v6. Closed.
  • Query duplicate-key CAssert: missing hash skipped. Closed.
  • Global Client::URL MLoc: _owns_urlp plus Reset only when owned. Closed.
  • Context-after-Release debug UAF: url_modified sampled first, then Release(), then inst->debug on the live instance. Closed.

Query-key abort would have been Medium even loaded. Rebase jail leftover is not HTTP High. This post is From/To after DoRemap.

How the stack pointer survives

RemapPlugins.cc:41-65 builds TSRemapRequestInfo on the stack. doRemap runs. Function returns. Cripts Epilogue.hpp already stored rri on Context at factory time.

do_send_response in the POC cript:

Plain text
borrow from = cripts::Remap::From::URL::Get();
borrow to = cripts::Remap::To::URL::Get();
resp["X-Lab-From"] = from.host.GetSV();

http_txn_cont always context->reset() before the hook. From/To unique_ptrs die. Get() makes new ones. _initialize:

Plain text
if (_context->rriValid()) {
 _urlp = _context->rri->mapFromUrl;
}

rriValid() reads through the dead pointer. Garbage non-null requestBufp goes to TSUrlHostGet. SDK Fatal.

CONTROL cript uses Client::URL::Get() on the same hook. 200. Process stayed up. That is the 58177 patch doing its job for Client and not for Remap.

Bundle::Headers %{FROM-URL:PATH} on do_send_response is the HRW-bridge gadget. Same UAF. The lab used a three-line cript so the sink is obvious.

Wrong turns, in order

Re-filing CVE-2026-58177. The disclosed UAF was Context after Release(). That path is closed. From/To RRI is the leftover.

Global cripts. rri == nullptr. rriValid() short-circuits on the pointer without reading fields. Fail closed. Remap cripts are the ones with a live-then-dead RRI.

Release build without Fatal. RelWithDebInfo keeps the SDK assert. The lab is that build. I am not claiming a silent memory corruption in -O3 without the assert. Crash is the class.

Calling it RCE. SDK Fatal abort is not RIP control. Class stays Crash.

Default install. ENABLE_CRIPTS off. No cripts on stock remap. Config-gated leftover of a named CVE still counts when the admin compiles cripts, which is the same gate the original CVE needed.

A reverse shell. Theatre. The oracle is SIGABRT exit 134 plus CONTROL 200 alive.

The lab

Loopback 127.0.0.1:18780. Compose ats-cripts-rri-uaf. Two remap hosts: ats-control Client::URL, ats-poc From/To. Same origin. RelWithDebInfo traffic_server.

CONTROL: HTTP 200, X-Lab-Control: ok, process up. POC: connection reset, container exit 134, logs sdk_sanity_check_mbuffer / SIGABRT.

Last line:

Plain text
SUCCESS ats-cripts-rri-uaf control-http=200 control-alive=yes poc-crash=sigabrt poc-http=None reset=1 ATS-CRIPTS-RRI-UAF-WITNESS

What an attacker can do

Hit a remap cript that touches From/To URL after do_remap. Unauth GET is enough.

Method Sink Privilege
Unauth GET Host ats-poc through a do_send_response cript that calls Remap::From::URL::Get / To::URL::Get stack TSRemapRequestInfo (RemapPlugins.cc:41-65); rriValid() deref (Context.hpp:70-73); _initialize (Urls.cc:525-561) Availability (lab-proven): SIGABRT, exit 134, connection reset. traffic_server dies.
Same SDK Fatal abort Integrity: not RIP control. Class stays Crash. Lab.

Lab-proven vs inferred: process death is lab. Memory corruption to RIP is not claimed.

Requires: Cripts built (ENABLE_CRIPTS=ON) and a remap cript that touches From/To URL after DoRemap. Default: no.

You take the cache down. You do not read ATS-uid files through this UAF. You do not get Rebase ../. You do not get Geo OOB. You do not get query-key abort. You do not get this on Client::URL. You do not get this on a stock proxy with Cripts off.

Adjacent surface that stays closed

Surface Why
CONTROL Client::URL only 200, process stayed up
File::Path::Rebase jail closed on this pin
Geo IPEndpoint sockaddr closed
Query-key abort closed
Global cripts rri == nullptr
Instance OOB / DoRemap-after-Release closed (CVE-2026-58177)
Stock remap / ENABLE_CRIPTS off leftover does not apply

The fix

Null the Context rri when TSRemapDoRemap returns, the way Lua does. Or treat From/To URL like Client::URL: do not re-init from RRI on later hooks. rriValid() must not dereference a dead struct. Copy mapFromUrl / mapToUrl into owned MLocs during do_remap if post-remap From/To is a feature.

Until then, do not evaluate %{FROM-URL:...} / %{TO-URL:...} (or borrow those URL objects) on do_post_remap / do_send_request / do_read_response / do_send_response / do_cache_lookup.

I am not going to print a Host header you can paste at someone else's cache. The dangling RRI is the useful part. If you own the proxy, run the proof of concept against loopback.

References