Research/ats-jsonrpc-unrestricted
0-dayNo CVEHighPublic

ATS 10.2.0 JSON-RPC UDS defaults unrestricted 0777

Apache Traffic Server 10.2.0 JSON-RPC unix socket defaults to restricted_api false and Linux mode 0777. A local uid dumps live records including SSL key paths and connection-tracker tuples. Writes stay behind SO_PEERCRED. Labbed. No CVE yet.

Name
ATS 10.2.0 JSON-RPC UDS defaults unrestricted 0777
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) default JSON-RPC unix. Local uid on the ATS host. Docs table says true/0700; code is false/0777.
Published
13 Oct 2026
Updated
14 Oct 2026
Tags
0-day, trafficserver, jsonrpc, privilege-leftover, local, cwe-276, cwe-284

The docs said 0700. The socket was 0777.

The proof of concept is on GitHub: abraxas/ats-jsonrpc-unrestricted (loopback client; @abraxas_null). The lab stack is lab/: Dockerfile, docker-compose.yml, run.sh, poc.py, jsonrpc.yaml, entrypoint.sh. Authorized lab only. It talks to loopback.

This is Apache Traffic Server 10.2.0 (b1df7334b39a878971c376da849e8da4984918b6). No CVE yet. CWE-276 / CWE-284. 7.1 High (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N). Local privilege leftover (recon). Not RCE. Not config write from a foreign uid.

JSON-RPC is on unless you turn it off. The only transport is a unix socket. Shipped jsonrpc.yaml is comments only, so the C++ initializer wins. That initializer is NON_RESTRICTED_API (false). On Linux, SO_PEERCRED is present, so bind mode is 0777. Any local uid that can reach the rundir inode connects with no token and dumps live records, including SSL key paths, plus connection-tracker 5-tuples.

Writes stay behind SO_PEERCRED. uid 10000 admin_config_set_records is Unauthorized. Docs table last sentence says true by default and 0700. Code and the commented example follow false. That disagreement is the leftover.

Why this socket, after the method ACL

Default reverse-proxy 10.2.0 has no unpublished RCE. Mixed-case HTTP/1 is the remote default door. JSON-RPC is the local one. SECURITY.md trusts administrative users. A world-connectable management socket with unrestricted reads is not that trust. It is every uid on the box.

I only wanted Critical or High. Local is in-scope. A 96KiB parse as local DoS is Medium. Same-uid traffic_ctl is intended admin. TCP JSON-RPC does not exist.

Pinning 10.2.0

Same peel as the method-case leftover: tag 10.2.0, commit b1df7334b39a878971c376da849e8da4984918b6, image ats-10.2.0-lab:local. CHANGELOG already named mgmt/rpc: create JSONRPC unix socket with intended mode atomically. That fix is the restricted path. Unrestricted bind still chmod 0777 after bind. I wanted to know which path was default.

The skip list, for this surface

  • CVE-2026-58159 HTTP UDS listener ACL. No HTTP UDS on default server_ports. Mgmt JSON-RPC is a different inode.
  • Atomic umask+bind when restricted_api is true. 0700. Held.
  • Old traffic_manager XMLRPC TCP. Gone. Only CommType::UNIX exists. RPCServer throws on anything else.

July smuggling / SNI / HPACK / ESI guards are closed. They are not this socket.

Default bind is the docs table inverted

RPC is on unless rpc.enabled: false. Missing jsonrpc.yaml keeps that default. Default inode: RecConfigReadRuntimeDir() + jsonrpc20.sock. Runtime dir is typically 0755 ATS-owned: other uids cannot replace the socket, they can connect if the socket is 0777.

Shipped jsonrpc.yaml.default is comments only:

YAML
# rpc:
# enabled: true
# unix:
# restricted_api: false

YAML decode only writes restrictedAccessApi when the key is present. Otherwise (IPCSocketServer.h:128-129):

C++
bool restrictedAccessApi{
 NON_RESTRICTED_API}; //... 0700(default) or 0777

NON_RESTRICTED_API is false. Comment says 0700 is default. Initializer is the unrestricted constant.

Bind (IPCSocketServer.cc:310-342):

  • restrictedAccessApi == false and has_peereid(): mode 00777.
  • restrictedAccessApi == false and no peereid: forced restricted, mode 00700.
  • restrictedAccessApi == true: mode 00700.

Linux has TS_HAS_SO_PEERCRED. Default install: mode 0777.

Docs (jsonrpc.yaml.en.rst) table text says non-root read-only JSON-RPC when false, writes still root/same-uid, then the last sentence of the same row says true by default and 0700 vs 0777. Unit test only asserts restricted_api: true yields 0700. Restore YAML omits the key. There is no test that the omitted key is 0700.

Authn on connect: none. Handler gate late_check_peer_credentials only if options.auth.restricted. Then getpeereid / SO_PEERCRED. Allow if euid == 0 or euid == geteuid(). gid is never checked.

