可靠的企业战略,数字化转型,智能化转型和企业架构智库

【网络安全】如何调整您的WAF安装以减少误报

使用调整的ModSecurity / Core规则集安装优化您的NGINX设置。

Watch the free webcast "Optimizing ModSecurity on NGINX and NGINX Plus," hosted by Christian Folini.

站点管理员使用Web应用程序防火墙(WAF)来阻止恶意或危险的Web流量,但也存在阻止某些有效流量的风险。误报是您的WAF阻止有效请求的实例。

误报是每次WAF安装的天敌。每个误报都意味着两件坏事:你的WAF工作太辛苦,消耗计算资源以便做一些不应该做的事情,并且不允许合法的流量通过。产生太多误报的WAF造成的伤害可能与成功攻击造成的伤害一样糟糕 - 并且可能导致您在挫折中放弃使用您的WAF。

调整您的WAF安装以减少误报是一个繁琐的过程。本文将帮助您减少NGINX上的误报,让您进行干净安装,允许合法请求通过并立即阻止攻击。


WAF引擎ModSecurity最常用于与OWASP ModSecurity核心规则集(CRS)协调。这为Web应用程序攻击创建了第一道防线,例如OWASP Top Ten项目所描述的攻击。

CRS是用于对传入请求中的异常进行评分的规则集。它使用通用黑名单技术在攻击应用程序之前检测攻击。 CRS还允许您通过更改配置文件crs-setup.conf中的Paranoia Level来调整规则集的激进程度。

误报与真实攻击相互混合


由于使用CRS导致的误报导致阻止合法用户的恐惧是真实的。如果您有大量用户或具有可疑流量的Web应用程序,则警报的数量可能会令人生畏。

开箱即用的CRS配置已经过调整,可以积极地减少误报的数量。但是,如果您对默认安装的检测功能不满意,则需要更改偏执等级以改善覆盖范围。在配置文件中提高偏执等级会激活默认关闭的规则。它们不是Paranoia Level 1默认安装的一部分,因为它们有产生误报的倾向。妄想症等级设置越高,实施的规则就越多。因此,规则集变得越激进,产生的误报就越多。

考虑到这一点,您需要一种减轻误报的策略。如果允许它们与真实攻击的痕迹混合,它们会破坏规则集的价值。所以,你需要摆脱误报,以便最终得到一个干净的安装,让合法的请求通过并阻止攻击者。

问题很简单:

  1. 如何识别误报
  2. 如何处理个人误报
  3. 实用的方法是什么样的? (或者:你如何扩展这个?)


当十几个误报出现时,很难识别它们。对应用程序的深入了解有助于从恶意的请求中获取良性但可疑的请求。但是,如果您不想逐个查看它们,则需要过滤警报并确保最终只得到包含误报的数据集。因为如果你不这样做,你可能最终会调出指向发生攻击的真实警报。

您可以使用IP地址来识别已知用户,已知本地网络等。或者,您可以假设成功通过身份验证的用户不是攻击者(可能是天真的,具体取决于您的业务规模)。或者您可以使用其他一些识别方法。确切的方法实际上取决于您的设置和您的测试过程。

当您确定个体误报时,有多种方法可以避免将来重复误报。使用CRS,您不会编辑规则集,因为它意味着作为一个不可编辑且连贯的整体运行。相反,您可以通过ModSecurity指令重新配置规则集的使用方式。这使您可以将相同的更改应用于CRS的未来版本,而无需重新创建编辑。

通常有四种处理误报的方法:

  1. 您可以完全禁用规则
  2. 您可以通过规则从检查中删除参数
  3. 您可以在运行时禁用给定请求的规则(通常基于请求的URI)
  4. 您可以在运行时从规则检查中删除给定请求的参数

很明显,禁用某些规则会影响规则集的检测率。实际上,您希望对规则集进行最小的更改,以允许良性但可疑的请求通过,从而避免误报。在各种情况下做出最佳选择需要一些经验。

我在cheatsheet上总结了四个一般变体,你可以从netnea.com下载

缩放调整过程


掌握这个调整过程需要一些练习。当你对这个新手时,看起来并不像它会扩展。事实上,许多新人只是接近警报并尝试通过它 - 通常没有太大的成功。

鉴于ModSecurity警报的可读性较低,人们采用这种方法的事实并不令人意外。以下是真实攻击的一个例子(真正的正面):

