Blocking IPs w/o Apache Directives
April 15th, 2009 at 11:28 am (Fruit Bats)
It’s considered gauche, not to mention, fruitless, to block ips, but still there are times when brute force is required just to introduce some level of serenity on a site exposed to attack by web server spammers. The provision of subsidiary Apache directives for this purpose usually are not available at the document root level of the web server, so as to avoid unnecessary hits on server performance. In these instances, one has to make do without the handy .htaccess file full of directives to do all sorts of nifty things, among them, blocking ips with the ‘deny from’ command.
An alternative involves using a php script with methods for accomplishing the same objective placed at the head of a file. The targeted objective in this case is not the individual ip address, but ranges encompassing whole countries — as best as can be determined from resources on the web. The resource of interest here is called Country IP Ranges Generator. A target country from the list provided is selected. Next, ‘formatting by input’ is selected. The format to use: {startip}/{netmask}. A complete list is spat out when the “generate” button is clicked. Each line in the list represents a range of possible networks in the country selected. Here is the most current list for the whole of Afghanistan:
#Afghanistan
58.147.128.0/255.255.224.0
110.34.40.0/255.255.248.0
117.55.192.0/255.255.240.0
117.104.224.0/255.255.248.0
119.59.80.0/255.255.248.0
121.100.48.0/255.255.248.0
121.127.32.0/255.255.224.0
125.213.192.0/255.255.224.0
202.56.176.0/255.255.240.0
202.86.16.0/255.255.240.0
203.174.27.0/255.255.255.0
203.215.32.0/255.255.240.0
210.80.0.0/255.255.224.0
210.80.32.0/255.255.224.0
For larger areas, such as China or the Russia Federation, these lists can be quite long, but still quite manageable. The ip ranges can be used selectively or wholesale depending on one’s policy. If you’re targeting a language group for inclusion in your web service, such as a forum or a weblog, you need to avoid wholesale blocking of countries who might include potential participants of the friendly sort. The thing is, the foes tend to wage their exploits from far-flung areas outside North America, if they can get away with it.
In any event, the script cobbled together from various bits found on the web — a practice pooh-poohed by php disciplinarians, btw — does several things. It retrieves the host ip as best it can, knowing full well there are ways of eluding accurate detection by these means. At the same time, it also attempts to reject proxy hosts — a part of the brute force policy. When an ip is retrieved, it is compared with a flat-file list of ip ranges of the type shown above using the netmask, converting the latter to cidr. If a match is found, the script directs the host to google.com. All of this is done before any other program in the script file is executed. The script was tested with phpBB3 3.04, inserted into three files in the root directory: index.php, posting.php, and ucp.php. The following shows where it was placed in each of these files:
<?php
/**
*
* @package phpBB3
* @version $Id: index.php 8479 2008-03-29 00:22:48Z naderman $
* @copyright (c) 2005 phpBB Group
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
/**
* @ignore
*/
define('IN_PHPBB', true);
#--script goes here--#
$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './';
$phpEx = substr(strrchr(__FILE__, '.'), 1);
include($phpbb_root_path . 'common.' . $phpEx);
include($phpbb_root_path . 'includes/functions_display.' . $phpEx);
The function to detect the “real ip” has many variations, but the one settled on looks like this:
$user_ip = "";
if ((isset($_SERVER['HTTP_X_FORWARDED_FOR'])) && (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))) {
$user_ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif ((isset($_SERVER['HTTP_CLIENT_IP'])) && (!empty($_SERVER['HTTP_CLIENT_IP']))) {
$user_ip = explode(".",$_SERVER['HTTP_CLIENT_IP']);
$user_ip = $user_ip[3].".".$user_ip[2].".".$user_ip[1].".".$user_ip[0];
} elseif ((!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) && (empty($_SERVER['HTTP_X_FORWARDED_FOR'])) &&
(!isset($_SERVER['HTTP_CLIENT_IP'])) && (empty($_SERVER['HTTP_CLIENT_IP']))) {
$user_ip = $_SERVER['REMOTE_ADDR'];
} else {
$user_ip = "0.0.0.0";
}
The flat-file includes 0.0.0.0/255.255.255.0 on a line by itself in the event 0.0.0.0 is assigned to the variable.
The function that intends to block proxy access is derived from Apache mod_rewrite directives published at perishablepress.com. The article there also shows how to add a whitelist of proxy servers that can be admitted past the proxy wall. The script does not provision for this option as of yet:
function checkProxHdrs() {
if(isset($_SERVER['VIA']) && !empty($_SERVER['VIA'])) {
return true;
} else if(isset($_SERVER['FORWARDED']) && !empty($_SERVER['FORWARDED'])) {
return true;
} else if(isset($_SERVER['USERAGENT_VIA']) && !empty($_SERVER['USERAGENT_VIA'])) {
return true;
} else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return true;
} else if(isset($_SERVER['PROXY_CONNECTION']) && !empty($_SERVER['PROXY_CONNECTION'])) {
return true;
} else if(isset($_SERVER['XPROXY_CONNECTION']) && !empty($_SERVER['XPROXY_CONNECTION'])) {
return true;
} else if(isset($_SERVER['HTTP_PC_REMOTE_ADDR']) && !empty($_SERVER['HTTP_PC_REMOTE_ADDR'])) {
return true;
} else if(isset($_SERVER['HTTP_CLIENT_IP']) && !empty($_SERVER['HTTP_CLIENT_IP'])) {
return true;
}
else {
return false;
}
}
Finally a filter is applied that identifies friend or foe as best it can. This portion is an adaptation of a combination of coder contributions over at php.net. The key functions are myip2long and slash_notation. The latter helps convert the ip/netmask to ip/cidr which myip2long then converts to numbers that can be compared, meaningfully.
function slash_notation($ip, $mask) {
return $ip."/".strlen(preg_replace("/0/", "", decbin(ip2long($mask))));
}
########### ncritten's function myip2long
function myip2long($ip) {
if (is_numeric($ip)) {
return sprintf("%u", floatval($ip));
} else {
return sprintf("%u", floatval(ip2long($ip)));
}
}
They are called within the filter function itself.
########### function to check ip if it's in one of denied/allowed networks =)
function ipfilter($ip) {
$match = 0;
### converting ip address to binary
$ip_addr = addLeadingZero(decbin(myip2long($ip)));
### the file which contains allowed/denied networks
if (fopen("iplist.txt", "r")) {
$source = file("iplist.txt");
foreach ($source as $line) {
if(preg_match("/^#/", $line)) continue;
### exploding each network to obtain network address and cidr
$ipnetmask = explode("/", $line);
$result = slash_notation($ipnetmask[0],$ipnetmask[1]);
$network = explode("/", $result);
$net_addr = addLeadingZero(decbin(myip2long($network[0])));
$cidr = $network[1];
### and finally checking for a match between network bits and ip bits within the range defined by the cidr
if (substr($net_addr, 0, $cidr) == substr($ip_addr, 0, $cidr)) {
$match = 1;
break;
}
}
}
return $match;
}
### this function will return 1 if there's a match and 0 if no match
Another function of great importance is the one that adds 1 or more leading zeroes to the binary representation of the two addresses compared within the filter function. It turns out the php function, decbin(), drops leading zeros in the first quad with values in the 0-127 range on conversion to binary. As a consequence, whole blocks of addresses are missed as bits shift to the left with each leading zero dropped in said quad. The function appends the missing zero values to those binary representations that are less than 32 bytes long.
function addLeadingZero($ip) {
if (($result = (32 - strlen($ip))) > 0)
return str_repeat("0", $result).$ip;
}
The script allows for comment lines that begin with the hash mark (#) in the flat-file accessed by it (in this case a file called iplist.txt). This enables you to identify the country represented by ip ranges listed, as shown in the Afghanistan example above. The script is not prepared to deal with empty lines in the flat-file, however.
Finally the call to ipfilter:
if (ipfilter($user_ip) == 1 || checkProxHdrs()) {
header("Location:http://www.google.com/");
}
It seems to slow things down as far as incoming goes and perhaps forces more risky behavior on the part of the baddies.

