作者: 纯情
时间: 2026-01-15
分类: 开源
Intro Tai-e作为一个优秀的静态分析框架,内置了指针分析、污点分析等等实现。为增强其作为一个底座框架的可扩展性,其提供了插件系统,通过插件系统可以控制在静态分析过程中的各个阶段的数据处理,更进一步的进行定制化分析的实现。如下图为Tai-e官方提供的有关于插件系统的原理图:
本文中提及的有关于微服务应用的静态分析框架MScan同样是基于Tai-e进行实现的,针对微服务应用中使用的一些特殊的API进行服务间的高速通信过程,传统的静态分析方式不能够原生支持该类服务间通信的污点流的传播,但是这里采用了上面介绍了插件系统的方式,为服务间的通信过程进行建模,定制化的支持该过程的数据流分析,例如是Grpc、Dubbo或者Feign等通信方式。 具体的分析因篇幅太长分为了上下两篇,上篇主要集中于理论层面的代码分析,剖析基于Tai-e框架的改造细节,明晰从source点提取到扩展的污点分析引擎工作原理的全流程。而下篇主要集中于实战层面的内容,在剖析微服务应用各服务间的通信建模方式,也即如何构建一个SDG(Service Dependence Graph),同时贴近实战批量拉取github\gitee高star项目进行自动化`clone-complie-scan`全流程。
Jar parser 首先这里设置了缓存机制,通过配置文件中的 Config.reuse 来控制是否使用缓存,如果不使用上次解析jar后的缓存则将对应的 targetPath 中记录的缓存信息进行删除
之后就是对于给定的jars包的处理过程,遍历给定的Jar列表依次进行 service discovery 以及类提取
1 首先来看 parseSerive 如何从目标Jar中获取service name的
a 首先是通过解析目标jar包中的 pom.xml 文件去得到对应的service name
其功能实现的核心基于以下几点 ⅰ 通过遍历jar包的所有文件获取到以 "bootstrap", "application", "entry" 开头, "yml", "yaml", "properties" 结尾的配置文件 ⅱ 筛选出文件中带有 application: 关键字符串标识的配置文件 ⅲ 在获取了包含有service name的配置文件之后,使用 snakeyaml 进行配置文件的解析,获取其中 spring.application.name 对应字段的值 ⅳ 这里还做了except处理,如果使用上述的解析yaml文件的方式不能够获取到service name时,则将 artifact id 作为 service name ⅴ 具体是遍历目标jar中包含的所有的 pom.xml 文件,创建一个XML解析器对 pom.xml 文件的内容进行解析,获取其中的 artifactId 字段进行返回 b 其次是根据在配置文件中预设定的 Config.classpathKeywords 去决定我们关注的class代码,避免引入了过多的第三方jar包的类造成过多的无效分析 c 最后就是对路由的配置进行解析
其核心实现同样可以归纳为以下几点步骤 ⅰ 首先是检查是否在配置文件中指定了待检测项目的路由配置文件 Config.routeConfigFile ,若已经明确制定了,直接进行获取并返回即可 ⅱ 如果没有指定,类似于前面提到了获取所有配置文件的方式,筛选路由配置文件,这里支持有 Sprint Cloud gateway 以及 zuul 的路由配置方式 遍历jar包的所有文件获取到以 "bootstrap", "application", "entry" 开头, "yml", "yaml", "properties" 结尾的配置文件
d 最后的最后就是维护了 GatewayParser.routeConfigFiles 以及 GatewayParser.services 去记录扫描到的所有路由配置文件以及services 1 如果在配置文件设置了进行上轮类抽取的重复利用,也即是 Config.reuse ,则直接跳过提取jar中类文件的操作,否则,就直接对所有类提取到目标文件夹下
Gateway parser 在微服务应用中,对于路由的解析是基于前面 jar parser 过程中扫描到的路由配置文件
其大概的实现逻辑如下 1 遍历扫描到的路由配置文件,读取配置文件信息
同样通过snakeyaml进行配置文件的解析,这里分为了两种两类不同的API网关进行针对性的解析 2 对于 zuul 这类的API网关
a 其将 zuul.routes 作为前缀获取路由信息 b 根据具体的 zuul 配置内容获取对应的 path 路由信息以及 service-id 对应的子服务对象,并对路径进行了有效处理 1 而对于 Spring Cloud Gateway 这类API网关
a 根据这类API网关的配置规则,将 spring.cloud.gateway.routes 作为前缀来获取路由信息 b 遍历获取的路由列表,获取对应的 uri ,根据 uri 信息去获取对应的 service name c 从配置文件中获取 predicates 以及 filters 用来确定路由的路由信息以及通过 filters 中的配置来确定是否需要跳过路由中的第一级路由
MScan
options.yml 在经过了前面的目标jar的解析以及路由的识别后,运行经过二开后的tai-e进行核心的指针分析以及污点分析,这里传入了一个 options.yml 配置文件
可以对照着tai-e得官方文档明白参数的作用 https://tai-e.pascal-lab.net/docs/current/reference/en/index-single.html 几个关键点参数 1 javaVersion: 8 使用JDK8下的依赖库进行分析 2 prependJVM: false 这个参数用来标识是否使用运行tai-e的JDK的依赖库进行分析,如果置为 true ,则将会抑制 javaVersion 的设置 3 analyses 这个参数用来指定在 tai-e-analyses.yml 中定义的一些分析,例如指针分析、调用图构建等等从 MethodAnalysis、ClassAnalysis、ProgramAnalysis 三中层面的基础上实现的分析
转回到这里 options.yml 针对于pta的配置 pta: taint-config:src/main/resources/taint-config.yml;only-app:true;implicit-entries:false;dump:false;time-limit:1200000;cs:4-call;advanced:pruning;plugins:[fdu.secsys.microservice.plugin.GatewaySourcePlugin,fdu.secsys.microservice.plugin.OpenFeignPlugin,pascal.taie.analysis.pta.plugin.taint.EnhanceTaintAnalysis] 首先在tai-e中的 tai-e-analyses.yml 中对pta的可选择的参数进行了说明
这里的pta配置大致分为了以下几点 a 配置了 taint-config 路径,用来启用 taint-analysis 以及指定污点分析的配置文件(包括有 sources/sinks/sanitizers 等) b only-app:true ,仅仅只分析 application code ,也即是只分析 -acp 指定的代码 c time-limit:1200000 ,设置了程序分析的超时限制,默认是 -1 也即是不存在超时 d cs:4-call ,对于 context-sensitivity analysis 其选用了 4-call-site 方法,根据调用点作用上下文的划分,当然,因为这里使用 advanced:pruning 所以一定程度上抑制了 cs 的配置 e advanced:pruning ,基于tai-e作者的四篇论文,实现了四种 advanced analysis ● Zipper-e (option value: zipper-e ): introduced in our TOPLAS'20 paper . ● Zipper (option value: zipper ): introduced in our OOPSLA'18 paper . ● Scaler (option value: scaler ): introduced in our FSE'18 paper . ● Mahjong (option value: mahjong ): introduced in our PLDI'17 paper . 而对于这里配置的 pruning 为自定义的内容,这里实现的是论文提及到的 distance-guided 的上下文敏感层级选择策略,核心是根据分析方法与 source-to-sink 路径的接近程度调整上下文敏感性程度,从而将更多的精力和资源集中在安全关键分析上,后续对其进行详细的分析
f plugins:[fdu.secsys.microservice.plugin.GatewaySourcePlugin,fdu.secsys.microservice.plugin.OpenFeignPlugin,pascal.taie.analysis.pta.plugin.taint.EnhanceTaintAnalysis] , 用来添加一些自定义实现的插件
feature based application 对于tai-e的插件系统,表示的是实现了 Plugin 接口的一群类,这里只分析Mscan二开的一些插件实现,不对tai-e原生的插件进行分析
该接口实现了一些在指针分析的生命周期中的一些回调接口,包括有如下 1 onStart : 在进行指针分析之前进行调用,可以进行指针分析的准备工作或者初始化插件 2 onFinish : 在指针分析结束之后被调用,可以对指针分析的结果进行筛选整理,但是不能修改指针分析的结果 3 onNewPointsToSet : 当存在有新的指针集指向时被调用 4 onNewCallEdge :当一个新的调用边被检测到时进行调用 5 onNewMethod :当一个新的可达方法被发现时进行调用 6 onNewStmt :当遭遇到一个新的代码语句时被调用 7 onNewCSMethod :当一个新的可达的上下文敏感的方法被发现时被调用(区分前面提到的正常方法) 8 onUnresolvedCall :当指针分析过程中对于 callee 的解析失败时调用该方法,例如在一个函数调用过程中,tai-e中的 callsite 中记录了本次被调用的 callee 是哪一个,但是该类并没有通过acp或者cp等参数进行加载,导致没有被soot进行分析,所有tai-e并不能够正常解析这样的 callee
GatewaySourcePlugin 这个类是用来进行入口点的识别的,核心是依赖于 fdu.secsys.microservice.plugin.gateway.EndpointHandler 类 该类实现了 onStart 方法,以及维护了 endpoints 在进行指针分析之前进行入口点的识别
其具体的实现可以来到 EndpointHandler#getEndpoints 方法
其实现可以归纳为以下步骤 1 首先,每一个入口点都被抽象成一个 Endpoint 对象,其包含有方法名、路由、在微服务中是否暴露在外、该入口点相关的service名等
2 首先是对进行指针分析之前通过LLM对 gateway 配置进行解析后的结果进行解析,获取根据网关配置文件得到的外部可访问以及内部可访问的接口列表
3 之后遍历所有得 applicationClasses 类,根据对应得注解信息去判断路由信息,进而获取到所有得 endpoint
具体细节分为下面几个步骤 a 首先使用 FeignUtil#getFirstMapping 方法去获取在 class 上注解的路由信息,核心逻辑位于 FeignUtil#getMaapings 方法,其通过 FeignUtil#isMapping 方法筛选需要的注解,这里支持通过 jax-rs 以及 Spring 两种规范的URL注解方式,同时值得注意的是实现了如果在当前方法中没有找到注解,会尝试去该方法的多级父类的对应方法中去寻找
b 其次在获取了一级路由也即是 class 上标注的路由信息后,去审查该类所有的方法是否满足Spring以及Jax-rs对于路由的规范,进而去获取对应 method 上标注的路由信息,也即是二级路由信息
c 后面就是组合类信息、方法信息、路由信息、路由对外暴露情况构建 Endpoint 对象实例,如果是存在有网关配置文件,通过路由的正则匹配,如果路由属于 external_entries 类,则将该 Endpoint 中的 isExposed 置为 true ,默认将其置为true,防止静态分析出现漏报。同时如果不存在有配置文件,则通过 service 名去判断是否路由暴露在外
EnhanceTaintAnalysis 该污点分析插件是按照 tai-e 原生的污点分析插件 TaintAnalysis 进行实现的一个增强版的污点分析实现, Mscan 这里运行了这两套污点分析 setSolver 方法在插件添加时执行
1 在进行插件的初始化过程中将会对 taint-config.yml 配置文件进行解析,这里对于配置文件的解析使用了 jackson 进行解析,使用的反序列化器用于获取配置文件中的 call-site-mode 以及 enhance_sinks 信息,构建一个 EnhanceTaintConfig 对象进行返回
2 同时添加了一系列和污点分析有关的插件 其污点分析的核心逻辑都是由其各个子插件进行实现的,仅仅在程序分析结束时,调用 onFinish 进行污点传播流路径的整理
其核心逻辑主要是基于 collectTaintFlows 的实现,该实现的大致逻辑如下步骤 1 首先获取到指针分析的结果 2 然后遍历指针分析所构建的调用图检索所有 reachable 函数调用点,筛选出其中调用有 sink 方法的可达方法,进而构建了一个 SinkPoint 对象用来存储该调用点
3 后续调用 checkTaint 方法检查污点传播情况 a 从指针分析结果中获取sink点关键参数的指针集并遍历,如果其是一个 taint-obj 则直接返回该对象,这里的 taint-obj 表示存在由外部可控的位置能够控制这里的对象,如果其不是一个 taint obj 则判断其是否是一个数组对象,若是一个数组对象则判断这个数组的元素是否存在有 taint obj
b 随后利用这里获取的 taint obj 从污点管理器中获取对应的 SourcePoint 位置,构建一个从 sourcePoint 到 sinkPoint 的污点传播流
4 针对于使用result为污点结果的sink规则,如果存在由对应的调用,将会遍历该方法所有的返回值,构建一个 SinkPoint 对象,标注了 excludeSourceParamAnno 以及 excludeCallSource 并进行污点传播的检查
5 在根据上述的逻辑完成了污点分析的逻辑之后,对分析的结果进行二次验证,检验是否存在一个完整的通路从 sourceMethod 到 sinkMethod 使得其为一个有效的 taintFlow 这里核心是使用广度优先遍历的算法进行调用链的查找,从指针分析的结果 CallGraph 检索是否存在完整的调用链,将存在有完整调用链的 taintflow 进行返回
6 后续则是根据 taintFlows 的结果进行污点流图的dump操作,将污点传播路径通过 .dot 文件的格式进行保存,方便可视化展示
SpringController 该插件作为污点分析插件的一个子插件,用来构建 source 点的污点入口
在静态分析遭遇新方法时触发 onNewCSMethod 事件 1 首先基于注解的方式判断该类是否是一个 Controller 类 2 其次通过参数类型的检查,判断该参数是否可以被外部可控,但是这里有点小疑问,这里将 safeClass 中存在的参数类型作为一种外部不可控的类型,但是感觉 HttpServletRequest 这类类型一定程度上也是可控的,例如通过 request.getParamter 等函数进行外部数据的获取,也会造成外部可控的情况,所以这里的规则可以进行完善
3 对每一个可控的参数位置构建一个 ParamSourcePoint 对象,并将其作为一个 taint ,特别的,这里也会对参数所在 class 的 fields 归类为一个 taint ,并通过 addPointsTo 添加对应的指针集,并且也支持对 controller param 所在类的父类所有 fields 以及 fields 所在类的 fields 归类为一个 taint ,递归的最大深度为4,这里主要是处理的是,现在基于Spring MVC的开发模式来讲,一个 controller 方法传入的参数通常是一个类对象,其中的每一个 field 对应的就是可传入的具体参数名。
MiscPlugin 这个插件主要是用来处理 upload 上传逻辑的污点传播 对于每一个 invoke 语句审查其是否调用的 sigs 中存在两类 upload 函数,则通过 addVarPointsTo 向该调用点的 base 变量指向一个指针集,这个指针集包含有其子类所有的 upload 方法
MybatisXmlPlugin 该插件主要是用于解决在mybatis这类ORM框架对于SQL注入攻击类型的识别,这里仅仅支持未进行预编译的识别,未对复杂的 order by 等语句的攻击进行识别 在静态分析程序未开始时触发 onStart 事件进行 mybatis mapper 文件的解析,并实时封装sink点添加到sink规则中
其大致通过以下逻辑进行sink点的动态生成 1 遍历在 jar parser 处理阶段筛选的所有XML文件,调用 submitFileProcessing 进行多线程处理,核心的逻辑存在 processXMLMapper 方法中 2 在 processXMLMapper 方法中,对XML文件的内容进行了XML解析,根据 mapper xml 文件中定义的sql语句,将其抽象成一个sql语句字符串后通过正则匹配的方式检索sql语句中是否存在有 ${} 包裹的内容
其包裹的内容则为可注入的点,在完成 injects 可注入点的获取后,根据xml文件中的 namespace 以及 id 去获取所对应的 Class 对象以及 Method ,根据 injects 的信息完成对注入点的识别,返回一个 method-injectPos 方法到注入点索引的映射
最后完成动态sink的封装
上述内容为静态分析过程开始前动态生成有关于mybatis这类ORM框架的sink,而该插件在遭遇新的方法时将会触发 onNewCSMethod 事件,该事件的作用主要是构建一个select查询语句调用函数的参数变量到该查询返回变量的一个映射 selectArgResultMap
同时在指针集存在变化时同样会触发 onNewPointsToSet 事件,其作用分为了两部分 1 遍历对应变量的指针集,将其指向的 taint obj 以及指向类的所有 fields 中所有指向的 taint obj 添加到 taintObjs 中 2 遍历调用了 select 查询语句的所有返回结果变量 resultVars ,将污点传递到了返回的结果信息,构建了一个新的污点对象 newTaintObj ,并使用 addPointsTo 函数将污点对象指向结果变量,完成在mybatis场景下的污点传播
SpringContainer 这个插件作为污点分析插件的子插件,主要是用于解决在Spring框架下的动态注入的机制,类似于基于注解的对象动态注入静态分析方法并不能够对其所指代的对象进行识别,就需要通过定制化的方式将对应的对象指向给补全 1 首先基于 Controller 等注解获取所有的入口类,并通过 addEntryPoint 为整个程序分析添加入口点 ( GatewaySourcePlugin 只是最所有Source类进行了聚合并没有添加程序分析入口,整个mscan的分析入口是在 SpringContainer 中添加的) 其中的识别方式支持有 Jax-rs、Spring
2 同时,在这个过程中也会进行java bean的识别,在Spring中 Bean 类的创建有多种形式,例如 Controller 对类进行注解,利用 Serivce 进行注解等等方式,将每一个Bean类抽象成一个 SpringBean 对象
如果注解中存在有明确的 bean name 则将其作为 Springbean 对象的name值,默认直接采用类名称的缩写 3 之后就是进行需要动态注入的 fields 以及 params 的信息的收集 对于 fields ,其检查所有的 SpringBean 类及其父类中存在 Resources 等注解的字段,存储需要动态注入字段到 diFieldInfos ,其key值存储的信息是带注入字段的 class-field ,其value值存储的信息为field所标注的各种信息
4 而对于 fields 的动态注入则是分为两类,若没有指定类名的话,则通过类继承关系从 BeanClass 中获取该field的子类,若指定了类名,则直接使用 ByName 的方式获取 BeanClass ,在得到了对应的 fieldBean 之后会将 fieldBean 类对象指向 field 的指针集,同时如果对应的 Springbean 存在有返回变量,则在指针流图中添加一条从返回变量到 field 的边
5 而对于method的param的注入过程,同样是基于类继承的方式进行检索,不同是,这里的每一个method不论是构造方法或者是 Bean 注解标注的方法都可以作为一个程序分析的入口
Ref https://tai-e.pascal-lab.net/docs/0.5.1/reference/en/index-single.html#how-to-develop-a-new-analysis-on-tai-e https://ieeexplore.ieee.org/abstract/document/11023345
标签: 漏洞挖掘 , 静态分析 , 插件系统 , 微服务 , Tai-e , 污点分析 , MScan , 服务间通信 , 指针分析 , 自动化扫描