如何阻止100,000多个个人IP地址

时间:2013-03-22 20:49:55

标签: php apache security .htaccess

简介

如何从Web应用程序/服务器中阻止大量IP address。显然,可以使用PHP或任何编程语言轻松完成

$ipList = []; // array list or from database
if (in_array(getIP(), $ipList)) {
    // Log IP & Access information
    header("https://www.google.com.ng/search?q=fool"); // redirect
    exit(); // exit
} 

或使用htaccess

order allow,deny
deny from 123.45.6.7
deny from 012.34.5.
# .... the list continues
allow from all

问题

  • 我试图阻止整个100k plus individual IPs而不是subnets
  • 我试图避免用户在阻止此类IP之前访问PHP
  • 100000+超过1.5MB,如果要始终在htaccess加载信息,那就很多了
  • IP数据库仍在增长......他们需要动态添加更多值
  • iptables设置100000+的禁令只是荒谬(可能是错的)

愚蠢的想法

order allow,deny
deny from database    <-------- Not sure if this is possible
allow from all

问题

  • htaccess是否可以从数据库中获取列表(Redis,Crunchbase,Mongo,MySQL甚至是Sqlite)......任何
  • 是否有可见的解决方案来管理生产中的此类问题
  • 我知道最好的解决方案是Block the IPs at the firewall level是否有任何方式可以实际添加/删除IP到防火墙

最后

我的方法可能完全错误......我想要的只是一个明显的解决方案,因为垃圾邮件制造者和僵尸网络正在崛起......

请注意这与DOS攻击毫无关系... get lost response

更新

  • 防火墙: Cisco PIX 515UR

11 个答案:

答案 0 :(得分:63)

您可以尝试的方法是在text file中保留要阻止的IP地址列表,或将其转换为dbm hash file,然后使用mod_rewrite的RewriteMap。您必须在server / vhost配置中进行设置。 您无法在htaccess文件中初始化地图

RewriteEngine On
RewriteMap deny_ips txt:/path/to/deny_ips.txt

RewriteCond ${deny_ips:%{REMOTE_ADDR}|0} !=0
RewriteRule ^ - [L,F]

/path/to/deny_ips.txt 文件看起来像这样:

12.34.56.78 1
11.22.33.44 1
etc.

基本上,您要拒绝的IP和空格然后是“1”。此文本文件中的任何IP都将导致服务器返回 403 Forbidden 。为了加快速度,您可以使用httxt2dbm生成dbm哈希,然后将映射定义为:

RewriteMap deny_ips dbm:/path/to/deny_ips.dbm

我不确定使用大量IP的mod_rewrite对性能的影响是什么,但是在linux下的3Ghz i686上运行的apache 2.2的快速基准测试,列表中的5个IP与102418之间的差异可以忽略不计。根据 ab 的输出,它们几乎相同。


解决具体问题:

  

htaccess是否有可能从数据库中获取列表(Redis,Crunchbase,Mongo,MySQL甚至是Sqlite)......任何

使用重写映射,您可以使用“prg”映射类型为映射类型运行外部程序。然后,您可以编写perl,php等脚本与数据库通信,以便查找IP地址。另请注意“警告”下列出的警告。然后,您可以像使用任何其他地图(RewriteCond ${deny_ips:%{REMOTE_ADDR}|0} !=0)一样使用此地图。这实际上会为所有请求创建瓶颈。不是与数据库交谈的最佳解决方案。

在apache 2.4中,有一个dbd/fastdbd地图类型,允许您通过mod_dbd创建查询。这是一个更好的选择,mod_dbd模块管理与数据库的连接,池连接等。因此,地图定义如下所示:

RewriteMap deny_ips "fastdbd:SELECT active FROM deny_ips WHERE source = %s"

假设您有一个表“ deny_ips ”,其中包含2列“来源”(IP地址)和“有效”(1为有效,0表示无效)。

  

是否有可行的解决方案来管理生产中的此类问题

