手把手教你打造Web应用静态安全测试工具

 

作者:飞蝠@阿里安全

利用漏洞清除债务,盗取数据,一键获取XX游戏的所有账户登陆权限,这些看似爽文里的“骚操作”其实早就在现实中上演。

2013年,一个自称RedHack的黑客小组利用土耳其政府网站的Web漏洞,清除了相关人员在政府机构的债务数据。2015年,某安全研究人员公开称,可以通过特定的注入漏洞攻破某知名汽车的网站,获得管理权限,并窃取用户数据。2019年,拥有超过3.5亿用户的在线游戏“XX垒之夜”被爆出一个SQL注入漏洞,可以让攻击者访问所有用户帐户。

这些都是Web应用程序的漏洞,Web应用程序已经广泛应用社交网络、网上支付等各个领域,真正影响人们的生活,因此Web应用中存在的各种安全漏洞越来越引起人们的重视。

为赶在恶意人员之前发现并修复Web应用程序中的安全漏洞,安全人员需要利用各种安全测试技术识别并修复Web应用程序中的缺陷。静态应用安全测试(Static Application Security Testing ,SAST)由于不需要运行被测程序,具有覆盖率高、自动化程度高、可以在开发生命周期早期使用等特点,是目前被业界广泛采用的应用安全测试技术之一。但是静态应用安全测试作为一种针对应用安全缺陷的自动化检测方法,本质上处理的是一个不可判定问题,理论上不可能同时做到没有误报也没有漏报。

大量的误报会使人对分析工具失去信心,而漏报会造成程序具有较高安全水位的假象,很多情况下减少误报和减少漏报就是一对矛盾体。为尽量减少不必要的漏报和误报,往往需要运用更复杂的分析技术,意味着更高的复杂度,因此分析的精度与分析的速度往往也是一对不可兼得的矛盾体。实用的静态应用安全测试工具需要根据分析目标和应用场景在误报、漏报、效率、易用性、可扩展性之间达到一个合理的平衡。

 

哪些缺陷类型需要“关心”

程序分析工具都有其针对的分析目标程序属性target program properties),通俗地来说分析目标程序属性就是分析工具关心哪些缺陷类型。当前许多通用的静态缺陷检测工具往往强调“大而全”,强调能够适应不同的扫描场景,强调能够覆盖更多的缺陷类型,实际上这里存在误区。实际中的程序千变万化,对不同类型的程序,在不同的场景下,人们关注的程序属性不一样,对分析工具的各项要求也不一样。静态分析都会引入某种程度上的抽象,最有效的抽象方法需要充分利用分析目标程序属性本身的特点。明确应用场景和分析目标程序属性(target program properties)是设计真正实用分析工具的首要任务。

Web应用静态安全测试的主要应用场景在开发阶段而不是开发完成之后,它的重要优势是能够在早期就检测出源码中的安全漏洞,从而大大降低修复安全问题成本,成熟的大型软件开发组织通常将其融入DevSecOps流程中。这更加要求分析工具能够在漏报、误报和效率之间达到合理的平衡,避免不必要地打扰和减缓正常开发流程。

Web应用静态安全测试的分析目标程序属性是常见的Web应用程序安全风险,核心关注的就是OWASP Top 10中列出的安全风险。下图是最新发布的OWASP Top 10Web应用安全风险:

Web应用程序安全风险和空指针解引用、数组越界、资源泄漏、数值溢出等内存安全程序属性有显著区别。Web应用程序安全风险从程序语义上看更加高层(更加靠近应用层),内存安全则更加底层,更加关注程序中变量的具体取值情况。为了开发真正实用的分析工具,需要充分利用分析目标程序属性的特点,选择能够达到最佳平衡的分析算法和策略。

 

Web应用安全风险程序属性建模

为了保证静态应用安全测试工具能够无二义性地识别和分析目标缺陷类型,我们将目标缺陷类型的分析配置描述为静态应用安全测试工具能够识别的规则。易用且可扩展性好的Web应用静态安全测试工具都应该做到规则和引擎分离。规则描述结构设计的过程本质上是对Web应用程序安全风险知识进行建模的过程。OWASP Top 10列表中的Web应用安全风险可分为两类:一类是和非正常数据流相关的安全风险,如注入、敏感信息泄露、XML外部实体、跨站脚本和不安全的反序列化。一类是和非正常控制流或状态相关的安全风险,如失效的身份认证、失效的访问控制、安全配置错误、使用含有已知漏洞的组件不足的日志和监控。我们采用两种不同的规则描述模型来支持上述两类Web应用安全风险:污染传播模型和状态机模型。

