基于污点分析的JSP Webshell检测(二)

简介: 基于污点分析的JSP Webshell检测

动态编译

手动编译的时候其实有一个坑:系统不包含servlet相关的库,所以会报错

这个好解决,只需要一个参数javac Webshell.java -cp javax.servlet-api.jar


在网上查了下如何动态编译,这个代码还是比较多的

但都没有设置参数,我们情况特殊需要classpath参数,最终看官方文档得到了答案

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(
    null, null, null);
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(
    new File("Webshell.java"));
// 加入参数
List<String> optionList = new ArrayList<>();
optionList.add("-classpath");
optionList.add("lib.jar");
// 不需要打印多余的东西
optionList.add("-nowarn");
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager,
                                                     null, optionList, null, compilationUnits);
task.call();


通过以上的代码会得到一个Webshell.class的字节码文件,这就是我们真正需要的东西

这里同样有一个坑:ToolProvider.getSystemJavaCompiler()这句话在java -jar xxx.jara的情况下是空指针,通过查询解决办法,发现需要在JDK/JRE的lib加入tools.jar并且将环境变量配到JDK/bin而不是JDK/JRE/bin或JRE/bin


当我们动态编译Webshell.java到Webshell.class后,读取字节码到内存中,就可以删除这两个临时文件了

byte[] classData = Files.readAllBytes(Paths.get("Webshell.class"));
Files.delete(Paths.get("Webshell.class"));
Files.delete(Paths.get("Webshell.java"));

模拟栈帧

JVM在每次方法调用均会创建一个对应的Frame,方法执行完毕或者异常终止,Frame被销毁

而每个Frame的结构如下,主要由本地变量数组(local variables)和操作栈(operand stack)组成

局部变量表所需的容量大小是在编译期确定下来的,表中的变量只在当前方法调用中有效

JVM把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈

参考我在Github的代码,该类构造了Operand Stack和Local Variables Array并模拟操作

在用ASM技术解析class文件的时候,模拟他们在JVM中执行的过程,实现数据流分析

1aca10b656990ccefb5b9f05e8b57f25_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


使用代码模拟两大数据结构

public class OperandStack<T> {
    private final LinkedList<Set<T>> stack;
    // pop push methods
}
public class LocalVariables<T> {
    private final ArrayList<Set<T>> array;
    // set get method
}

在进入方法的时候,JVM会初始化这两大数据结构

  • 清空已有的元素
  • 根据函数入参做初始化
public void visitCode() {
    super.visitCode();
    localVariables.clear();
    operandStack.clear();
    if ((this.access & Opcodes.ACC_STATIC) == 0) {
        localVariables.add(new HashSet<>());
    }
    for (Type argType : Type.getArgumentTypes(desc)) {
        for (int i = 0; i < argType.getSize(); i++) {
            localVariables.add(new HashSet<>());
        }
    }
}


在方法执行的时候,对这两种数据结构进行POP/PUSH等操作,随便选了其中一部分供参考

@Override
public void visitInsn(int opcode) {
    Set<T> saved0, saved1, saved2, saved3;
    sanityCheck();
    switch (opcode) {
        case Opcodes.NOP:
            break;
        case Opcodes.ACONST_NULL:
        case Opcodes.ICONST_M1:
        case Opcodes.ICONST_0:
        case Opcodes.ICONST_1:
        case Opcodes.ICONST_2:
        case Opcodes.ICONST_3:
        case Opcodes.ICONST_4:
        case Opcodes.ICONST_5:
        case Opcodes.FCONST_0:
        case Opcodes.FCONST_1:
        case Opcodes.FCONST_2:
            operandStack.push();
            break;
        case Opcodes.LCONST_0:
        case Opcodes.LCONST_1:
        case Opcodes.DCONST_0:
        case Opcodes.DCONST_1:
            operandStack.push();
            operandStack.push();
            break;
        case Opcodes.IALOAD:
        case Opcodes.FALOAD:
        case Opcodes.AALOAD:
        case Opcodes.BALOAD:
        case Opcodes.CALOAD:
        case Opcodes.SALOAD:
            operandStack.pop();
            operandStack.pop();
            operandStack.push();
        ......
    }
}

为什么能够这样操作,参考Oracle的JVM指令文档:官方文档


上文其实略枯燥,接下来结合实例和大家画图分析,这将会一目了然


检测实现

新建一个ClassVisitor用于分析字节码,以下这三部是ASM规定的分析字节码方式

ClassReader cr = new ClassReader(classData);
ReflectionShellClassVisitor cv = new ReflectionShellClassVisitor();
cr.accept(cv, ClassReader.EXPAND_FRAMES);

大家需要注意ASM是观察者模式,需要理解阻断和传递的思想


其实ReflectionShellClassVisitor不是重点,因为我们的JSP Webshell逻辑都写在Webshell.invoke方法中,所以检测逻辑在ReflectionShellMethodAdapter类中

