2

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

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

4

3 回答 3

5

一种常见的方法是使用 memcached 之类的东西来按分钟存储请求,我已经开源了一个实现此目的的小类:php-ratelimiter

如果您有兴趣更全面地解释为什么需要按分钟存储请求,请查看这篇文章

总而言之,您的代码最终可能如下所示:

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;
}
于 2013-04-19T08:49:10.420 回答
2

介绍

在防止 PHP 脚本被淹没之前已经回答了一个类似的问题,但这可能还不够充分:

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

多个请求!= 多个下载

防止多次请求与多次下载完全不同,为什么?

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

10MB * 100 per min

要解决此问题,您可以查看下载 - 每个用户的最大连接数?.

多个请求

您可以使用返回页面访问SimpleFloodmemcache限制每秒用户数。它用于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连接检测和不同限制
  • ETC
于 2013-04-19T12:49:09.383 回答
1

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

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

于 2013-04-21T16:56:41.773 回答