【问题标题】:Quick and easy flood protection?快速简单的防洪?
【发布时间】:2011-03-02 21:11:12
【问题描述】:

我有一个网站,用户使用 AJAX 将消息提交到名为 like.php 的文件中。在此文件中,用户消息被提交到数据库,然后将链接发送回用户。在我的 Javascript 代码中,我禁用了用户在提交 AJAX 请求时输入的文本框。

唯一的问题是,恶意用户可以不断地向like.php 发送 POST 请求并淹没我的数据库。所以我想实现简单的防洪。

我真的不想要另一个数据库表记录用户 IP 等的麻烦......好像他们正在淹没我的网站,会有很多数据库读/写会减慢它的速度。我考虑过使用会话,比如有一个包含时间戳的会话,每次向like.php 发送数据时都会检查该时间戳,如果当前时间早于时间戳,则让他们将数据添加到数据库中,否则会发出错误并阻止他们。如果允许他们向数据库中输入内容,请使用新的时间戳更新他们的会话。

你怎么看?这是最好的方法还是有更简单的替代方法?

感谢您的帮助。 :)

【问题讨论】:

  • 垃圾邮件机器人无法识别会话 cookie,因此可能根本不使用会话。它还可能每次都恶意更改会话 ID,使您无法跟踪它。您可能无法绕过某种 IP 日志记录。
  • 强制用户登录后提交邮件? (只接受来自已登录帐户的like.php's)然后阻止用户在规定的时间内发送太多?
  • 感谢您的建议。正如我在回答评论中所说,我真的不喜欢用户系统,因为该网站的主要观点是它应该是快速且无忧的。我将不得不查看 IP 地址记录 :)
  • 这是 memcached 的完美之选。快速、轻便,如果您丢失了这些数据,也没什么大不了的。在此处搜索“速率限制”:carsonified.com/blog/dev/…

标签: php protection spam


【解决方案1】:

我考虑过使用会话,例如 有一个包含一个会话 每次检查的时间戳 他们将数据发送到like.php

这不会阻止机器人,因为它们可以接收和发送与用户相同的 cookie。

您确实应该让用户登录到这样的系统。似乎值得保护访问。您还可以考虑限制每个 ip 每分钟的帖子,但多个机器人仍然可以发送许多垃圾邮件。

如果您不想实现登录,那么许多网站会使用captcha 来尝试减少此类尝试。

http://www.phpcaptcha.org/

【讨论】:

  • 谢谢。我真的不喜欢用户系统或验证码的想法,因为该站点的主要目标是快速发送消息。看来我终究还是要记录 IP 地址。
【解决方案2】:

使用令牌。您生成令牌并将其添加到发起请求的页面。在like.php 中,您验证请求是否包含有效令牌,这意味着它来自您的页面,而不是直接发布的外部页面。

【讨论】:

  • 然后,最重要的是,一旦使用了令牌,您就会使其无效。将令牌附加到会话还添加了另一层反机器人代码。 (原始机器人不会做 cookie。)现在,高级机器人仍然可以工作,但机器人作者需要做更多的工作。
  • 这实际上是完美的。但是我将如何生成令牌?只是随机数的哈希值还是什么?还有like.php 怎么知道什么令牌是有效的?我应该通过 SESSION 发送它吗? :) 干杯!
  • 什么是阻止某人查看页面一次,获取令牌,然后使用有效令牌自动请求/POST?
  • 这只是将脚本的 POST 循环变成了 GET&POST 循环。
【解决方案3】:

Session 是最容易做到这一点的,而且开销也最少。您可以在会话中存储两位数据,最后一个帖子的时间戳,帖子来自的 ip。以下是您检查合法性的方法:

session_start();
if(isset($_SESSION['ip']) && $_SESSION['last_post'] + MININTERVAL < time()) die('too early');

$_SESSION['last_post'] = time();
$_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
// store the message