如果要将所有被阻止的IP存储在数据库中,则需要管理数据库表的内容。如果您正在使用dbm映射类型,我至少知道perl has a DBI用于管理dbm文件,因此您可以使用它来从拒绝列表中添加/删除IP条目。我之前从未使用它,所以我真的不能说太多。管理平面文本文件会变得更加棘手,特别是如果您计划删除条目,而不仅仅是附加条目。除了使用数据库和apache 2.4的mod_dbd之外,我认为这些解决方案都没有开箱即用或生产就绪。这将需要定制工作。

  

我知道最好的解决方案是阻止防火墙级别的IP是否有任何方法可以实际添加/删除IP到防火墙

对于IPtables,有perl interface标记为Beta,但我以前从未使用过它。有libiptc,但根据netfilter's faq

  

是否有用于添加/删除规则的C / C ++ API?

     

不幸的是答案是:否。

     

现在你可能会想'但是libiptc呢?'正如已经在邮件列表上多次指出的那样,libiptc NEVER 意味着用作公共接口。我们不保证一个稳定的接口,并计划在下一个linux数据包过滤的版本中将其删除。 libiptc太低层,无论如何都无法合理使用。

     

我们非常清楚这种API基本缺乏,我们正在努力改善这种状况。在此之前,建议使用system()或打开管道进入iptables-restore的stdin。后者将为您提供更好的表现。

因此,如果没有API稳定性,我不知道libiptc解决方案的可行性。

答案 1 :(得分:31)

另一个观点

您好。您可以通过访问两个长度为8KB的数据块中的两个字节来检查地址是否被阻止。是的,我很认真......请耐心等待,因为解释它需要一点时间。

理论

IP地址是一个地址,实际上是一个4字节的数字。

问题是,如果我们要解决比特位置怎么办?

答案:好吧,我们会有

  2^32 = 4 Giga Bits 

解决空间问题

 4Gb/8 = 512 Mega Bytes

分配。 哎哟!但不要担心,我们不会阻止ipverse中的所有内容,512MB是夸大其词。

这可以为我们提供解决方案的途径。

Lilliputian案例

想想一个只有0到65535的ip地址的Lilliputian世界。所以地址就像0.1或42.42到255.255。

现在,这个世界的国王希望阻止几个L-IP(lilliput ip)地址。

首先,他构建了一个256 * 256位长的虚拟2D位图,占据了:

 64 K Bits = 8 K Bytes.

他决定阻止他讨厌的那个讨厌的“革命”网站,因为他是国王,例如地址是56.28。

Address     = (56 * 256) + 28  = 14364.(bit position in whole map)
Byte in map = floor(14364 / 8) =  1795.
Bit position= 14364 % 8        =     4.(modulus)

他打开地图文件,访问第1795个字节并设置第4位(通过| 16),然后将其写回以将网站标记为已阻止。

当他的脚本看到56.28时,它执行相同的计算并查看该位,如果设置了,则阻止该地址。

现在这个故事的寓意是什么?好吧,我们可以使用这种小型的结构。

实践

真实案例

我们可以将Lilliputian案例应用于现实世界,并且“在需要时使用”方法,因为分配512MB文件不是一个好选择。

考虑一个名为BLOCKS的数据库表,其中包含以下条目:

IpHead(key): unsigned 16 bit integer,
Map        : 8KB BLOB(fixed size),
EntryCount : unsigned 16 bit integer.

另一个表只有一个条目,结构如下,名为BASE

Map        : 8KB BLOB(fixed size).

现在假设您有一个传入地址56.28.10.2

脚本访问BASE表并获取Map。

查找高阶 IP号码56.28:

Address     = (56 * 256) + 28  = 14364.(bit position in whole map)
Byte in map = floor(14364 / 8) =  1795.
Bit position= 14364 % 8        =     4.(modulus)

查看Map中的字节1795 bit 4.

如果未设置位,则不需要进一步操作,这意味着56.28.0.0 - 56.28.255.255范围内没有阻塞的IP地址。

