0x1 CVE-2024-53477 ApiForm.java 文件存在反序列化漏洞
豆包提示
Java 反序列化
ObjectInputStream.readObject()ObjectInputStream.readUnshared()XMLDecoder.readObject()XStream.fromXML()Yaml.load()ObjectMapper.readValue()JsonMapper.readValue()JSON.parseObject()← 本漏洞利用点JSON.parse()Gson.fromJson()Kryo.readObject()Kryo.readClassAndObject()
JSON.parse():直接解析JSON字符串,未指定目标类型,可能触发自动类型识别(autotype),存在反序列化漏洞风险。JSON.parseObject():若未明确指定目标类,或参数来自外部输入,可能被利用。JSONObject.parse():同样可能触发类型自动识别。
前言
乱搜索,看着一个顺眼的。


0x1 追链条(翻车)

直接获取p
java
private String apiNo; // 接口码
private Integer pageNo; // 页数
private Integer pageSize; // 页码
private String method; // 方法名
private String version; // 版本
private String apiUser; // 调用用户
private String time; // 时间戳
private String checkSum; // 校验和
private String p; // 参
java
private JSONObject getParams() {
JSONObject json = null;
try {
String params = "";
//p可控
params = this.p;
boolean flag = ConfigCache.getValueToBoolean("API.PARAM.ENCRYPT");
if (flag) {
params = ApiUtils.decode(params);
}
//这里反序列化
//Object
json = JSON.parseObject(params);
} catch (Exception e) {
log.error("apiform json parse fail:" + p);
return new JSONObject();
}
return json;
}
追寻链

