2014年4月11日星期五

墙外楼: 编程随想:对 OpenSSL 高危漏洞 Heartbleed 的感慨、分析和建议

墙外楼
网络热门话题追踪 
#1 Retargeting Platform

Our Average Customer Earns $10 For Every $1 Spent
From our sponsors
编程随想:对 OpenSSL 高危漏洞 Heartbleed 的感慨、分析和建议
Apr 11th 2014, 09:25, by 墙外仙

Source

  4月7日曝光的 Heartbleed 漏洞(编号CVE-2014-0160)已经在相关的 IT 领域(尤其是信息安全领域)造成很大的风波。在安全圈混了十多年,不写点啥有些说不过去。所以今天就这个话题,谈谈俺个人的观点,并给普通用户、程序员、开源社区、安全从业人员 分别提点建议。

★关于 Heartbleed 漏洞

  这个 Heartbleed,直译成中文就是"心脏出血"。听上去挺吓人滴,现实中也确实挺吓人滴。简而言之,这个漏洞足以载入史册。这几天,大伙儿正在经历信息安全历史上的一个重要时刻。或许你应该觉得荣幸 :)
  关于这个漏洞的描述,请看中文维基百科的"这里",俺就不浪费口水了。

★先发点感慨

  先发一通抱怨。不喜欢听俺抱怨的,请直接略过。

◇安全行业的耻辱

  对整个安全行业而言,这是巨大的耻辱。
  OpenSSL 是安全行业内应用非常广泛的开源加密库。一个用来保护安全的软件包,本身居然出现如此严重的漏洞(导致内存信息泄漏),实在太讽刺了。
  耻辱的还不光是 OpenSSL——就在一个月之前,GnuTLS 同样出现过高危漏洞(编号CVE-2014-0092)。这么短的时间之内,涉及到加密的两个最常用的基础库接连爆出高危漏洞,俺不禁要问:整个安全行业的面子要往哪儿搁?

◇开源社区的耻辱

  对整个开源社区而言,这是巨大的耻辱。
  OpenSSL 作为非常知名的开源项目之一,这么严重的漏洞居然两年后才曝光(2012年3月的稳定版就已经包含此漏洞)。难道关键模块的代码变更之后不进行【Code Review】?亦或是 Code Review 只是走过场?

★对该漏洞的风险评估

  该漏洞曝光前后,风险是不同滴。以下俺分别介绍。
  下面会涉及两个术语——"未公开漏洞"和"零日漏洞"——两者的区别请看俺之前的博文。

◇漏洞曝光【之前】——作为"未公开漏洞"

  目前已经有报道指出,在4月7日之前,这个漏洞就已经被骇客利用(报道在"这里")。另外,圈内也有小道消息流传,说某些攻击者早已拿到此漏洞并加以利用。
  在这个阶段,知道漏洞的人很少。这时候,该漏洞属于"宝贵资源",掌握它的人只会拿来攻击"高价值目标"。所以,如果你只是一个普通网友,或者只是一个小型网站,通常不用担心这个阶段的风险。

◇漏洞曝光【之后】——作为"零日漏洞"

  4月7日那天,OpenSSL 发布了公告。发现漏洞的 CloudFlare 公司也发布了公告。于是这个漏洞瞬间传遍全球。几小时之内,大量的攻击工具应运而生(这个漏洞太容易利用了)。然后大量的"脚本小子"(洋文叫 script kiddie)就拿着这些别人写好的工具,对大量的网站进行扫描。
  由于时差的关系,以及某些大公司的官僚风气,很多网站来不及在第一时间升级 OpenSSL 这个软件包(这就是所谓的"零日风险")。估计某些效率低下的公司,甚至到今天都还没升级。
  在这个阶段,对于普通网友
假设你登录过某个网站
假设该网站没有及时升级 OpenSSL
假设该网站存储的是【明文密码】 或者 该网站只在【服务端】加密/Hash密码
假设在你登录期间正好有人利用该漏洞进行信息收集
如果上述几个条件【同时】成立,那么你的密码【有一定的概率】可能被攻击者收集到。

