php - Show captcha when unexpected navigation detected to prevent traffic abuse


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

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;




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 samePublic IP Address
  • There are so manyFirefox 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 of10MB that would take1min to download , If you limit users to say100 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 useSimpleFlood which extendmemcache to limit users per second. It usescookies to resolve the shared connection issue and attempts to get the real IP address

$flood = new SimpleFlood();
$flood->addserver(""); // add memcache server
$flood->setLimit(2); // expect 1 request every 2 sec
try {
} 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 thatSimpleFlood::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() {
        $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) {
                setcookie("xf", null, time() - 3600);
                throw new Exception("Last IP did not match");

            if ($cookie->hash != sha1($cookie->key . $this->salt)) {
                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(
        ) 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;


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 forTor 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.

People are also looking for solutions to the problem: mysql - When user hits the submit button, index.php does not catch the action and update database


Didn't find the answer?

Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.

Ask a Question

Write quick answer

Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.

Similar questions

Find the answer in similar questions on our website.