The lab

One container. Compose project ats-jsonrpc-unrestricted. Loopback HTTP 127.0.0.1:18720 so the stack is alive. The leftover is the unix socket inside the container, not that HTTP port.

Lab user uid 10000. ATS uid nobody. Shipped commented jsonrpc.yaml. traffic_ctl as the ATS user plants proxy.config.proxy_name=ATS-JSONRPC-UNRESTRICTED-WITNESS. uid 10000 then speaks JSON-RPC 2.0 on jsonrpc20.sock.

stat of the socket: mode 777. admin_lookup_records returned the witness name, SSL server/client private-key paths under etc/trafficserver, and proxy.config.admin.user_id=nobody. Key bytes are not in records. get_connection_tracker_info returned inbound/outbound tables. admin_config_set_records as uid 10000: error 10 Unauthorized, Denied privileged API access for uid=10000. Witness record unchanged. /proc/net/tcp had 8080 and 18080. No JSON-RPC TCP listen.

Last line:

Plain text
SUCCESS ats-jsonrpc-unrestricted how=uds-0777-unrestricted-jsonrpc uid=10000 sock-mode=0777 lookup-proxy_name=ATS-JSONRPC-UNRESTRICTED-WITNESS ssl-key-path=etc/trafficserver tracker=ok write=unauthorized tcp-jsonrpc=absent ATS-JSONRPC-UNRESTRICTED-WITNESS

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

Wrong turns, in the order I made them

Believing the docs table. The last sentence says true / 0700. The initializer is false. I almost skipped the surface as "intended restricted."

Calling this RCE. Writes are SO_PEERCRED. uid 10000 cannot set records, reload plugins, or drain. Class is recon. Inflating it to LPE is wrong.

Treating CVE-2026-58159 as covering mgmt JSON-RPC. That CVE is the HTTP UDS listener ACL. This inode is jsonrpc20.sock. Different bind, different methods.

Looking for a TCP leftover of traffic_manager. Only UNIX exists. I grepped /proc/net/tcp anyway. HTTP 8080 / origin 18080. No JSON-RPC port.

Network FS that ignores umask. Below bar. Default local Linux FS honors bind+chmod.

A reverse shell. Theatre. The oracle is a planted proxy_name plus SSL key path strings, then a denied set.

What an attacker can do

Local uid on the ATS host (not root, not ATS euid). RPC enabled. JSON-RPC unix on. Typical rundir 0755 ATS-owned. Socket $RUNTIMEDIR/jsonrpc20.sock.

Method Sink Privilege
admin_lookup_records as uid 10000 on 0777 jsonrpc20.sock unrestricted JSON-RPC (RpcAdminPubHandlers.cc) Recon (lab-proven): live proxy.config.proxy_name=ATS-JSONRPC-UNRESTRICTED-WITNESS, SSL key paths etc/trafficserver, proxy.config.admin.user_id=nobody. Key bytes are not in records.
get_connection_tracker_info same socket Recon (lab-proven): inbound/outbound tracker tables.
get_server_status, admin_host_get_status, admin_storage_get_device_status, filemanager.get_files_registry same unrestricted bit Recon (inferred): drain state, host markdown, cache disk paths, absolute config paths.
admin_config_set_records as uid 10000 SO_PEERCRED restricted Integrity: closed (lab-proven). Unauthorized. Witness record unchanged.
drain / cache clear / storage offline restricted methods Availability: closed for a foreign uid.

Lab-proven vs inferred: 0777 socket, uid 10000 lookup, tracker, write denied, TCP JSON-RPC absent: lab. Other unrestricted read methods: inferred from the same gate.

Requires: local uid on the ATS host. JSON-RPC unix on (default). Linux SO_PEERCRED so unrestricted bind is 0777.

You get live config values and tracker tuples, not a write, not plugin load, not a TCP management port. You do get SSL key paths. You do not get key bytes from this method.

Adjacent surface that stays closed

Surface Lab
admin_config_set_records as uid 10000 Unauthorized; witness record unchanged
TCP / HTTP JSON-RPC absent; HTTP 8080 / 18080 only
Plugin .so load via RPC SO_PEERCRED
Same-uid traffic_ctl intended admin
Atomic umask+bind when restricted_api is true 0700, CHANGELOG fix held
CVE-2026-58159 HTTP UDS listener ACL not this socket

The fix

Default restricted_api to true and bind 0700, matching the docs table. Align the commented jsonrpc.yaml example. Keep unrestricted reads behind an explicit opt-in. Writes already require SO_PEERCRED; do not open them.

Until then, every local uid that can see $RUNTIMEDIR can read the management API the docs said was 0700.

I am not going to print a unix-socket JSON-RPC client you can paste at someone else's rundir. The default false is the useful part. If you own the host, run the proof of concept against loopback.

References

ATS 10.2.0 JSON-RPC UDS defaults unrestricted 0777 · Abraxas Labs