如果设置了位,则脚本将访问BLOCKS表。

高阶IP数为56.28,这给出了14364,因此脚本使用索引IpHead = 14364查询BLOCKS表。获取记录。记录应该存在,因为它标记在BASE。

脚本执行低阶 IP地址

的计算
Address     = (10 * 256) + 2   = 2562.(bit position in whole map)
Byte in map = floor(2562 / 8) =   320.
Bit position= 2562 % 8        =     2.(modulus)

然后通过查看字段Map的字节320的第2位来检查地址是否被阻止。

完成工作!

Q1:我们为什么要使用BASE,我们可以直接用14364查询BLOCKS。

A1:是的我们可以,但BASE地图查找将比BTREE搜索任何数据库服务器更快。

Q2: BLOCKS表中的EntryCount字段是什么?

A2:这是在同一记录的地图字段中阻止的IP地址计数。         因此,如果我们取消阻止ip,并且EntryCount达到0,则BLOCKS记录变为         不必要。它可以被擦除,BASE映射上的相应位将被取消。

恕我直言,这种做法会很快。对于blob分配也是每条记录8K。由于数据库服务器将blob保留在单独的文件中,因此具有4K,8K或4K分页倍数的文件系统将做出快速反应。

如果阻止的地址过于分散

这是一个问题,这将使数据库BLOCKS表不必要地增长。

但是对于这种情况,替代方案是使用256 * 256 * 256位立方体,其长度为16777216位,相当于2097152字节= 2MB。

对于我们之前的示例,更高的Ip解析是:

(56 * 65536)+(28 * 256)+10      

所以BASE将成为2MB文件而不是db表记录,它将被打开(fopen等)并且将通过搜索来寻址(如fseek,从不读取整个文件内容,不必要的)然后使用以下结构访问BLOCKS表:

IpHead(key): unsigned 32 bit integer, (only 24 bit is used)
Map        : 32 unsigned 8 bit integers(char maybe),(256 bit fixed)
EntryCount : unsigned 8 bit integer. 

以下是用于块位检查bitplane-bitplane(8K 8K)版本的php示例代码:

备注:此脚本可以通过消除多个调用等进一步优化。            但是这样写是为了让它易于理解。

<?
define('BLOCK_ON_ERROR', true); // WARNING if true errors block everyone

$shost = 'hosturl';
$suser = 'username';
$spass = 'password';
$sdbip = 'database';
$slink = null;

$slink = mysqli_connect($shost, $suser, $spass, $sdbip);
if (! $slink) {
    $blocked = BLOCK_ON_ERROR;
} else {
    $blocked = isBlocked();
    mysqli_close($slink); // clean, tidy...
}

if ($blocked) {
    // do what ever you want when blocked
} else {
    // do what ever you want when not blocked
}
exit(0);

function getUserIp() {
    $st = array(
            'HTTP_CLIENT_IP',
            'REMOTE_ADDR',
            'HTTP_X_FORWARDED_FOR'
    );
    foreach ( $st as $v )
        if (! empty($_SERVER[$v]))
            return ($_SERVER[$v]);
    return ("");
}

function ipToArray($ip) {
    $ip = explode('.', $ip);
    foreach ( $ip as $k => $v )
        $ip[$k] = intval($v);
    return ($ip);
}

function calculateBitPos($IpH, $IpL) {
    $BitAdr = ($IpH * 256) + $IpL;
    $BytAdr = floor($BitAdr / 8);
    $BitOfs = $BitAdr % 8;
    $BitMask = 1;
    $BitMask = $BitMask << $BitOfs;
    return (array(
            'bytePos' => $BytAdr,
            'bitMask' => $BitMask
    ));
}

function getBaseMap($link) {
    $q = 'SELECT * FROM BASE WHERE id = 0';
    $r = mysqli_query($link, $q);
    if (! $r)
        return (null);
    $m = mysqli_fetch_assoc($r);
    mysqli_free_result($r);
    return ($m['map']);
}

