iRule Fun Fact - HTTP::uri Doesn't Decode
Here's something unpleasant I learned recently. Take the following sample irule, which looks at the incoming URI of a request and rejects the connection if /jmx-console/
is matched:
when HTTP_REQUEST { switch -glob [HTTP::uri] { "/jmx-console*" { reject } } }
It's one of those irules where you say, "Golly...I did that with just 5 lines! That's awesome! I am going to add other well-known, exploitable URIs and then roll this out everywhere!!"
There's a couple of problems with the pattern matching above. First, HTTP::uri
only matches the literal string "/jmx-console*
" What if one were to URL encode the request so that /jmx-console/
becomes /%6A%6D%78%2D%63%6F%6E%73%6F%6C%65/
. Well, that gets right through. HTTP::uri
doesn't decode the URI before handling like mod_proxy does so any URL encoded portion of jmx-console is not going to be matched.
The second issue: /JMX-CONSOLE != /jmx-console
, which makes sense because URI's are case-sensivite after all. So, that will get through, (although you'll get a blank page in jboss for requests matching /JMX-CONSOLE) but if you've got some passionate security folks on your team, you'd probably want to handle that consistently.
The third issue: Place a couple of slashes in front of your url encoded jmx-console URI and that will go through, too. I think that requests for ///jmx-console
are going to get through, too, but I don't recall testing that specific case.
Here's an updated version of the above sample rule:
when HTTP_REQUEST { set tmpUri [string tolower [HTTP::uri]] set uri [URI::decode $tmpUri] switch -glob $uri { "*/jmx-console*" { HTTP::respond 404 content {<html><head><title>Page Not Found</title></head><body>Page Not Found</body></html> } } }
The updated rule does a few things. First, it sets a temporary variable tmpUri
, which sets the value of the incoming request's HTTP::uri
to a lower case value. In the second line, I then decode that URI and set that to a second variable uri
. The nice thing about URI::decode
is that it doesn't matter if the request comes in as jmx-console
, /%6A%6D%78%2D%63%6F%6E%73%6F%6C%65/
, or even /j%6Dx%2Dc%6Fn%73o%6Ce/
—it will still decode to jmx-console
. (There might be a way to do this all in one line with one variable but this is what I've got so far.) Once all this business is worked out, then the switch statement evaluates the value of $uri
instead of HTTP::uri
to see if a pattern is matched. Note that I've prepended an asterisk in front of the slash in order to pick up those cases where multiple slashes are placed in front of the resource name.
Finally, don't reject or drop requests. Worms, vulnerability scanners, and script kiddies might be fooled/satisfied/fooled but folks that are determined may have their interests piqued if they send a bunch of requests to your web site and some of them result in a tcp reset while others result in a 404.
More investigation continues to see whether I can find other ways to get jmx-console requests through but this is a big improvement over what I was doing in the past.