【讨论】:

  • 这有点用......但即使在 10 分钟后它仍然不允许我提交评论(这就是我使用它的目的)MININTERVAL 多长时间?好像超过一分钟,有没有其他方法可以给$_SESSION['last_post']增加一分钟?
  • MININTERVAL 是您要使用的秒数的占位符。如果要设置 2 分钟间隔,请将行更改为 if(isset($_SESSION['ip']) &amp;&amp; $_SESSION['last_post'] + 120 &lt; time()) die('too early');
  • 哦,我明白了,哎呀。哈哈。谢谢! +1
  • Majid 做错了: if(isset($_SESSION['ip']) && ($_SESSION['last_post'] + MININTERVAL) > time()) die('too early ');
  • @devman 不,这不是会话的工作方式。如果请求中不存在会话 cookie,即创建一个空会话,即在访问者发送的第一个请求中,可以不使用die。后续请求将有一个会话 cookie,session_start() 将导致$_SESSION 变量填充我们上次分配给它的任何值——即last_postip
【解决方案4】:

另一种方法是使用 jQuery 将隐藏的表单输入写入页面(调用 like.php)。机器人不会使用 javascript,因此您的隐藏表单字段将不存在。

检查隐藏字段(为其分配值和名称),如果存在,则使用请求访问数据库。

另一种方式;将隐藏元素编码到页面中 (&lt;input style='display:none;' name='nospam' value='' /&gt;)。机器人会自动填充表单中的每个字段,因此您只需检查该字段是否已填充 - 用户看不到它,因此如果您有内容,您就知道它是机器人。

使用 jQuery 设置样式 (display:none;)...同样,机器人不会看到 jQuery,所以它会认为这是一个合法的表单输入。

您可能希望在某处为用户指定“此页面需要运行 javascript”通知。一些替代建议。毕竟 - 你说'简单';)

