📚 第九章 网络安全
📄 一句话概括

特征匹配能抓住「见过的」,抓不住「换个写法的」。 误报和漏报不可能同时为零,这是所有检测系统的宿命。

📝 本节和前面几节的关系

防火墙按「地址和端口」放行,它不知道放过去的包里装的是什么。 IDS 就是补这一块的:它不拦路,只盯着看。 Demo 用五条特征跑七段流量,看漏在哪、误在哪,再改个参数看能救回多少。

一、两条完全不同的思路

检测入侵有两套办法,思路正好相反:

思路 怎么做 强项 软肋
特征匹配 比对已知的坏字符串 误报低 新攻击抓不到
异常检测 先学正常,再看谁不像 能抓没见过的 误报高

第一行是主流,因为它的结论好解释:报警时能直接说「它命中了这条规则」,运维一看就懂。 但它的前提很硬: 必须有人先见过这种攻击,把它写进特征库。 所以它对「零日」完全无效——第一天出现的攻击,特征库里一定没有。 第二行听着更聪明,落地却很难:「正常」这件事本身没法定义, 一个每天有新业务的网络,天天都在「异常」。 所以现实中的产品是把两者混着用: 特征负责「已知的、必须抓的」,异常负责「冒个头提醒你去看一眼」。

二、三种绕过手法

特征匹配的对手不是「更强的攻击」,是「换个写法」。 手法只有三类,但每一类都很难防。 第一类是编码。 把 ../ 写成 %2E%2E%2F,服务器收到后会解码再处理,IDS 看到的是没解码的原文。 两边对同一串字节的理解不一样,检测就漏了。 这个现象有个名字,叫解析器差异,它是绕过检测最经典的手法的根。 第二类是拆分。 把攻击字符串拆到两个包里,第一个包发 un,第二个包发 ion select。 如果 IDS 只看单个包、不做重组,它就永远看不见完整的特征。 所以好的 IDS 必须做流重组,而重组又要耗内存、要考虑乱序和超时。 第三类是变慢。 把一次扫描拉长到几天,每个包之间隔几十秒。 任何「单位时间内次数超阈值就报警」的规则都会失效,因为阈值是按秒算的。 这类攻击在日志里看不出来,只能靠「跨天长周期的基线」发现——这也是为什么日志要留那么久。

三、IDS 和 IPS,一字之差

IDS 只报警,IPS 会当场阻断。 这一个字的差别,带来的是完全不同的责任。 报警错了,代价是有人白跑一趟;阻断错了,代价是业务断了。 所以 IPS 的规则必须更保守,宁可漏也不能误杀,而 IDS 可以激进一点,多报几条让人去筛。 部署位置也完全不同:IDS 挂在镜像口上,只看不改,断网也不影响业务; IPS 必须串在链路里,它一挂,业务就断。 所以 IPS 一定要有 bypass,也就是「自己出故障时自动短路」的能力。 最后一个判断值得记住:「误报率」和「漏报率」不可能同时为零。 想少漏,就把阈值放松,误报就多;想少误报,就收紧,漏报就多。 这不是产品做得好不好的问题,是检测这件事的数学性质—— 任何一个二分类器,都只能在两个错误之间选一个位置。

四、动手 Demo:特征匹配的检出率,取决于攻击者会不会换个写法

五条特征、七段流量(四段恶意),先按区分大小写跑一遍,再改成不区分跑一遍。

SIGS = ["../", "<script>", "union select", "/etc/passwd", "cmd.exe"]

TRAFFIC = [
    ("普通穿越",   "GET /../../etc/passwd HTTP/1.1",        True),
    ("穿越做了编码", "GET /%2e%2e%2f%2e%2e%2fetc/passwd",      True),
    ("普通注入",   "GET /q?id=1 union select pwd",           True),
    ("注入改大写", "GET /q?id=1 UNION SELECT pwd",           True),
    ("正常文章",   "GET /post?title=<script>入门",           False),
]

def scan(s, ci):
    hit = []
    for sig in SIGS:
        if (sig.lower() in s.lower()) if ci else (sig in s):
            hit.append(sig)
    return hit

for name, payload, bad in TRAFFIC:
    print(name, bad, scan(payload, False), scan(payload, True))

实跑输出:

--------------------------------------------------------------------
特征匹配的检出率,取决于攻击者会不会换个写法
--------------------------------------------------------------------
**IDS 最常见的一种做法是特征匹配**:**准备一串「坏字符串」,流量里出现了就报警。**
**下面五条特征、七段流量,其中四段是恶意的。**

  序     特征
  ----------------------
  1      `../`
  2      `<script>`
  3      `union select`
  4      `/etc/passwd`
  5      `cmd.exe`

  流量                 真的恶意吗     IDS 怎么说     命中的特征
  ----------------------------------------------------------------------
  正常首页             **否**         **放过**       `—`
  正常搜索             **否**         **放过**       `—`
  普通穿越             **是**         **报警**       `../、/etc/passwd`
  穿越做了编码         **是**         **放过**       `—`
  普通注入             **是**         **报警**       `union select`
  注入改了大写         **是**         **放过**       `—`
  正常文章带尖括号     **否**         **报警**       `<script>`

**四段恶意只抓到 2 段,七段流量还错报了 1 段。**
**这个检出率不是调参调得不好,是方法本身的上限。**

**漏掉的第一段只做了一件事:把 `../` 写成 `%2e%2e%2f`。**
**服务器收到后会先解码再处理,而 IDS 看到的是没解码的原文**——
**两边对同一串字节的理解不一样,检测就漏了。**
**这叫「解析器差异」,是绕过检测最经典的手法**,所以现代 IDS 会做多层解码再匹配。

**漏掉的第二段只改了大写**:**`UNION SELECT` 和特征里的 `union select` 差了三对字母的大小写。**
**对数据库来说这两句完全等价,对字符串比较来说完全不同。**

**误报的那段更能说明问题**:**它只是一个讲 `script` 标签的技术文章标题**,
**因为标题里真的写了那对尖括号,就被当成了攻击。**
**所以误报的根源往往不是「特征写错」,而是「这段文本本来就该出现这个字符串」。**

**那把比较改成不区分大小写呢**:

  流量                 不区分大小写     命中的特征
  ---------------------------------------------------------
  正常首页             **放过**         `—`
  正常搜索             **放过**         `—`
  普通穿越             **报警**         `../、/etc/passwd`
  穿越做了编码         **放过**         `—`
  普通注入             **报警**         `union select`
  注入改了大写         **报警**         `union select`
  正常文章带尖括号     **报警**         `<script>`

**检出从 2 段涨到 3 段,但误报一段没少,编码那段照样漏。**
**所以「调参数」能治一种病,治不了另外两种。**

**最后说清 IDS 和 IPS 的分别**:**IDS 只报警,IPS 会当场阻断。**
**报警错了只是烦人,阻断错了就是事故**——**所以 IPS 的规则通常更保守,宁可漏也别误杀。**
**这个「误报与漏报不可能同时为零」的取舍,是所有检测系统的宿命。**

第一张表里有两行漏了:「穿越做了编码」和「注入改了大写」。 四段恶意只抓到两段,而七段流量里还错报了一段。 漏的第一段只是换了个编码,漏的第二段只是改了大写。 攻击者一行代码都没多写,检测率就从百分之百掉到一半。 这就是特征匹配的上限:它比的是一串字符,而字符有无数种等价写法。 误报的那一段更值得琢磨:它只是一个讲 script 标签的技术文章标题, 因为标题里真的出现了那对尖括号,就被当成了攻击。 所以误报的根源往往不是「特征写错了」,而是「这段文本本来就该出现这个字符串」。 把比较改成不区分大小写以后,检出从两段涨到三段,但误报一段没少,编码那段照样漏。 所以「调参数」能治一种病,治不了另外两种—— 这也解释了为什么今天的检测越来越依赖行为和上下文,而不是单看字符串。

30 秒实验

  1. 算一下:如果特征库有一千条规则、每天十亿个包,每条都对每个包做一次子串查找,是多少次比对? (一万亿次)
  2. 想一下:为什么「拆分到两个包」能让 IDS 漏检?
  3. 想一下:IPS 为什么必须有 bypass?

常见误区

  1. 以为「IDS 能抓住所有攻击」。 它抓的是「写得出来的特征」,换个编码就漏。
  2. 以为「调严一点就安全了」。 误报和漏报是跷跷板,压下一头就抬起另一头。
  3. 以为「IDS 和 IPS 只是叫法不同」。 一个只看、一个会拦,责任和部署方式完全不同。
自测三问(点开对答案) 1. 特征匹配和异常检测各自的软肋? —— **前者抓不到没见过的,后者误报高**,所以现实中混着用。 2. 什么叫「解析器差异」? —— **服务器会解码后再处理,IDS 看的是原文**,两边理解不一致就漏检。 3. 为什么 IPS 的规则要比 IDS 保守? —— **阻断错了就是业务事故**,宁可漏也不能误杀。

下一节看最后一道防线:装在你自己电脑上的那个东西。

相关笔记