A path holding a character Go escapes reaches the app re-encoded, with an encoded slash turned into a slash #126

Open
opened 2026-10-14 18:13:36 +02:00 by clawbot · 0 comments
Collaborator

internal/proxy/request.go, forward and rewrite: httputil.ReverseProxy writes the path the app receives from URL.EscapedPath(). When the path as the client sent it holds a character Go's net/url escapes, such as |, ^, {, }, \ or a byte above 127, EscapedPath does not return that path. It decodes the whole path and escapes it again, so an encoded slash (%2F) reaches the app as /.

Repro, at the defaults, each request sent as written on a raw connection:

  • GET /a%2Fb|c HTTP/1.1: the app receives /a/b%7Cc.
  • GET /feat%2Fx/file^1 HTTP/1.1: the app receives /feat/x/file%5E1.
  • GET /x%2Fy/café HTTP/1.1, its UTF-8 bytes unescaped: the app receives /x/y/caf%C3%A9.

A test in internal/proxy that compares the app's r.RequestURI with the target sent fails on all three. Browsers send | and ^ in a path unescaped.

README, "What it does so far": "Passes each request to the app and the app's answer back unchanged: method, path, query, headers, body and status." An encoded slash turned into a slash can route the request to another resource in the app.

It also breaks what pathExempt relies on since #77, that the path as sent is the path the app receives. With SWWAF_RATE_LIMIT_EXEMPT_PATHS=/assets/, /assets/x%2Fy|z is exempt (a test of pathExempt shows it), though the README says a request "whose path as sent holds an encoded slash (%2F or %2f), is never exempt". The app receives /assets/x/y%7Cz there, so the exemption fits what the app gets, and the README's description does not.

Severity: minor. It needs an encoded reserved character and a character Go escapes in the same path, and it lets through or refuses nothing it should not.

Model: opus-5-5

`internal/proxy/request.go`, `forward` and `rewrite`: `httputil.ReverseProxy` writes the path the app receives from `URL.EscapedPath()`. When the path as the client sent it holds a character Go's `net/url` escapes, such as `|`, `^`, `{`, `}`, `\` or a byte above 127, `EscapedPath` does not return that path. It decodes the whole path and escapes it again, so an encoded slash (`%2F`) reaches the app as `/`. Repro, at the defaults, each request sent as written on a raw connection: - `GET /a%2Fb|c HTTP/1.1`: the app receives `/a/b%7Cc`. - `GET /feat%2Fx/file^1 HTTP/1.1`: the app receives `/feat/x/file%5E1`. - `GET /x%2Fy/café HTTP/1.1`, its UTF-8 bytes unescaped: the app receives `/x/y/caf%C3%A9`. A test in `internal/proxy` that compares the app's `r.RequestURI` with the target sent fails on all three. Browsers send `|` and `^` in a path unescaped. README, "What it does so far": "Passes each request to the app and the app's answer back unchanged: method, path, query, headers, body and status." An encoded slash turned into a slash can route the request to another resource in the app. It also breaks what `pathExempt` relies on since https://git.eeqj.de/sneak/smallwebwaf/issues/77, that the path as sent is the path the app receives. With `SWWAF_RATE_LIMIT_EXEMPT_PATHS=/assets/`, `/assets/x%2Fy|z` is exempt (a test of `pathExempt` shows it), though the README says a request "whose path as sent holds an encoded slash (`%2F` or `%2f`), is never exempt". The app receives `/assets/x/y%7Cz` there, so the exemption fits what the app gets, and the README's description does not. Severity: minor. It needs an encoded reserved character and a character Go escapes in the same path, and it lets through or refuses nothing it should not. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/smallwebwaf#126