PDA

View Full Version : Revised x-forwarded-for handling code


kjkoster
27-05-2009, 07:09
Dear All,

I was running with the code that I posted earlier (http://java-monitor.com/forum/showthread.php?t=43) about getting the remote IP address from behind a proxy. It turns out that code is buggy, in the sense that the x-forwarded-for header may appear more than once. This is explained by Sascha Ottolski in this discussion about a Zope bug (http://www.apsis.ch/pound/pound_list/archive/2004/2004-09/1094650639000/index_html?fullMode=1#1094661829000).

A particularly nasty issue for programmers is that not all entries may be IP addresses. They may also be the word "unknown" (http://dev.robertmao.com/2007/06/30/what’s-inside-x-forwarded-for-and-how-to-handle/). This means that you may have to skip entries in order to find the IP address of your client, or of the proxy closest to your actual client. My previous code was not robust against this situation either.

To quote from that article:

Thus having
...
X-Forwarded-for: client1, proxy1
...
X-Forwarded-for: proxy2
...

should mean exactly the same as
...
X-Forwarded-for: client1, proxy1, proxy2
...


My code depended on there only being a single x-forwarded-for header. Woops. my bad. Here is the new code.


@SuppressWarnings("unchecked")
public static InetAddress remoteIp(final HttpServletRequest request)
throws UnknownHostException {
final Enumeration<String> headers = request.getHeaders(X_FORWARDED_FOR);

if (headers == null) {
log.warn("no access to headers");
} else {
while (headers.hasMoreElements()) {
final String[] ips = headers.nextElement().split(",");
for (int i = 0; i < ips.length; i++) {
final String proxy = ips[i].trim();
if (!"unknown".equals(proxy) && !proxy.isEmpty()) {
try {
return InetAddress.getByName(proxy);
} catch (UnknownHostException e) {
log.warn("ignoring host " + proxy + ": "
+ e.getClass().getName() + ": "
+ e.getMessage());
}
}
}
}
}

return InetAddress.getByName(request.getRemoteAddr());
}


As a side note, I have been reading the wikipedia entry on the x-forwarded-for header (http://en.wikipedia.org/wiki/X-Forwarded-For). It is not a really trustworthy source of information I am afraid. Malicious proxies can easily forge the header and send your code off into the woods if you rely on it too much.

Enjoy.

Kees Jan

Cyrille Le Clerc
17-07-2009, 22:55
Hello Jan,

As you say, malicious people (or proxies) can add erroneous ips in the X-Forwarded-For header and require special processing. Firefox Modify Headers (https://addons.mozilla.org/en-US/firefox/addon/967) and X-Forwarded-For Spoofer (https://addons.mozilla.org/en-US/firefox/addon/5948) are simple add-ons to do so.

Apache Httpd will integrate the mod_remoteip (http://httpd.apache.org/docs/trunk/mod/mod_remoteip.html) module to handle X-Forwarded-For header preventing ip spoofing thanks to the concept of trusted proxies.

Here are two java ports of mod_remoteip to handle X-Forwarded-For at the Tomcat level with a valve and at the WAR level with a servlet filter : RemoteIpValve (http://code.google.com/p/xebia-france/wiki/RemoteIpValve) and XForwardedFilter (http://code.google.com/p/xebia-france/wiki/XForwardedFilter). In addition to handle X-Forwarded-For, they also integrate X-Forwarded-Proto.

Thanks to this, request.getRemoteAddr(), request.getRemoteHost(), request.isSecure(), request.getScheme() and request.getServerPort() will expose the values transmitted by X-Forwarded-For and X-Forwarded-Proto rather than the values of the preceding proxy / load balancer.

The RemoteIpValve has been proposed to the Tomcat project as Bug 47330 - proposal : port of mod_remoteip in Tomcat as RemoteIpValve (https://issues.apache.org/bugzilla/show_bug.cgi?id=47330) .

Hope this helps,

Cyrille

kjkoster
20-07-2009, 19:32
Dear Cyrille,

Thank you for this excellent follow-up. Looks like I will have to start using them myself.

Could you please explain a bit more in-depth how this would work for a chain of proxies? Imagine a departmental proxy, forwarding to a company-wide proxy, forwarding to a national proxy, forwarding to my Apache and finally to my Tomcat. This would mean the chain of trust reaches from Tomcat to Apache, or also onwards to the others?

If so, how do I know I can trust them? If not, what difference would mod_remoteip make, since the browser is three or more untrusted hops away?

Kees Jan

Cyrille Le Clerc
22-07-2009, 02:25
Hello Kees Jan,

The concept of trusted proxies mainly applies to "reverse proxies" and network components located between your application server (ie Tomcat) and the Internet. All these trusted proxies are chained together in your data Center.

The main scenario I see about trusted reverse proxies is an architecture with :
Internet Client ---> Internet --> Hardware Load Balancer --> Web Server --> Tomcat.

Hardware Load Balancer : active / passive product like F5 Big IP or Nortel Alteon. Sticky session is NOT needed. SSL must be unencrypted in this component (to allow http header access). X-Forwarded-For header is set at this layer to the client-ip whatever was the previous value of this header. The simpler scenario is to overwrite the value of x-forwarded-for header if it exists in the incoming request.
Web Server : cluster of N web servers like Apache + mod_proxy_balancer + sticky session activated on mod_proxy_balancer. X-Forwarded-For is enriched at this layer to append the hardware-load-balancer-ip.
Tomcat : cluster of M Tomcat servers with a simple http connector (NIO /APR will offer you send file). The number M of nodes in the Tomcat cluster is usually higher than the number N of nodes in the Web Servers cluster.

What about proxies between the client and the hardware load balancer ? typically a forward proxy configured in web browsers ?
Some of these proxies, clearly identified ones, could be trusted but you then will have to do a special configuration on your hardware load balancer to tell it : if the remote address is a trusted forward proxy, then enrich the incoming X-Forwarded-For, otherwise, don't trust this incoming X-Forwarded-For header and overwrite it (1).

To come back to the departmental and company-wide proxies, they look like the forward proxies of my scenario and you would have to declare them as trusted proxies by the X-Forwarded-For algorithm of the hardware load balancer.

Hope this helps,

Cyrille
--
Cyrille Le Clerc
cleclerc@xebia.fr
http://blog.xebia.fr

(1) F5 Big IP basic configuration sample for X-Forwarded-For (always overwrite the X-Forwarded-For header) : F5 DevCentral : Using "X-Forwarded-For" in Apache or PHP (http://devcentral.f5.com/weblogs/macvittie/archive/2008/06/02/3323.aspx)

kjkoster
22-07-2009, 09:03
Dear Cyrille,

Ah, that clarifies it. You'd use this to trust your own machines. Spoofing may still be present outside your network's virtual walls, but at least you know you can trust your front-end web heads.

Kees Jan

Cyrille Le Clerc
08-11-2009, 16:49
Hello,

The RemoteIpValve and the XForwardedFilter have been integrated in the Tomcat Project. The RemoteIpValve will be available in the forthcoming Tomcat 6.0.21 version (http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/catalina/valves/RemoteIpValve.java?annotate=833535&pathrev=833536) when the XForwardedFilter has been renamed RemoteIpFilter and will be integrated in Tomcat 7 (http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/filters/RemoteIpFilter.java?annotate=833155).
The Google Code version of XForwardedFilter (http://code.google.com/p/xebia-france/wiki/XForwardedFilter) will still be interesting for people who want to integrate this features in other servlet containers (Glassfish, JBoss, Weblogic, WebSphere, etc) without importing Tomcat jars.

Cyrille.

kjkoster
09-11-2009, 07:05
Dear Cyrille,

Well then. No need for my little hacks any longer. Excellent work.

Thanks for letting us know.

Kees Jan

kjkoster
02-02-2010, 15:06
Dear Cyrille,

Could you please clarify: does this valve or the filter modify the value of request.getRemoteIp() or not?

Kees Jan

Cyrille Le Clerc
02-02-2010, 23:44
Hello Kees Jan ,

The RemoteIpValve and the XForwardedFilter do modify the remoteAddr and remoteHost attributes of the ServletRequest according to the value of the X-Forwarded-For header.

Moreover, you can enable the update of the secure, scheme and serverPort attributes of the ServletRequest according to the value of the X-Forwarded-Proto header.

By the way, the RemoteIpValve is available in Tomcat standard distribution since the version 6.0.24. We didn't have the time yet to merge in Tomcat's documentation all the docs we wrote. Interesting pieces can be found in :

Google Code : RemoteIpValve (http://code.google.com/p/xebia-france/wiki/RemoteIpValve) (english)
Tomcat, SSL, communications sécurisées et X-Forwarded-Proto (http://blog.xebia.fr/2009/11/13/tomcat-ssl-communications-securisees-et-x-forwarded-proto/) (french but google translate friendly)
Tomcat : Adresse IP de l’internaute, load balancer, reverse proxy et header Http X-Forwarded-For (http://blog.xebia.fr/2009/05/05/tomcat-adresse-ip-de-linternaute-load-balancer-reverse-proxy-et-header-http-x-forwarded-for/) (french but google translate friendly)


I hope this helps,

Cyrille

kjkoster
03-02-2010, 09:38
Dear Cyrille,

This certainly does. I am particularly happy that it is standard in the new Tomcats. It is just easier to program when the API works ad advertised. :) Thanks again.

Kees Jan