检测到意外导航时显示验证码以防止滥用流量

时间:2013-04-16 11:49:02

标签: php jquery captcha web-traffic abuse

我注意到有些用户通过下载多个文件(例如同时500个文件)来重载我的网站并在较短的时间内打开更多页面,如果用户检测到意外导航,我想显示验证码

我知道如何实施 Captcha ,但我无法弄清楚使用(PHP)检测流量滥用的最佳方法是什么?

3 个答案:

答案 0 :(得分:5)

一种常见的方法是使用像memcached这样的东西来存储请求,我已经开源了一个实现这个目标的小类:php-ratelimiter

如果您有兴趣更详细地解释为什么需要在一分钟内存储请求,check this post

总而言之,您的代码可能最终看起来像这样:

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

实际上,代码是基于每分钟的,但您可以很容易地将其调整为每30秒一次:

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;
}

答案 1 :(得分:2)

<强>简介

Prevent PHP script from being flooded之前已经回答了类似的问题,但这可能不是充分的理由:

  • 它使用$_SERVER["REMOTE_ADDR"],并且它们是某些共享连接具有相同的Public IP Address
  • 有很多Firefox addon可以允许用户为每个请求使用多个代理

多个请求!=多个下载

防止多个请求与多个下载完全不同?

要想象一个10MB的文件需要1min下载,如果你限制用户说100 request per min这意味着你有权访问用户下载

10MB * 100 per min

要解决此问题,您可以查看Download - max connections per user?

多个请求

返回页面访问权限,您可以使用SimpleFlood扩展memcache来限制每秒用户数。它使用cookies来解决共享连接问题并尝试获取真实IP地址

$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());
}

请注意,SimpleFlood::setLimit(float $float);接受浮动,因此您可以

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

使用的课程

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;
    }
}

结论

这是概念的基本证明,可以添加其他图层,例如

  • 为差异设置不同的限制URLS
  • 在阻止用户使用特定分钟数或小时数的情况下添加对处罚的支持
  • Tor连接的检测和不同限制

答案 2 :(得分:1)

我认为你可以在这种情况下使用会话。 初始化会话以存储时间戳[使用microtime获得更好的结果]然后获取新页面的时间戳。可以使用差异来分析被访问页面的频率并显示验证码。

您还可以在被访问的页面上运行计数器,并使用2d数组来存储页面和时间戳。如果正在访问的页面的值突然增加,那么您可以检查时间戳差异。