3个,触发一 一排查,只要有触发就可以
0x1 get(String key)
java
public String get(String key) {
return getParams().getString(key);
}
0x2 getJSONArray(String key)
java
public JSONArray getJSONArray(String key) {
return getParams().getJSONArray(key);
}
0x3 getJSONObject(String key)
java
public JSONObject getJSONObject(String key) {
return getParams().getJSONObject(key);
}
这样找找到猴年马月都不知道触发点,所以得知己知彼,ai启动
- get(String key)
java
public String get(String key) {
return getParams().getString(key);
}
-
功能:根据参数名 key ,从请求参数中获取一个字符串类型的值。
-
底层:调用 getParams() 获取框架封装的参数容器,再调用其 getString(key) 方法,返回对应参数的字符串形式。
-
场景:获取普通表单字段、URL 查询参数等文本类参数(比如用户名、邮箱、头像URL)。
- getJSONArray(String key)
java
public JSONArray getJSONArray(String key) {
return getParams().getJSONArray(key);
}
-
功能:根据参数名 key ,从请求参数中获取一个JSON数组类型的值。
-
底层:从参数容器中解析并返回 JSONArray 对象,适合处理前端传来的数组格式 JSON 参数(比如 ids: [1,2,3] )。
-
场景:接收前端提交的数组型 JSON 数据,比如批量操作的ID列表、多选数据等。
- getJSONObject(String key)
java
public JSONObject getJSONObject(String key) {
return getParams().getJSONObject(key);
}
-
功能:根据参数名 key ,从请求参数中获取一个JSON对象类型的值。
-
底层:从参数容器中解析并返回 JSONObject 对象,适合处理前端传来的嵌套 JSON 参数(比如 user: {"name":"xxx", "age":20} )。
-
场景:接收前端提交的复杂 JSON 对象,比如用户信息、业务数据实体等。
不对
入口处就有一个三元运算,调用了,我这不是蠢吗?
java
@ControllerBind(controllerKey = "/api")
@Before(ApiInterceptor.class)
public class ApiController extends BaseProjectController {
ApiService service = new ApiService();
/**
* api测试入口
*
* 2016年10月3日 下午5:47:55 flyfox 369191470@qq.com
*/
public void index() {
ApiForm from = getForm();
renderJson(new ApiResp(from).addData("notice", "api is ok!"));
}
java
public ApiResp addData(String key, Object value) {
Map<String, Object> dataMap = getData();
if (dataMap == null) {
dataMap = new HashMap<String, Object>();
}
dataMap.put(key, value);
this.data.put("data", dataMap);
return this;
}
java
public Map<String, Object> getData() {
return this.data.get("data");
}
cc链
/api--》addData(String key, Object value)--》 getData()--》get()--》parseObject(params)
0x2 看触发(翻车)
也就是说只要/API路由就可以触发这一条链,那我们就得看可控参数了,由于nday知道了p可控,那么我们该如何控制它呢?
从这里开始
java
public void index() {
//获取前端HTTP请求的参数
ApiForm from = getForm();
//看前端HTTP请求的参数apino有没有值,追加提示
renderJson(new ApiResp(from).addData("notice", "api is ok!"));
}
ApiResp()
java
public ApiResp(ApiForm from) {
setFrom(from);
}
java
public ApiResp setFrom(ApiForm from) {
if (from != null) {
this.apiNo = from.getApiNo();
}
return this;
}
前提是apiNo不为空
java
public String getApiNo() {
return apiNo;
}
getForm()
java
public ApiForm getForm() {
ApiForm form = getBean(ApiForm.class, null);
return form;
}
问ai
JFinal的getBean:把前端HTTP请求的参数自动封装到JavaBean里,核心是请求参数自动注入,和SpringMVC的Controller方法参数自动封装是同一个作用
如
java
// 接收前端以「model.xxx」为前缀的参数(如model.title_url、model.userid),封装到对应实体类
User user = getBean("model", User.class);
// 无参前缀,直接匹配前端参数名与实体类属性名
User user = getBean(User.class);

Post,get,json都可以。
renderJson()
java
public void renderJson(Object object) {
//instanceof 是一种二元运算符,主要用于运行时类型检查,判断一个对象是否属于某个类、其子类或实现了某个接口。
//判断这个对象属不属于JsonRender
//如果传进来的 object 本来就是 JsonRender那我就直接用它,强转一下就行否则就把这个 object 包装成一个新的 JsonRender 返回
render = object instanceof JsonRender ? (JsonRender)object : renderManager.getRenderFactory().getJsonRender(object);
}
/api--》addData(String key, Object value)--》 getData()--》get()--》parseObject(params)
addData(String key, Object value)前提是apiNo不为空追加
0x3 断点(翻车)
失败了
看了半天都找错了,这里没有触发点
0x2 正确链
看了一下别人写的,找到了错误,正入口偏了,倒追出错了。
version=1.0.1&apiNo=1000000&pageNo=1&pageSize=1&method=folders&time=20170314160401&p={siteId:1}
有正确的版本号才会触发流动

入口
java
//版本号拦截
@Before(ApiInterceptor.class)
public void action() {
long start = System.currentTimeMillis();
//这里可控
ApiForm from = getForm();
//检查模板是否为空
if (StrUtils.isEmpty(from.getMethod())) {
String method = getPara();
from.setMethod(method);
}
// 调用接口方法
ApiResp resp = service.action(from);
// 没有数据输出空
resp = resp == null ? new ApiResp(from) : resp;
// 调试日志
if (ApiUtils.DEBUG) {
log.info("API DEBUG ACTION \n[from=" + from + "]" //
+ "\n[resp=" + JsonKit.toJson(resp) + "]" //
+ "\n[time=" + (System.currentTimeMillis() - start) + "ms]");
}
renderJson(resp);
}
但是这时候我又不知道是怎么进入方法的了,这怎么能忍?
调试器总是莫名其妙的步出get,那肯定就是这里了,但是有时我真的什么都不知道,我没看答案,我又该怎么找呢?
大多数都是用的倒追法,但是我第1次翻车了,我又该如何改进?

Alt + F7
Ctrl + Alt + H
其实有个很笨的方法,我可以在这些上面全都打上断点然后一步一步的找断掉的线索,但是我之后去挖掘的时候真的可以这样吗?

java
public class ApiUtils {
private static final Map<String, IApiLogic> map = new HashMap<String, IApiLogic>();
/**
* 调试日志
*/
public static boolean DEBUG = false;
//首次加载这个类执行
static {
addApi("1.0.0", new ApiV100Logic());
addApi("1.0.1", new ApiV101Logic());
}
java
public ApiResp action(ApiForm form) {
try {
//
if (methodList.contains(form.getMethod())) {
// 登陆验证标识
boolean validFlag = ConfigCache.getValueToBoolean("API.LOGIN.VALID");
if (validFlag) {
// 先进行登陆验证。如果验证失败,直接返回错误
ApiResp validResp = getApiLogic(form).valid(form);
if (validResp.getCode() != ApiConstant.CODE_SUCCESS) {
return validResp;
}
}
// 调用接口方法,利用反射更简洁
ApiResp apiResp = (ApiResp) ReflectionUtils.invokeMethod(getApiLogic(form), form.getMethod(), //
new Class<?>[] { ApiForm.class }, new Object[] { form });
return apiResp;
}
return ApiUtils.getMethodError(form);
} catch (Exception e) {
log.error("action handler error", e);
return ApiUtils.getMethodHandlerError(form);
}
}
看了几篇文章才弄清楚,不过我自己也快搞对了。

如果再看几个调用应该就对了吧
反思一下,倒推的时候应该多看一下那些方法用了这个方法,边调试边看看。
链条
action()--》action(ApiForm form)--》ApiUtils--》ApiV100Logic()--》getInt()--》get()--》parseObject(params)
0x3 验证

不对,如果是刚刚没有触发,为什么一开始进入的是这里,所以我又看了一下别人的资料。
最终
action()--》action(ApiForm form)--》folders--》getInt()--》get()--》parseObject(params)
java
public ApiResp action(ApiForm form) {
try {
//
if (methodList.contains(form.getMethod())) {
// 登陆验证标识
boolean validFlag = ConfigCache.getValueToBoolean("API.LOGIN.VALID");
if (validFlag) {
// 先进行登陆验证。如果验证失败,直接返回错误
ApiResp validResp = getApiLogic(form).valid(form);
if (validResp.getCode() != ApiConstant.CODE_SUCCESS) {
return validResp;
}
}
// 调用接口方法,利用反射更简洁
//当请求参数 method=folders 时,form.getMethod() 返回 "folders",就会调用 getApiLogic(form) 返回的实例(如 ApiV100Logic)中的 folders 方法,并将 form 作为参数传入。
//getApiLogic(form),根据ApiLogic获取业务实例
//反射调用实例方法
ApiResp apiResp = (ApiResp) ReflectionUtils.invokeMethod(getApiLogic(form), form.getMethod(), //
new Class<?>[] { ApiForm.class }, new Object[] { form });
return apiResp;
}
return ApiUtils.getMethodError(form);
} catch (Exception e) {
log.error("action handler error", e);
return ApiUtils.getMethodHandlerError(form);
}
}
}
0x4 触发
java
public void action() {
long start = System.currentTimeMillis();
//可控
ApiForm from = getForm();
if (StrUtils.isEmpty(from.getMethod())) {
String method = getPara();
from.setMethod(method);
}
// 调用接口方法
ApiResp resp = service.action(from);
// 没有数据输出空
resp = resp == null ? new ApiResp(from) : resp;
// 调试日志
if (ApiUtils.DEBUG) {
log.info("API DEBUG ACTION \n[from=" + from + "]" //
+ "\n[resp=" + JsonKit.toJson(resp) + "]" //
+ "\n[time=" + (System.currentTimeMillis() - start) + "ms]");
}
renderJson(resp);
}
java
//form可控
public ApiResp action(ApiForm form) {
try {
//
if (methodList.contains(form.getMethod())) {
// 登陆验证标识
boolean validFlag = ConfigCache.getValueToBoolean("API.LOGIN.VALID");
if (validFlag) {
// 先进行登陆验证。如果验证失败,直接返回错误
ApiResp validResp = getApiLogic(form).valid(form);
if (validResp.getCode() != ApiConstant.CODE_SUCCESS) {
return validResp;
}
}
// 调用接口方法,利用反射更简洁
//// 调用接口方法,利用反射更简洁
//当请求参数 method=folders 时,form.getMethod() 返回 "folders",就会调用 getApiLogic(form) 返回的实例(如 ApiV100Logic)中的 folders 方法,并将 form 作为参数传入。
//getApiLogic(form),根据ApiLogic获取业务实例
//反射调用实例方法
//form.getMethod(),通过modle获取方法名
//version获取类名
//new Class<?>[] { ApiForm.class }, new Object[] { form }限定传参
// new Object[] { form }from传入
//new Class<?>[] { ApiForm.class }类型
ApiResp apiResp = (ApiResp) ReflectionUtils.invokeMethod(getApiLogic(form), form.getMethod(), //
new Class<?>[] { ApiForm.class }, new Object[] { form });
//返回方法
return apiResp;
}
java
public String getMethod() {
return method;
}
java
private String apiNo; // 接口码
private Integer pageNo; // 页数
private Integer pageSize; // 页码
private String method; // 方法名
private String version; // 版本
private String apiUser; // 调用用户
private String time; // 时间戳
private String checkSum; // 校验和
private String p; // 参数
java
public IApiLogic getApiLogic(ApiForm form) {
IApiLogic apiLogic = ApiUtils.getApiLogic(form);
return apiLogic;
}
java
public static IApiLogic getApiLogic(ApiForm form) {
return map.get(form.getVersion());
}
java
public String getVersion() {
return version;
}
java
@Override
//from可控
public ApiResp folders(ApiForm form) {
//获取siteId
List<TbFolder> list = folderServer.getFolders(form.getInt("siteId"));
return new ApiResp(form).addData("list", list);
}
java
public int getInt(String key) {
return NumberUtils.parseInt(get(key));
}
java
public String get(String key) {
return getParams().getString(key);
}
java
private JSONObject getParams() {
JSONObject json = null;
try {
String params = "";
//可控
params = this.p;
boolean flag = ConfigCache.getValueToBoolean("API.PARAM.ENCRYPT");
if (flag) {
params = ApiUtils.decode(params);
}
//反序列化
json = JSON.parseObject(params);
} catch (Exception e) {
log.error("apiform json parse fail:" + p);
return new JSONObject();
}
//返回
return json;
}