HexBrain

curl · Report #4027511

Encoded slash traversal in the IPFS/IPNS URL rewrite escapes the configured gateway namespace

Informativestatus
mediumseverity
11upvotes
14 Sept 2026disclosed

How it was found

curl 8.22.0's IPFS/IPNS CLI rewrite decodes the user path after its first dot-segment cleanup, then places that decoded path after the configured gateway base and /ipfs/<cid> or /ipns/<name> namespace. An encoded slash can therefore become a structural separator too late for the first cleanup, and the final HTTP URL parse removes attacker-created .. components together with the trusted prefixes. With an authenticated gateway configured at /private/gateway, an attacker-supplied IPFS URL made curl request /admin/config, attach the same gateway Authorization header needed for normal IPFS content, and return the protected response. The attached loopback PoC reproduces this twice on the signed 8.22.0 source release and twice on current master. Safe, unauthenticated, and literal-dot controls show that the trigger is the late encoded-separator decode, while a narrow fixed copy rejects it before making a request. No public issue, patch, advisory, test, mailing-list post, or disclosed HackerOne report was found for this tool_ipfs.c sink.

AI disclosure: AI assistance was used during report drafting. The claims were checked against the official 8.22.0 build, saved raw traces, a fixed-copy control, and the cited public history.

Source: https://github.com/curl/curl/blob/curl-8_22_0/src/tool_ipfs.c#L125-L200

The behavior was introduced by commit 859e88f6533f9e1f890f6f39f34178871c48869e, first released in curl 8.5.0. It is present in release tag commit 01346829096c61b372692f6dc43ffa778c6caccd and master commit 0b04700029149d740f50b7925dd162aa270e3908 tested on 2026-09-13.

This is not an experimental or off-by-default feature: CMake sets CURL_DISABLE_IPFS to OFF, Autotools enables IPFS with HTTP unless it is disabled, and the default 8.22.0 build used here lists both IPFS and IPNS.

Steps to reproduce

  1. Build the 8.22.0 source release:
curl -O https://curl.se/download/curl-8.22.0.tar.xz
tar -xf curl-8.22.0.tar.xz
cd curl-8.22.0
./configure
make -j2
  1. From the attachment directory, run:
python3 poc.py --curl-bin /absolute/path/to/curl-8.22.0/src/curl --output evidence-822
  1. The final line is overall_pass=True. The saved result shows:
attack1, attack2:       HTTP 200  /admin/config
safe authenticated:    HTTP 200  /private/gateway/ipfs/<valid-cid>/safe
safe unauthenticated:  HTTP 401  /private/gateway/ipfs/<valid-cid>/safe
literal-dot control:   HTTP 200  /private/gateway/ipfs/<valid-cid>/admin/config
attack unauthenticated: HTTP 401  /admin/config
IPNS attack:            HTTP 200  /admin/config

The attack input is:

ipfs://bafybeigagd5nmnn2iys2f3doro7ydrevyr2mzarwidgadawmamiteydbzi/%2f..%2f..%2f..%2f..%2f..%2fadmin/config

The script uses a valid CID from curl's own documentation, requires the same synthetic header for both normal gateway content and /admin/config, binds only to loopback, and makes no public IPFS request. Request logs store only whether Authorization was present; saved traces contain [REDACTED_CANARY].

(sidenote for my binary, PZ is the prefix I use for identifying my poc zips.)

Impact

An application that accepts an untrusted IPFS/IPNS URL and fetches it through an authenticated private gateway can be made to perform a same-origin arbitrary GET outside both its configured gateway base and content-addressed namespace. The gateway credential is attached and the response is returned to the caller, so a reverse proxy or private gateway with another sensitive route on that origin can disclose it.

This does not change the host or port, and it does not assume stock Kubo exposes its admin RPC port through the gateway.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original