一、静态 SCA 工具的“警报疲劳”
SCA 工具在识别和管理应用程序中的第三方依赖及风险方面起着至关重要的作用,是云原生时代下数字供应链风险治理必不可少的基础设施。然而,基于源代码和二进制制品的静态 SCA 工具在落地实践时通常会面临两个让安全运营人员和研发人员头疼的问题:误报和修复优先级。
SCA 工具为用户(主要是开发人员和安全运营人员)提供了两大核心能力,一是对软件组成成分进行梳理,形成软件物料清单(SBOM);二是基于 SBOM 内容,对其中涉及安全漏洞或许可合规风险的组件进行告警。
所谓“警报疲劳”是指用户被 SCA 工具的海量报告淹没,导致疲于对漏洞修复优先级排序或对误报进行处理。造成这一问题的原因首先是由于现代软件变得越来越复杂,一个应用程序通常会有数十或数百个直接依赖,其间接依赖数量更是直接依赖的两倍甚至更多;其次,SCA 工具难免会产生一些误报:报告存在已知风险但并未被真正引用或无法直接利用的组件。这种误报会导致开发人员对工具失去信心,即产生“警报疲劳”。
SCA 工具提供的上下文信息不足也是导致“警报疲劳”的原因之一。如果 SCA 报告中没有提供已识别风险的影响、严重程度、修复建议等信息,研发人员就需要花费更多的精力在了解漏洞细节上,增加了修复漏洞的平均时间。 为了减轻“警报疲劳”,需要提高漏洞的检测的准确性、减少误报、对海量结果修复的优先级进行排序,提供更详细的漏洞描述信息。然而,实现上述功能对静态 SCA 工具来说,仍有较长的距离。
二、运行时 SCA - 供应链安全新视角
1、漏洞可达性分析
为了避免“警报疲劳”,一个行之有效的方案是对漏洞的可达性进行分析。
可达性是一个用来确定应用程序中的任何调用路径是否可以到达某个脆弱函数的术语。这种分析有助于理解一个存在于间接依赖中的漏洞如何被到达和利用。直接依赖是与代码直接关联的,开发者通常清楚其作用;间接依赖是直接依赖本身所依赖的组件(一般在构建过程中间接导入所需的代码),可能开发者并未意识到。可达性分析不仅是评估软件漏洞风险的关键概念,也是轻松修复漏洞的关键。通过分析软件架构中的各种路径和依赖关系,可达性有助于识别潜在的攻击向量,以及修复的最快方式。理解可达性有助于优先考虑修复工作,引导开发者关注直接依赖可以提高修复的效率和速度。它还有助于通过确定哪些第三方包负责大部分漏洞并需要打补丁,来识别对组织的真正风险。
可达性分析主要有静态分析和运行时分析两种方式:
- 静态分析会遍历源代码,试图绘制出所有函数调用路径,来检查是否可以到达脆弱函数。但静态分析通常不太容易绘制反射或者动态加载操作的调用路径。并且由于静态分析会穷举出所有可能的调用路径,其结果仍然需要花费精力去审计。
- 运行时分析则是将一段代码(代理,库)植入到应用程序的运行时环境中,观察正在运行的应用程序,监控加载到运行时环境中的类、在运行时调用的函数,并检查是否实际使用了脆弱的函数/类。运行时使用分析可以帮助用户进一步梳理确定漏洞修复优先级列表。运行时 SCA 检查的是运行态的可达性,因此可以正确反映出漏洞真实利用路径,更适合在流水线中使用。
2、运行时SCA技术特性及原理
运行时SCA在数字应用的测试和上线运营阶段进行组件级资产测绘,实现准确评估数字业务在运行时真实加载的第三方组件及相关风险。运行时SCA可通过运行时监控技术,检查程序运行时加载的第三方组件,可排除未执行加载的冗余组件,相较于静态SCA,运行时SCA检测精度更高。
如《IAST 技术进阶系列(一):关键语言支持》中所介绍,运行时 SCA 需要根据不同编程语言特性单独编写“探针”,来获取加载到应用运行时环境的组件信息。通常来讲,按照是否需要编译可将编程语言分为三类:编译型、解释型和混合型。
- 编译型编程语言:C/C++、Go、Rust 等就是典型的编译型编程语言,这类型的运行时插桩探针通常需要通过类似“劫持”的方式,监控或接管编译器的行为,来达到获取编译后制品中包含的第三方组件信息的目的。
- 解释型编程语言:常见的解释型编程语言有 JavaScript、Python、PHP等,这类型的运行时插桩探针一般是通过对底层函数包装后,通过调用解释器内置模块,例如 python的 sys.modules;Node.js 的 require.cache 等方法获取已经加载到运行时环境的组件信息。
- 混合型编程语言:指的是例如 Java、.Net 这类需要编译生成中间文件,然后在 JVM、CLR 中运行的半编译半解释型编程语言。JVM和 CLR 都提供了可以获取运行时依赖信息的接口,例如 Instrumentation API 和 Reflection API 。
通过上述方式获取到运行时 SCA 的依赖信息后,就可以像静态 SCA 工具一样,通过知识库匹配得到更详细的组件信息和漏洞信息了。
图1:运行时SCA技术基本原理
静态 SCA 工具通常会依赖包管理器配置文件或二进制特征指纹识别组件,若组件被声明在依赖中,但代码中并未引用,就会存在误报。而通过运行时 SCA,便可以准确拿到在应用运行时阶段使用了哪些第三方组件依赖。如下图所示,为检索所有在运行时环境中依赖 Log4j2.x 组件的所有应用。
以 Java 为例,为了实现漏洞可达性分析,我们需要先通过 JVM Instrumentation 接口的方法获取加载到运行时环境的所有类。结果部分内容示意:
... java.util.concurrent.Executors java.util.concurrent.Executors$DefaultThreadFactory org.apache.logging.log4j.core.LifeCycle$State org.apache.logging.log4j.internal.LogManagerStatus org.apache.logging.log4j.core.LoggerContext org.apache.logging.log4j.spi.Terminable org.apache.logging.log4j.core.config.ConfigurationListener org.apache.logging.log4j.spi.LoggerContextShutdownEnabled org.apache.logging.log4j.core.AbstractLifeCycle org.apache.logging.log4j.core.util.ExecutorServices org.apache.logging.log4j.core.config.NullConfiguration org.apache.logging.log4j.core.config.AbstractConfiguration org.apache.logging.log4j.core.filter.AbstractFilterable org.apache.logging.log4j.core.Filter org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate org.apache.logging.log4j.core.util.Watcher org.apache.logging.log4j.core.lookup.StrLookup org.apache.logging.log4j.core.Layout org.apache.logging.log4j.core.layout.Encoder org.apache.logging.log4j.core.Appender org.apache.logging.log4j.core.net.Advertiser org.apache.logging.log4j.core.util.NanoClock org.apache.logging.log4j.core.config.ConfigurationSource org.apache.logging.log4j.core.config.Property org.apache.logging.log4j.core.config.DefaultAdvertiser org.apache.logging.log4j.core.lookup.Interpolator org.apache.logging.log4j.core.lookup.AbstractConfigurationAwareLookup org.apache.logging.log4j.core.config.ConfigurationAware org.apache.logging.log4j.core.lookup.AbstractLookup org.apache.logging.log4j.core.lookup.MapLookup org.apache.logging.log4j.core.lookup.Log4jLookup org.apache.logging.log4j.core.lookup.SystemPropertiesLookup org.apache.logging.log4j.core.lookup.EnvironmentLookup org.apache.logging.log4j.core.lookup.MainMapLookup org.apache.logging.log4j.core.lookup.MarkerLookup org.apache.logging.log4j.core.lookup.JavaLookup org.apache.logging.log4j.core.lookup.LowerLookup org.apache.logging.log4j.core.lookup.UpperLookup org.apache.logging.log4j.core.lookup.JndiLookup org.apache.logging.log4j.core.lookup.JmxRuntimeInputArgumentsLookup org.apache.logging.log4j.core.lookup.DateLookup org.apache.logging.log4j.core.lookup.ContextMapLookup ...
通过上述信息,即可对存在漏洞的类进行检索。例如 Log4j2 漏洞,若 CVE 没有披露 Log4j2 中具体哪些类存在风险,那么在上述列表中只要涉及一条 Log4j2 的记录,配合组件版本识别,就足以证明该应用存在风险,可以作为修复漏洞的理由;若 CVE 中提供了这些信息,那么开发人员就可以利用这些信息进一步确认漏洞在自己的应用中是否可达,或者安全运营人员通过对风险函数进行埋点,通过 PoC 验证漏洞是否可达。
此外,也可以反方向进行排查。例如,先圈定需验证可达性的漏洞范围,对这些漏洞涉及的类及方法进行埋点,然后通过 PoC 或者 DAST/Fuzzing 工具等方式进行测试,验证漏洞是否可达。这个方案适合周期性地对一批应用进行筛查。例如若要在一批应用中排查是否涉及 Fastjson远程代码执行漏洞(CNVD-2019-22238),可在com.alibaba.fastjson.parser.DefaultJSONParser$parseObject 处埋点,通过构造利用 Payload 验证其是否可达。在灵脉 IAST 中验证情况如下:
图2:风险方法埋点
图3:可达性验证结果
3、运行时威胁免疫
运行时 SCA 不仅可提供运行时依赖的监控和审查,也可以通过植入额外的代码来实现运行时威胁的自我免疫(RASP)。实现 运行时威胁免疫有以下两个思路:
- 组件安全加固:对所有底层调用进行加固,例如命令执行、文件读写等,在执行敏感操作时进行过滤和拦截
(1)通过运行时SCA技术,RASP探针可以在应用程序运行过程中识别到其依赖的所有组件清单;
(2)通过RASP云端组件漏洞数据库对比分析,可以自动下发组件漏洞修复热补丁,实现对组件风险的代码级加固防御,从根源上杜绝漏洞被利用的可能性。
以Java为例,当由用户代码、第三方组件等代码一起编译生成的混源应用制品与RASP探针共同加载到JVM虚拟机中时,在Instrumentation API的transform阶段,我们不仅可以动态地修改目标Hook方法,同时也可以获取到所有加载到JVM的第三方组件信息。此时,借助云端运行时SCA分析引擎,即可获得在运行阶段加载第三方组件的风险情况,借助热修复补丁库,云端可自动下发对应的热修复补丁,动态地将风险组件的风险方法屏蔽或注入额外的安全判断逻辑,在不直接修改源代码情况下实现对风险组件的代码级加固。 - 应急风险响应:根据 CVE 针对性下发策略,动态地对具有风险的调用进行拦截。例如,阻断 Log4j2 的方法;或是在 fastjson执行前插入类型推断的逻辑。
关于运行时威胁免疫的更多原理介绍,也可参考RASP技术进阶系列(四):基于安全共生的供应链安全风险防御。
三、运行时 SCA 实践方案
1、方案框架
运行时 SCA 作为静态 SCA 工具的补充,可以覆盖软件生命周期的更多阶段,对运行时应用实现更加便捷且自动化的依赖风险发现、验证、处置的闭环管理流程,解决开源组件漏洞治理难以落地的难点。
如下图所示,将源码 SCA 工具与运行时 SCA 相结合,覆盖应用从编码、测试、上线运行多个阶段,配合供应链安全情报预警,实现漏洞利用可达性验证闭环。
图4:漏洞利用可达性验证
- 在开发阶段通过 CI/CD 流程接入源代码 SCA 工具,梳理识别项目中依赖的第三方组件信息。
- 在测试阶段,通过部署例如 IAST 工具、RASP工具的运行时插桩探针来获取运行时依赖的第三方组件信息。
- 在上述阶段的工具中,都接入开源组件威胁情报,持续获取最新的开源组件风险信息、漏洞利用函数调用链路(漏洞可达性信息)、漏洞验证信息(漏洞利用 Payload)。安全运营人员可参考这些数据,圈定出需要处置的风险范围。
- 当应用进入测试阶段,将基于开源组件威胁情报的“漏洞验证信息”和“漏洞可达信性”录入到 IAST 或 RASP 工具中后,检查应用运行环境,判断运行环境是否具备漏洞利用条件(如:Spring4Shell漏洞利用需要运行在JDK9或更高版本),然后监控漏洞风险入口函数,判断组件漏洞风险是否可触达。在应用运行过程中,如果风险函数被调用,则认为风险可达。需注意以下情况:
(1)部分组件漏洞可在应用正常功能触发时自动进行验证(如文件上传、反序列化、命令执行等通用类型漏洞)。
(2)剩余部分组件漏洞,需要进行发送payload请求验证:可以通过 IAST/DAST 工具的流量引擎自动发送,或手工构造 payload 发送进行验证。 - 将SCA工具的静态可达性分析和IAST 工具的运行时可达性分析结果汇总合并后,由安全运营人员统一评估是否需要修复。
- 对于没有源代码的应用,可通过二进制SCA 代替源代码 SCA,配合运行时可达性分析进行漏洞修复优先级研判。
- 在应用上线后常态化安全运营中,可通过运行时 SCA 配合开源威胁情报持续对应用进行风险监控。相比静态 SCA 方案,运行时 SCA 可以在线下发风险函数监控或阻断规则,在不重启应用的情况下,监控风险函数是否可达、是否被触发、是否被利用。同时,运行时SCA利用DSDX SBOM能够实现对存量资产的开源治理和组件级资产测绘。
2、方案亮点
- 通过静态 SCA,梳理出资产清单,获得一个大致治理范围的漏洞治理清单;
- 通过开源组件威胁情报,持续更新各类组件的漏洞风险入口函数及其对应的验证 Payload;
- 通过在测试环境中部署运行时 SCA 监控探针,根据功能测试流量和验证 Payload 自动收敛漏洞治理清单。
收敛流程如下图所示:
图5:漏洞风险收敛流程
通过该方案,将静态 SCA、供应链安全情报、运行时 SCA 能力集成到软件生命周期中,通过漏洞严重性、漏洞可利用性、漏洞可达性、修复稳定性几个维度,确定漏洞修复的优先级,将安全运营人员和开发人员从“警报疲劳”的苦海中解脱出来,将精力集中到关键漏洞上。
3、漏洞动态修复
伴随着应用执行,运行时SCA可结合代码疫苗热补丁技术对漏洞进行动态修补。在修复阶段,由于应用已经安装了集成有运行时SCA能力的IAST或RASP 探针,则可以一键下发或自定义漏洞利用触发告警或者漏洞利用阻断的热修复补丁,在不重启应用的情况下对漏洞调用路径进行监控和阻断。对于已经备案且暂时无需修复的漏洞,也可通过下发相关风险入口函策略,起到风险预警和监控的作用。
四、结语
运行时SCA凭借精准识别数字应用运行加载时真正使用到的第三方组件及依赖,在应用测试和上线运营场景有着更广泛的应用。源鉴SCA在满足实现运行时SCA技术的基础上,结合二进制SCA技术、源码级检测技术及漏洞可达性分析等技术,有效帮助开发人员更好地管理和维护软件成分,减少无效漏洞的运营干扰,提高软件的安全性和可靠性,助力企业建立并有效落地数字供应链安全治理体系,保障数字供应链安全。