2

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)?

Zoe
  • 27,060
  • 21
  • 118
  • 148

3 Answers3

5

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;
}
akirk
  • 6,757
  • 2
  • 34
  • 57
2

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
Community
  • 1
  • 1
Baba
  • 94,024
  • 28
  • 166
  • 217
1

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.

Sudo Reboot
  • 220
  • 2
  • 11
  • I also tried to do it using session. after implementing it I faced some overhead during calculating number of visits and updating it for each new visit... –  Apr 21 '13 at 18:16
  • Well there won't be many traffic abusers i guess. You can create a database table for it and after some IPs seem secure by above calculations add them to the list.You may also add bad IPs to database with some distinguishing key.So if once you recognize some IP address as bad run calculations seldomly or instead show captcha after every x number of page visits per ip – Sudo Reboot Apr 22 '13 at 07:22
  • But, sometimes, good user wants to download list of files, but not always, that's why I need to show captcha only when unexpected behavior detected –  Apr 22 '13 at 09:07
  • A good user wont download too quickly and if it does you show the captcha. Increase the duration of captcha after each captcha verification if you like – Sudo Reboot Apr 23 '13 at 15:54