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

介绍

如何从您的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数据库还在不断增长……他们将不得不dynamic地添加更多的价值
  • iptables设置禁止100000+只是荒谬的(可能是错误的)

愚蠢的想法

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

  • htaccess有可能从数据库(Redis,Crunchbase,Mongo,MySQL甚至Sqlite)获取列表…任何
  • 是否有明显的解决scheme来pipe理生产中的这类问题
  • 我知道最好的解决scheme是Block the IPs at the firewall level是否有任何方法务实地添加/删除IP到防火墙

最后

我的方法可能是完全错误的…我想要的只是一个可见的解决scheme,因为垃圾邮件发送者和僵尸networking正在崛起。

请这与DOS攻击没有任何关系…简单的get lost response

更新

  • 防火墙: Cisco PIX 515UR

你可以尝试的是保留一个你想在文本文件中阻塞的IP地址列表,或者把它转换成一个dbm哈希文件 ,然后使用mod_rewrite的RewriteMap 。 您必须在您的服务器/虚拟主机configuration中进行设置。 您无法在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 

我不确定使用mod_rewrite有多大的IP,但是在linux下运行在3Ghz i686上的apache 2.2的快速基准testing,列表中的5个IP与102418之间的差异是微不足道的。 根据ab的输出,他们几乎是相同的。


解决具体问题:

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

使用重写映射,可以使用“ prg ”映射types为映射types运行外部程序。 然后,您可以编写一个perl,php等脚本来查询数据库,以查找IP地址。 另外请注意“警告”下列出的注意事项。 然后,您会像使用其他地图一样使用此地图( RewriteCond ${deny_ips:%{REMOTE_ADDR}|0} !=0 )。 这实质上会造成所有请求的瓶颈。 不是与数据库交谈的最佳解决scheme。

不过在apache 2.4中,有一个dbd / fastdbd映射types,它允许你通过mod_dbd创build查询。 这是一个更好的select,mod_dbd模块pipe理与数据库,池连接等的连接。因此,地图定义如下所示:

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

假设你有一个表“ deny_ips ”,有两列“ ”(IP地址)和“ 激活 ”(1表示激活,0表示不激活)。

是否有明显的解决scheme来pipe理生产中的这类问题

如果要将所有被阻止的IP存储在数据库中,则需要pipe理数据库表的内容。 如果您使用的是dbm映射types,我知道至lessperl有一个用于pipe理dbm文件的DBI ,因此您可以使用它来从拒绝列表中添加/删除IP条目。 我从来没有使用过,所以我不能说太多。 pipe理一个扁平的文本文件将变得更加棘手,特别是如果你打算删除条目,而不是附加到它。 除了使用数据库和apache 2.4的mod_dbd之外,我不认为这些解决scheme是开箱即用的。 这将需要定制工作。

我知道最好的解决scheme是阻止IP在防火墙级别是否有任何方法务实地添加/删除IP到防火墙

对于IPtables,有一个标记为Beta的perl接口 ,但是我从来没有使用它。 有libiptc,但根据netfilter的常见问题 :

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

答案不幸的是:没有。

现在你可能会想'但是libiptc呢?' 正如在邮件列表中多次指出的那样,libiptc 从来不会被用作公共接口。 我们不保证一个稳定的接口,并且计划在接下来的linux包过滤中将其删除。 无论如何,libiptc的方式太低层,无法合理使用。

我们清楚地意识到,这样的API基本缺乏,我们正在努力改善这种情况。 在此之前,build议使用system()或打开一个pipe道到iptables-restore的stdin。 后者会给你更好的performance。

所以我不知道如果没有API稳定性,libiptc解决scheme是多么可行。

另一个angular度

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

理论

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

问题是,如果我们要解决位位置呢?

答案:好吧,我们会有的

  2^32 = 4 Giga Bits 

处理空间,将采取

  4Gb/8 = 512 Mega Bytes 

的分配。 哎哟! 但是不要担心,我们不会阻止ipverse的一切,512MB是夸张的。

这可以为我们打开解决scheme的道路。

Lilliputian案例

考虑一个只有IP地址从0到65535的Lilliputian世界。所以地址是0.1或者42.42到255.255。

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

