static.html

— a blog about web development and whatnot by Steve Webster

Using mod_rewrite, it’s possible to proxy requests onto other servers by adding the P flag to your RewriteRules. This is a common technique to have Apache acting as the public-facing server for a number of disparate services. However, unless you’re careful with how you craft your rewrite rules, it’s possible for an attacker to gain access to your internal network or use your server to anonymously attack another.

In short, never do this:

RewriteRule  (.*)  http://some.internal.server$1  [P,L]

That is, in a RewriteRule with the P flag set, never concatenate a destination hostname with the request path (or any data coming from the request, for that matter) unless you’ve used some other mechanism to protected yourself from malformed requests.

The above RewriteRule allows an attacker to trick mod_rewrite into proxying the request to a host of their choosing, simply by crafting a HTTP request similar to this:

GET :foo@192.168.0.15:8080/foo/bar HTTP/1.1
Host: example.com

You can’t do this with a browser — at least, you shouldn’t be able to — but it’s trivial to do this with telnet or just about any scripting or programming language that has raw socket support.

The request, together with way the RewriteRule is written, means that the URL mod_rewrite passes to mod_proxy will look like this:

http://some.internal.server:foo@192.168.0.15:8080/foo/bar

The key to the exploit here is the @ symbol, which is used to separate user authorisation information from the hostname and port in the authority section of a URI. According to the parsing rules laid out in RFC 3986: Uniform Resource Identifier (URI): Generic Syntax, the above URI is interpreted as:

scheme:      http
authority:
  userinfo:  some.internal.server:foo
  host:      192.168.0.15
  port:      8080
path:        /foo/bar

In other words, the request is sent to 192.168.0.15 port 8080; some.internal.server:foo is used as user authorisation credentials, which is ignored if your server isn’t expecting that information. Since the destination host and port came from the request path, the attacker is in complete control of where requests are proxied.

Using this exploit, an attacker can access any internal and external server visible to this web server. Oops.

Protecting your Apache config

The good news is that protecting yourself from the above attack is relatively straightforward.

According to RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1 the URI in the request line of an HTTP resource request must be an absolute path, which means it must begin with a forward slash.

We can use mod_rewrite to reject requests that do not match this criteria before we proxy requests on:

RewriteCond  %{REQUEST_URI}  !^$
RewriteCond  %{REQUEST_URI}  !^/
RewriteRule  .*              -    [R=400,L]

RewriteRule  (.*)  http://some.internal.server$1  [P,L]

This will return a 400 Bad Request response for requests where the REQUEST_URI is not empty and does not start with a forward slash. This must be placed before any other mod_rewrite statements.

If you’ve got a layer 7 capable firewall, you could also block these requests before they even get to Apache. Just make sure you terminate SSL at or before the firewall though, otherwise the firewall won’t be able to inspect HTTPS request.

Now the only requests that get through to the original RewriteRule either have an empty REQUEST_URI or one that begins with a forward slash. Since RFC 3986 § 3.2 says the authority section of a URI ends as soon as a forward slash, question mark or octothorpe is encountered, there’s nothing the attacker can do to alter the authority section of the destination URL.

Requests such as the following will be blocked because the REQUEST_URI doesn’t begin with a forward slash:

GET :foo@192.168.0.15:8080/foo/bar HTTP/1.1
Host: example.com

…and prefixing the path with a forward slash like this:

GET /:foo@192.168.0.15:8080/foo/bar HTTP/1.1
Host: example.com

…results in a destination URL of:

http://some.internal.server/:foo@192.168.0.15:8080/foo/bar

The additional forward slash means that the URL will be interpreted thusly:

scheme:      http
authority:
  userinfo:  -
  host:      some.internal.server
  port:      -
path:        /:foo@192.168.0.15:8080/foo/bar

As you can see, now all the attacker has been able to do is request a non-existent path.

Need more mod_rewrite help?

This is one of my more frequently visited posts. mod_rewrite is powerful, but with that power comes complexity. Configuring it correctly for your specific use cases can be very tricky. When asked by developers if there's a more friendly source than the Apache docs for learning the intricacies of mod_rewrite, I usually suggest grabbing a copy of mod_rewrite: The Definitive Guide.