Reputation: 3
I have an htaccess file with a lot of 'deny from ' entries. Most of these with common lower octets.
To try and improve htaccess processing time, for IPs that are not banned, I am trying to implement a php filter, which is called from a rewrite rule.
My htaccess rewrite rule:
RewriteCond %{REMOTE_ADDR} ^37\.115\. [OR]
RewriteCond %{REMOTE_ADDR} ^46\.118\.
RewriteRule (.*) check_banned.php?ip=%{REMOTE_ADDR}
... More rewrite rules
Note, there is no '[L]' at the end of the rewrite rule. If the IP is not banned, I would like htaccess to continue processing rules. The current php file:
$ip = strip_tags(trim($_GET['ip']));
// Read file with list of banned ips
$file = ban_list.txt;
$myfile = fopen($file, "r") or die("Unable to open file!");
$ban_list = fread($myfile, filesize($file));
fclose($myfile);
// Check if ip is in ban list
$pos = strpos($ban_list, $ip);
if($pos === false) $ip_found = false;
else $ip_found = true;
if($ip_found){
header('HTTP/1.0 403 Forbidden');
}
else {
// What to do if ip is not banned?
}
This is working fine if the ip is found in the ban list. However, I cannot find a way to handle the case when the ip is not banned. I just get a blank page, with header '200 ok'.
Also, I was thinking of passing (.*) to the file check_banned.php, convert the php code to a function, which would return the string in case the ip is not banned. Have to admit I have not tried this yet.
RewriteRule (.*) check_banned.php?ip=%{REMOTE_ADDR}&str=$1
In php file:
$string = strip_tags(trim($_GET['str']));
if($ip_found){
header('HTTP/1.0 403 Forbidden');
}
else {
return $string;
}
Update: Thanks to Mike Nickaloff, here are some tweaks. Unfortunately I have a shared hosting account, with no access to server settings.
I set the file permissions for the php filter script to 600, and the filtering still appears to work. Will this take care of the vulnerabilities?
If the ip is not banned, the php script reads the requested page and echoes it. It would be nice if it didn't have to do that, and only send a message back to .htaccess indicating it should continue processing rules.
Changes to check_banned.php:
<?php
ob_start(); // suppress any output
$ip = strip_tags(trim($_SERVER["REMOTE_ADDR"]));
// Read file with list of banned ips
$file = ban_list.txt;
$myfile = fopen($file, "r") or die("Unable to open file!");
$ban_list = fread($myfile, filesize($file));
fclose($myfile);
// Check if ip is in ban list
$pos = strpos($ban_list, $ip);
if($pos === false) $ip_found = false;
else $ip_found = true;
if($ip_found){
ob_end_clean(); // Allow output
header('HTTP/1.0 403 Forbidden');
}
else{
// ip Was not found in ban list
$file = "";
$uri = "";
if(isset($_SERVER["REQUEST_URI"])){
$uri = strip_tags(trim($_SERVER["REQUEST_URI"]));
}
if($uri == "" || $uri == "/")
$file = $_SERVER['DOCUMENT_ROOT']."/index.php";
else $file = $_SERVER['DOCUMENT_ROOT'].$uri;
$myfile = fopen($file, "r") or die("Unable to open file!");
$file = fread($myfile, filesize($file));
fclose($myfile);
ob_end_clean(); // Allow output
echo $file;
}
?>
Upvotes: 0
Views: 838
Reputation: 676
While you have a Fair amount of code written (and have probably moved on), I think you were on the right track.
I use http://www.ip-approval.com which offers a way to allow and or block IP's and also has an option to redirect those visitor to a different page or site.
See: http://www.ip-approval.com/instructions
~
In the .htaccess file, I have added:
php_value auto_prepend_file /check_banned.php
...which will add the code to every page of the site. If you have folders/pages.html you can either add the check_banned.php file to the folder, or add:
php_value auto_prepend_file none
...to the .htaccess file in the folder and it will not be added to that folder.
So, if you wanted php_value auto_prepend_file would work
OR
As others have noted you can just add the following to each page:
<?php
include_once("check_banned.php");
?>
Upvotes: 0
Reputation: 2168
Here's one way you could handle this:
have PHP load whatever page is requested by loading the proper URL
This will only work when requesting PHP scripts from the server, so the htaccess rules have to be slightly changed to match only .php files. Match against .* is a bad idea anyways, but this method isn't exactly the safest way to do something like this. You may want to invest in free firewall like UFW, which allows you to block IP addresses from the entire server, rather than on a per-request basis.
Using a firewall is a much more efficient and secure way to block an IP address.
The method where PHP acts as the firewall is vulnerable to DDoS attacks and Recursion attacks. For example -- what happens if someone requests the page check_ip.php? it has to redirect to somewhere -- so it would redirect to check_ip.php, which would redirect to check_ip.php, and it would redirect to check_ip.php -- so to make a long story short,
DO NOT USE THIS METHOD -- IT IS FOR DEMONSTRATIVE PURPOSES AND IS NOT A REPLACEMENT FOR A FIREWALL.
In theory, if we did use this unsafe method, here's how we would do it:
in .htaccess change it to
RewriteRule ^([\s\S]+)(.php|.phps)$ /check_banned.php [L]
... More rewrite rules
Then in check_banned.php you could do something like this:
<?php
$ip = strip_tags(trim($_SERVER["REMOTE_ADDR"]));
// Read file with list of banned ips
$file = ban_list.txt;
$myfile = fopen($file, "r") or die("Unable to open file!");
$ban_list = fread($myfile, filesize($file));
fclose($myfile);
// Check if ip is in ban list
$pos = strpos($ban_list, $ip);
if($pos === false) $ip_found = false;
else $ip_found = true;
if($ip_found){
header('HTTP/1.0 403 Forbidden');
}
else {
// What to do if ip is not banned?
$sPage = $_SERVER["PATH_TRANSLATED"];
if (file_exists($sPage)) {
include_once($sPage);
}
}
?>
WARNING -- REMEMBER -- DO NOT USE THIS METHOD AS IT IS VULNERABLE TO ATTACKS --
Edit: In response to the edit on the OP, here's one secure way to do IP filtering without a firewall:
In the file check_banned.php
<?php
$ip = strip_tags(trim($_SERVER["REMOTE_ADDR"]));
// Read file with list of banned ips
$file = ban_list.txt;
$myfile = fopen($file, "r") or die("Unable to open file!");
$ban_list = fread($myfile, filesize($file));
fclose($myfile);
// Check if ip is in ban list
$pos = strpos($ban_list, $ip);
if($pos === false) $ip_found = false;
else $ip_found = true;
if($ip_found){
header('HTTP/1.0 403 Forbidden');
// here is the important change
die("script terminated because IP is in ban list");
}
else {
// if IP not banned, do nothing.
}
?>
Then, at the top of every PHP script you wish to protect, you could simply do this at the very top:
<?php
include_once("check_banned.php");
?>
This will basically cause the script to terminate using the "die" command if the IP matches the ban list and not process anything further, then if their IP is not in the ban list, it will continue to load the page.
I've seen sites use this technique -- wordpress sites are a great example of a framework that uses a similar tactic to perform per-request pre-processing.
I would stay away from the mod_rewrite rules unless they're really necessary as they can open up a whole host of vulnerabilities if you're not careful.
Upvotes: 1