Creating a PHP ACL and even Rat out Users using Proxies!
Now before some snotty nosed individual points out that ACLs can be done via .htaccess, let me cover my arse by saying although it can be done that way, we're not doing it that way. More technically speaking, however, albeit Apache is the better way it's not the most practical way due to the manual updating of .htaccess files. Unless you wish to parse the file - which is not advised for security and speed reasons, then PHP is the way to be heading.
With the disclaimer out the way and placed nicely in a corner, let's move onto the world of ACLs. Imagine you have a nasty little imp on your website who's causing all sorts of mischief, wouldn't it be nice to not only ban him, but out-smart him when he thinks he'll use a proxy to bypass the ban? Let me state for the record though that we can never truly ban anyone. You see, a little networking background is unfortunately required. HTTP is a layer 3 protocol in the TCP suite, and frankly, it doesn't give a monkeys - this is more technically defined as a stateless protocol. However, HTTP is not alone, IP also plays its part. IP is a layer 3 protocol which is your fingerprint on the Internet. IP addresses can be spoofed, but using the TCP suite spoofing an IP is pointless because the returning SYN-ACK will be returned to the wrong location. Nonetheless, connections can be relayed via a middle-man server - a proxy server. A proxy server receives your query and then acts on your behalf for the connection to the end location - then finally returns the data to you. Fundamentally, the connection from the proxy to the server will use the proxy's IP address, not yours.
Technical stuff over. The conclusion we can draw from the above paragraph is that an IP address can be spoofed in the sense that a middle-man server is involved in the equation. Bugger!
Let's now jump into the code, bear in mind that I'm using a pre-defined array but this array can quite easily come from a database table.
$aIPs = array('22.214.171.124');
$szIPAddress = $_SERVER['REMOTE_ADDR'];
The above code will take your IP address and then subsequently check to see if it is in the array. If the script finds the IP in the array then it will return true. Once you have found the individual that has been previously banned, you can dispose with him how you wish. Place your brain in overdrive and be imaginative! It can be a lot of fun.
The code is all good and well, it will find the user, and as the REMOTE_ADDR cannot be spoofed as it is taken directly from the packet's header, you may use it to your heart's content and have no concerns what-so-ever. Sadly, the little imp could well be 1 of these plainly average computer geeks who knows 1 or 2 things about the Internet. He (or she, I don't mean to be sexist) connects to an open proxy and tries to access your website again. As the IP address is now the IP address of the proxy and not the imp's IP, your cleverly constructed ACL now stands in undiluted embarrassment at carelessly allowing naughty little devils back in!
Let me introduce you to a HTTP header attribute that is optional! Remember that as we can never rely on it completely (although I know of a popular forum package that used to rely on it completely). X_FORWARDED_FOR is that very attribute. When an end-user connects to the proxy server and accesses a web-page, all being well, the proxy server should attach the header attribute with the end-user's IP address. If for instance my IP address is 126.96.36.199 then my HTTP header may look something like the following:
GET /index.php HTTP/1.1
Our script can now check for the existence of this header attribute and use it accordingly to see if our elusive-but-soon-to-be-gutted user's IP address matches.
Note: I've tried the 2 most common web-based proxies to see if they set the X_FORWARDED_FOR attribute. Unsurprisingly they do not but who's going to use a web-based proxy for any great length of time? Let me also take this moment to share a piece of mind on how terribly coded I think CGIProxy and PHProxy are.
$aIPs = array('188.8.131.52');
We are now putting the X_FORWARDED_FOR header attribute to some good use. We are first checking for its existence as it is optional, and is highly unlikely to be set on normal traffic, if it is found then we use it, if not then we use the user's reported IP address nicely extracted from the TCP packet for us by the grand old PHP. This method is rather clever as the user will think we've simply set a cookie which is preventing them from accessing the web-page - but we haven't, instead what we have done is got one over on him by using a fairly unknown HTTP attribute.
Note: We're using regular expressions to check if the IP address in the X_FORWARDED_FOR attribute is a valid IP address consisting of 4 octets. As it's header attribute it could be absolutely anything so this ensures that we do not let any junk through.
To take this even further, and to be somewhat of a cliché, we could also set a cookie. Albeit I'm not going to show you how to do this for 2 reasons:
It's 1 of the first things a semi-technical user would think of;
Everyone knows how to use setcookie() in PHP!
OK, OK! Just for those who are sat there shaking their heads profusely right now, let me show you how to set a cookie in this instance:
// This will set a cookie that expires in 30 days from today.
setcookie('TalkPHPACL', $_SERVER['REMOTE_ADDR'], time() + 2592000, '/');
Once you have set the cookie you can check upon every page reload for its availability. All cookies are automatically retrieved and parsed by PHP into a beautiful array for you. In our case, if the cookie is set, it may be accessed like so:
For those of you unaware of what does var_dump(), all it does it merely echoes out the value with the string length. Nice and handy and will even echo out integers that are all on their lonesome. A simple echo will not print out integers on their own without appending some text first.
Thankfully now we have got rid of our irritating gremlin who has been creating incessant problems on your website. Gone for good! Well, not quite, admittedly, but it's as good it's going to get unless you don't mind sacrificing a little speed to check for open proxies before any of the website is displayed. IRC servers tend to check for open proxies but this is only due to the fact that most people expect IRC connections to take a good 10 seconds. Most people expect and even demand with attitude that web-sites load in the blink of an eye.
The man who comes back through the Door in the Wall will never be quite the same as the man who went out.