【问题标题】:how can i improve this script firewall?我怎样才能改进这个脚本防火墙?
【发布时间】:2011-08-28 02:35:57
【问题描述】:

最近我的一台服务器被一些中国 IP(这些 ip 并不总是相同)的一些 dos 攻击(数千个请求/分钟)作为目标。

所以在我的框架开始时,我做了一个小函数来阻止一个 ip,如果它发出了太多请求。

function firewall() {
  $whitelist = array('someips');

  $ip = $_SERVER['REMOTE_ADDR'];

  if (in_array($ip,$whitelist))
    return null;

  if (search($ip,$pathToFileIpBanned))
    die('Your ip did too many requests')

  appendToFile($ip,$pathTofileIpLogger); //< When the file reaches 13000 bytes truncate it

  if (search($ip,$pathTofileIpLogger) > $maxRequestsAllowed)
     appendToFile($ip,$pathToFileIpBanned);   
}
  • 基本上,脚本会检查当前 ip 是否在文件 'ipBlocked' 中找到,如果发现它就会死掉。
  • 如果未找到,则将当前 ip 添加到文件记录器“ipLogger”中。
  • 在此之后,它会计算文件 ipLogger 中 ip 的出现次数,如果它们 > $max,它会通过将 ip 添加到文件 ipBlocked 来阻止该 ip

ATM 正在工作.. 它已经禁止了一些中文/tw ips

这个脚本的瓶颈是搜索功能,它必须计算一个字符串(ip)文件中出现的次数。出于这个原因,我将文件保持在低位(iplogger 文件在记录到 600-700 ips 后立即被截断)

当然要在文件中添加 ips 而不必担心竞争条件我这样做:

file_put_contents($file,$ip."\n",FILE_APPEND | LOCK_EX);

我遇到的唯一问题是 NAT 背后的人。它们都具有相同的 IP,但不应阻止它们的请求

【问题讨论】:

  • 您可以将序列化的数组存储到您的 ipLogger。在这个数组中,您将使用 ip 作为键,使用访问次数作为值。这样你就不会有这么大的文件了。
  • 天哪,这太棒了。回复可能会添加一些信息

标签: php ip firewall ddos denial-of-service


【解决方案1】:

您应该为每个被阻止的 IP 创建一个文件。这样您就可以通过.htaccess 阻止访问者,如下所示:

# redirect if ip has been banned
ErrorDocument 403 /
RewriteCond %{REQUEST_URI} !^/index\.php$
RewriteCond /usr/www/firewall/%{REMOTE_ADDR} -f
RewriteRule . - [F,L]

如您所见,它只允许访问index.php。通过这种方式,您可以在发出大量数据库请求之前在第一行执行一个简单的file_exists(),并且您可以抛出一个 IP 解锁验证码以避免永久阻止误报。与不返回任何信息或没有解锁机制的简单硬件防火墙相比,您可以获得更好的用户体验。当然,您可以抛出一个简单的 HTML 文本文件(以 php 文件作为表单目标)来避免 PHP 解析器也正常工作。

