PHP, .htaccess, DDoS & speedy request protection

2019-06-24 02:21发布

问题:

I have a question, i built this little script to check if a certain ip is flooding my website. When it does, i deny the ip in the .htaccess file. My question is, can somebody tell me if this script is completely useless or worth trying... The script is called in the config file therefore it runs on every pageload.

<?php
#get the visitor ip
$ip = $_SERVER["REMOTE_ADDR"];

#start the session
@session_start();

#check if the ip is banned
if( $_SESSION['~b'] ){

#check if we can open htaccess
$fp = @fopen('./.htaccess','a'); 
    if($fp){
        #add the ip to htaccess
        @fwrite($fp,"\r\ndeny from $ip"); 
        #close
        @fclose($fp);
        #destroy the session
        @session_destroy();
        @mail("my-email","IP Banned","Ip: $ip");
    }
    #let the user know why we deny him or her access
    die('To many requests.');
    }
#get the filename and location
$f = './log/'.@ip2long($ip);

#check if the file exists
if ( @is_file($f) ) {
        #get the last filetime
        $a = @filemtime($f);
        #touch the file, give a new filetime
        @touch($f,time());
        #the ip is not banned
        $_SESSION['~b']  = false;
        #add the time diff
        $_SESSION['~r'] += @time()-$a;
        #add the latest hit
        $_SESSION['~h'] += 1;
    }else{
        #create the file if it doesn't exist
        @file_put_contents($f,''); #size: 0kb
        #if touch() doesn't work
        #chmod($ipfile,0755); 
    }

#calculate the diff after 10 hits, and ban when the avg is smaller than 0.25 seconds
if( $_SESSION['~h'] > 10 && ($_SESSION['~r']/$_SESSION['~h']) < 0.25 ) $_SESSION['~b'] = true;
?>

Just followed the advice to avoid SESSIONS, so i made it file based, without having to be depending on cookies and session:

<?php
# get the visitor ip
$i = $_SERVER["REMOTE_ADDR"];
# get the filename and location
$f = './log/'.ip2long($i).'.dat';
# check if the file exists and we can write
if ( is_file($f) ) {
    # get the last filetime
    $a = filemtime($f);
    # get the file content
    $b = file_get_contents($f);
    # create array from hits & seconds
    $d = explode(':',$b);
    # calculate the new result
    $h = (int)$d[0] + 1;
    $s = (int)$d[1] + (time()-$a);  
    # add the new data tot text file
    file_put_contents($f,"$h:$s",LOCK_EX);
    unset($d);
}else{
    # create the file if it doesn't exist hits:seconds
    file_put_contents($f,"1:1",LOCK_EX); #size: 3kb
    # to make sure we can write
    # chmod($f,0755); 
    # set the hits to zero
    $h = 0;
}
# create a result var
$r = $h > 10 ? (float)$s/$h : (float)1;
# calculate the diff after 10 hits, and ban when the avg is smaller than 0.20 seconds (5 hits per second)
if( $r < 0.20 ) {
    # check if we can open htaccess
    $fp = @fopen('./.htaccess','a'); 
    if($fp){
        # add the ip to htaccess
        @fwrite($fp,"\r\ndeny from $i"); 
        # close
        @fclose($fp);
        # mail the admin
        @mail("email","IP Banned","Ip: $i with $r sbh (Seconds Between Hits)");
    }
    # let the user know why we deny him or her access
    die('To many requests.');
    # remove the file
    unlink($f);
}
# if the user leaves, reset
if( $r > 30 ) {
    unlink($f);
}
echo 'Result: '.$r.'sbh (Seconds Between Hits)';
?>

回答1:

If you want to stop the casual user from sending too many requests in a certain amount of time, then yes, the script could work. Bring up a catpcha screen and you're in business.

BUT

The real answer is no.

The primary mistake with this code is depending on a session to determine the frequency of the user's activity. A "good" attacker can flood your server with requests with cookies disabled, as well as spoof his/her IP.

One way to stop attacks is to go to the server level, and install iptables. In fact, iptables ships with most linux distros. It needs little configuration and works well out of the box.

Another way, if you have root access to your server, is to move session handling to Memcached. It has a function called flood control that is pretty BOSS.

Another route to prevent DDOS are from third party services such as blockdos http://www.blockdos.net/

Kinda pricey, but it could work for you.

But PHP by itself cannot be configured to handle DDOS attacks. You need to put some kind of appliance or firewall in front of all requests to be vetted before going to your PHP scripts.



回答2:

My two cents. It is a good idea however you might end up stopping legitimate users from accessing your website. It will also be a problem with bots such as the Google bot. As such I would be careful. Perhaps also check the user agent and if it comes up as Google then allow it regardless of other conditions. Of course this then opens up a new hole for malicious users. Normally things like this are done on the web server rather than with PHP code. Another approach is the token system. That said, I think with some further refinement you could be onto something with your approach.

Also have you seen .htaccess or PHP protection code against multiple speedy requests question? It suggests that what you are designing has been tried before. This tells you are probably heading in the right direction.



回答3:

Mitigating DDoS by setting request-rate based IP blocking rules will always end badly, because:

  1. such rules will affect legitimate users (false positives)
  2. for IP spoofing, it is very common that this will not work against most, if not all attackers.

This is also true for IPTables. You can learn more about it here: http://www.incapsula.com/ddos/ddos-protection-service#iptables-ddos-protection



回答4:

The solution is quite helpful. Meanwhile, regarding the question asked by @lopata you may rather comment out the unlink() functions in the script and set-up a cron-job that will be unblocking the IPs from the htaccess and removing the files after sometime.

<?php

function unban_ip($ip){
    $filename='.htaccess';
    $line="deny from $ip";
    file_put_contents($filename, str_replace("\r\n".$line, "", file_get_contents($filename)));
    $f = './log/'.@ip2long($ip);
    if(@is_file($f))unlink($f);
    return true;
}

$time=time();   
$path='./log';
$handle=@opendir($path);
$x=0;

while(($file=@readdir($handle))!==false){
    if($file=='.'||$file=='..')continue;
    $filepath="$path/$file";

    if(is_file($filepath)){
        $ftime=@filemtime($filepath);
        if($time-$ftime>600){  //unban after 10 minutes
            $ip=long2ip($file);
            unban_ip($ip);
            $x++;
        }
    }
}

echo "$x IPs Unbanned";