// 继承自ClassVisitor
public class ReflectionShellClassVisitor extends ClassVisitor {
    private String name;
    private String signature;
    private String superName;
    private String[] interfaces;
    public ReflectionShellClassVisitor() {
        // 基于JDK8做解析
        super(Opcodes.ASM8);
    }
    @Override
    public void visit(int version, int access, String name, String signature,
                      String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        // 当前类目描述符父类名等信息有可能用到
        this.name = name;
        this.signature = signature;
        this.superName = superName;
        this.interfaces = interfaces;
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor,
                                     String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        // 不用关注构造方法只分析invoke方法即可
        if (name.equals("invoke")) {
            // 稍后分析该类
            ReflectionShellMethodAdapter reflectionShellMethodAdapter = new ReflectionShellMethodAdapter(
                    Opcodes.ASM8,
                    mv, this.name, access, name, descriptor, signature, exceptions,
                    analysisData
            );
            // 出于兼容性的考虑向后传递
            return new JSRInlinerAdapter(reflectionShellMethodAdapter,
                    access, name, descriptor, signature, exceptions);
        }
        return mv;
    }
}

重点放在ReflectionShellMethodAdapter类

首先我们要确认可控参数,也就是污点分析里的Source,不难得出来自于request.getParameter

这一步的字节码如下

    ALOAD 0
    LDC "cmd"
    INVOKEINTERFACE javax/servlet/http/HttpServletRequest.getParameter (Ljava/lang/String;)Ljava/lang/String; (itf)
    ASTORE 3


这四步过程如下:

  • 调用方法非STATIC所以需要压栈一个this对象
  • 方法执行时弹出参数,方法执行后栈顶是返回值保存至局部变量表

207eaed15a1c840edd4f8b1dccec4aab_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


我们可以在INVOKEINTERFACE的时候编写如下代码

@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
    if (opcode == Opcodes.INVOKEINTERFACE) {
        // 是否符合request.getParameter()调用
        boolean getParam = name.equals("getParameter") &&
            owner.equals("javax/servlet/http/HttpServletRequest") &&
            desc.equals("(Ljava/lang/String;)Ljava/lang/String;");
        if (getParam) {
            // 注意一定先让父类模拟弹栈调用操作,模拟完栈顶是返回值
            super.visitMethodInsn(opcode, owner, name, desc, itf);
            logger.info("find source: request.getParameter");
            // 给这个栈顶设置个flag:get-param以便于后续跟踪
            operandStack.get(0).add("get-param");
            return;
        }
    }
}



相关文章
|
存储 Java 关系型数据库
JSP考试质量分析系统myeclipse开发mysql数据库bs框架java编程web网页结构
JSP 考试质量分析系统是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0,使用java语言开发,系统主要采用B/S模式开发。
249 1
|
Java 关系型数据库 MySQL
JSP故障诊断分析管理系统myeclipse开发mysql数据库BS模式java编程jdbc
JSP 故障诊断分析管理系统是一套完善的web设计系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,开发环境为TOMCAT7.0,Myeclipse8.5开发,数据库为Mysql5.0,使用java语言开发,系统主要采用B/S模式开发。
90 1
JSP故障诊断分析管理系统myeclipse开发mysql数据库BS模式java编程jdbc
|
Java 物联网 Shell
Jsp Webshell在物联网的应用
Jsp Webshell在物联网的应用
115 0
|
Java
基于污点分析的JSP Webshell检测(三)
基于污点分析的JSP Webshell检测
222 0
基于污点分析的JSP Webshell检测(三)
|
安全 Java
基于污点分析的JSP Webshell检测(一)
基于污点分析的JSP Webshell检测
424 0
基于污点分析的JSP Webshell检测(一)
|
安全 Java
浅谈JSP Webshell进阶免杀(三)
浅谈JSP Webshell进阶免杀
714 0
|
算法 JavaScript Java
浅谈JSP Webshell进阶免杀(二)
浅谈JSP Webshell进阶免杀
308 0
|
5月前
|
Java 容器
【学习笔记】Jsp与Servlet技术
【学习笔记】Jsp与Servlet技术
121 0
|
7月前
|
SQL Java 数据库
jsp中使用Servlet查询SQLSERVER数据库中的表的信息,并且打印在屏幕上
该博客文章介绍了在JSP应用中使用Servlet查询SQL Server数据库的表信息,并通过JavaBean封装图书信息,将查询结果展示在Web页面上的方法。
jsp中使用Servlet查询SQLSERVER数据库中的表的信息,并且打印在屏幕上
|
7月前
|
供应链 前端开发 Java
JSP+servlet+mybatis+layui服装库存管理系统(大三上学期课程设计)
这篇文章通过一个服装库存管理系统的实例,展示了在Spring Boot项目中使用Ajax、JSON、layui、MVC架构和iframe等技术,涵盖了注册登录、权限管理、用户管理、库存管理等功能,并提供了系统运行环境和技术要求的详细说明。
JSP+servlet+mybatis+layui服装库存管理系统(大三上学期课程设计)
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等