Ask a Jedi: Using ColdFusion to detect a Proxy Server

This post is more than 2 years old.

This week I had a nice (email) conversation with Dave Dugdale. His question was:

I would like to detect if someone is using a proxy server when visiting my site. I found a script in PHP but I couldn't find one written in ColdFusion on Google or your site. Have you ever done one of these?

I certainly had not heard of such a beast, but I asked to see his PHP code. I mean, let's be real, anything written in PHP should be easier in ColdFusion, right?

Dave sent along the PHP code. I'm not sure if this is "good" code or not, but here it is:

<?php if ( $_SERVER['HTTP_X_FORWARDED_FOR'] || $_SERVER['HTTP_X_FORWARDED'] || $_SERVER['HTTP_FORWARDED_FOR'] || $_SERVER['HTTP_CLIENT_IP'] || $_SERVER['HTTP_VIA'] || in_array($_SERVER['REMOTE_PORT'], array(8080,80,6588,8000,3128,553,554)) || @fsockopen($_SERVER['REMOTE_ADDR'], 80, $errno, $errstr, 30) )

{ exit('Proxy detected'); } else // print the IP address on screen //echo ( getenv('REMOTE_ADDR') . ' Welcome 1' ); //echo ( $_SERVER['REMOTE_ADDR'] . ' Welcome 2' ); //echo ( @$REMOTE_ADDR . ' Welcome 3' ); //echo ( getenv('REMOTE_ADDR') . ' Welcome 4' ); echo ( ' Welcome 5' );

// start code

// if getenv results in something, proxy detected

?>

I looked this over. The first thing I told him was that $_SERVER was most likely just a pointer to ColdFusion's CGI scope. Any place he saw that he could just switch it with CGI. For example:

<cfif cgi.http_x_forwarded_for neq "">

You could then simply add the 4 other CGI variables to the CFIF.

The inArray looks to be a simple "Does this value exist in the array". For that I suggested just using listFindNoCase.

<cfif listFindNoCase("8080,80,etc",cgi.remote_port)>

All together, I wrote this up as:

<cfif cgi.http_x_forwarded neq "" or cgi.http_x_forwarded neq "" or cgi.http_forwarded_for neq "" or cgi.http_client_ip neq "" or cgi.http_via neq "" or listFind("8080,80,6588,8000,3128,553,554", cgi.remote_port)> Proxy! </cfif>

This seems to work well. But the last clause makes no sense to me or Dave:

@fsockopen($_SERVER['REMOTE_ADDR'], 80, $errno, $errstr, 30)

I'd guess fsockopen is analogous to CFHTTP, but as to what it is checking here, I have no idea. Anyone want to help complete the puzzle?

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Garrett Johnson posted on 4/22/2009 at 8:16 AM

I believe it just connects to a host on a certain port to be passed in... otherwise it returns false (or something like that)

He could probably do this in his check...

<cfscript>
connection = createObject("java", "java.net.Socket");
connection.init(cgi.remote_addr, 80);

connected = connection.isConnected();
</cfscript>

This probably does something similar to the PHP function. If not, one of the methods in the Socket Class could!

Comment 2 by shag posted on 4/22/2009 at 8:52 AM

In my experience, REMOTE_ADDR is the proxy if HTTP_X_FORWARDED_FOR exists. In which case, HTTP_X_FORWARDED_FOR is the client IP.

php docs says fsockopen is opening a socket, so for some reason he reaches out to touch the proxy. I'm not sure it makes any sense to do that, but I'm also not sure of the need for any of the other checks if you are simply trying to ID a Proxy use.

I think this should work:
<cfif cgi.HTTP_X_FORWARDED_FOR neq "">
proxy
</cfif>

Also, in your version, there should be one HTTP_X_FORWARDED_FOR instead of two HTTP_X_FORWARDED.

Comment 3 by cod3master posted on 4/22/2009 at 3:23 PM

i think he tries to connect to port 80, to check if there is a responding server.

in my opinion this is wrong. he needs to check on the port the connection is comming from (8080, etc.) and not only port 80.
the idea is that if the user uses a proxyserver i should be able to connect back to it too.