function getBlocksMap($link, $IpHead) {
    $q = "SELECT * FROM BLOCKS WHERE IpHead = $IpHead";
    $r = mysqli_query($link, $q);
    if (! $r)
        return (null);
    $m = mysqli_fetch_assoc($r);
    mysqli_free_result($r);
    return ($m['map']);
}

function isBlocked() {
    global $slink;
    $ip = getUserIp();
    if($ip == "")
        return (BLOCK_ON_ERROR);
    $ip = ipToArray($ip);

    // here you can embed preliminary checks like ip[0] = 10 exit(0)
    // for unblocking or blocking address range 10 or 192 or 127 etc....

    // Look at base table base record.
    // map is a php string, which in fact is a good byte array
    $map = getBaseMap($slink); 
    if (! $map)
        return (BLOCK_ON_ERROR);
    $p = calculateBitPos($ip[0], $ip[1]);
    $c = ord($map[$p['bytePos']]);
    if (($c & $p['bitMask']) == 0)
        return (false); // No address blocked

    // Look at blocks table related record
    $map = getBlocksMap($slink, $p[0]);
    if (! $map)
        return (BLOCK_ON_ERROR);
    $p = calculateBitPos($ip[2], $ip[3]);
    $c = ord($map[$p['bytePos']]);
    return (($c & $p['bitMask']) != 0);
}

?> 

我希望这会有所帮助。

如果您对细节有疑问,我将很乐意回答。

答案 2 :(得分:8)

您需要使用外部防火墙,而不是PHP。我推荐pfSensePF。我之前使用它,它非常易于使用,非常直观,而且非常强大。它是最好的系统管理员的选择。我在FreeBSD上运行它,但它也适用于OpenBSD。我是一个Linux人,所以我很难说这个,但尝试在Linux上运行它。 BSD很简单,你可以快速搞清楚。

pfSense的一个很棒的功能是能够使用脚本进行配置并限制对单个网络接口的配置访问(这样只有LAN上的东西才能配置它)。它还具有几个ID10T级别的功能,可以防止您意外切断自己的访问。

您还应该知道,许多垃圾邮件制造者可以使用Tor等内容快速切换IP。要解决此问题,您应该在阻止列表中包含已知退出节点的地址(此列表可从各个位置获得)。

答案 3 :(得分:7)

使用iptables和ipset阻止流量到达www服务器。

假设您的Web服务器位于同一台计算机上,请在INPUT链的过滤器表中捕获列入黑名单的IP流量。如果您在路由器上阻止IP,则需要FORWARD链。

首先创建ipset:

ipset create ip_blacklist hash:ip

IP可以通过以下方式添加:

ipset add ip_blacklist xxx.xxx.xxx.xxx

将ipset匹配规则添加到iptables(DROP所有数据包匹配到ipset):

iptables --table filter --insert INPUT --match set --match-set ip_blacklist src -j DROP

这将在www服务器之前停止列入黑名单的流量。

编辑:我有机会查找默认的最大大小,它是65536,所以你需要调整它以支持100000多个条目:

ipset create ip_blacklist hash:ip maxelem 120000

您还可以调整哈希值:

ipset create ip_blacklist hash:ip maxelem 120000 hashsize 16384(必须是2的幂)

我的经验是ipset查找对我的系统影响可以忽略不计(约45000个条目)。网上有很多测试用例。该集的记忆是一个限制因素。

答案 4 :(得分:5)

如果您想通过代码添加/删除方法,请查看denyhosts。您可以通过代码维护IP列表,也可以修补源代码以从您想要的任何位置读取。

答案 5 :(得分:4)

有一个名为ipset的netfilter项目,所以你可以在列表中添加或删除ip,你只需要针对这个列表创建规则

http://ipset.netfilter.org/

答案 6 :(得分:3)

