Friday, March 17, 2023
HomeColdFusionAttempting To Get The Most Reliable IP Handle For A Consumer In...

Attempting To Get The Most Reliable IP Handle For A Consumer In ColdFusion


On a current Penetration Check (PenTest), certainly one of our techniques was flagged for not correctly validating the X-Forwarded-For HTTP header, which is a recording of the assorted IP addresses alongside the community path being made by an inbound request. To be trustworthy, I’ve by no means actually thought deeply about IP addresses from a safety standpoint earlier than; however, having this present up on a PenTest despatched me down a little bit of a rabbit gap. And, I believed it may be value speaking a bit about why IP addresses pertain to safety in ColdFusion.

After I first began programming in ColdFusion, every little thing lived on a single server. And, in these days, utilizing the cgi.remote_addr to entry the person’s IP tackle was easy and secure – this CGI worth was not a price that could possibly be simply spoofed (as I understood it). As such, utilizing the cgi.remote_addr for issues like audit logging and fee limiting in ColdFusion was simple.

However, once we moved our ColdFusion server behind a load-balancer for the primary time, issues began breaking. It’s because the load-balancer sat in between the person and the ColdFusion server and altered the worth being saved in cgi.remote_addr: it was not the person’s IP tackle, it was the load-balancer’s IP tackle.

You may think about that this instantly tripped all rate-limiting circuit breakers since all inbound request visitors appeared to be coming from only a handful of (inside) IP addresses.

To repair that difficulty, we began utilizing the X-Forwarded-For HTTP header. This HTTP header is a comma-delimited record of IP addresses, beginning with the person’s IP tackle and adopted by every subsequent reverse proxy’s IP tackle.

And, I have never given IP addresses an excessive amount of thought since then. Till this Penetration Check. And after I began to read-up on utilizing and validating X-Forwarded-For headers, I got here throughout this amazingly in-depth article on “actual” IP addresses by Adam Pritchard (March 2022). In that article, Pritchard states that the X-Forwarded-For header worth ought to be thought-about a user-provided worth and due to this fact can’t be trusted.

The cautions in his article have since been included within the Mozilla Developer Community (MDN) docs on X-Forwarded-For:

This header, by design, exposes privacy-sensitive info, such because the IP tackle of the shopper. Subsequently the person’s privateness should be saved in thoughts when deploying this header.

The X-Forwarded-For header is untrustworthy when no trusted reverse proxy (e.g., a load balancer) is between the shopper and server. If the shopper and all proxies are benign and well-behaved, then the record of IP addresses within the header has the that means described within the Directives part. But when there is a danger the shopper or any proxy is malicious or misconfigured, then it is attainable any half (or everything) of the header might have been spoofed (and is probably not a listing or include IP addresses in any respect).

If any trusted reverse proxies are between the shopper and server, the ultimate X-Forwarded-For IP addresses (one for every trusted proxy) are reliable, as they have been added by trusted proxies. (That is true so long as the server is barely accessible by these proxies and never additionally straight).

Any security-related use of X-Forwarded-For (corresponding to for fee limiting or IP-based entry management) should solely use IP addresses added by a trusted proxy. Utilizing untrustworthy values may end up in rate-limiter avoidance, access-control bypass, reminiscence exhaustion, or different unfavorable safety or availability penalties.

Conversely, leftmost (untrusted) values should solely be used the place there shall be no unfavorable influence from the potential for utilizing spoofed values.

For somebody like me who would not spend time fascinated by how networks and proxies work, this language is a bit onerous to parse. However, my understanding is that the X-Forwarded-For header is inherently untrustworthy; and, that we should always solely belief the worth being supplied by the primary trusted proxy within the community hops.

Fortunately, Pritchard goes on in his article to speak in regards to the several types of proxies which might be generally in use; and, how we – as ColdFusion builders – can lean on the assorted technical selections that stated proxies are making so as write safer code.

At work, our total system sits behind Cloudflare. And, as Pritchard states in his article, Cloudflare injects an HTTP header worth on inbound requests that we are able to contemplate “trusted”:

Let’s begin with some excellent news.

Cloudflare provides the CF-Connecting-IP header to all requests that cross by it; it provides True-Shopper-IP as a synonym for Enterprise customers who require backwards compatibility. The worth for these headers is a single IP tackle. The fullest description of those headers that I may discover makes it sound like they’re simply utilizing the leftmost XFF (X-Forwarded-For) IP, however the instance was sufficiently incomplete that I attempted it out myself. Fortunately, it seems to be like they’re truly utilizing the rightmost-ish.

After studying this, I went to search for the Cloudflare docs on HTTP headers, and located the identical idea:

CF-Connecting-IP offers the shopper IP tackle connecting to Cloudflare to the origin net server. This header will solely be despatched on the visitors from Cloudflare’s edge to your origin net server.