首先他build立一个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结构。

实践

真实世界的案例

我们可以将Lilliputian案例应用到现实世界中,因为分配一个512MB的文件并不是一个好的select。

考虑一个名为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表并获取地图。

它查找更高阶的 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) 

看地图中的字节1795位4。

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

如果位被设置,那么脚本访问BLOCKS表。

更高阶的IP号码是56.28,它给出14364,所以脚本查询带有索引IpHead = 14364的BLOCKS表。获取logging。 logging应该存在,因为它在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地图查找会比任何数据库服务器的BTREEsearch更快。

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

A2:同一条logging在地图字段中被封锁的IP地址数。 所以如果我们解封ip的EntryCount达到0,BLOCKSlogging变得不必要了。 它可以被删除,BASE地图上的相应位将被删除。

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

如果被阻止的地址太分散

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

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

对于我们前面的例子,更高的Ipparsing是:

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

因此,BASE将成为一个2MB的文件,而不是一个数据库表logging,这将打开(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. 

这里是位平面位平面(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); } ?> 

我希望这有帮助。

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

您需要使用外部防火墙,而不是使用PHP。 我推荐pfSense或PF 。 我以前使用过它,使用起来非常简单,非常直观,而且非常强大。 这是最好的系统pipe理员的select。 我在FreeBSD上运行它,但是在OpenBSD上也很好。 我是一个Linux的家伙,所以我很难说这个,但不要试图在Linux上运行它。 BSD很容易,你可以很快地弄清楚。

pfSense的一个很棒的function是能够使用脚本进行configuration,并将configuration访问权限制在单个networking接口上(这样只有局域网上的东西才能configuration它)。 它也有一些ID10T级别的function,以防止意外切断自己的访问。

你也应该知道,很多垃圾邮件发送者可以使用像Tor这样的东西来快速切换IP地址。 为了解决这个问题,你应该在你的阻止列表中包含已知的出口节点地址(这个列表可以从不同的地方获得)。

使用iptables和ipset阻止stream量到达www服务器之前。

在INPUT链的filter表中捕获黑名单的IPstream量,假定您的Web服务器位于同一台计算机上。 如果您在路由器上阻止IP,您将需要FORWARD链。

首先创buildipset:

 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服务器之前的黑名单stream量。

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

 ipset create ip_blacklist hash:ip maxelem 120000 

你也可以调整散列大小:

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

我的经验是ipset查找对我的系统(~45000条目)有微不足道的影响。 网上有很多testing用例。 记忆是一个限制因素。

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

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

http://ipset.netfilter.org/

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

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

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

我知道一个方法
它的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,因为它的速度或仅仅是因为防止,因为它很难调用所有页面的function? 如果避免一些ip访问php is.avoiding主题来看内容的唯一原因。
所以我有了一个想法,并以这种方式推荐你。
使用一个入口点。

我曾与这个解决scheme。

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

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

htaccess将执行如下所示的内容而不更改url。 所以用户不会感觉到任何东西。

 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']; 

它是如此简单,其真实是如此复杂,但主要思想是一样的。 你怎么看?

看来,我们大多数人都同意在防火墙层面进行封锁

您可以有一个程序,监听您的网站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你复制运行configuration开始configuration。

我不知道,但你可能想知道现在什么是你的防火墙的限制。

最好,

对列表中的IP进行地理查找。 我自己的经验表明,大多数恶意(即垃圾邮件)连接都来自中国。 如果你觉得你的情况是一样的,你也没有特别需要去服务中国,那么看看你是否可以在防火墙层面有效地阻止整个国家。

恕我直言,这个问题可以从几个angular度考虑

  1. 你有一个非常大的唯一IP地址来阻止。 你越早在服务器上阻止他们,你将浪费在他们身上的处理能力就越less。 您可以通过来自PHP的适当系统调用,在防火墙中添加/删除IP。

考虑到上述选项,唯一相关的问题是:

  • 他们经常访问吗? =>如果是这样,那么解决scheme(1)是你最好的select。
  • 你的防火墙可以有效地处理吗? =>否则,您可能需要考虑其他解决scheme。

.htaccess将是我的第二select。