[备份]AST在Java代码审计领域的尝试

介绍

最近想做一些自动化的代码审计工具,发现存在难点

最初考虑采用纯正则等方式匹配,但这种方式过于严格,程序员编写的代码有各种可能的组合

于是尝试自行实现Java词法分析和语法分析,稍作尝试后发现这不现实,一方面涉及到编译原理的一些算法,另外相比C语言等,Java语言本身较复杂,不是短时间能搞定的,深入研究编译原理背离了做审计工具的目的

后来找到了几种解决方案:Antlr,JavaCC,JDT,javaparser

经过对比,最终选择javaparser项目,该项目似乎是基于JavaCC,核心开发者是effective java的作者。使用起来比较方便,可以简单地以依赖的方式导入

笔者本想采用Golang编写该工具,查找相关资料后发现,Golang本身提供AST库,可以对Golang本身做语法分析,但找不到实现Java语法分析的库(考虑后续复习下编译原理自己尝试)

XSS实例

javaparser最根本的类是CompilationUnit,如果我们想对代码做分析,首先需要实例化该对象

给出一段最简单的XSS代码

针对于该案例,我们写审计工具的原理

验证导包

关于验证导入包的情况,简单做了一个方法

如果要验证请求和相应包的导入情况

获得类节点

首先拿到Demo这个Class,因为一个java文件中不一定只有一个类

进一步,我们需要判断该类是否继承自HttpServlet

只有得到类节点,才可以继续遍历抽象语法树拿到方法等信息

获得方法

遍历得到方法节点,并且拿到具体的请求和响应参数名称

之所以要拿到方法参数名,是为了做进一步的追踪

确认参数可控

审计漏洞的关键点就在于参数的可控,这也是难点

就本案例而言,如果某个参数是req.getParameter("...")获取的,那么就可以认为是可控

实际上这个req并不一定是req,可能是request,requ等,这也是上一步需要一个map保存的原因

可以加上参数校验

获取所有的赋值表达式,确定是否调用了req.getParameter这样的参数

并且参考上文的方式使用map保存这个参数结果,用于后续校验

确定触发点

触发点在本案例中是resp.getWriter().write()

这是一个方法调用,所以搜索MethodCallerExpr

针对于这个基础案例,可以再加入几个规则,针对于response.getOutputStream方式

测试

尝试让原来的XSS代码复杂一些,看看审计的效果

运行后成功检测到XSS img

只针对最基本的Servlet XSS做了审计,实际上无论从广度还是深度,都有巨大的工作量:

简单写了个输出html的页面: img

SQL注入实例

审计案例

JDBC

从最简单的JDBC原生SQL注入来看,怎样的语句是存在注入的?

1.不使用prepareStatement而使用createStatement

  1. 调用了executeQueryexecuteUpdate方法
  2. SQL语句进行了拼接操作
  3. 拼接的地方应该是String类型

给出以下三个案例:

1.直接在方法内拼接

2.新建一个SQL变量拼接赋值并传入

3.用String.format参数进行格式化

其实还应该有多种情况,但其他方式解析方式类似

问题不在于如何解析,而在于考虑多种情况必然出现疏漏,无法完整地覆盖

比如有两个问题待解决,这里就是局限性:

  1. 传入的如果不是String,而是DTO包装类,在拼接的时候get属性如何处理
  2. 在拼接的时候如果调用了其他Util,应该如何处理

除了JDBC原生,注意到SpringJdbcTemplateJPA也存在问题,但审计和分析原理大同小异。同样存在局限性:无法考虑到所有的编写情况

Mybatis Annotation

Mybatis框架是Java开发常用的框架,这里先看注解形的审计规则

  1. 某类是接口类型(interface)并且有注解Mapper
  2. 参数必须有Param注解并且类型要求是String
  3. 方法注解必须是Select等,并且value内包含了${}(其实Mybatis这里是一个值得讨论的点,并不是说有了一定存在注入,也不是说有#一定安全。存在一些复杂的问题,但目前先粗略地认为只要有那么就是有漏洞的,可以参考大佬文章MyBatis 和 SQL 注入的恩恩怨怨

局限性:

  1. 如果传入的是一个包装类,然后#{}${}取的是类属性如何处理
  2. 如何从controller->service->mapper这一个流程进行追踪

Mybatis XML

使用注解方式的Mybatis是最常见的手段,原理类似上文,对${}做检查,简单的规则可以总结如下:

  1. 解析XML找到mapper标签下的select等标签
  2. 如果select标签内容匹配到${}认为存在漏洞

问题以及局限性:

  1. 如果传入的是包装类
  2. mapper标签其实并不是必须,因为Spring可以配置扫描包路径
  3. mybatis的xml支持多种标签,比如if,where,when等,${}是有可能在这些标签里的(从实践来看,不少的后端开发程序员并不喜欢这些标签,更喜欢自己手写SQL语句)

代码实现

代码实现这里从简单到难,先从Mybatis这两种分析,再到结合具体语法分析的JDBC

Mybatis XML

解析XML

分析Value

跑了下

img

Mybatis Annotation

拿到interface并遍历所有method,对于类注解mapper忽略,因为不是必须

验证方法的参数注解是否合规,拿到可能存在注入的参数,做进一步分析

分析注解内的Value

跑了下

img

JDBC

拿到非接口非抽象类的类对象,遍历所有方法

定义了三个map,分别是最终的判断条件,方法内变量,方法参数

对传入的参数进行保存,后续判断会用到

检查方法内变量

对之前拿到的MethodCall进行分析(值得一说的是lambda里的return是continue,很奇怪)

前两种情况

最后一种情况的分析

跑了下

img

总结

从上文的分析可以得出,AST存在较多的缺点,难以处理的缺点:

  1. 无法完全覆盖语句编写方式:虽然我这里处理了几种常规的方式,然而远远少于实际的可能性
  2. 无法从源头确认参数可控:难以实现调用关系与数据流动的分析

解决

一种方式是基于字节码和Java Code之间的代码,又被称为IR,可以有效地分析数据流动。另一种方式是使用ASM,从字节码本身触发,直接解析字节码,进而得到调用关系与数据流动(参考gadget inspector的实现)