每周异常:第 4期,如何避免客户端攻击造成的大量异常报警

在 JavaScript 异常监控过程中,发现有很多由于客户端连续攻击造成的大量异常,导致 监控系统报警。

2013-08-07 9 58 37

(图中大量不连续的波动,都是攻击造成的)

这种少量用户(UV)发起攻击造成的大量异常(PV)波动,不是我们实时监控需要关注的部分。 我们实时监控最重要的是要发现由于系统发布过程中,我们的发布的代码有问题导致大量 用户受影响而出现的波动(波形可能跟上图的每一个异常波形相似)。

理论上导致异常数据波动的场景包括:

  • 少量用户发起大量攻击。
  • 大量用户同时涌入或离开。
  • 发布问题代码影响到大量用户。

少量用户发起大量攻击造成数据波动,是我们可以不必实时关注实时报警的部分。

大量用户同时涌入或离开造成的异常数量(PV)波动,也是我们无需实时关注的部分, 另外我们也可以通过异常率规避波动。

场景 异常 PV 异常 UV 异常率(PV) 异常率(UV)
攻击 波动 无波动 波动 无波动
涌入 波动 波动 无波动 无波动
发布 波动 波动 波动 波动

注:

  • 异常率(PV) = 异常PV / 所在页面PV
  • 异常率(UV) = 异常UV / 所在页面UV

从上面的对照表可以看出,理论上我们只需要关注异常率(UV) 是否正常波动就可以了。

理想很性感,现实很骨感。

我们的数据仓库、数据分析系统对于实时 PV 有很强的处理能力,但是对于 UV 就感觉力有不逮。

起初想到在实时日志处理逻辑中使用缓存(伪代码):

class LogParse(){

  var user_cache = {};
  var MINUTES_FORMAT = "YYYYMMDDHHmm";
  var lastTime = moment().format(MINUTES_FORMAT);

  function parse(log){
    // PV 统计

    var now = moment().format(MINUTES_FORMAT);
    if(now !== lastTime){
      user_cache = {};
      lastTime = now;
    }
    if(user_cache.hasOwnProperty(user_id)){return;}
    // UV 统计
  }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 但是实时数据分析系统是分布式的,由多台服务器组成的集群同时处理日志,所以里面 的内部状态只有本机有效
  • 另外更致命的是,实时数据分析系统是无状态的,下次执行会 new 新的 LogParser 实例, 同一台机器的状态也保证不了。

于是我们又想到一种从客户端来标识用户的方法:

  1. 客户端单位时间内同一个页面抛出的同一个异常,第一次打上 uv 标记。
  2. 数据处理时遇到 uv 标记的日志,同时统计算做一个异常 uv。

由于客户端时间和服务端有时间差,每个客户端时间和服务端时间差也不一致,导致这种 方案会有稍大误差,但也基本能解决问题。

但是要在客户端打标记,则需要在客户端(cookie 或其他本地存储)记录每个异常 (url, file, line, message 相同被当作同一个异常)最后抛出的时间,每次抛出异常 都需要读写本地存储,稍嫌臃肿。

通过深入分析实际的攻击案例,发现攻击引发大量异常的场景中,客户端页面是不刷新或 重新访问的,都是直接在当前访问的页面执行上批量脚本。

因此我们可以使用局部变量,将当前访问的页面的每个异常最后抛出时间记录在内存中, 重新访问页面则重新开始记录。

这种方案对于防范攻击造成的大量异常非常有效;对于大量用户涌入引起的异常也有一定 效用,毕竟正常用户较少会频繁触发异常。

总之,通过这个方案,我们可以非常有效的实时监控异常波动,较少误报。

p.s. 这个想法是早上洗澡的时候想到的,还没开始实践。但是我信心满满写下这篇, 数据会来证明我是对的。

续 on 2013-08-20

是时候结帐了。正好发布一天,大家看数据:

2013-08-20 3 55 10

图中的灰色竖线是发布点,橙色是 JavaScript 异常,绿色是静态资源异常。

妈妈再也不用担心我的手机会收到误报的报警短信了。

Help
[count]gg 跳转到第 [count] 行,默认第 1 行。
[count]G 跳转到第 [count] 行,默认最后一行。
[count]j 向下跳转 [count] 行,默认跳转一行。
[count]k 向上跳转 [count] 行,默认跳转一行。
/ 开始搜索。按 <Esc> 退出。
gh 跳转到首页。
gb 跳转到博客首页。
gw 跳转到 Wiki 首页。
gt 跳转到我的 Twitter Profile 页。
gp 跳转到我的 Github Profile 页。
? 打开帮助。按 <Esc> 退出。