Deserialization Twice

Deserial Twice

最近遇到了很多java题目,大都弄了个类继承ObjectInputStream,重写其resolveClass方法,在里面添加对反序列化类黑名单的校验。比如下面这个

public class MyObjectInputStream extends ObjectInputStream {

   private static final String[] blacklist = new String[]{
           "java\\.security.*", "java\\.rmi.*",  "com\\.fasterxml.*", "com\\.ctf\\.*",
           "org\\.springframework.*", "org\\.yaml.*", "javax\\.management\\.remote.*"
   };

   public MyObjectInputStream(InputStream inputStream) throws IOException {
      super(inputStream);
   }

   protected Class resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
      if(!contains(cls.getName())) {
         return super.resolveClass(cls);
      } else {
         throw new InvalidClassException("Unexpected serialized class", cls.getName());
      }
   }

   public static boolean contains(String targetValue) {
      for (String forbiddenPackage : blacklist) {
         if (targetValue.matches(forbiddenPackage))
            return true;
      }
      return false;
   }
}

或是这样子

当然平时没事的时候可以研究一下这些黑名单中的类在反序列化中的关键用途

但是在比赛做题的时候就很恼火了,若没有积累充足的Java反序列化利用链经验,很难绕过;比赛时临时去找触发类也挺难的。

Java题就变成一道类的排列组合题了🤯,拼出一条可以打通的在黑名单之外的利用链。

这时候就可以考虑一下二次反序列化了,不用你定义的检测黑名单的ObjectInputStream去加载序列化对象,而是找到一条可以触发readObject的链子,用原生的ObjectInputStreamresolveClass

SignedObject

java.security.SignedObject#getObject

这个类在Hessian反序列化中用过,由于Hessian反序列化的特殊性,不会执行类的readObject来反序列化,而是通过反射获取field再填充进一个空的实例化对象,_tfactory又是transient修饰,writeObject不会写进去,导致TemplatesImpl不能利用。

🚩触发方式:能够执行类的getter方法,比如配合ROMEFastJson

SerializationUtils

org.springframework.util.SerializationUtils.deserialize

RMIConnector

javax.management.remote.rmi.RMIConnector#findRMIServerJRMP

若能控制base64参数的内容就可以任意反序列化。

往上回溯

path/stub/开头就能进到findRMIServerJRMPpath/stub/为序列化字节的base64编码

pathdirectoryURL#getURLPath得到

在往上发现connectdoStart调用了findRMIServer

利用CC链的InvokerTransformer来触发connectdoStartprotected修饰,不能用InvokerTransformer触发)

下面以CC6为例

可以看到findRMIServerJRMP支持jndistubiiop

跟进path/jndi/开头的分支:findRMIServerJNDI

熟悉的InitialContext#lookup,改一下path就可以jndi注入了

WrapperConnectionPoolDataSource

com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setuserOverridesAsString可以跟进到

C3P0ImplUtils#parseUserOverridesAsString

注意这里字符截取是从HASM_HEADER.length() + 1userOverridesAsString.length() - 1,最后一位会吃掉

SerializableUtils#fromByteArray

配合fastjsonROME

Reference

Last updated

Was this helpful?