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.
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 !