66

介绍

IP address您如何从您的 Web 应用程序/服务器中阻止大量数据。显然,这可以很容易地用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 数据库仍在增长......他们将需要动态添加更多价值
  • 为 100000+设置禁令iptables是荒谬的(可能是错误的)

愚蠢的想法

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
4

11 回答 11

67

您可以尝试将要阻止的 IP 地址列表保存在文本文件中或将其转换为dbm 哈希文件,然后使用 mod_rewrite 的RewriteMap. 您必须在服务器/虚拟主机配置中进行设置。您不能在 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 的快速基准测试,列表中的 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 有一个用于管理 dbm 文件的 DBI,因此您可以使用它从拒绝列表中添加/删除 IP 条目。我以前从未使用过它,所以我不能说太多。管理一个平面文本文件会很棘手,特别是如果您打算删除条目,而不仅仅是附加到它。除了使用数据库和 apache 2.4 的 mod_dbd 之外,我认为这些解决方案中的任何一个都不是开箱即用或生产就绪的。这将需要定制工作。

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

对于 IPtables,有一个标记为 Beta 的perl 接口,但我以前从未使用过它。有libiptc但根据netfilter 的常见问题解答

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

不幸的是,答案是:不。

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

我们很清楚这种 API 根本就缺乏,我们正在努力改善这种情况。在此之前,建议使用 system() 或打开管道进入 iptables-restore 的标准输入。后者将为您提供更好的性能。

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

于 2013-03-22T22:19:46.480 回答
34

另一个视角

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

理论

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

问题是,如果我们让它解决位位置怎么办?

答案:好吧,我们会有

  2^32 = 4 Giga Bits 

寻址空间,这将需要

 4Gb/8 = 512 Mega Bytes

的分配。哎哟! 不过不用担心,我们不会阻止 ipverse 中的所有内容,512MB 有点夸张。

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

小人国案

想象一个小人国世界,它只存在从 0 到 65535 的 IP 地址。所以地址就像 0.1 或 42.42 到 255.255。

现在这个世界之王想要封锁几个L-IP(小人国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 位 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 map 查找会比 BTREE 搜索任何数据库服务器更快。

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

A2:是在同一记录的map字段中被阻塞的ip地址的数量。因此,如果我们解除阻塞 ip 并且 EntryCount 达到 0,那么 BLOCKS 记录就变得不必要了。它可以被擦除,并且 BASE map 上的相应位将被取消设置。

恕我直言,这种方法将闪电般快速。同样对于 blob 分配是每条记录 8K。由于 db 服务器将 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);
}

?> 

我希望这有帮助。

如果您对详细信息有任何疑问,我将很乐意为您解答。

于 2013-03-28T10:23:57.773 回答
11

使用 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(删除所有与 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 个条目)。网上有很多测试用例。集合的内存是一个限制因素。

于 2013-10-26T05:42:46.727 回答
8

您需要使用外部防火墙而不是 PHP 来执行此操作。我推荐pfSensePF。我以前用过它,它非常好用,非常直观,而且非常强大。这是最好的系统管理员的选择。我在 FreeBSD 上运行它,但它在 OpenBSD 上也很好用。我是一个 Linux 人,所以这样说我很痛苦,但不要试图在 Linux 上运行它。BSD 很简单,你可以很快搞清楚。

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

您还应该知道,许多垃圾邮件发送者可以使用Tor之类的东西快速切换 IP 。要解决此问题,您应该在阻止列表中包含已知的出口节点地址(此列表可从各个地方获得)。

于 2013-03-22T21:33:37.620 回答
5

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

于 2013-03-22T21:09:52.897 回答
4

有一个名为 ipset 的带有 netfilter 的项目,因此您可以将 ip 添加或删除到列表中,您只需针对该列表创建规则

http://ipset.netfilter.org/

于 2013-03-31T09:48:08.740 回答
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 的唯一原因是。避免主题查看内容。
所以我有一个想法,我建议你这样。
使用一个入口点。

我已经使用过这个解决方案。

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

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

htaccess 将在不更改 url 的情况下执行以下内容。所以用户不会有任何感觉。

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

所以每个请求都变成了一个具有不同 $_GET['r'] 参数的请求。所以对于每一个请求,我们都会执行 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'];

它是如此简单。它的真实是如此复杂。但主要思想是相同的。你怎么看?

于 2013-03-31T13:30:43.143 回答
3

如果您要阻止 IP,您确实应该在防火墙级别执行此操作(您不希望来自不受欢迎的 IP 地址的用户进入您的系统很远)。因此,我建议编写一个 bash 脚本来查询数据库并相应地修改您的防火墙配置文件(这假设您想要一个利用存储在您的 Web 数据库中的 IP 地址的解决方案——很可能有一个更好的地方来存储这些信息)。

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

如果您使用 iptables,则需要使用以下命令将 IP 地址添加到黑名单:http ://www.cyberciti.biz/faq/linux-iptables-drop/

于 2013-03-22T20:58:33.937 回答
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]

然后,您将此脚本加载到另一个程序,该程序对您的固件 CLI 执行远程 telnet 或 ssh 调用。

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

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

最好的,

于 2013-03-31T20:59:12.037 回答
0

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

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

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

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

.htaccess将是我的第二选择。

于 2013-03-31T19:54:40.790 回答
0

对列表中的 IP 进行地理查找。我自己的经验表明,大多数恶意(即垃圾邮件)连接都来自中国。如果你发现自己也是这样,而且你没有特别需要为中国服务,看看你能不能在防火墙级别有效地封锁整个国家。

于 2013-03-26T20:10:46.000 回答