2018/01/15 18:52:34 [info] 7962#7962: *1 ModSecurity: Warning. Matched "Operator `PmFromFile' with parameter `lfi-os-files.data' against variable `ARGS:test' (Value: `/etc/passwd' ) [file "/home/dune73/data/git/nginx-crs/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf"] [line "71"] [id "930120"] [rev "4"] [msg "OS File Access Attempt"] [data "Matched Data: etc/passwd found within ARGS:test: /etc/passwd"] [severity "2"] [ver "OWASP_CRS/3.0.0"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-lfi"] [tag "OWASP_CRS/WEB_ATTACK/FILE_INJECTION"] [tag "WASCTC/WASC-33"] [tag "OWASP_TOP_10/A4"] [tag "PCI/6.5.4"] [hostname "127.0.0.1"] [uri "/index.html"] [unique_id "151603875418.798396"] [ref "o1,10v21,11t:utf8toUnicode,t:urlDecodeUni,t:normalizePathWin,t:lowercase"], client: 127.0.0.1, server: localhost, request: "GET /index.html?test=/etc/passwd HTTP/1.1", host: "localhost"

 


如果您有数千个这样的日志条目,警报疲劳是正常的反应。你需要工具来找到自己的方式。最简单的工具是一组shell别名,它们以更易读的方式提取信息并显示它 - 我开发了一组执行此任务的shell别名。您也可以从netnea下载这些。

以下是此文件中的一个示例,它提供了警报消息的摘要:

alias melidmsg='grep -o "\[id [^]]*\].*\[msg [^]]*\]" | sed -e "s/\].*\[/] [/" -e "s/\[msg //" | cut -d\  -f2- | tr -d "\]\"" | sed -e "s/(Total .*/(Total ...) .../"'


以上面的示例消息为例,并应用此别名:

$> cat sample-alert.log | melidmsg
930120 OS File Access Attempt


这样做要好得多:ID为930120的规则是由请求触发的,指向操作系统文件访问尝试。可疑请求试图访问/ etc / passwd-服务器的本地密码文件,Web服务器永远不应该访问该文件。这是真正攻击的明显迹象。

现在让我们列出一个在Paranoia Level 3上使用CRS安装的典型非调整站点上发现的误报列表。我应用了melidmsg别名并按规则对它们求和(sort | uniq -c | sort -n):

      

 8 932160 Remote Command Execution: Unix Shell Code Found
     30 921180 HTTP Parameter Pollution (ARGS_NAMES:op)
     75 942130 SQL Injection Attack: SQL Tautology Detected.
    275 942200 Detects MySQL comment-/space-obfuscated injections and backtick termination
    308 942270 Looking for basic sql injection. Common attack string for mysql, oracle and others.
    402 942260 Detects basic SQL authentication bypass attempts 2/3
    445 942410 SQL Injection Attack
    448 921180 HTTP Parameter Pollution (ARGS_NAMES:fields[])
    483 942431 Restricted SQL Character Anomaly Detection (args): # of special characters exceeded (6)
    541 941170 NoScript XSS InjectionChecker: Attribute Injection
   8188 942450 SQL Hex Encoding Identified


给出的第一个数字是误报的数量。第二个数字是触发的规则的ID。后面的字符串是规则的文本描述。

这是分布在11个不同规则上的数千个错误警报。这不仅仅是我们可以一口咬下来的,而只是从第一个警报开始就不会让我们到任何地方。至少我们获得了定量信息。好像最大的问题似乎是看起来是十六进制编码的信息,CRS认为它可能是一个混淆的攻击。通常,这是一个会话ID,如果它在cookie中。

然而,即使以最大数量的误报开始也会给我们带来即时的满足感,但这并不是最好的方法。当我们点击XSS规则时,由此规则产生的541警报(编号941170)很可能会转换为数十种不同的形式和参数。这将使得在单个块中正确处理它们变得非常困难。

所以,我们不要急于求成。让我们想出一个更好的方法。

为调整过程带来意义和理由


我们需要开发更好的方法是一个不同的视角。我们不应该再看不同的规则了。我们应该查看触发规则的请求。因此,我们将查看成千上万的请求,并将误报放在一边,而不是几千个警报。

为此,我们需要知道服务器上所有请求的异常分数。不仅是那些得分,还有那些没有引起任何警报的人。该分数显然为零,​​但必须知道此类别中的请求数量,以便更好地了解误报的数量。

当我以前在Apache上工作时,让ModSecurity在服务器的访问日志中报告每个请求的异常分数是相当容易的。使用NGINX并不容易。因此,我们需要采用不同的技术:我们添加一条规则,在请求完成后报告异常分数(在ModSecurity的日志记录阶段5)。我们分配此规则ID 980145。

SecAction \
    "id:980145,\
    phase:5,\
    pass,\
    t:none,\
    log,\
    noauditlog,\
    msg:\'Incoming Anomaly Score: %{TX.ANOMALY_SCORE}\'"


当我们grep这个规则ID时,我们得到每个请求的分数。

重新格式化输出,我们可以有效地提取异常分数:

$> cat error-2.log | grep 980145 | egrep -o "Incoming Anomaly Score: [0-9]+" | cut -b25-
0
0
0
5
0
0
10
0
3
0
0
0
...


这些价值如何分配?

$> cat error-2.log | grep 980145 | egrep -o "Incoming Anomaly Score: [0-9]+" | cut -b25- | modsec-positive-stats.rb --incoming

INCOMING                     Num of req. | % of req. |  Sum of % | Missing %
Number of incoming req. (total) | 897096 | 100.0000% | 100.0000% |   0.0000%

Empty or miss. incoming score   |      0 |   0.0000% |   0.0000% | 100.0000%
Reqs with incoming score of   0 | 888984 |  99.0957% |  99.0957% |   0.9043%
Reqs with incoming score of   1 |      0 |   0.0000% |  99.0957% |   0.9043%
Reqs with incoming score of   2 |      3 |   0.0003% |  99.0960% |   0.9040%
Reqs with incoming score of   3 |   1392 |   0.1551% |  99.2512% |   0.7488%
Reqs with incoming score of   4 |      0 |   0.0000% |  99.2512% |   0.7488%
Reqs with incoming score of   5 |    616 |   0.0686% |  99.3199% |   0.6801%
Reqs with incoming score of   6 |      1 |   0.0001% |  99.3200% |   0.6800%
Reqs with incoming score of   7 |      0 |   0.0000% |  99.3200% |   0.6800%
Reqs with incoming score of   8 |     10 |   0.0011% |  99.3211% |   0.6789%
Reqs with incoming score of   9 |      0 |   0.0000% |  99.3211% |   0.6789%
Reqs with incoming score of  10 |   5856 |   0.6527% |  99.9739% |   0.0261%
Reqs with incoming score of  11 |      0 |   0.0000% |  99.9739% |   0.0261%
Reqs with incoming score of  12 |      0 |   0.0000% |  99.9739% |   0.0261%
Reqs with incoming score of  13 |     12 |   0.0013% |  99.9752% |   0.0248%
Reqs with incoming score of  14 |      0 |   0.0000% |  99.9752% |   0.0248%
Reqs with incoming score of  15 |     82 |   0.0091% |  99.9843% |   0.0157%
Reqs with incoming score of  16 |      0 |   0.0000% |  99.9843% |   0.0157%
Reqs with incoming score of  17 |      0 |   0.0000% |  99.9843% |   0.0157%
Reqs with incoming score of  18 |      5 |   0.0005% |  99.9849% |   0.0151%
Reqs with incoming score of  19 |      0 |   0.0000% |  99.9849% |   0.0151%
Reqs with incoming score of  20 |      5 |   0.0005% |  99.9855% |   0.0145%
Reqs with incoming score of  21 |      0 |   0.0000% |  99.9855% |   0.0145%
...
Reqs with incoming score of  29 |      0 |   0.0000% |  99.9855% |   0.0145%
Reqs with incoming score of  30 |    126 |   0.0140% |  99.9995% |   0.0005%
Reqs with incoming score of  31 |      0 |   0.0000% |  99.9995% |   0.0005%
...
Reqs with incoming score of  60 |      4 |   0.0001% |  99.9999% |   0.0001%

Incoming average:   0.0796    Median   0.0000    Standard deviation   0.9168

在这里,我使用了一个名为modsec-positive-stats.rb的脚本,我也在netnea网站上发布了这个脚本。它需要STDIN的异常分数并对它们运行几个统计数据。您可以获得各种百分比,平均分数,中位数甚至标准差。

让我们现在专注于第二列:它给出了传入异常分数的分布。现在我们有了数字,我们可以想象它们:

fixme图形分布

fixme graphic distribution
图1.传入请求的异常分数的分布。 Y轴上每个分数的请求数,以对数标度表示。资料来源:O'Reilly。
我们得到一条非常崎岖不平的曲线,在长尾中向右延伸。最高请求数的异常得分为0.这是所有通过规则集而没有任何警报的请求被记录的地方。在此位置右侧是触发一个或多个警报的请求。

