سؤال

I noticed that some user overloading my website by downloading multiple files (for example 500 files at same time) and opening more pages in small duration, I want to show captcha if unexpected navigation detected by user.

I know how to implement Captcha, but I can't figure out what is the best approach to detect traffic abuse using (PHP)?

هل كانت مفيدة؟

المحلول

A common approach is to use something like memcached to store the requests on a minute basis, I have open sourced a small class that achieves this: php-ratelimiter

If you are interested in a more thorough explanation of why the requests need to be stored on a minute basis, check this post.

So to sum it up, your code could end up looking like this:

if (!verifyCaptcha()) {
    $rateLimiter = new RateLimiter(new Memcache(), $_SERVER["REMOTE_ADDR"]);
    try {
        $rateLimiter->limitRequestsInMinutes(100, 5);
    } catch (RateExceededException $e) {
        displayCaptcha();
        exit;
    }
}

Actually, the code is based on a per-minute basis but you can quite easily adapt this to be on a per 30 seconds basis:

private function getKeys($halfminutes) {
    $keys = array();
    $now = time();
    for ($time = $now - $halfminutes * 30; $time <= $now; $time += 30) {
        $keys[] = $this->prefix . date("dHis", $time);
    }
    return $keys;
}

نصائح أخرى

Introduction

A similar question has be answered before Prevent PHP script from being flooded but it might not be sufficient reasons :

  • It uses $_SERVER["REMOTE_ADDR"] and they are some shared connection have the same Public IP Address
  • There are so many Firefox addon that can allows users to use multiple proxy for each request

Multiple Request != Multiple Download

Preventing multiple request is totally different from Multiple Download why ?

Lest Imagine a file of 10MB that would take 1min to download , If you limit users to say 100 request per min what it means you are given access to the user to download

10MB * 100 per min

To fix this issue you can look at Download - max connections per user?.

Multiple Request

Back to page access you can use SimpleFlood which extend memcache to limit users per second. It uses cookies to resolve the shared connection issue and attempts to get the real IP address

$flood = new SimpleFlood();
$flood->addserver("127.0.0.1"); // add memcache server
$flood->setLimit(2); // expect 1 request every 2 sec
try {
    $flood->check();
} catch ( Exception $e ) {
    sleep(2); // Feel like wasting time 
    // Display Captcher
    // Write Message to Log
    printf("%s => %s %s", date("Y-m-d g:i:s"), $e->getMessage(), $e->getFile());
}

Please note that SimpleFlood::setLimit(float $float); accepts floats so you can have

$flood->setLimit(0.1); // expect 1 request every 0.1 sec

Class Used

class SimpleFlood extends \Memcache {
    private $ip;
    private $key;
    private $prenalty = 0;
    private $limit = 100;
    private $mins = 1;
    private $salt = "I like TO dance A #### Lot";

    function check() {
        $this->parseValues();
        $runtime = floatval($this->get($this->key));
        $diff = microtime(true) - $runtime;
        if ($diff < $this->limit) {
            throw new Exception("Limit Exceeded By :  $this->ip");
        }
        $this->set($this->key, microtime(true));
    }

    public function setLimit($limit) {
        $this->limit = $limit;
    }

    private function parseValues() {
        $this->ip = $this->getIPAddress();
        if (! $this->ip) {
            throw new Exception("Where the hell is the ip address");
        }

        if (isset($_COOKIE["xf"])) {
            $cookie = json_decode($_COOKIE["xf"]);
            if ($this->ip != $cookie->ip) {
                unset($_COOKIE["xf"]);
                setcookie("xf", null, time() - 3600);
                throw new Exception("Last IP did not match");
            }

            if ($cookie->hash != sha1($cookie->key . $this->salt)) {
                unset($_COOKIE["xf"]);
                setcookie("xf", null, time() - 3600);
                throw new Exception("Nice Faking cookie");
            }
            if (strpos($cookie->key, "floodIP") === 0) {
                $cookie->key = "floodRand" . bin2hex(mcrypt_create_iv(50, MCRYPT_DEV_URANDOM));
            }
            $this->key = $cookie->key;
        } else {
            $this->key = "floodIP" . sha1($this->ip);
            $cookie = (object) array(
                    "key" => $this->key,
                    "ip" => $this->ip
            );
        }
        $cookie->hash = sha1($this->key . $this->salt);
        $cookie = json_encode($cookie);
        setcookie("xf", $cookie, time() + 3600); // expire in 1hr
    }

    private function getIPAddress() {
        foreach ( array(
                'HTTP_CLIENT_IP',
                'HTTP_X_FORWARDED_FOR',
                'HTTP_X_FORWARDED',
                'HTTP_X_CLUSTER_CLIENT_IP',
                'HTTP_FORWARDED_FOR',
                'HTTP_FORWARDED',
                'REMOTE_ADDR'
        ) as $key ) {
            if (array_key_exists($key, $_SERVER) === true) {
                foreach ( explode(',', $_SERVER[$key]) as $ip ) {
                    if (filter_var($ip, FILTER_VALIDATE_IP) !== false) {
                        return $ip;
                    }
                }
            }
        }

        return false;
    }
}

Conclusion

This is a basic prove of concept and additional layers can be added to it such as

  • Set different limit for differences URLS
  • Add support for penalties where you block user for certain number of Mins or hours
  • Detection and Different Limit for Tor connections
  • etc

I think you can use sessions in this case. Initialize a session to store a timestamp[use microtime for better results] and then get timestamp of the new page.The difference can be used to analyzed the frequency of pages being visited and captcha can be shown.

You can also run a counter on pages being visited and use a 2d array to store the page and timestamp.If the value of pages being visited increases suddenly then you can check for timestamp difference.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top