我怎样才能改进这个脚本防火墙?

时间:2011-05-18 14:02:30

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

最近,我的一台服务器正在通过一些中国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的出现,如果它们是&gt; $ 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,但不应阻止它们的请求

4 个答案:

答案 0 :(得分:3)

虽然这会在他们做任何更重的事情之前停止请求,比如数据库读取等,但您可能需要考虑将其降低到Web服务器的水平,甚至更进一步到软件/硬件防火墙。

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

答案 1 :(得分:1)

以下是我的几点说明,希望你发现它们很有用。

在我看来,功能防火墙做得太多,而且名称不是很具体。它既处理ip / visits的保存,也结束脚本或什么都不做。我打算在调用这个功能时将墙壁设置为火焰。

我会采用更加面向对象的方法,防火墙不是防火墙,而是黑名单。

$oBlackList = new BlackList();

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

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

通过这种方式,您可以按照自己喜欢的方式进行创作,并且不仅限于功能主体。您可以使用函数展开对象以将地址添加到列表中。或许$oBlackList->addToList($sIpAddress);

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

无论如何,只是漫无目的!

答案 2 :(得分: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。
  6. 如果你真的想依赖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);
    

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

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

答案 3 :(得分:0)

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

<?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函数中。