
Apache Tomcat 11.0.27 - High - Remote - DoS - RewriteValve [NS] worker pin
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · tomcat-rewrite-ns-loop
Class: DoS Reach: Remote
Apache Tomcat 11.0.27 (52f3b364) - tomcat.apache.org
RewriteValve.parseRuleFlag prefix-matches. After NC/NE, any remaining flag that startsWith("N") enables next. Copied httpd [NS] (nosubreq) and [NV] (novary) become [N]. Substitution - leaves substitution null; RewriteRule.evaluate then returns the original URL. isNext() plus a non-null newtest sets i = -1 with no round cap, so one unauthenticated GET that matches the rule pins the HTTP connector thread until the client drops or the process restarts. Explicit [N] and [S=-1] are the same pin. [L,NS] is safe because isLast() breaks first.
CVE-2026-65927 (Important, 11.0.25) fixed [N] restarting at the second rule. This pin still has no round cap. Same-day main after 11.0.27 added MAX_REWRITE_ROUNDS=1000 and exact flag matching (2da9fddb, 8992dbdd) without a security advisory.
| ID | no CVE yet |
| Class | DoS |
| Reach | Remote (unauthenticated HTTP) |
| CWE | CWE-835 |
| CVSS | High: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
| Product | Apache Tomcat |
| Affected | 11.0.27 (52f3b364e5882a5dbcfc42cb8313a7c2ae532c64) RewriteValve |
| Auth | unauthenticated HTTP |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
I pinned tag 11.0.27 and read RewriteValve.invoke. The valve is not in stock conf/server.xml. Default Host path is conf/Catalina/localhost/rewrite.config. The lab is loopback HTTP on 127.0.0.1:18910. Official 11.0.27 tarball. Not RCE.
| Method | Sink | Privilege |
|---|---|---|
Unauth GET /loop matching Host rewrite.config RewriteRule ^/loop$ - [NS] | RewriteValve.invoke i = -1 because startsWith("N") on token NS | Availability (lab-proven): pin one HTTP connector thread until the client drops or the process is restarted. Lab: /loop timed out at 4.002s. |
Unauth GET /next with explicit [N] | same isNext() restart | Availability (lab-proven): same pin. Lab: /next timed out at 4.003s. |
Unauth GET /skip with [S=-1] | i += -1 then the for increment | Availability (lab-proven): same pin. Lab: /skip timed out at 4.002s. |
| One matching request per worker | same sink, maxThreads concurrent matching URLs | Availability (inferred): fill the connector pool (lab used 32; stock default is 200). New requests then queue or refuse. Sibling /ok still answered while one /loop was in flight. |
Hang vs 13ms 404 on /ok | timing of next/skip vs [L] | Recon (lab-proven): tell that a next/skip rule matched. Lab: 4.002s hang vs 0.013s 404. |
Lab-proven vs inferred: /loop [NS] hang, /next [N] hang, /skip [S=-1] hang, /ok [L] 404 in 13ms, /safe [L,NS] 404 in 5ms, and sibling /ok 404 while /loop is in flight are lab-proven. Pool exhaustion is inferred from one-thread pin plus maxThreads.
Requires: RewriteValve on the Host or Context and a matching rewrite.config rule. Valve is opt-in, same class as commented-out AJP. Copied httpd [NS] is a matching rule, not an intentional infinite [N] loop.
| Surface | Lab |
|---|---|
[L,NS] (httpd last+nosubreq) | /safe 404 in 5ms (L-ns-safe=yes) |
| Other in-flight requests while one thread is pinned | /ok 404 in 13ms during /loop (sibling-alive=yes) |
Stock server.xml without RewriteValve | valve absent; leftover does not fire |
| RCE / WEB-INF read / constraint bypass of CVE-2026-65927 | [N] now restarts at rule 0; this leftover is availability only |
| Manager / JMX / AJP / DefaultServlet PUT | unchanged; PUT still readonly |
No code execution as the Tomcat user. No read of conf/ or WEB-INF. No session steal. No Manager deploy. The JVM stays up; other workers still answer until the pool is filled.
cd lab
./run.sh
Loopback 127.0.0.1:18910 maps container 8080. Compose project tomcat-rewrite-ns-loop. Image tag tomcat-rewrite-ns-loop:11.0.27. Official apache-tomcat-11.0.27.tar.gz SHA512 a617896307cada4884b24f80c4e977ddad8b3e0f88ce0e2a6eea29d5f332498e276ae5976c227a9c8daecf05c7c90def684a3766dd5a81f47a56d8b1adf43dc3. Witness TOMCAT-REWRITE-NS-LOOP-WITNESS. Last line:
SUCCESS TOMCAT-REWRITE-NS-LOOP control-ok=yes ns-hang=yes L-ns-safe=yes n-hang=yes skip-hang=yes sibling-alive=yes TOMCAT-REWRITE-NS-LOOP-WITNESS
Lab rewrite.config:
RewriteRule ^/loop$ - [NS]
RewriteRule ^/next$ - [N]
RewriteRule ^/skip$ - [S=-1]
RewriteRule ^/ok$ - [L]
RewriteRule ^/safe$ - [L,NS]
Cap rewrite rounds (MAX_REWRITE_ROUNDS=1000 on main after this tag, commit 2da9fddb). Exact-match flags so [NS] fails parse (commit 8992dbdd). Reject skip < 0. Until those land in a release: avoid [NS]/[NV]/[N]/[S=-1] on matching rules, or drop RewriteValve.
52f3b364e5882a5dbcfc42cb8313a7c2ae532c64)RewriteValve.java isNext() restart and startsWith("N")RewriteRule.java substitution - returns the original URL