标签 Tai-e 下的文章

Intro Tai-e作为一个优秀的静态分析框架,内置了指针分析、污点分析等等实现。为增强其作为一个底座框架的可扩展性,其提供了插件系统,通过插件系统可以控制在静态分析过程中的各个阶段的数据处理,更进一步的进行定制化分析的实现。如下图为Tai-e官方提供的有关于插件系统的原理图:

image.png

本文中提及的有关于微服务应用的静态分析框架MScan同样是基于Tai-e进行实现的,针对微服务应用中使用的一些特殊的API进行服务间的高速通信过程,传统的静态分析方式不能够原生支持该类服务间通信的污点流的传播,但是这里采用了上面介绍了插件系统的方式,为服务间的通信过程进行建模,定制化的支持该过程的数据流分析,例如是Grpc、Dubbo或者Feign等通信方式。 具体的分析因篇幅太长分为了上下两篇,上篇主要集中于理论层面的代码分析,剖析基于Tai-e框架的改造细节,明晰从source点提取到扩展的污点分析引擎工作原理的全流程。而下篇主要集中于实战层面的内容,在剖析微服务应用各服务间的通信建模方式,也即如何构建一个SDG(Service Dependence Graph),同时贴近实战批量拉取github\gitee高star项目进行自动化 clone-complie-scan全流程。 DistancePruning 该类的实现对应着论文中提及到的基于距离引导的上下文选择策略,但是感觉具体对其的实现还是和论文中的描述存在出入,后面具体分析其实现

options.yml中若对advance进行配置,将会使用特定的上下文选择器,这里的动态上下文选择策略的实现和核心逻辑在DistancePruning#run,核心是三个原则 1 对于一个方法,其能够调用到某一个sink点方法且能够被某一个source点方法调用到(不局限于单次调用,只要在调用图上能找到一个调用链即可),对于这样的方法,将其csMap的值设为MAX,也即是这样的方法采用最大的上下文进行分析 2 对于仅仅能够形成调用链到sink点方法,但不能够某个source点方法调用的方法,这样的方法,将其csMap的值设为固定的2,在分析时采用2-call的方法进行上下文的选择 3 而对于上述两种情况都不满足的情况,则直接将其上下文选择为MIN,采用最小的上下文 总的来说,虽然与论文中提出的基于一个方法到达最近的source-sink链的距离进行上下文的选择有所出路,但是这里的上下文选择方法也是基于一个context-insensitivity的分析结果,所以对于可能的source-to-sink调用链长度进行最大上下文的选择也一种有效的避免假阳性的方法

与此同时,注意到在Pruning类也存在有两种上下文选择的思路 1 csMapByTaintNum方法,基于一种成本控制的思路进行上下文的选择,首先通过流式处理,从指针分析结果(pta)的调用图中获取所有可到达的方法(reachableMethods),对于每个方法,计算其参数中属于“污点”(Taint)的数量。然后过滤掉污点参数数为0的方法,并将剩余方法按污点参数数从高到低排序。确保了那些更可能涉及敏感数据流的方法会被优先考虑 总的来说,上下文的大小是由一个动态的分析成本预算控制的。它优先处理污点参数多的方法,但同时严格限制方法的分析成本总和不超过上限(硬件条件)。这种设计巧妙地在分析广度(覆盖更多方法)和深度(分析复杂方法)之间取得了平衡,防止资源消耗无限增长

对于每个方法,只有当累计成本 count小于阈值(1e5)时,才会将其加入 csMap并标记为 "5",同时计数器 count5增1 如果方法非抽象,则计算其分析成本:变量数 * (调用者数量)^4,并将此成本累加到 count 一旦 count的值达到或超过 1e5,循环便会停止,后续方法不再被加入 csMap 2 csMapByTaintFlow方法,这个方法猜测是想基于通过上下文不敏感的静态分析结果得到的TaintFlow进行上下文的动态选择,但是感觉后面可能烂尾了,没有实现完

SDG (Service Dependence Graph) OpenFeignPlugin 该插件核心是用来建立通过Feign方式进行跨服务调用的调用边,用于构建SDG (Service Dependence Graph) 对于该插件同样是实现了标准的Plugin接口,其实现了onStart方法以及onNewCSMethod方法用于在程序分析前进行处理以及在遭遇新的方法时进行处理 对于onstart主要是在静态分析前对FeignClient进行处理,获取所有的feign类型的路由以及实现类,保存在mappingEdges

而对于onNewCSMethod实现了一个访问者模式,遍历遇到的所有新方法的所有Stmt,如何遇到函数调用的Stmt则会考虑其是否是一个invokeInterface类型的调用,也即是是否调用的是实现的接口的方法,这里是用来处理Feign这种方式进行跨服务通信的机制,根据feignClient类的类签名从mappingEdges获取所有的实现方法,并通过addCallEdge为这个调用过程建立一个调用边

GrpcPlugin 这个插件所起的作用和OpenFeignPlugin类似,均是用来处理微服务中的各个service间的调用关系 前者是用来处理Feign这种调用方式,这里的插件是用来处理通过Grpc这种方式进行调用的方式 对于onStart方法,其主要是用于构建invoke-callee的映射,也即是调用关系,Grpc服务端以及客户端stub的实现分别是实现了io.grpc.BindableService或者io.grpc.stub.AbstractStub 1 通过获取所有自己实现的io.grpc.BindableService类,将其有参类方法存储在serviceMethod,作为对位提供的grpc方法 2 筛选所有Grpc客户端的实现方法,通过审查所有的invoke函数调用,若被调用的函数所在类属于io.grpc.stub.AbstractStub实现,则认为其是一个客户端stub,获取这个远程调用方法的第一个参数变量,构建了一个var-invoke的映射,同时如果该方法能够在grpc服务端实现的可调用方法中找到的话,会构建一个从客户端调用点到被具体调用的方法的一个映射invoke2calleeMap

onNewCSMethod同样是在基于访问者模式构建一个跨服务调用的关系 1 对于所有跨服务调用点,在PFG (Pointer Flow Graph)上构建一个被调用方法参数传递的边,同时构建一个调用边 2 处理在微服务中采用guice这种轻量级的依赖注入组件,通过寻找其实现类的方式直接通过addPointsTo建立联系

RestTemplatePlugin 该插件用来处理使用RestTemplate进行各服务间通信的调用关系 1 最开始通过筛选exchange函数的调用点,构建var2InvokeMap用来映射exchange的传参以及调用点 2 在指针集发生变化时,通过var2InvokeMap中var所对应的指针集去获取想要请求的URI是什么,并保存在targetString

3 遍历上面收集的targetString,与GatewaySourcePlugin插件中识别到的endpoint的路由做比对,如果存在匹配成功的情况,将会构建一个从exchange函数调用点到对应路由提供者方法的一个调用边,并通过addPFGEdge将传入的参数进行跨服务传递

DubboPlugin dubbo作为一个RPC服务开发框架,同样提供一种在微服务架构中进行不同服务通信的方式,这里的DubboPlugin也即是对其进行支持,构建dubbo场景下的服务依赖图 在静态分析前基于注解进行dubbo服务端的识别

在指针分析过程中实时筛选所有的函数调用过程,如果存在调用了dubbo服务的函数,则建立此调用点到dobbo服务中定义的目标函数的调用边

KafkaPlugin 该插件用来处理在微服务框架中采用kafka进行服务间通信的方式 首先在进行静态分析之前,onStart方法中,从ApplicationClass中获取被KafkaListener注解的消费者方法,并以topic-method的映射保存在kafkaListeners中。同时从获取到生产者方法保存在kafkaSendMethods

其次是在onNewStmt事件触发时,判断是否是调用的生产者方法,若是的话,构建生产者方法的第一个参数,也就是topic和方法调用的一个映射

最后则是在指针集发生变化是触发的onNewPointsToSet事件中,判断是否topic对应的指向出现变化,遍历获取其指代的所有topic后在kafkaListeners寻找是否存在有消费该topic的消费者方法,若存在,将会通过addPFGEdge构建一个从生产者方法生产的消息内容到消费者方法消费的消息内容的指向边,以及通过addCallEdge构建一个从生产者方法到消费者方法的调用边

RabbitMQPlugin 该插件和kafka处理的对象都是消息队列的跨服务通信的依赖构建,且都是采用消息队列的方式,实现逻辑也类似 1 将消费者的监听队列以及处理时间方法映射保存在rabbitmqListeners中,以及将生产者的消息发送方法保存在rabbitmqSendMethods

2 构建消息发送函数调用同exchangeroute key的映射关系,同时构建消息处理函数调用同queue, exchange, route key的映射关系

3 类似的,最后就是根据route key以及exchange去匹配对应的消费者方法,同时构建从发送者方法所发送消息到消费者方法所消费消息的pointer edge,以及构建在消息发送点到消息处理点的call edge

Full progress 对于tai-e的整个流程大致可以分为以下的过程 1 进行静态分析前的准备工作,包括有指定appClassPath以及ClassPath 而对于这里的Mscan,包括有以下几点: 将配置文件中的Config.classpathKeywords添加到classpathKeywords 将前面Jar parser中提取到到的${targetPath}/BOOT-INF/classes中的类添加到appClassPath Jar parser提取到的${targetPath}/BOOT-INF/lib中的jar包添加到classPath

2 通过options中的配置去生成对应的plan文件

3 调用Soot对所有的类进行解析,包括有BOOT-INF/classes以及BOOT-INF/lib中的类,核心是使用了SootWorldBuilder#build方法进行处理

4 执行前面生成的analysis plan,对于pta,则使用对应的配置调用PointerAnalysis#analyze进行分析 a 首先是构建一个Heap abstraction,用来将动态时无限的对象抽象为有限,通常选用为Allocation-Site这一抽象方式 b 其次则是构建ContextSelector,优先使用advanced中的配置,若没有配置advanced,则根据makePlainSelector去正常获取上下文选择器,支持有以下context selector variant

ci: context-insensitive analysis k-obj/call/type c 在构建了heap abstraction以及context selector后调用runAnalysis进入指针分析逻辑

d 在核心的指针分析逻辑中,其主要是根据heapModel以及selector构建一个Solver对象,通过其中的solve方法进行分析 值得注意的是,tai-e设计中存在有一个扩展性极强的插件系统,详情可见https://tai-e.pascal-lab.net/docs/current/reference/en/index-single.html#analysis-plugin-system

e 对于solve方法,其实现了指针分析算法

f 其中算法的伪代码中的添加入口点以及addReachableDafaultSolver#initialize方法实现,其首先对一些全局变量进行了初始化,核心是通过插件系统的onStart方法调用去实现,依靠插件系统可以实现在整个程序分析的生命周期中的各个环节的实时计算,这里通过onStart方法调用,一方面对装载的各个插件进行初始化,另一方面对算法中的addEntry以及addReachable进行实现

g 而对于solve方法的第二部分,也即是analyze则对应于伪代码中的work list的处理过程,核心是对于work list中的各个元素,首先判断其指针集是否存在变化,若存在变化则处理对应的store以及load操作

1 对于指针分析的分析结果其通过构建一个PointerAnalysisResultImpl对象,存储了调用图,指针流图,指针集等丰富的信息,且最终的分析结果根据analysis-id的对应关系保存在了World

Real world 上述内容主要是对静态分析框架的整个框架的原理以及代码实现进行了阐述,下面基于上面的静态分析框架为基座,构建了一个clone-complie-scan全流程的自动化漏洞检测闭环 clone 首先是clone环节,对于目标项目的选择,我们采用github以及gitee平台提供的筛选的功能对高star的Java项目进行初步筛选,后续得益于LLM的理解能力,通过LLM对初筛的项目文档进行理解对项目进行分类,具体可以从两个角度进行分类 1使用maven进行项目编译还是gradle进行项目编译:通过识别项目的编译方式以便于下一步的自动化编译过程 2项目所具备的特征:例如是一个微服务项目或者电商项目,通过这样的方法对业务进行分类 同时,在收集的过程中,也不单单局限于仅对微服务相关项目进行收集,可对全部的基于Java开发的项目进行收集进行批量检测

image.png

如上图所示,则是收集的一些Java项目的样例,通过yml文件的方式将待检测项目进行归类 之后分别提取每一个项目的URL,通过调用系统命令 git clone的方法将项目克隆到本地

compile 而对于编译阶段,核心是对上一阶段克隆的项目进行编译处理,能够将项目打包成一个一个完成的jar包,以便于收集这些项目包使用静态分析工具进行漏洞检测任务。 通过前面项目收集过程中标注的该项目所采用的项目是基于Maven还是Gradle进行开发的,我们选择不同的系统命令进行Java项目的编译

经过我的全过程的测试,值得注意的是,在进行项目编译的过程中不仅仅需要动态的选择不同的编译命令进行Java项目的编译,在编译过程中其核心会使用 JAVA_HOME这一环境变量所指向的JDK版本环境参与项目的编译过程,千人千面,不同的Java项目所能够支持的最低JDK版本不同,这里需要进行尝试性编译,也即是动态的调整JDK版本,按照从高到低的JDK版本对项目进行自动化编译,能够明显的降低仅采用同一种JDK版本进行编译而导致的编译失败几率。 在编译成功后会在对应目录中生成打包的Jar包,Maven项目默认的编译目录为 target,而Gradle项目默认的编译目录为 build

image.png

scan 上一阶段仅仅是对克隆的项目进行了编译、打包Jar任务,对于多模块开发的Java项目,其生成的Jar包散落在各个文件夹下的 target目录中,以便于静态工具进行扫描,我们首先需要将编译成功的Jars包进行收集整理到一起

通过上述代码可以根据规则提取生成的jar包

image.png

而对于核心的扫描任务,我们首先对Mscan进行改造,使得将其打包后可以动态的修改options.yml文件以便于指定待检测项目以及检测过程中产生文件的保存位置

通过以上代码能够对所有编译成功的项目执行静态分析任务 其检测结果保存在每一个项目名文件夹下的 microservice-taint-flows.txt文件中

image.png

对于不存在Taint通路的项目其内容为空,在大量项目中筛选存在有通路的可以使用以下脚本输出可能存在漏洞的项目

对于最终的检测结果也算是有所收获

image.png

Conclusion 上文对Mscan针对微服务应用这一特定应用进行了建模,针对微服务应用中的各个服务间通过OpenFeign、Grpc、Kafka以及RabbitMQ等框架进行通信的方式构建了一个服务依赖图,用于表征数据流的传递路径,进一步的进行污点传播进行外部可控的Web漏洞检测。通过对类似于OpenFeign等框架的通信机制的分析,使用Tai-e插件系统提供的生命周期API构建调用边,对于一些其他未使用这类框架进行服务间通信的微服务应用可以采用类似的方式扩展的构建调用边以便于支持其漏洞检测任务。同时也对静态分析框架在完整流程的重要阶段过程进行了阐述,也即是Soot程序分析,以及指针分析算法的实现。最后也是基于静态分析框架为核心构建了一个 clone-compile-scan全流程的workflow。


Intro

Tai-e作为一个优秀的静态分析框架,内置了指针分析、污点分析等等实现。为增强其作为一个底座框架的可扩展性,其提供了插件系统,通过插件系统可以控制在静态分析过程中的各个阶段的数据处理,更进一步的进行定制化分析的实现。如下图为Tai-e官方提供的有关于插件系统的原理图:

image.png



本文中提及的有关于微服务应用的静态分析框架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位置,构建一个从sourcePointsinkPoint的污点传播流



4 针对于使用result为污点结果的sink规则,如果存在由对应的调用,将会遍历该方法所有的返回值,构建一个SinkPoint对象,标注了excludeSourceParamAnno以及excludeCallSource并进行污点传播的检查



5 在根据上述的逻辑完成了污点分析的逻辑之后,对分析的结果进行二次验证,检验是否存在一个完整的通路从sourceMethodsinkMethod使得其为一个有效的taintFlow这里核心是使用广度优先遍历的算法进行调用链的查找,从指针分析的结果CallGraph检索是否存在完整的调用链,将存在有完整调用链的taintflow进行返回



6 后续则是根据taintFlows的结果进行污点流图的dump操作,将污点传播路径通过.dot文件的格式进行保存,方便可视化展示

SpringController

该插件作为污点分析插件的一个子插件,用来构建source点的污点入口



在静态分析遭遇新方法时触发onNewCSMethod事件

1 首先基于注解的方式判断该类是否是一个Controller

2 其次通过参数类型的检查,判断该参数是否可以被外部可控,但是这里有点小疑问,这里将safeClass中存在的参数类型作为一种外部不可控的类型,但是感觉HttpServletRequest这类类型一定程度上也是可控的,例如通过request.getParamter等函数进行外部数据的获取,也会造成外部可控的情况,所以这里的规则可以进行完善



3 对每一个可控的参数位置构建一个ParamSourcePoint对象,并将其作为一个taint,特别的,这里也会对参数所在classfields归类为一个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