what happens if i have a webserver open on my router on port 80 (which is default if i don't use a proxy)? that will be detected as a connection and every connection would mean proxy in this script. bad idea.

Comment 4 by Dave Dugdale posted on 4/22/2009 at 7:56 PM

Thanks Ray and everyone else for helping me out with this code. The reason for the code if anyone is wondering is to detect when a scammer from Nigeria is trying to trick my site into thinking they're from the US.

I've played around with the code above on this page,
I created this page test http://www.pickrent.com/mis... I can't seem to get it to work.

Here is the code that is on the link above (from Shag):

<cfif cgi.HTTP_X_FORWARDED_FOR neq "">
<h4>You ARE coming from a proxy.</h4>
<CFELSE>
<h4>You are NOT coming from a proxy.</h4>
</cfif>

The code above always indicates that I am NOT coming from a proxy whether I am or not.

I also tried Ray's code above but it doesn't matter if I am coming from a proxy or not, the code always say that I AM coming from a proxy.

Comment 5 by Raymond Camden posted on 4/22/2009 at 8:00 PM

@Dave - is there one particular clause of the CFIF that is returning a false positive?

Comment 6 by shag posted on 4/22/2009 at 8:11 PM

@dave, my memory may have served me wrong, and it could be that my bubble works differently. I would suggest cfdump the cgi scope and run it directly and behind a proxy. You should be able to figure something out. I'm wondering if different proxies give different responses.

Comment 7 by Dave Dugdale posted on 4/22/2009 at 9:18 PM

@shag

I liked your idea of running the cfdump behind the proxy, so I added that to the code on the test page http://www.pickrent.com/mis....

Here are my results that are different:

Normal then proxy:
HTTP_ACCEPT_ENCODING gzip,deflate
HTTP_ACCEPT_ENCODING [empty string]

Normal then proxy:
HTTP_ACCEPT_LANGUAGE en-us,en;q=0.5
HTTP_ACCEPT_LANGUAGE [empty string]

Normal then proxy:
HTTP_CONNECTION keep-alive
HTTP_CONNECTION [empty string]

Normal then proxy:
HTTP_HOST www.pickrent.com
HTTP_HOST www.pickrent.com:80

Normal then proxy:
HTTP_REFERER [empty string]
HTTP_REFERER http://www.pickrent.com/misc

Looking at the differences couldn't I just look at the HTTP_HOST to see if it CONTAINS '80'?

@ray I have not tried that yet.

Comment 8 by DanaK posted on 4/22/2009 at 11:08 PM

Just an aside, it's relatively easy to manipulate the 'forwarded for' just as the 'remote_addr'. I'd imagine anyone trying to seriously cause trouble would have taken this into account when doing their thing. It'll still catch the casual ones though.

Comment 9 by Gary Funk posted on 4/22/2009 at 11:28 PM

I hit you through a proxy and the dump gave me the normal resluts.

Comment 10 by Dave Dugdale posted on 4/22/2009 at 11:32 PM

@gary that's a bummer.

I would have thought others with CF sites would have a similar issues as mine and there would be code out there in use to detect proxies.

Comment 11 by Gary Funk posted on 4/22/2009 at 11:46 PM

We all have that problem, but there is no sure-fire way to find a proxy as the software has gotten much better. And then there is the proxy through a proxy.

Comment 12 by Nate Beck posted on 4/23/2009 at 12:33 AM

Hmmm... MegaProxy is tricky.

https://free.megaproxy.com/...

Comment 13 by shag posted on 4/23/2009 at 9:57 AM

@dave, that's very interesting. It must be my bubble. It worked perfectly for me. When I was behind my company proxy, I was notified I was on a proxy. When I didn't connect through the proxy, it showed me not coming through a proxy.

Based on what you identified, I'm not sure where to go. Sorry that didn't work out so great for you.

Comment 14 by David posted on 4/23/2009 at 12:29 PM

If this is for a commercial application you could use the Geo Intelligence from a company like Quova - http://www.quova.com/.
They'll give you very accurate information on an IP; I'm not just talking about the usual country & roughly the nearest state/county data; real "this is where they are" and a confidence factor with the data too :)
They also tell you things like the speed of the link, type of link (adsl,cable,satellite,etc) and proxy type (public, private, aol, etc).
Could be very useful for you. Remember that not everyone behind a proxy is bad. Some people have to use a proxy from their company. I'd recommend you give a confidence factor to the order based on multiple attributes.

