目录

阿里味儿的代码审计随想

目录

笔者几年前曾就职于阿里,虽然现在已经不在,但仍对其中的一些企业文化印象深刻。“阿里味儿” 是对于阿里企业文化的统称,其中最有味儿的话之一是 点线面体局势,大致是对应不同层级的员工要求。比如 P6 是点,在某些技能上实现专精;P7 是线,会懂得横向规划和发展,……诸如此类。

但是本文不谈阿里,也不谈企业文化,只是借此为引申谈谈最近在代码审计过程中的一些额外感想。

许多新手安全研究员在代码审计的时候喜欢盯着一些单点去看问题,比如一些敏感函数的调用或者特定的代码模式。在 C/C++ 中是通过分析 system、execve、popen 等调用去查找命令注入,通过 gets、scanf、strcpy 等查找缓冲区溢出,通过 printf 查找格式化字符串漏洞等;在 PHP 中则是通过 eval、popen、passthru 等危险函数去寻找 RCE,……

这种代码审计方法可以归类于之前文章中所说的“自底向上”方法,简单但是有效,往往能很快找到一些 “Low Hanging Fruit”。但是缺点也很明显,这些单点漏洞很容易被静态分析工具检测出来,甚至一个简单的正则表达式都可以轻易匹配到,人工审计在其中的竞争力不大;另外从单点上看往往会忽略上下文信息,造成无效的审计。

线

如果说单点审计是 Sink,那么进一步就是将可控输入源与故障点结合起来的 Source+Sink 审计方法。两点连成一线,从外部输入到危险函数的过程连接中可以发现真正存在的漏洞。比如从 Web 请求参数、请求头到最终的命令注入或者 JNDI 注入、开放性反序列化。

在线性的审计过程中,审计者需要重点关注的是数据流和控制流。目前许多高级的静态分析工具可以借助编译器或者解释器去实现代码抽象语法树的数据化,在结构化代码节点和边的情况下去使用特定规则进行搜索和查询,从而辅助对调用流图的分析。当然这类工具也有一定的局限性,比如对于 OOP 多态调用和底层语言的指针操作会存在一些误报和漏报,对于过深的调用链路在搜索过程中也会出现路径爆炸的情况。因此通常还是需要在工具的基础上借助专家经验进行进一步的约束优化以及必要的人工审计验证。

从点到线,已经实现了漏洞挖掘能力的一大提升,但在很多人眼里却只是入门。究其原因是这种方法往往只能找到一些中小型软件或者冷门框架的简单漏洞,而对于当前的一些复杂的代码项目,往往在设计之初或者迭代过程中对一些危险函数进行了严格的评估,很难在这些项目中找到格式化字符串漏洞或者命令注入了。

在这种情况下,审计者其实需要退一步从更大的方面上考虑,这里的面也可以理解为 “攻击面”。漏洞之所以称之为漏洞,是因为系统接受了非预期的数据、产生了非预期的行为,从而造成系统稳定性、完整性甚至用户数据隐私泄露的影响。从最终影响去反推可能造成危害的因素,对特定系统进行威胁建模,一方面可以保证目标的审计覆盖度,另一方面也可以在全面分析的过程中发现一些意料之外的边界情况。

曾经栈溢出只是一个讨厌的 segment fault,直到 1996 年有人在 Phrack 杂志中发表了一篇著名的文章 Smashing The Stack For Fun And Profit,从此拉开了二进制漏洞利用的黄金时代,极客与黑客席卷全球。从这个角度看,攻击面并不是一成不变的,而第一个发现新攻击面的人通常也是安全领域的个中翘楚。发现新攻击面的门槛看似很高,但实际上我们可以聚焦在某个软件或者其中的某个子系统中去实现属于自己的微创新。

达到攻击面的覆盖其实已经是一个优秀的安全工程师了,但在目前代码审计的过程中其实是“缺心眼儿”的。为什么这么说,因为到目前为止代码审计也只是为了漏洞挖掘而进行,眼里的代码只是一个个类和方法、中间有无数的数据交换,我们的目的只是在其中发现一条不经意裂缝并将其扩大和摧毁(漏洞利用)。用一些活动家的话来说就是,没有做到 “视人为人”。

代码只是算法和数据结构的载体,忽略整体而关注部分的最终结果往往是一叶障目不见泰山。以笔者的审计经验为例,之前在分析 Linux 内核某个网络子系统漏洞的时候,在代码中往复来回跳了半天最终也没找到某个 socket 复制的调用来源,最终还是通过动态调试回溯才找到的调用路径。当时在复盘的时候其实也发现了,如果在分析代码的时候能退后一步考虑整体功能,就可以很容易想到正确的分析方向,因为 socket 的生命周期就是 TCP 握手的生命周期,发生复制的正是在握手成功 accept 系统调用返回的阶段,返回的文件句柄正对应了所创建的客户端套接字。(关于当时的分析记录感兴趣的可以参考 Linux内核代码审计之CVE-2018-9568(WrongZone))

总而言之,代码审计的同时也要跳出代码本身,从功能角度去思考代码开发者这样实现的目的,这样才能在作者的基础上用更高的视野去发现作者本身所忽略的关键要素。

如果说“体”表示一个子系统,那么“局”就是整个目标项目本身。软件项目的开发通常是瀑布流自顶向下的,先设计全局架构,然后再划分成不同的子模块,由不同的团队去进行开发和维护。为了各个团队的开发者能够更好地协作,不可避免地会存在一个或者多个架构师的角色,定义各模块的边界,定义整体的编码规范和各自的对接方法。在更多情况下,“安全” 是软件架构中的一个基本功能,因此也由架构师进行统筹规划。

审计大型软件项目时,可能会发现不同的子模块有不同的编码习惯,但是子模块之间又有一些共性,比如使用了相同的整数溢出检查方法。在完成若干个子模块的代码审计后,正是一个众观全局的绝佳时机,通过对比不同的子模块,可以知道哪些模块的开发者是偏执狂,在每个偏僻的角落都加上了防御性编程;可以知道哪些模块年久失修,是一个藏污纳垢的好地方,更有可能找出新的漏洞;也可以知道架构师做了哪些安全布局(但偏偏就是有人不听话),从而找出潜在的遗漏点。

通览全局,从架构师的角度去审视代码,而不仅仅是站在开发者的角度,大概就是 “局” 所表达的含义了。

安全行业素有 “态势感知” 一说,用来表示发现未知攻击,以期防患未然,提前防御。而个人理解在代码审计中应该也有对应的境界。所谓 “势”,就是举一反三、一叶知秋,不仅仅是一个项目代码中的问题,而是在相关行业中普遍存在。

以 SSRF 漏洞为例,大家都知道是服务器对客户端指定 URL 校验不严格导致的请求伪造问题,一般只是分析校验的实现或者黑盒测试,漏洞报告总是 case by case。但某安全研究员就通过对 URI RFC 的深入研究和对不同 Parser 的分析,批量总结出了当今常用语言的 URL 解析所存在的漏洞,发现了行业性的问题。

https://img-blog.csdnimg.cn/f00d3bfa1fb748b2ac7333a9ca9c2c03.png
ssrf

出自 A New Era of SSRF - Exploiting URL Parser in Trending Programming Languages

大概就是这样吧,我编不下去了。


版权声明: 自由转载-非商用-非衍生-保持署名 (CC 4.0 BY-SA)
原文地址: https://evilpan.com/2022/05/01/code-audit-thoughts/
微信订阅: 有价值炮灰
TO BE CONTINUED.