【讨论】:

    【解决方案5】:

    好吧,我制作了一个脚本来仅针对核心请求处理它(没有会话请求或其他不调用核心的请求)。如果你看看谷歌,你会发现脚本/类会因为每次高负载而杀死你的服务器。事实上,许多人使用 SESSION 并且可能还使用 SQL/Database 将使您获得作为服务器杀手的泛洪保护。此外,SESSION 需要 Cookie(或 GET SID),因此您可以轻松地操纵 SESSION 以获得新的 SESSION ID。

    我的函数是基于文本的,做一个简单的处理。坏事是您可能不得不不时使用 CronJob 删除 ips。与其他脚本相比,它的速度大约快 10*(并且比会话节省更多)。

    我不知道它是否真的有用。 ;) 您可能希望将 rpm 值更改为 less 或/以及 200 req。我的设置是禁止机器人在

    <?php
    function ht_request_limiter() {
        if (!isset($_SERVER['REMOTE_ADDR'])) { return; } // Maybe its impossible, however we check it first
        if (empty($_SERVER['REMOTE_ADDR'])) { return; } // Maybe its impossible, however we check it first
        $path = '/your/path/ipsec/'; // I use a function to validate a path first and return if false...
        $path = $path.$_SERVER['REMOTE_ADDR'].'.txt'; // Real file path (filename = <ip>.txt)
        $now = time(); // Current timestamp
        if (!file_exists($path)) { // If first request or new request after 1 hour / 24 hour ban, new file with <timestamp>|<counter>
            if ($handle = fopen($path, 'w+')) {
                if (fwrite($handle, $now.'|0')) { chmod($path, 0700); } // Chmod to prevent access via web
                fclose($handle);
            }
        }
        else if (($content = file_get_contents($path)) !== false) { // Load existing file
            $content = explode('|',$content); // Create paraset [0] -> timestamp  [1] -> counter
            $diff = (int)$now-(int)$content[0]; // Time difference in seconds from first request to now
            if ($content[1] == 'ban') { // If [1] = ban we check if it was less than 24 hours and die if so
                if ($diff>86400) { unlink($path); } // 24 hours in seconds.. if more delete ip file
                else {
                    header("HTTP/1.1 503 Service Unavailable");
                    exit("Your IP is banned for 24 hours, because of too many requests.");
                }
            }
            else if ($diff>3600) { unlink($path); } // If first request was more than 1 hour, new ip file
            else {
                $current = ((int)$content[1])+1; // Counter + 1
                if ($current>200) { // We check rpm (request per minute) after 200 request to get a good ~value
                    $rpm = ($current/($diff/60));
                    if ($rpm>10) { // If there was more than 10 rpm -> ban (if you have a request all 5 secs. you will be banned after ~17 minutes)
                        if ($handle = fopen($path, 'w+')) {
                            fwrite($handle, $content[0].'|ban');
                            fclose($handle);
                            // Maybe you like to log the ip once -> die after next request
                        }
                        return;
                    }
                }
                if ($handle = fopen($path, 'w+')) { // else write counter
                    fwrite($handle, $content[0].'|'.$current .'');
                    fclose($handle);
                }
            }
        }
    }
    

    编辑:我测试请求时间的方法是使用 microtime 并模拟 10'000 个用户。我问谷歌并测试(例如)http://technitip.net/simple-php-flood-protection-class

    所以我不知道那里应该简单吗?您一次有大约 3 个 SQL 请求,例如:

    $this -> user_in_db($ip))
    $this->user_flooding($ip);
    $this->remove_old_users();
    

    它可能提供更多功能,但所有合法用户都白白使用服务器时间。 ;)

    【讨论】:

    • 当脚本的多个副本同时运行并处理来自同一IP地址的请求时,这里的脚本存在竞争条件。 Counter 有时只会为两个或多个请求增加一次,有时您会收到 unlink() 失败的错误消息(另一个脚本同时删除了它)。 related question
    【解决方案6】:

    您不需要浏览整个记录文件。而是:

    <?php
    define("FLOODPOOL", ".");
    define("FLOODPOOL_LIMIT", 30);
    define("FLOODPOOL_DURATION", 60 * 60 * 24);
    define("FLOODPOOL_AUTOCLEAN", true);
    
    // Record and check flood.
    // Return true for hit.
    function floodpool_check($id){
        $fp = fopen(FLOODPOOL . DIRECTORY_SEPARATOR . 'fp_' . basename($id), 'a+');
        fwrite($fp, pack('L', time()));
        if(fseek($fp, -4 * FLOODPOOL_LIMIT, SEEK_END) === -1) {
            return false;
        }
        $time = reset(unpack('L', fread($fp, 4)));
        fclose($fp);
        if(time() - $time < FLOODPOOL_DURATION) {
            if(FLOODPOOL_AUTOCLEAN){
                @floodpool_clean();
            }
            return true;
        }
        return false;
    }
    
    
    // Clean the pool.
    function floodpool_clean(){
        $handle = opendir(FLOODPOOL);
        while(false!==($entry=readdir($handle))){
            $filename = FLOODPOOL . DIRECTORY_SEPARATOR . $entry;
            if(time() - filectime($filename) > FLOODPOOL_DURATION && substr($entry, 0, 3) === 'fp_'){
                unlink($filename);
            }
        }
        closedir($handle);
    }
    

    使用示例:

    if(floodpool_check($_SERVER['REMOTE_ADDR'])){
        header("HTTP/1.1 429 Too Many Requests");
        exit("Hit some *");
    }
    

    【讨论】:

      【解决方案7】:

      如果您想停止淹没搜索页面,可以这样尝试:

      $flood_protection_interval = 2;
      session_start();
      if(
          isset($_SESSION['ip']) && 
          $_SESSION['counter'] > 10 &&
          $_SESSION['last_post'] + $flood_protection_interval > time()
          ){
              // $_SESSION['counter'] = 0; // Use this if you want to reset counter
              die("<pre>\n\n\n\t<b>FLOOD PROTECTION</b>");
      }
      $_SESSION['counter']++;
      $_SESSION['last_post'] = time();
      $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
      

      因此,如果您的访问者在下面搜索 10 次,例如2秒他就会被拦住!

      【讨论】:

        猜你喜欢
        • 2011-09-17
        • 2016-05-25
        • 1970-01-01
        • 1970-01-01
        • 2010-12-11
        • 2014-11-28
        • 2015-01-09
        • 1970-01-01
        相关资源
        最近更新 更多