如果你阻止IP,你真的应该在防火墙级别这样做(你不希望用户从不受欢迎的IP地址进入你的系统)。因此,我建议编写一个bash脚本来查询数据库并相应地修改你的防火墙配置文件(假设你想要一个利用存储在你的网络数据库中的IP地址的解决方案 - 很可能是存储这些信息的更好的地方)。

编辑:如果你想在PHP级别的黑名单中添加IP地址,正如@Populus建议的那样,这里是关于如何在PHP中使用系统调用的手册:http://php.net/manual/en/function.system.php

如果你正在使用iptables,那么你需要使用这些命令将ip地址添加到你的黑名单中:http://www.cyberciti.biz/faq/linux-iptables-drop/

答案 7 :(得分:3)

我知道一种方式 它的PHP。 正如你在这个问题的开头所提到的那样。

$ipList = []; // array list or from database
if (in_array(getIP(), $ipList)) {
    // Log IP & Access information
    header("https://www.google.com.ng/search?q=fool"); // redirect
    exit(); // exit
} 

我读过你写过的东西。但是等一下 为什么要这样做。这并不重要。但是为什么会出现这种情况 我的意思是你为什么要避免访问php,因为它的速度或只是因为防止,因为它很难在所有页面调用该功能? 如果避免一些ip访问php的唯一原因是.avoiding主题来查看内容 所以我有了一个想法,我就是这样给你。
使用一个入口点。

我使用过这个解决方案。

首先使用简单的htaccess将所有请求发送到一个名为入口点的页面。(如index.php)
通过简单的重写规则,我会把它给你。 所以当用户请求时

mysite.com/some/path/page.php or anything

htaccess将执行类似以下内容而不更改网址。所以用户不会有任何感觉。

mysite.com/index.php?r=some/path/page.php

所以每个请求都变成了一个带有不同$ _GET ['r']参数的请求。所以对于evey请求,我们将执行index.php。现在我们可以在index.php中做这样的事情

$ipList = []; // array list or from database
if (in_array(getIP(), $ipList)) {
    // Log IP & Access information
    header("https://www.google.com.ng/search?q=fool"); // redirect
    exit(); // exit
}
//if after this execute means the IP is not banned
//now we can include file that $_GET['r'] points to
include $_GET['r'];

它如此简单。它真实的那么复杂。但主要思想是一样的。 你觉得怎么样?

答案 8 :(得分:2)

似乎我们大多数人同意阻止防火墙级别

您可以拥有一个程序来监听您的网站,以便ips阻止并生成脚本:

ip = getNextIpToBlock()
an = increment_unique_alphanum_generator()
script = generate_script(ip, an)

脚本看起来像这样(其中[an]是一个字母数字值,[ip]是你阻止的ip):

en [enter]
*password* [enter]
conf t [enter]
access-list [an] deny ip [ip] 0.0.0.0 any [enter]
access-group [an] in interface outside [enter]

然后将此脚本加载到另一个执行远程telnet或ssh调用FW CLI的程序。

不要忘记注销,也许每100次ips复制正在运行的配置以启动配置。

我不知道,但您现在可能想知道防火墙有哪些限制。

最佳,

答案 9 :(得分:0)

对列表中的IP进行地理查找。我自己的经验表明,大多数恶意(即垃圾邮件)连接都来自中国。如果您发现同样的情况,并且您没有特别需要为中国服务,请查看您是否可以在防火墙级别有效阻止整个国家/地区。

答案 10 :(得分:0)

恕我直言,有几个角度可以考虑这个问题

  1. 您要阻止一组非常大的唯一IP地址。该 越早在服务器上阻止它们,处理能力就越低 会浪费在他们身上。您可以通过PHP的适当系统调用在防火墙中添加/删除IP。
  2. 考虑到上述选项,唯一相关的问题是:

    • 他们经常访问吗? =&gt;如果是这样,那么解决方案(1)是你最好的选择。
    • 您的防火墙可以有效地处理它吗? =&GT;如果没有,您可能需要考虑其他解决方案。

    .htaccess将是我的第二选择。