关于 DoS,我认为您不应仅依赖 IP 地址,因为这会导致许多误报。或者您有第二级将代理 ips 列入白名单。例如,如果一个 ip 被多次解锁。阻止不需要的请求的一些想法:

  1. 是人类还是爬行者? (HTTP_USER_AGENT)
  2. 如果是爬虫,是否尊重robots.txt
  3. 如果是人类,他是否访问了人类无法访问的链接(例如通过 css 使链接不可见或移出可见范围或表单...)
  4. 如果是爬虫,白名单呢?
  5. 如果是人类,他会像人类一样打开链接吗? (例如:在 stackoverflow 的页脚中,您会找到 tour help blog chat data legal privacy policy work here advertising info mobile contact us feedback。我认为没有人会打开其中的 5 个或更多,但是一个糟糕的爬虫可能 = 阻止它的 ip。

如果你真的想依赖 ip/min 我建议不要使用LOCK_EX 并且只使用一个文件,因为它会导致瓶颈(只要锁存在,所有其他请求都需要等待)。只要 LOCK 存在,您就需要一个备用文件。示例:

$i = 0;
$ip_dir = 'ipcheck/';
if (!file_exists($ip_dir) || !is_writeable($ip_dir)) {
    exit('ip chache not writeable!');
}
$ip_file = $ip_dir . $_SERVER['REMOTE_ADDR'];
while (!($fp = @fopen($ip_file . '_' . $i, 'a')) && !flock($fp, LOCK_EX|LOCK_NB, $wouldblock) && $wouldblock) {
    $i++;
}
// by now we have an exclusive and race condition safe lock
fwrite($fp, time() . PHP_EOL);
fclose($fp);

这将产生一个名为12.34.56.78_0 的文件,如果遇到瓶颈,它将创建一个名为12.34.56.78_1 的新文件。最后,您只需要合并这些文件(尊重锁!)并检查给定时间段内的许多请求。

但现在你面临下一个问题。您需要开始检查每个请求。这不是一个好主意。一个简单的解决方案是在开始检查之前使用mt_rand(0, 10) == 0。另一种解决方案是检查filesize(),这样我们就不需要打开文件了。这是可能的,因为每个请求都会增加文件大小。或者你检查filemtime()。如果最后一次文件更改是在同一秒或仅一秒前完成的。附: Both functions are equal fast.

至此,我得出了我的最终建议。仅使用touch()filemtime()

$ip_dir = 'ipcheck/';
$ip_file = $ip_dir . $_SERVER['REMOTE_ADDR'];
// check if last request is one second ago
if (filemtime($ip_file) + 1 >= time()) {
    mkdir($ip_dir . $_SERVER['REMOTE_ADDR'] . '/');
    touch(microtime(true));
}
touch($ip_file);

现在您为每个可能是 DoS 攻击的 ip 创建了一个文件夹,其中包含其请求的 microtime,如果您认为它包含许多这些请求,您可以使用 touch('firewall/' . $_SERVER['REMOTE_ADDR']) 阻止该 ip。当然你应该定期清理整个事情。

我的experiences (German)使用这样的防火墙非常好。

【讨论】:

    【解决方案2】:

    这是我的一些笔记,希望对你有用。

    在我看来,防火墙功能做的太多了,它的名字也不是很具体。它既可以处理 ip/visits 的保存,也可以结束脚本或什么都不做。我希望调用这个函数时墙壁会被点燃。

    我会采用更面向对象的方法,其中防火墙不被命名为防火墙,而是类似于黑名单。

    $oBlackList = new BlackList();
    

    这个对象只负责黑名单本身,但仅此而已。 它可以说一个ip地址是否在黑名单上,因此实现如下功能:

    $oBlackList = new BlackList();
    if ($oBlackList->isListed($sIpAddress)) {
        // Do something, burn the intruder!
    }
    

    这样,您可以按照自己喜欢的方式进行创作,而不受函数体的限制。您可以使用函数扩展对象以将地址添加到列表中。 $oBlackList-&gt;addToList($sIpAddress); 或许。

    这样,访问量的处理或存储不限于您的防火墙主体。您可以实现数据库存储、文件存储(就像您现在使用的那样)并随时切换而不会使您的黑名单失效。

    不管怎样,只是漫无目的!

    【讨论】:

    • 我表示选择了尽可能少的程序方式。使用此功能,我只需在我需要保护的脚本中加载我的框架后执行firewall()
    【解决方案3】:

    一些非常基本的文件/序列化代码,您可以用作示例:

    <?php
    $ip = $_SERVER['REMOTE_ADDR'];
    
    $ips = @unserialize(file_get_contents('%path/to/your/ipLoggerFile%'));
    if (!is_array($ips)) {
      $ips = array();
    }
    
    if (!isset($ips[$ip])) {
      $ips[$ip] = 0;
    }
    
    $ips[$ip] += 1;
    file_put_contents('%path/to/your/ipLoggerFile%', serialize($ips));
    
    if ($ips[$ip] > $maxRequestsAllowed) {
      // return false or something
    }
    

    当然,您必须以某种方式将其集成到您的 firewall 函数中。

    【讨论】:

    • 我不认为我能做到+= 1; 会受到竞争条件的影响
    • 那么我认为你应该真正考虑尼克斯的答案,而不是在 php 中这样做。
    • 如果我安装了 APC,我可以做到这一点。那会摇滚
    【解决方案4】:

    虽然这是在请求执行更重的操作(例如读取数据库等)之前停止请求,但您可能需要考虑将其降低到 Web 服务器,甚至进一步降低到软件/硬件防火墙。

    较低的级别将更优雅地处理此问题,并且开销更少。请记住,通过提出 PHP,他们仍然会在一段时间内消耗您的一个工人。

    【讨论】:

    • 是的,我知道,但在我的托管公司中,一个简单的防火墙每月花费 20 欧元,太贵了 xD。无论如何,只是在我的脚本开始时阻止我节省了很多电力
    • 也许可以打开你的机器/mysql 功能,但正如这里所说的——你的 apache 会死掉——你的服务器会宕机。
    • 嗯不...我没有受到那种 ddos​​ 攻击
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-18
    • 2023-01-05
    • 1970-01-01
    相关资源
    最近更新 更多