污染传播分析

污染传播分析又被称作信息流分析(information-flow analysis),它是用于追踪程序中特定数据传播和依赖的一种数据流分析技术。污染传播分析是Web应用安全缺陷检测的主要方法之一,其基本思想是通过对不可信的源头(source)引入的数据进行污染标记,跟踪被标记的污染数据在程序中的传播,若污染数据在进入敏感操作(sink)前未经过恰当的净化操作(sanitizer),则表明存在潜在的安全缺陷。

污染传播规则中的配置包括sourcesanitizersink、安全类型等。安全类型指的是对于当前目标程序属性来说认为肯定不会污染的类型,例如对于注入类安全风险可以认为所有的枚举类型、布尔类型、日期类型、浮点类型等都是安全的。

对于常见的web注入安全风险,我们的解法是,提供统一的安全方法给开发人员调用,这些安全方法也是静态分析工具能够识别的统一的净化操作。污染传播分析沿控制流在每个程序位置上计算当前污染变量集合,并在污染变量间建立污染传播依赖关系。

状态机分析

理论上来说,Web应用安全缺陷检测所检查的缺陷类型都属于时序安全属性(temporal safety properties),安全属性描述的是坏的事情不会发生一类属性,通常可以描述成如下统一模式:程序中某些动作或行为构成时序上的约束,一旦违背这种约束即被认为是一个缺陷

有限状态机(finite state machine)是描述时序安全属性的有效工具,污点传播相关的缺陷类型本质上也属于时序安全属性,即来自source的数据在进入sink点之前必须经过sanitizer操作,否则报告一个缺陷(但为了缺陷报告时更好地给出污染在变量间的传播路径,将其采用专门的污染传播分析进行建模)。状态机规则中的配置包括状态集合、状态间的转换集合以及各转换所需满足的条件集合等。

在实际的状态机分析过程中还需要引入状态机实例的概念,在程序中的每个分析入口函数处会创建一个状态机实例,并沿控制流在每个程序位置上的计算它的可能状态。如果一旦某个状态机实例的当前可能状态中出现了error状态则报告一个缺陷。

 

分析策略选择

从不同的抽象和近似精度角度,静态分析方法可以区分是否流敏感(flow sensitive)、路径敏感(path sensitive)、上下文敏感(context sensitive)、域敏感(field sensitive)、对象敏感(object sensitive)等。

具体来说流敏感需要在分析中考虑语句的执行顺序,路径敏感需要在分析中考虑分支判断条件的组合关系并排除不可达路径(infeasible path),上下文敏感需要在分析中考虑被调函数在不同调用点的上下文影响,域敏感需要在分析中区分同一对象的不同字段,对象敏感需要在分析中区分同一字段的不同对象。一般来说,敏感性支持得越多,分析精度越高,实现越复杂,分析速度越慢。对于实际的静态分析工具来说,最好的选择是根据分析目标和应用场景的需要花最少的代价获得最大的回报。对于Web应用程序安全风险来说,引入路径敏感的性价比并不高。

污染传播分析和状态机分析本质上都可纳入传统的数据流分析框架,污染传播分析在每个程序位置上计算当前污染变量集合,其对应数据流分析值为当前污染变量集合。状态机分析在每个程序位置上计算状态机实例的的当前可能变量集合,其对应数据流分析值为状态机实例的的当前可能变量集合。污染变量集合和可能状态集合的转换函数都是满足分配率。

传统数据流分析基于迭代收敛完成过程内数据流分析,基于图可达算法完成过程间数据流分析。对于本文所关注的静态应用安全测试分析目标程序属性来说,传统数据流分析算法的性价比太低。传统数据流分析中的迭代主要是因为实际的程序中存在循环和递归结构,需要通过迭代并收敛于不动点来处理控制流图中循环回边带来的影响。而实际上对于污点分析和状态机分析(Web应用安全风险这类目标程序属性)来说控制流图中的回边在实际中分析结果中的影响很小。至于图可达算法考虑的过程间调用和返回匹配问题可以在搜索污染传播跟踪路径时进行单独处理。在不考虑循环和递归结构的前提下(相当于循环和递归只展开1次),污染传播分析和状态机分析可以做到接近线性复杂度O(n)n为程序中的语句条数。

 

函数摘要