Something that has proven to be quite cheap & effective is if you're taking an order on your site then you're probably taking their address. You could use the free GeoLite city DB (http://www.maxmind.com/app/... and then do a distance between their IP location and their address. If it's more than say 50miles then minus 10 from the confidence factor. Simple, free & very effective. (NB The key is not to rely on any one attribute!)

Disclaimer: I do NOT work for either of those companies!

Comment 15 by Dave Dugdale posted on 4/23/2009 at 6:46 PM

@David Thanks for the advice, I actually just installed the Maxmind DB a few weeks ago. Here is my test page for that:
http://www.pickrent.com/mis...

It works really well.

Comment 16 by Mike posted on 8/26/2009 at 10:46 PM

Dave -- Did you ever find a solution for this? I'm facing the exact same issue. I've been considering using an ASAPI Rewrite list of known proxy IPs. I'm just wondering if you found a good way of protecting your site.

Comment 17 by Dave Dugdale posted on 8/27/2009 at 1:33 AM

No I never did, but I figured out some other ways to give the scammers a run for their money.

Comment 18 by Jarod Knoten posted on 11/24/2009 at 3:24 AM

I found a somewhat interesting solution to sniffing out proxy servers. Its not fool proof and will occasionally return false positives but so far it is the best automated method for checking proxies I have come up with. We have been having issues with order fraud coming from behind proxy servers on orders that would otherwise had never thrown red flags. So we started google searching IP's from larger orders to see if they appeared on any proxy listing websites. To automate this for us I do an HTTP post to google (or you can use the search engine of your choice) with the ip address of the order and parse the results for keywords indicating a proxy (proxy, socks, socks5, socks4, etc.) if it detects any of these keywords
it flags the order as a potentially placed behind a proxy.

So far this method has thrown up red flags a few times on orders we would have otherwise shipped. Its not elegant, but it works great!

Comment 19 by Dave Dugdale posted on 11/25/2009 at 8:15 PM

Jarod,

Interesting approach, I might have to try that with my next scammer. Thanks.

Comment 20 by RanI posted on 8/16/2011 at 11:40 PM

@ Hi Group, i would like to share my opinion also to this matter since i need a solution to that too.
So, first of all, code like bellow
<cfif cgi.HTTP_X_FORWARDED_FOR neq "">
<h4>You ARE coming from a proxy.</h4>
<CFELSE>
<h4>You are NOT coming from a proxy.</h4>
</cfif>

won't have any chance to see between cgi variables. i have testing for weeks with various platforms and different versions of ACF and Railo 3.2 final too. Strage thing is that on the same apache instance a simple code like from the first post or, even better one like:

srv#cat testp.php
<?php
if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) ||($_SERVER['HTTP_USER_AGENT']=='') || ($_SERVER['HTTP_VIA']!=''))
{
die("Don't use proxies, please.");
}
?>

will act like it is suppose to do. Note HTTP_VIA variable which apears only when it is a matter of anon or high-anon proxy server.

Second, Garrett Johnson gave us a clue maybe without knowing (no ofence please). So, have you ever has to install, running and administrate a proxy server before, even for a small lan ? I have few years ago and still remember something about "little" SQUID proxy server. Basicly for everyone who's goingo to use proxy server must
put proxy ip and port into browser connection dialog .. etc (sorry for this long explanation i am sure all of you have know already).
So, if you can not access header values for those variables via cgi dump what if we reverse the process ? We still have access to remote_addr and remote_host values. Now, i just want to test if remote_addr ip it is a proxy or not. How ? Trying to connect ot it. Remember that a proxy server must accept connection, right ? Well...

<!-- define function to check connection; it will return true or false if it could connect or not --->
<cfscript>
function checkMe(host,port) {
connection = createObject("java", "java.net.Socket");
connection.init(host, port);
connected = connection.isConnected();
return connected;
}
</cfscript>

<cftry>
<cfoutput>
<!--- define a variable whitch hold remote address and a variable for counting open ports --->
<cfset ip="#CGI.REMOTE_ADDR#" />
<cfparam name="q" default="0" />

<!--- checking remote ip connection within well-known range of ports --->
<cfif checkMe(#ip#,80)>
<cfset p1 = true />
<cfset q = 1 />
</cfif>

<cfif checkMe(#ip#,8080)>
<cfset p2 = true />
<cfset q =q+1 />
</cfif>

<cfif checkMe(#ip#,3128)>
<cfset p3 = true />
<cfset q =q+1 />
</cfif>
</cfoutput>

<!--- here leave empty in case this is not a proxy not to show anything --->
<cfcatch type="Any">

</cfcatch>
</cftry>

<!--- display and parsing the results
it is very basic in order to see on whitch port remote ip accept connection;
--->
<cfoutput>
<cfif IsDefined("p1")>
proxy on port 80: #p1#<br />
<cfelse>
no proxy on port 80<br />
</cfif>
<cfif IsDefined("p2")>
proxy on port 8080: #p2#<br />
<cfelse>
no proxy on port 8080<br />
</cfif>
<cfif IsDefined("p3")>
proxy on port 3128: #p3#<br />
<cfelse>
no proxy on port 3128<br />
</cfif>
</cfoutput>

<!--- then just logical detection --->
<cfif IsDefined("variables.q") AND variables.q NEQ 0>
<h2>No proxy allowed here, mate!</h2>
<cfelse>
<!--- normal processing page code as visitor it is not using a proxy --->
</cfif>

Obviously this method could be more elaborate incorporated into a CFC where to just pass only CGI.REMOTE_ADDR and wait for a boolen answer.

Hope this helps; If any has anotjer suggestion....

best regards from Romania