★对该漏洞的技术分析

  (本节聊的是该漏洞在技术层面的细节。对 C 语言了解不多的同学,请自行略过,以免浪费时间)
  这个漏洞从本质上讲,就是"缓冲区溢出(buffer overflow)"。几乎所有这类漏洞的根源,都是由于程序员的疏忽——对缓冲区涉及的相关"长度"没有仔细检查。
  已经有老外写了详细的 Heartbleed 漏洞分析文档(洋文在"这里")。对于懒得看长篇的同学,俺把精华摘录如下:
  问题出在 OpenSSL 中的 ssl/d1_both.c 文件中的 dtls1_process_heartbeat 函数(看名称就知道该函数是干啥滴)。相关代码有如下(俺补了中文注释)

int dtls1_process_heartbeat(SSL *s)
{
  unsigned char *p = &s->s3->rrec.data[0], *pl; //p 指向对端发来的心跳数据包
  unsigned short hbtype;
  unsigned int payload;
  unsigned int padding = 16; /* Use minimum padding */
……
  hbtype = *p++; //心跳数据包的第0个字节,表示心跳包的类型
  n2s(p, payload); //接下来2个字节是长度。n2s 是个宏,把这2个字节取出,保存为整型,然后指针移2字节
  pl = p; //此时p指向第3个字节——也就是对端提供的心跳包载荷
……
  unsigned char *buffer, *bp;
  int r;
  buffer = OPENSSL_malloc(1 + 2 + payload + padding); //根据 payload 分配内存,额外的3字节用于存放类型和长度
  bp = buffer;
……
  *bp++ = TLS1_HB_RESPONSE; //填充类型
  s2n(payload, bp); //填充长度
  memcpy(bp, pl, payload); //填充回应包的载荷【亮点在这里】

  如果对端发来的心跳包有猫腻——包长度跟实际载荷不匹配,那么在发送回应包的时候,那句 memcpy 语句就会把心跳包之后的内存区块也一起 copy 进去,然后发给对端。内存信息就泄露了。
  搞清楚问题的根源之后,补丁其实很简单——只需加入2个判断语句(寥寥4行代码)。

if (1 + 2 + 16 > s->s3->rrec.length)
  return 0; //忽略长度为 0 的心跳包
hbtype = *p++;
n2s(p, payload);
if (1 + 2 + payload + 16 > s->s3->rrec.length)
  return 0; //忽略长度与载荷不匹配的心跳包
pl = p;

★给普通用户的建议

  前面的"风险评估"已经分析了——对于普通网友而言,你的风险主要在于"漏洞曝光之后那1-2天"(也就是"0day"导致的风险)。
  从4月7日开始的这几天(尤其是4月8日和4月9日),如果你曾经登录过某个重要的帐号,为了以防万一,改一下密码。国内的很多大网站(包括:电子邮件、电子商务、网银、社交网络、等等)都被发现有 Heartbleed 漏洞。
  不过值得庆幸的是,Google 应该没事。根据 OpenSSL 官方的公告(链接在"这里"),漏洞的发现者之一 Neel Mehta 是 Google 的人。所以,俺非常确信,Google 的服务器肯定在4月7日之前就解决此问题了。

★给程序员/架构师的建议

◇"多进程"在安全方面的优点

  刚开博的头一个季度,俺就发过一篇帖子《架构设计:进程还是线程?是一个问题!》,其中提到了多进程的若干优点。今天再来老调重弹,说说"多进程"在安全方面的优点。
  针对"缓冲区溢出"
  绝大多数的"缓冲区溢出攻击",都只对当前进程有效。如果你的系统切分成若干个进程,一旦不幸碰上这类攻击,顶多只有一个进程出问题——不至于死得太难看。
  不少程序员存在一个误解:以为只有 C/C++ 才会存在缓冲区溢出。其实不然。像 Java/Python 之类的,虽然没有原生的指针,虽然语言的虚拟机/解释器本身对缓冲区越界有严格的检查。但是不要忘了:这些语言的虚拟机/解释器本身也是用 C/C++ 来编写的。说不定虚拟机自己就有问题。
  针对"信息泄露"
  这次的"Heartbleed漏洞",从技术上讲属于"缓冲区溢出"类型,从逻辑讲属于"信息泄漏"类型。
  如果整个系统切分成很多轻量级的进程,每个进程包含的内存信息自然就少了。一旦出现"信息泄漏"的漏洞,损失会小很多。
  针对"拒绝服务"
  多进程除了可以降低上述两类的受伤程度,还可以降低"拒绝服务攻击"导致的受伤程度。
  比如某些漏洞通过让"进程崩溃"来起到"拒绝服务"的效果。如果系统的架构是多进程的,并且相关进程具有一定的自我恢复机制,那么就可以降低这类攻击造成的伤害程度。

◇关于密码的"客户端加密/散列"

  如今比较成熟的网站,应该不会采用"明文"的方式存储用户密码了。很多程序员/架构师都明白,要把用户密码进行散列(Hash)之后再存储。但是这里面有一个细节,很多人忽略了。那就是:"服务端散列"vs"客户端散列"?
  所谓的"服务端散列"就是:用户输入密码之后,以明文的方式传送到服务端,然后在服务端进行散列,散列的结果保存到数据库。
  所谓的"客户端散列"就是:用户输入密码之后,现在浏览器这端用 JavaScript 计算散列值,然后把算好的散列值传送到服务端,再保存到数据库。
  到目前为止,大多数网站(包括很多大型网站)还是采用服务端散列。其实捏,"客户端散列"比"服务器散列"的安全性更好。为啥捏?
  对于成熟的大型网站,虽然用户登录过程都是基于加密 HTTPS 传输。但是 HTTPS 不是绝对安全的。俺相信 HTTPS 协议本身的设计是很完备的,但是"协议没问题"不等于"整个HTTPS传输没问题"。整个 HTTPS 传输,可能出问题的环节如下:
  1. 软件可能出问题
  比如这次的 Heartbleed 漏洞,就属于典型的"基础软件库出问题"
  2. 中间人攻击(比如证书出问题)
  因为 HTTPS 的加密需要依赖于证书体系。如果证书体系出问题就没戏了。
  说两种常见的可能性。
可能性之一:某个 CA 的根证书私钥被盗,那么盗用者就可以随意伪造CA证书。真实的案例是 2011年 DigiNotar 被入侵。
可能性之二:某个 CA 自己就不检点。最贴切的例子非 CNNIC 莫属。这方面的介绍请参见俺4年前的博文《CNNIC 干过的那些破事儿》、《CNNIC 证书的危害及各种清除方法》
  看完上述介绍,你应该明白,这几个环节出问题,都有可能让攻击者收集到传输过程中的【明文】密码。如果密码已经在客户端进行散列,那么风险就降低很多。有经验的开发人员会对密码进行【撒盐】的散列处理,并且精心选择合适的散列算法(速度越慢越好)。如此一来,即使攻击者拿到散列结果,也非常非常难逆向推导出原始的密码。

★给有志于成为黑客的同学的建议

  先声明:"黑客"与"骇客"是完全不同的两类人。俺之前发过一篇《每周转载:关于黑客文化和黑客精神》,或许有助于你了解两者的差异。
  俺敢跟任何人打赌,OpenSSL 和 GnuTLS 的代码中,类似的低级错误还有好多。而且 GnuTLS 可能比 OpenSSL 还多。有志于成为黑客的同学,可以考虑去分析这俩的代码,找出其中的漏洞。
  通过上面那段代码分析,你应该会意识到:这个漏洞本身并没有涉及到多么高深的 C 语法或技巧。那么,发现该漏洞的难点在哪里?俺觉得难在:
1. 对整体结构的把握
因为 OpenSSL 和 GnuTLS 的代码量还是比较庞大的。很少有人能把握整体的结构。
2. 对相关协议的了解
想通过看代码分析这两个软件的漏洞,你还需要对相关的协议很熟悉。比如这次的漏洞,就涉及到"心跳协议"的细节。
3. 耐心/恒心
同时具备上述两条的人本来就不多。然后捏,这些人还未必有足够的耐心去把整个代码(哪怕是其中某个模块的代码)通读一遍。
(第三点或许才是最难的)

  如果能够搞定上述三点,并且你的运气足够好,那么你有望独立发现一个高危漏洞。

Shop Amazon's New Kindle Fire

相关日志

You are receiving this email because you subscribed to this feed at blogtrottr.com.

If you no longer wish to receive these emails, you can unsubscribe from this feed, or manage all your subscriptions

没有评论:

发表评论