What I consider this all means is that the CF-Connecting-IP HTTP header worth is basically the cgi.remote_addr as seen from the CDN’s perspective. Which suggests, it ought to be the non-spoofable IP tackle of no matter machine is connecting to the CDN.

Taking this info, I up to date our ColdFusion (Lucee CFML) logic to provide precedence to the CF-Connecting-IP HTTP header whether it is current. And, since now we have to cope with Staging and Native improvement environments that do not sit behind Cloudflare, falling again to older technique of entry then person’s IP tackle:

NOTE: Within the following code, I embrace the usage of the Java Commons IP Math library to truly attempt to parse the inbound IP tackle. I included that for funzies utilizing Lucee’s skill to load JAR information on the fly; however, in manufacturing, I simply use the “lightweight validation”.

<cfscript>

	echo( "Hi there from IP: #getRequestIpAddress()#" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I get probably the most applicable IP tackle for the present, incoming HTTP request.
	*/
	public string perform getRequestIpAddress() {

		// In Manufacturing, we're sitting behind the Cloudflare CDN. Cloudflare injects the
		// header, "CF-Connecting-IP", which is (in line with their docs) "the shopper IP
		// tackle connecting to Cloudflare". Since Cloudflare is a "trusted proxy", this
		// worth represents probably the most reliable IP tackle within the request chain. If this
		// header is on the market, it ought to take priority.
		var ipAddress = getHeaderValueSafely( "CF-Connecting-IP" );

		// In non-production environments, we're NOT behind the Cloudflare CDN; however, we
		// are behind a number of proxies (load balancers, nginx, and so forth). If the Cloudflare
		// header is not accessible, fallback to the "X-Forwarded-For" proxy-injected worth.
		// --
		// CAUTION: This may be a USER-PROVIDED VALUE and ought to be consumed with a lot
		// warning. A malicious actor would possibly present this header in a crafted request; and,
		// most proxies are designed to only accept-and-augment any present header.
		if ( ! ipAddress.len() ) {

			ipAddress = getHeaderValueSafely( "X-Forwarded-For" )
				.listFirst()
				.trim()
			;

		}

		// If no header-based IP tackle is on the market, fallback to utilizing the distant IP
		// tackle.
		// --
		// CAUTION: Relying in your server configuration, this worth could also be an automated
		// reflection of one of many header-based IP values. For instance, Tomcat will be
		// configured to make use of the "X-Forwarded-For" header to populate CGI.remote_addr. As
		// such, this worth might also be thought-about a USER-PROVIDED worth.
		if ( ! ipAddress.len() ) {

			ipAddress = cgi.remote_addr;

		}

		// Since we could also be coping with a user-provided worth, we have to apply some
		// validation, ensuring the supplied IP tackle seems to be like an IP tackle. If
		// any of the validation fails, we'll return this fallback IP.
		var fallbackIp = "0.0.0.0";

		// LIGHT-WEIGHT VALIDATION: The longest legitimate IPv6 tackle ought to be not
		// than 45-characters (from what I've learn).
		if ( ipAddress.len() > 45 ) {

			return( fallbackIp );

		}

		// LIGHT-WEIGHT VALIDATION: Ensure that the IP-address (IPv4 or IPv6) accommodates
		// solely the anticipated characters. This does not validate formatting - solely that every one
		// of the characters are within the anticipated ASCII vary.
		if ( ipAddress.reFindNoCase( "[^0-9a-f.:]" ) ) {

			return( fallbackIp );

		}

		// HEAVY-WEIGHT VALIDATION: We may parse the IP tackle and be sure that it's
		// semantically legitimate. Nevertheless, for the reason that IP tackle is already to not be trusted,
		// I'm not certain that the overhead of parsing it holds a lot value-add.
		if ( ! isValidIpFormat( ipAddress ) ) {

			return( fallbackIp );

		}

		return( ipAddress.lcase() );

	}


	/**
	* I get the HTTP header worth with the given title; or, an empty string if none exists.
	*/
	public string perform getHeaderValueSafely( required string title ) {

		var headers = getHttpRequestData( false ).headers;

		if ( ! headers.keyExists( title ) ) {

			return( "" );

		}

		var worth = headers[ name ];

		// Whereas the overwhelming majority of headers are easy string values, the specification
		// permits for a number of headers (with the identical title) to be supplied in an HTTP
		// request. ColdFusion combines these like-named headers as an index-based (ie,
		// Array-Like) Struct. This UDF makes an arbitrary determination to break down the header
		// worth into a listing in such instances. Workflows which might be anticipating advanced headers
		// within the request want to make use of a non-generic answer.
		if ( ! isSimpleValue( worth ) ) {

			worth = headerValueStructToList( worth );

		}

		return( worth.trim() );

	}


	/**
	* I convert the given advanced HTTP header worth to a collapsed, comma-delimited record.
	*/
	public string perform headerValueStructToList( required struct worth ) {

		var objects = [];
		var dimension = worth.dimension();

		for ( var i = 1 ; i <= dimension ; i++ ) {

			objects.append( worth[ i ] );

		}

		return( objects.toList( ", " ) );

	}


	/**
	* I exploit the Commons IP Math library to parse the given IP tackle (thereby validating
	* that it's within the correct format).
	* 
	* Commons IP Math: https://github.com/jgonian/commons-ip-math
	*/
	public boolean perform isValidIpFormat( required string enter ) {

		var jarPaths = [ "./commons-ip-math-1.32.jar" ];

		var IpClass = enter.discover( ":" )
			? createObject( "java", "com.github.jgonian.ipmath.Ipv6", jarPaths )
			: createObject( "java", "com.github.jgonian.ipmath.Ipv4", jarPaths )
		;

		// There isn't any validation methodology on the IP courses. As such, we merely must attempt
		// and parse the enter worth and see if an error is thrown.
		attempt {

			IpClass.parse( enter );
			return( true );

		} catch ( any error ) {

			return( false );

		}

	}

</cfscript>

As you may see within the getRequestIpAddress() methodology, I’m giving priority to the Cloudflare header first; then falling again to the opposite IP tackle values if the Cloudflare one would not exist.

In Prichard’s article he warns towards having generic fallbacks:

A default record of locations to search for the shopper IP is senseless

The place try to be searching for the “actual” shopper IP may be very particular to your community structure and use case. A default configuration encourages blind, naive use and can lead to incorrect and probably harmful conduct as a rule.

If you happen to’re utilizing Cloudflare you need CF-Connecting-IP. If you happen to’re utilizing ngx_http_realip_module, you need X-Actual-IP. If you happen to’re behind AWS ALB you need the rightmost-ish X-Forwarded-For IP. If you happen to’re straight related to the web, you need RemoteAddr (or equal). And so forth.

There’s by no means a time while you’re okay with simply falling again throughout a giant record of header values that don’t have anything to do together with your community structure. That is going to chunk you.

However, my fallbacks aren’t generic. They’re particularly searching for and giving highest priority to the CDN that we use at work.

To check this, I attempted making a neighborhood HTTP request to this above ColdFusion script utilizing varied mixtures of header:

<cfscript>

	// Attempt with each HTTP headers.
	http
		outcome = "apiResponse"
		methodology = "get"
		url = "http://127.0.0.1:57833/x-forward-for/goal.cfm"
		{

		httpParam
			sort = "header"
			title = "CF-Connecting-IP"
			worth = "101.101.101.101"
		;

		httpParam
			sort = "header"
			title = "X-Forwarded-For"
			worth = "202.202.202.202"
		;
	}

	echo( apiResponse.fileContent & "<br />" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Attempt with one fallback header.
	http
		outcome = "apiResponse"
		methodology = "get"
		url = "http://127.0.0.1:57833/x-forward-for/goal.cfm"
		{

		httpParam
			sort = "header"
			title = "X-Forwarded-For"
			worth = "202.202.202.202"
		;
	}

	echo( apiResponse.fileContent & "<br />" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Attempt with no fallback headers.
	http
		outcome = "apiResponse"
		methodology = "get"
		url = "http://127.0.0.1:57833/x-forward-for/goal.cfm"
	;

	echo( apiResponse.fileContent & "<br />" );

</cfscript>

And, once we run this ColdFusion code, we get the next output:

Three IP addresses being pulled from CF-Connecting-IP, X-Forwarded-For, and CGI.remote_addr, respectively, in Lucee CFML

As you may see, when the CF-Connecting-IP is current, it takes priority and is used because the “trusted” IP tackle for the inbound request. However, when that header isn’t any current (which it will not be in non-production environments), we fallback to the X-Forwarded-For header and the cgi.remote_addr worth.

On the earth of net improvement, safety is a endless journey. And, now that I am utilizing Cloudflare’s CF-Connecting-IP because the supply of fact for inbound IP addresses in manufacturing, I really feel like I took a baby-step ahead in securing our ColdFusion software.

Lucee Dev Discussion board put up on Docker. Apparently, in Tomcat, you may configure the server to make use of the X-Forwarded-For header to drive the cgi.remote_addr worth:

<Valve
	className="org.apache.catalina.valves.RemoteIpValve"
	remoteIpHeader="X-Forwarded-For"
	requestAttributesEnabled="true"
/>

Now, I do not know something about low-level server stuff. So, I do not even know the place this “Valve” is. However, given every little thing that I’ve learn just lately about IP addresses and safety, this setting appears … not nice. Which means, it is primarily taking the user-provided / untrusted X-Forwarded-For HTTP header and jamming it in what I’ve traditionally regarded as a trusted worth, cgi.remote_addr. That is gotta be a foul concept, proper?

I will be discussing this setting with our safety group.

Need to use code from this put up?
Take a look at the license.



RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments