[备份]基于GI的自动Java代码审计工具实现

本文将介绍笔者探索出的一种比较完善的Java自动代码审计方式,实现了可控参数判断数据流分析

简介

一开始尝试用AST做事情,遇到了较大的困难,于是考虑从字节码层面分析

在上一篇文章中,分析了反序列化漏洞链自动挖掘工具

之所以要深入学习GI这个工具,因为笔者是在此工具的基础上重写的一套代码

本文会有较多部分和GadgetInspector原理类似,尽量不重复写之前文章中的内容

流程分析

输入

工具最终的目标是:用户输入一个SpringBoot的Jar包,经过该工具的分析后直接得出漏洞报告

img

输入参考GI,读取SpringBoot和JDK的代码大致流程如下:

  1. 解压用户提供的Jar包
  2. 解压后BOOT-INF/classes为项目代码,创建InputStream
  3. 解压后BOOT-INF/lib为项目依赖库,包含各种普通Jar包
  4. 将依赖库全部普通Jar包解压,为其中所有class文件创建InputStream
  5. 根据String类找到rj.jar利用Guava的库得到JDK所有class文件,创建InputStream

基本信息

获得所有class文件的InputStream后,利用ASM技术分析所有文件,得出以下信息:

  1. 所有类和方法的信息(参考GI的MethodDiscoveryClassVisitor
  2. 所有方法内的方法调用信息(参考GI的MethodCallDiscoveryClassVisitor
  3. 分析所有类和方法的继承实现关系(参考GI的InheritanceDeriver

以上信息的分析代码较简单,将list传入ClassVisitorMethodVisitor,重写visitMethod等方法,每visit到一个新的类或方法,都会将当前信息加入集合中。最终得到的这个list即是我们需要的基本信息

Spring信息

不同于GI,笔者要实现的主要是针对于SpringBoot代码的审计,所以有针对性的对Spring信息进行收集是有必要的

而Spring生态中,需要重点关注的是SpringMVC相关,这里能够确认用户的输入,也就是整个分析流程的起点

例如下方代码,通过/demo传入的String型demo变量,这个是我们需要跟踪的起点

通过一些简单的ASM代码,笔者实现了分析Controller层信息的代码,最终得到每个路径映射的参数,路径,方法等信息

DataFlow信息

DataFlow是数据流分析的关键,参考自GI的实现,该类模拟了JVM中的Operand StackLocal Varaibles。个人理解相当于把完全静态的代码做成了半动态,也只有这种情况下才能做到数据的“流动”

一句话:根据分析得到当前方法的返回值与哪些参数有关

原理在以前的文章中有过深入的分析,这里不再多说

CallGraph信息

CallGraph信息是指方法的调用图,这部分在之前的文章中没有写,所以会稍微多一些篇幅

例如这样的代码

那么应该有一个这样的调用关系:

由于test1方法是静态方法,而demotest2方法不是。需要考虑到正常情况下方法参数索引0为this

caller参数a的索引为1,target参数索引在静态情况下为0,正常情况下为1

这部分代码在GI中的实现不难:

ASM规定,在真正visit方法体之前会先调用visitCode,所以应该在这里做Operand StackLocal Variables的初始化

先看父类,进行清空然后根据方法传参清空重新对Local Variables赋值,这里是模拟JVM的真实操作

子类给每一个方法设置上当前的参数索引

在遇到方法内的方法调用时,会执行visitMethodInsn方法

漏洞分析

获得了以上信息,我们就可以对漏洞进行深入分析了,掌握DataFlowCallGraph的原理,那么数据流跟踪不是问题

SSRF实例

之所以用SSRF做实例,因为这是一种比较简单的审计,主要对几个请求相关的函数做监控即可

挑软柿子捏,也是一种抛砖引玉,根据这种简单的思路其实可以做大部分web漏洞的事情了

主要思路:确定请求参数是否可以到达关键函数

入口

递归分析

用递归的方式实现完整调用链的分析

SSRFClassVisitor

这里是分析的核心部分

SSRFMethodAdapter

该类是全文的重点,最核心的类

笔者将GI的TaintTrackingMethodVisitor修改一部分重写为CoreMethodAdapter

并设置SSRFMethodAdapter继承自CoreMethodAdapter

暂时不解释目的,分析完自然可以看出

初始化

visitCode

之前有提到过visitCode方法是在进入方法体之前调用的,优先度很高

所以这里主要的目的是设置Operand StackLocal Variables的初始化状态

参考之前文章中画的图:理解方法是如何调用的

img

visitMethodInsn

笔者抛砖引玉,给出最简单的SSRF调用

字节码如下

第一步分析:

第二步分析:

第三步:

最终分析:

如果第一步传入的data是可控参数,或者说是从请求中获取的参数,并且符合这三步规则,那么认为存在SSRF漏洞

这里是分析的重点部分,如果方法中出现方法调用,那么会执行到此方法

注意:可以看到在末尾执行的super.visitMethodInsn,这表示代码效果类似Debug,在Stack有变化之前做的分析

注意:

  1. 实例是三步在一起,连续执行。实际如果分散开来,情况和上文的三步分析一致,照样可以检测
  2. 这里做到的事是跟踪请求参数能到达的每一处,所以不用担心检测的深度

实践

自己写了个SpringBoot的项目,打了个Jar包(可以看到三步调用比较分开,而且引入了接口和实现)

打包调用:指定一下项目的package路径即可

效果截图:见最后一行

img

图中可以看出,分析时间较长,需要5分钟左右

所以笔者每一次调试都是受罪,跑一次就是5分钟,本来有思路的情况等一会后就忘记了哈哈

总结

项目开源了,大部分内容来自GI项目,但我做了一些优化和简化

已经做到分析SpringMVC传入的参数,做数据流的跟踪和分析,不会局限于SSRF

也许在目前的基础上,改写下,就可以实现其他的漏洞检测

距离最终的目标:输入一个jar直接出报告,其实不算很远了