使用太阿(Tai-e)进行静态代码安全分析(Servlet容器篇-零)
极*冻 发表于 北京 历史精选 1576浏览 · 2024-05-06 16:40

author:SpringKill

概述

Tai-e是针对java的静态程序分析框架,支持包括指针分析、数据流分析、污点分析在内的诸多静态程序分析。由于Tai-e并非专门用来做静态代码安全分析,所以并非开箱即用,在实际安全分析中使用有许多问题。准备通过大致如下多篇文章,逐渐将Tai-e改造为开箱即用的静态代码安全分析框架。

  • TomcatServlet API调用适配web应用。
  • FiltersListeners等处理。
  • 不同Java Servlet容器的适配
  • ……

本文是在看到了larck和Keanu两位大佬的文章之后发现同样有对Tai-e感兴趣的人所以写出,其实还有一些问题没有解决本文也不算是第一篇的完整版就叫第零篇了。

Tomcat

原理部分前面两篇文章讲得很详细,我就不过多赘述了,有兴趣的可以参阅:
https://xz.aliyun.com/t/13775
https://xz.aliyun.com/t/14058

入口点

tai-e默认是以main方法作为入口点的,所以要对Java Web程序进行分析需要手工添加入口。
首先要支持的就是最直接的部署方式,Tomcat的servlet编程。
在使用servlet编程的时候,后端对每个路径都要有对应的servlet类,在servlet类中以注解@WebServlet()来表示访问的路径名,并根据传入的http请求类型决定调用其中的doGet()doPost()等方法。
那么根据这一点我们就可以直接使用Tai-e提供的方法,扫描所有的类并尝试获取@WebServlet()注解,对于获取到注解的类我们扫描其全部方法,提取出全部的服务方法,将其作为入口点进行分析。
当然也有使用web.xml形式配置的方法,只需要扫描web.xml文件获取即可,以后也会提到。
具体做法是,拓展一个插件,然后重写onstart方法,在程序开始的时候对程序中扫描到的所有class进行扫描,然后找到包含javax.servlet.ServletRequest注解的类,这些类就是入口点。
但是要注意的是,由于Tai-e的堆模拟无法直接模拟接口类,所以我们要手工添加一个模拟类,这个类要实现当前的被模拟的接口(javax.servlet.http.HttpServletRequest接口)。
tomcat中,请求实际拿到的是org.apache.catalina.connector.RequestFacadeorg.apache.catalina.connector.ResponseFacade,所以在设置入口点的时候我们将其作为mockobj放进去。

具体实现如下:

final JClass requestFacade = World.get().getClassHierarchy().getClass("org.apache.catalina.connector.RequestFacade");
final JClass responseFacade = World.get().getClassHierarchy().getClass("org.apache.catalina.connector.ResponseFacade");

@Override
public void onStart() {
    List<JClass> list = solver.getHierarchy().applicationClasses().toList();
    for (JClass jClass : list) {
        String a = jClass.getName();
        if (a.matches("^(javax\\.servlet\\.ServletRequest).+$")) {
            System.out.println("found: " + a);
        }
        HeapModel heapModel = solver.getHeapModel();
        jClass.getAnnotations().forEach(annotation -> {
            if (annotation.getType().matches("javax.servlet.annotation.WebServlet")) {
                jClass.getDeclaredMethods().forEach(jMethod -> {
                    if (jMethod.getName().matches("\\b(doGet|doPost|doPut|doDelete)\\b")) {
                        Type requestFacadeType = jMethod.getParamType(0);
                        Type responseFacadeType = jMethod.getParamType(1);
                        String requestFacadeAlloc = "<" + requestFacadeType.toString() + ">";
                        String responseFacadeAlloc = "<" + responseFacadeType.toString() + ">";
                        Obj mockRequest = heapModel.getMockObj(ENTRY_DESC, requestFacadeAlloc, requestFacade.getType(), jMethod);
                        Obj mockResponse = heapModel.getMockObj(ENTRY_DESC, responseFacadeAlloc, responseFacade.getType(), jMethod);
                        Obj mockServlet = heapModel.getMockObj(ENTRY_DESC, "<http-controller>", jClass.getType());
                        SpecifiedParamProvider paramProvider = new SpecifiedParamProvider.Builder(jMethod)
                        .addThisObj(mockServlet)
                        .addParamObj(0, mockRequest)
                        .addParamObj(1, mockResponse)
                        .build();
                        solver.addEntryPoint(new EntryPoint(jMethod, paramProvider));
                    }
                });
            }
        }
                                       );
    }
}

source

添加了入口点,我们还需要添加source让初始数据携带污点,这个时候就需要重写onNewCSMethod方法,具体原理可以参考Springboot篇(一)和(二)。
使用如下代码:

@Override
public void onNewCSMethod(CSMethod csMethod) {

JMethod method = csMethod.getMethod();
Context context = csMethod.getContext();
boolean isDoMethod = method.getName().matches("\\b(doGet|doPost|doPut|doDelete)\\b");
if (isDoMethod) {
    IR ir = method.getIR();
    Var param = ir.getParam(0);
    SourcePoint sourcePoint = new ParamSourcePoint(method, new IndexRef(IndexRef.Kind.VAR, 0, null));
    Obj taint = manager.makeTaint(sourcePoint, param.getType());
    solver.addVarPointsTo(context, param, taint);
}

这里的代码和上面很类似,不过上面是为了添加入口,这里是为了添加source,扫描方法名,并将request对象(也就是do方法的第0个参数)添加为source
然后就可以配置transfer了,transfer的配置直接通过配置文件就可以实现,这里举个例子,以getParameter方法为例子:

- { method: "<org.apache.catalina.connector.RequestFacade: java.lang.String getParameter(java.lang.String)>", from: base, to: result }

getParameter方法最为简单,添加最为方便,实际还有getParameterValuesgetParameterMaphearderscookies等等,对于这些点的transfer添加略微复杂,打算放在下一篇文章来说。
sink点配置因为是检测命令执行,所以直接对exec添加sink就可以:

- { vuln: "Remote Code Execution", level: 1, method: "<java.lang.Runtime: java.lang.Process exec(java.lang.String)>", index: 0 }

可以看到污点的流图了:

仍然存在的问题

对于Map中的数据,在上下文不敏感的分析中是可行的,但是在进一步的上下文敏感分析中,Tai-e的指针分析或者说污点分析不能精确的识别,导致污点中断(也可能是我使用的问题),不过目前问题已经初步解决但还不完善,水平有限,希望以后的学习能带来更多有意思有质量的东西,如本文存在错误,请大佬们指正。

0 条评论
某人
表情
可输入 255