jfinal_cms-v5.1.0 白盒 nday

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启动

  1. get(String key)

java

public String get(String key) {

return getParams().getString(key);

}

  • 功能:根据参数名 key ,从请求参数中获取一个字符串类型的值。

  • 底层:调用 getParams() 获取框架封装的参数容器,再调用其 getString(key) 方法,返回对应参数的字符串形式。

  • 场景:获取普通表单字段、URL 查询参数等文本类参数(比如用户名、邮箱、头像URL)。

  1. getJSONArray(String key)

java

public JSONArray getJSONArray(String key) {

return getParams().getJSONArray(key);

}

  • 功能:根据参数名 key ,从请求参数中获取一个JSON数组类型的值。

  • 底层:从参数容器中解析并返回 JSONArray 对象,适合处理前端传来的数组格式 JSON 参数(比如 ids: [1,2,3] )。

  • 场景:接收前端提交的数组型 JSON 数据,比如批量操作的ID列表、多选数据等。

  1. 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;
	}
相关推荐
纤纡.2 小时前
基于 PyTorch 手动实现 CBOW 词向量训练详解
人工智能·pytorch·python·深度学习
词元Max2 小时前
2.5 Python 类型注解与运行时类型检查
开发语言·python
沪漂阿龙2 小时前
深度解析Pandas数据组合:从concat到merge,打通你的数据处理任督二脉
python·数据分析·pandas
童园管理札记2 小时前
2026实测|GPT-4.5+Agent智能体:3小时搭建企业级客服系统,附完整源码与部署教程(一)
经验分享·python·深度学习·重构·学习方法
福楠2 小时前
现代C++ | C++14甜点特性
linux·c语言·开发语言·c++
大飞记Python2 小时前
【2026更新】Python基础学习指南(AI版)——安装
自动化测试·python·ai编程
charlie1145141913 小时前
嵌入式C++教程实战之Linux下的单片机编程:从零搭建 STM32 开发工具链(4)从零构建 STM32 构建系统
linux·开发语言·c++·stm32·单片机·学习·嵌入式
钰fly3 小时前
Halcon联合编程适应图像的方法(picture)
开发语言·前端·javascript
束尘3 小时前
Vue3一键复制图片到剪贴板
开发语言·javascript·vue.js