Side note : I just switched to writing my blog posts in english. I hope they will still be understandable ;-) My french readers are probably fine with this language and should not be negatively impacted by this change.
During some recent pentests, I used the "Max-Forwards" trick to identify some "hidden" reverse HTTP proxies. My customers were surprised by the information found and asked me a copy of the tool. I then choose to take some time to polish and release it. Btw, thanks to Julien Cayssol for the initial versions !
Some background information about the Max-Forwards trick ... The RFC 2616 (HTTP/1.1) and 3261 (SIP) define this HTTP header (resp. in section 14.31 and 8.1.1.6) :
14.31 Max-Forwards
The Max-Forwards request-header field provides a mechanism with the
TRACE (section 9.8) and OPTIONS (section 9.2) methods to limit the
number of proxies or gateways that can forward the request to the
next inbound server. This can be useful when the client is attempting
to trace a request chain which appears to be failing or looping in
mid-chain.
Max-Forwards = "Max-Forwards" ":" 1*DIGIT
The Max-Forwards value is a decimal integer indicating the remaining
number of times this request message may be forwarded.
Each proxy or gateway recipient of a TRACE or OPTIONS request
containing a Max-Forwards header field MUST check and update its
value prior to forwarding the request. If the received value is zero
(0), the recipient MUST NOT forward the request; instead, it MUST
respond as the final recipient. If the received Max-Forwards value is
greater than zero, then the forwarded message MUST contain an updated
Max-Forwards field with a value decremented by one (1).
8.1.1.6 Max-Forwards The Max-Forwards header field MAY be ignored for all other methods defined by this specification and for any extension methods for which it is not explicitly referred to as part of that method definition. The Max-Forwards header field serves to limit the number of hops a request can transit on the way to its destination. It consists of an integer that is decremented by one at each hop. If the Max-Forwards value reaches 0 before the request reaches its destination, it will be rejected with a 483(Too Many Hops) error response. A UAC MUST insert a Max-Forwards header field into each request it originates with a value that SHOULD be 70. This number was chosen to be sufficiently large to guarantee that a request would not be dropped in any SIP network when there were no loops, but not so large as to consume proxy resources when a loop does occur. Lower values should be used with caution and only in networks where topologies are known by the UA.
But this is RFC, not a real life implementation. In fact, the TRACE method is often blocked at the perimeter and we need some smarter ways to identify the reverse proxies. Given my experience, using the TRACE and GET methods is in most cases sufficient to collect weird behaviors. These behaviors are then checked against a few heuristic rules in order to calculate a score. A score greater than zero indicates a possible reverse proxy.
The heuristic rules used are the following :
The tool was run against the Alexa's Top 100, using the TRACE and GET methods. Some interesting results were identified and we will now examine them.
Target : www.ask.com / Method : TRACE
[==] Target URL : http://www.ask.com:80/
[==] Used method : TRACE
[==] Max number of hops : 3
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Title:
Hop #0 : Access Denied
Hop #1 : Access Denied
Hop #2 : Access Denied
Server:
Hop #0 : AkamaiGHost
Hop #1 : AkamaiGHost
Hop #2 : AkamaiGHost
Content-Type:
Hop #0 : text/html
Hop #1 : text/html
Hop #2 : text/html
StatusCode:
Hop #0 : 403 Forbidden
Hop #1 : 403 Forbidden
Hop #2 : 403 Forbidden
[--] No reverse proxy
Nothing interesting was detected :-( However, if we switch to GET :
[==] Target URL : http://www.ask.com:80/
[==] Used method : GET
[==] Max number of hops : 3
[++] HTTP 502 : Probably a reverse proxy
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Title:
Hop #0 : 502 Proxy Error
Hop #1 : Undef
Hop #2 : Ask.com Web Search
Server:
Hop #0 : Undef
Hop #1 : Apache
Hop #2 : Apache
Content-Type:
Hop #0 : text/html; charset=iso-8859-1
Hop #1 : text/html
Hop #2 : text/html;charset=UTF-8
StatusCode:
Hop #0 : 502 Bad Gateway
Hop #1 : 500 Internal Server Error
Hop #2 : 200 OK
[++] Found a reverse proxy, score is 8
The overall score is now 8 and we can assume there's probably 2 reverse-proxies (hops #1 and #2) ! If we try 't.co', we see that TRACE is blocked. However, we have a score of 1 because of the change in 'Server' headers :
[==] Target URL : http://www.t.co:80/
[==] Used method : TRACE
[==] Max number of hops : 3
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Title:
Hop #0 : 405 Method Not Allowed
Hop #1 : 405 Method Not Allowed
Hop #2 : 405 Method Not Allowed
Server:
Hop #0 : Apache
Hop #1 : hi
Hop #2 : hi
Content-Type:
Hop #0 : text/html; charset=iso-8859-1
Hop #1 : text/html; charset=iso-8859-1
Hop #2 : text/html; charset=iso-8859-1
StatusCode:
Hop #0 : 405 Method Not Allowed
Hop #1 : 405 Method Not Allowed
Hop #2 : 405 Method Not Allowed
[++] Found a reverse proxy, score is 1
't.co' again, now using GET :
[==] Target URL : http://www.t.co:80/
[==] Used method : GET
[==] Max number of hops : 3
[++] HTTP 502 : Probably a reverse proxy
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Title:
Hop #0 : Twitter / Over capacity
Hop #1 : t.co / Twitter
Hop #2 : t.co / Twitter
Server:
Hop #0 : Apache
Hop #1 : hi
Hop #2 : hi
Content-Type:
Hop #0 : text/html; charset=UTF-8
Hop #1 : text/html; charset=utf-8
Hop #2 : text/html; charset=utf-8
StatusCode:
Hop #0 : 502 Bad Gateway
Hop #1 : 200 OK
Hop #2 : 200 OK
[++] Found a reverse proxy, score is 5
Did you notice the case mismatch between 'utf8' and 'UTF8' ? ;-) There's too some situations where internal IP or hostnames are leaked ! First example, 'typepad.com' and 10.17.*.* :
[==] Target URL : http://www.typepad.com:80/
[==] Used method : TRACE
[==] Max number of hops : 3
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
X-Fwd:
Hop #0 : [Your_IP], 10.17.141.102
Hop #1 : [Your_IP], 10.17.141.102
Hop #2 : [Your_IP], 10.17.141.102
Server:
Hop #0 : Apache
Hop #1 : Apache
Hop #2 : Apache
Content-Type:
Hop #0 : message/http
Hop #1 : message/http
Hop #2 : message/http
StatusCode:
Hop #0 : 200 OK
Hop #1 : 200 OK
Hop #2 : 200 OK
[++] Found a reverse proxy, score is 3
Second example, '163.com' and the host 'stcz164' listening on port 8103 :
[==] Target URL : http://www.163.com:80/
[==] Used method : GET
[==] Max number of hops : 3
[++] "Via" header : Probably a reverse proxy
[++] "Via" header : Probably a reverse proxy
[++] "Via" header : Probably a reverse proxy
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Via:
Hop #0 : 1.1 stcz164:8103 (Cdn Cache Server V2.0), 1.1 dg49:8106 (Cdn Cache Server V2.0)
Hop #1 : 1.1 stcz164:8103 (Cdn Cache Server V2.0), 1.1 dg49:8106 (Cdn Cache Server V2.0)
Hop #2 : 1.1 stcz164:8103 (Cdn Cache Server V2.0), 1.1 dg49:8106 (Cdn Cache Server V2.0)
Title:
Hop #0 : ���
Hop #1 : ���
Hop #2 : ���
Server:
Hop #0 : nginx
Hop #1 : nginx
Hop #2 : nginx
Content-Type:
Hop #0 : text/html; charset=GBK
Hop #1 : text/html; charset=GBK
Hop #2 : text/html; charset=GBK
StatusCode:
Hop #0 : 200 OK
Hop #1 : 200 OK
Hop #2 : 200 OK
[++] Found a reverse proxy, score is 3
Third example, 'zhaopin.com' and '192.168.9.*' :
[==] Target URL : http://www.zhaopin.com:80/
[==] Used method : TRACE
[==] Max number of hops : 3
[++] "Via" header : Probably a reverse proxy
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
[++] "X-Forwarded-For" in body when using TRACE : Probably a reverse proxy
--------------------------------------------------------------------------------
[---------------------------] Heuristic Report [-------------------------------]
--------------------------------------------------------------------------------
Via:
Hop #0 : 1.0 squid91 (squid/3.0.STABLE13)
Hop #1 : Undef
Hop #2 : Undef
X-Fwd:
Hop #0 : [Your_IP]
Hop #1 : [Your_IP], 192.168.9.113
Hop #2 : [Your_IP], 192.168.9.98
Server:
Hop #0 : squid/3.0.STABLE13
Hop #1 : Apache
Hop #2 : Apache
Content-Type:
Hop #0 : text/plain
Hop #1 : message/http
Hop #2 : message/http
StatusCode:
Hop #0 : 200 OK
Hop #1 : 200 OK
Hop #2 : 200 OK
[++] Found a reverse proxy, score is 9
This can be used on SIP networks too, using the INVITE method and looking for HTTP 483, but I didn't test it. You can download the tool (v0.5) here. Enjoy !