函数摘要(function summary)是静态分析过程中常用的一种过程间分析技术,它根据函数内分析结果生成被调函数的摘要信息,以此替代函数的实际展开,从而避免同一个函数的多次展开分析,提高函数间分析效率。函数摘要是对已展开分析过函数的抽象和总结,函数摘要可以看作是以函数为单位,对函数实际语义的抽象。其理想的效果是:使用函数摘要得到的计算结果等于展开实际函数调用得到的计算结果。

需要指出的是函数摘要是对函数实际语义的抽象,这种抽象必然是面向实际分析目标程序属性特点的,应该根据不同的分析目标程序属性设计并收集对应的函数摘要。由于静态分析的不可判定性,通用的精确的函数摘要必然退化为函数的另一个完整实现。由于污染变量集合和可能状态集合的转换函数满足分配律,面向污染传播和状态机的函数摘要完全可以满足:使用函数摘要得到的计算结果=展开实际函数调用得到的计算结果。

 

跨应用、多语言支持

这里所谓的跨应用,其实用跨扫描单元来表述可能更为准确,扫描单元是扫描过程中确定和区分被扫对象的基本管理单位,它与缺陷的关闭与合并,权限管理,扫描中间结果的记录等有关,直观地理解就是一次源码扫描过程中的被扫对象。

目前市面上的静态应用安全测试工具都局限于处理单个扫描单元内部的污染数据传播,无法处理跨扫描单元传播的场景,随着微服务/Serverless/FaaS等开发技术和模式的逐步演进,业务开发代码日趋碎片化,使得这一问题愈发严重,成为静态应用安全测试技术的重大挑战。跨扫描单元所关注的单元间依赖关系分为两类:一类是通过引用二三方包而形成的依赖关系,另一类是通过各种远程调用服务(remote procedure call , RPC)形成的依赖关系。前一类依赖关系通常在编译时可以通过类似maven这样的机制获取,后一类依赖关系通常需要通过分析具体的配置文件获取或者扫描时由用户额外提供。

最直接的跨单元扫描解决方案是将所有相关扫描单元代码进行合并扫描。但是在实际中,这样会使得单次扫描的代码量急剧膨胀而难以实现。另外,对于被依赖扫描单元来说,每次的重复扫描也将会是极大的资源浪费。

在实际中通常平均每个应用依赖的其他扫描单元(包括二三方包依赖和RPC依赖)超过200,最大甚至超过2000。对于这些依赖非常多的应用,所有相关扫描单元代码合并后的代码将会超过1000万行。本文所述分析工具当前运行节点的典型配置为832G内存,单个扫描任务要求扫描时间不超过10分钟。工具的内存开销与代码行数基本成线性关系,实际中平均每10万行代码消耗内存约2G左右。时间开销与代码行数关系略高于On),10万行代码的扫描时间约1分钟左右。因此合并扫描的方案在实际中由于空间、时间以及存储成本的限制而无法实现。

一种更好的跨单元扫描解决方案是:首先,在不损失精度的前提下,以扫描单元为单位建立被依赖单元的静态应用安全检测中间结果表示,并将这些中间结果持久化地保存下来;其次,在其他单元扫描前,先依据其依赖关系将需要的中间结果下载到本地,并应用于当前单元的扫描过程。

根据上一节的讨论,基于污染传播和状态机的中间结果表示核心即是函数摘要。另外,对于通常的RPC调用来说,只要在接口层面进行了统一,具体用何种语言实现对调用方是完全透明的,因此,基于函数摘要的跨单元扫描解决方案天然支持跨语言。上述方案如下图所示:

经验与体会

本文讨论的Web应用安全静态测试工具主要的使用方式是与应用持续集成/发布平台对接,作为持续集成/发布的一个环节,在发布之前集成发布平台调用工具进行代码安全扫描,如果发现安全缺陷则通知用户进行修复,修复后才可以发布上线。在这样的应用场景下有如下几点总结和体会:

1.  实用的静态应用安全测试工具通常是根据分析目标和应用场景在误报、漏报、效率、易用性、可扩展性之间达到一个合理的平衡。

2.  Web应用安全风险和通用的内存安全漏洞有不同的特点,这些特点决定了分析算法和策略的选择。

3.  函数摘要对于Web应用安全风险相关的静态分析来说是一种重要的技术,它不仅可以大大提高应用内扫描的效率,也是支持跨应用多语言扫描的一种方法。

静态应用安全测试方法有许多天然的优点,但也有其固有的缺点,在实际中亟需将它与其他方法结合起来,才能更好地面对不断涌现的Web应用安全检测新场景和新需求。

(完)