我们越靠右,分数越高。你可以说这些是最可疑的请求。左侧更多,分数较低,我们看到仅触发一个或两个规则的请求。也许这些甚至都不是关键规则,但那些具有更多信息的弯曲 - 就像缺少Accept标题等。

如您所知,核心规则集是一种异常评分规则集。请求首先通过所有规则,并计算异常分数。然后,我们将累积的异常分数与异常阈值进行比较。默认阈值为5,这意味着单个关键规则警报将导致阻止请求。这就是我们希望它在安全设置中的方式。

实际上,如果我们愿意,我们可以更改此限制。在整合过程中,这实际上很有意义。如果我们将限制设置为10,000(是的,我在生产系统中看到了10,000分),我们可以确定核心规则集不会阻止任何合法请求。这样,我们可以使用真实数据检查规则集和我们的服务,并开始调整误报。

目标是将异常评分阈值降低到5,但我们不要试图一步完成。最好从10,000到100,然后到50,到20,到10,再到5。

如果我们采取这条道路,那么采取第一步并将异常限制设定为较低值的方法是什么?再看一下图1中的图表。如果我们在该图表中将异常阈值设置为50,我们将以60的分数阻止请求。因此,如果我们想要降低限制,我们会更好地处理这些请求以及所有其他要求得分更高的请求超过目标限制。

我们不需要立即查看大量剩余的误报 - 通常是令人生畏的景象。相反,我们专注于少量具有最高异常分数的请求作为开始。如果我们确定了这些,我们可以过滤导致这些高分的警报。我们可以借助作为ModSecurity警报消息一部分的唯一标识来完成此操作。

$> grep 980145 error.log  | grep "Incoming Anomaly Score: 60\"" | melunique_id > ids


然后使用这些唯一ID来过滤那些导致上述得分的警报:

$> grep -F -f ids error.log | melidmsg | sort | uniq -c | sort -n
  8 941140 XSS Filter - Category 4: Javascript URI Vector
 12 932130 Remote Command Execution: Unix Shell Expression Found
 12 941210 IE XSS Filters - Attack Detected.
 16 941170 NoScript XSS InjectionChecker: Attribute Injection


然后我们可以调整这些误报并使我们能够降低异常阈值,同时高度保证不会阻止合法客户。如果您对此不熟悉,不要急于求成。调整一些误报,然后让调整后的规则集运行几天。

随着时间的推移,您已准备好进行下一次迭代:调整误报,阻止您进一步降低限制。当你看到这些请求时,你会发现一个惊人的发现。第一次调整迭代还减少了您在第二轮中必须检查的请求数。

如果你想一下这一点,那就很有意义了。导致得分最高的许多规则是导致中等分数的相同规则。因此,如果我们处理导致得分最高的第一批规则,则第二次调整迭代只需处理那些在得分最高的请求中不存在的误报。

这整个方法允许您构建规则集的调整以消除误报。看起来像是一个不可逾越的山峰,已经变成了一系列较低的,可行的山丘。您可以逐个使用它们,通常在给定的迭代中解决5到10个误报。 (如果更多,请采取更小的步骤。)

选择正确的误报来处理不再是猜谜游戏,您将能够快速降低异常分数阈值。因此,从降低异常限制的那一刻起,您就可以提高站点的安全性。 (根据我的经验,实质性保护只从20或更低的阈值开始)。

简而言之,调整过程


让我总结一下这种处理误报的方法。我们总是在阻止模式下工作。我们最初将异常阈值设置为一个非常高的数字,并进行多次迭代:

  1. 查看具有最高异常分数的请求并处理其误报
  2. 将异常分数阈值降低到下一步
  3. 冲洗并重复,直到异常评分阈值为5

我通常称之为误报调整的迭代方法。有些人称之为Folini方法,因为我似乎是唯一一个以这种方式使用ModSecurity和CRS教授课程的人。

许多人已经在Apache上以非常成功的方式使用了这种技术多年。我强烈建议您试试NGINX ModSecurity / CRS设置。

 

Links:

This post is a collaboration between O'Reilly and NGINX. See our statement of editorial independence.

 

原文:https://www.oreilly.com/ideas/how-to-tune-your-waf-installation-to-reduce-false-positives

本文:http://pub.intelligentx.net/how-tune-your-waf-installation-reduce-false-positives

讨论:请加入知识星球或者小红圈【首席架构师圈】