尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
SQL注入黑名单绕过实战:从强网杯随便注到GYCTF Blacklist
如果你刷过 SQL 注入方向的题目大概率对这两个名字不陌生强网杯 2019 的“随便注”以及 GYCTF2020 的“Blacklist”。这两道题几乎是一对双胞胎——第一道题的核心是教会你“select 被禁掉之后MySQL 里还有哪些姿势能把数据读出来”第二道题则是在同一套环境下把常见路子又堵掉几条逼着你再往前走一步。很多人刷到 Blacklist 时都会卡住不是因为看不懂而是因为还在用第一题的思路去套结果发现 rename 和 alter 也没了一下子不知道该怎么收场。这篇文章我就把这两道题从头到尾拆开讲一遍从注入点的判断、过滤规则的探测到堆叠注入、表名置换、handler 读取、预处理语句绕过最后再聊几个我实际调试过程中踩过的坑。内容不依赖任何特定工具你手上有浏览器和 HackBar 或者 Burp 就能跟完整个过程。适合已经会基础 union 注入、但还没接触过“select 被过滤”这类进阶场景的朋友也适合想系统整理黑名单绕过思路的读者。1. 两道题的关系与核心考点1.1 随便注一次“绕过 select”的思维启蒙强网杯 2019 的“随便注”在当年算是很有代表性的一道 Web 题。表面上它只是一个再普通不过的 SQL 注入点一个查询框输入数字 id 返回对应内容输入1报语法错误很明显是字符型注入。但你一旦把union select掏出来就会被拦得很彻底——服务端直接过滤了 select 关键字而且不是只过滤一次是select|update|delete|insert|where|drop这一串全给你禁掉。很多人到这里就卡住了因为传统 SQL 注入三板斧——union 注入、报错注入、布尔盲注前两个都严重依赖 select 关键字布尔盲注也绕不开where被过滤的问题。但这道题的巧妙之处在于它没有拦分号也就是说支持堆叠注入。你可以用分号把多条 SQL 语句拼在一起让数据库一次执行完。既然读数据的 select 被禁了那就用 show、rename、alter 这些操作去“偷天换日”最后让页面自己把 flag 查出来。这个思路在当时确实是很多人的启蒙。1.2 Blacklist同一套环境加了几道锁GYCTF2020 的 Blacklist 明显是照着“随便注”改的。界面几乎一样过滤规则也更长把第一题里用到的 rename、alter 甚至 handler 都加进了黑名单。它想要传达的信息很明确你知道换表名的套路行那我把换表名的路也堵死你知道用 handler 直接读表行那我也把 handler 拉黑。到这一步剩下的选择其实就聚焦在“预处理语句”这种动态执行方式上了。当然不同版本、不同部署环境里的 Blacklist 实际过滤规则会有细微差别我后面会给出具体探测方法。它能成为经典恰恰是因为它不是单纯加关键词而是逼着你理解 SQL 语句从“字符串”变成“可执行语句”的整个过程理解了这一点黑名单长什么样其实都不重要了。2. 前置知识为什么 select 被禁还能操作数据库2.1 从查询逻辑看注入点先确定一个基础事实。这类型的注入点服务端代码长什么样我见过很多复现环境核心逻辑基本是$id $_POST[id]; $sql select * from words where id $id; $result mysqli_query($conn, $sql);也就是说我们的输入被直接拼进了单引号包裹的字符串里。输入1时SQL 变成select * from words where id 1正常回显。输入1时SQL 变成select * from words where id 1多出一个单引号导致语法错误页面直接报错。这就确认了注入点在 id 参数上且闭合方式就是单引号。在这个基础查询里页面默认查的是 words 表。这个细节特别重要后面换表名方案能不能成功靠的就是对这个默认查询语句的精准把握。2.2 堆叠注入的原理和安全边界常规注入里你输入1 union select ...最终执行的是一条完整的 select 语句。而堆叠注入利用的是分号1;show tables;#在数据库端会被拆成两条独立语句。先执行select * from words where id 1再执行show tables最后#把原 SQL 末尾的注释掉保证整条语句语法正确。这个能力不是凭空来的。PHP 里如果用的是mysqli_query()这种函数它默认只允许执行单条语句分号后面的内容会被直接拒绝。能支持堆叠注入说明代码底层用了类似mysqli_multi_query()或者 PDO 的多语句模式。这也从另一个角度印证了堆叠注入本身就是一种“多语句执行”引发的安全问题在做防御设计时禁用多语句是一个性价比极高的选择。堆叠注入的价值在于你能执行的语句类型不再限制在 selectshow、set、rename、alter、handler、prepare、update、delete 全部可以调用。当然能不能用还取决于过滤规则。这正好是“随便注”和“Blacklist”玩出花来的基础。2.3 MySQL 的几个特殊命令show、rename、alter、handler、prepare这一节把后面要用的几条 MySQL 命令一次说清楚方便你对照着理解 payload。show 在 MySQL 里用来展示元数据信息。show databases列出所有库名show tables列出当前库的表名show columns from 表名列出某个表的所有列名和类型。它不涉及数据行读取所以在很多过滤 select 的场景里show 成了信息收集的第一利器。rename 和 alter 属于 DDL 语句。rename table a to b可以直接把整张表改名alter table 表名 change 旧列名 新列名 类型可以修改列的名字和类型。这两条命令本来和 SQL 注入没什么关系但因为它们能改变表结构所以成了“狸猫换太子”的关键道具。handler 是 MySQL 提供的一条底层读取命令它绕过 SQL 解析层直接以存储引擎的接口方式逐行读取表数据。语法是handler 表名 open as 别名打开表然后handler 别名 read next往下读一行。handler 不走 select 这条路所以在 select 被过滤的场景里它经常可以作为备用读取通道。prepare、execute 是 MySQL 的预处理语句功能可以先用字符串变量定义一条 SQL然后交给 prepare 编译再用 execute 执行。这个机制本身是用来提升重复执行效率和防止注入的但一旦攻击者能控制这个字符串变量它就成了“动态执行任意 SQL”的窗口。3. 随便注的完整解题流程3.1 信息收集与过滤规则探测拿到题目环境后第一步永远是确定注入类型和过滤规则。我当时的操作路径是这样的先提交1页面正常。再提交1页面报语法错误确认存在字符型注入。然后准备用1 union select 1,2#试试字段数结果页面直接提示被拦截。这时候需要判断拦截的是哪个关键字。把 payload 拆开测试比如分别提交1 union#、1 select#、1 update#就能摸出黑名单的边界。实测下来拦截关键词是select|update|delete|insert|where|drop正则还带/is修饰符大小写混合绕不过去。提示测试过滤规则时尽量用不会真正影响数据库的语句比如1 union#或者1;show databases;#这种避免不小心执行到 update、delete、drop 造成环境损坏。3.2 堆叠注入show databases 和 show tables既然 select 不能用但分号没被过滤那就走堆叠注入。提交1;show databases;#注意#在 HTTP 请求里要 URL 编码成%23否则会被当成锚点截断。返回结果里有几个数据库名其中有一个明显是题目库。接下来定位当前库的表1;show tables;#结果出来了当前库有两张表words和1919810931114514。看到这个数字表名基本就能猜到 flag 大概率在里面。words 表就是页面默认查询的那张表存的是页面展示用的普通数据而那个纯数字表名一看就是为了防你直接猜表名。3.3 定位目标表与列名想知道 words 表结构继续用 show1;show columns from words;#返回两列id和data。这和页面查询语句select * from words where id $id是吻合的。再看数字表1;show columns from 1919810931114514;#这里必须给数字表名加反引号不加的话 MySQL 会把1919810931114514当成数字常量直接报语法错误。返回结果只有一列列名是flag。到这一步flag 的位置已经很明确了表1919810931114514列flag。但问题是 select 被过滤怎么把这一列的数据读出来3.4 换表思路rename alter 置换表名与列名直接读不行那就让页面替我们读。思路是这样的页面默认查询的是words表我们只要把原来的words表改走再把存有 flag 的表改名为words并且把它的列名flag改成id这样页面执行select * from words where id xxxx时查询的对象就变成了原来的 flag 表数据自然就回显出来了。完整 payload1;rename table words to words2;rename table 1919810931114514 to words;alter table words change flag id varchar(100);#一条一条拆开看rename table words to words2把原来的 words 表改名腾出 words 这个名字。rename table \1919810931114514 to words把存 flag 的表改成 words让它成为页面查询的目标表。alter table words change flag id varchar(100)把原来叫 flag 的列改名为 id。这一步很关键因为页面查询条件是where id ...如果列还叫 flagwhere 条件会报“列不存在”的错误数据也回显不出来。提交之后再回到查询框输入1 or 11%23或者干脆0 or 11%23让 where 条件恒真。这时候页面查的就是新 words 表也就是原来的 flag 表flag 内容直接显示在回显里。3.5 为什么表名要加反引号很多新手在这里会栽跟头把 payload 里的反引号漏掉然后死活跑不通。MySQL 里反引号的作用是转义标识符表名、列名如果包含特殊字符或者是纯数字就必须用反引号包裹否则词法分析阶段就会出错。对比一下rename table 1919810931114514 to words rename table 1919810931114514 to words第二行在 MySQL 里被解析成数字常量1919810931114514后面跟着一个关键字to必然报语法错误。同样的道理如果某个表名包含空格、点号、横线也要用反引号。这个习惯在后续处理其他题目的数字表名、特殊表名时非常有用。4. Blacklist 的过滤升级与绕过方案4.1 黑名单对比哪些路被堵死了Blacklist 的界面和第一题一模一样但过滤规则明显加码。常见的过滤列表大致是select|update|delete|insert|where|drop|rename|alter|handler对比第一题多堵了 rename、alter 和 handler。也就是说第一题的“换表名”解法直接失效handler 直接读表也可能被拦。不过实际部署版本之间会有差异有些版本过滤列表里可能还有 set、prepare有些则没有。做这题的第一步依然是先探测当前环境的黑名单范围。我的建议是准备一份关键词清单逐个放到 payload 里测试例如分别提交1;rename# 1;alter# 1;handler# 1;set# 1;prepare#看哪一条触发了拦截就能还原出当前环境完整的黑名单。只有先拿到准确的黑名单后面的绕过方案才谈得上有针对性。4.2 方案一handler 逐行读取如果当前环境的黑名单里没有 handler直接用它读取表中数据是最省事的方案。先通过堆叠注入查看表名1;show tables;#假设表名是FlagHere接下来1;handler FlagHere open as h;handler h read next;#这条 payload 的含义是以别名 h 打开 FlagHere 表然后读取第一行数据。如果表里有多行记录而第一行不是我们要的 flag可以继续追加handler h read next;一行一行往后读。也可以先handler h read first;重置到第一行再handler h read next;依次往下走。handler 是存储引擎层的命令不走 select 这条 SQL 路径所以 select 被过滤对它没有影响。同时它读取的是真实数据行不需要知道列名打开表之后默认返回整行的所有字段非常契合这种“不知道列结构也能读内容”的场景。4.3 方案二set prepare execute 预处理如果 handler 也被过滤了那就得换一条更绕的路把完整的 select 语句编码成十六进制字符串再用 set 定义一个变量交给 prepare 编译最后 execute 执行。payload 长这样1;set a0x73656c656374202a2066726f6d2060466c61674865726560;prepare s from a;execute s;#其中0x...这段十六进制对应的是字符串select * from FlagHere运行流程是set a0x...把十六进制串赋值给用户变量 a此时黑名单正则匹配的是“0x73656c...”这一串里面没有出现 select 明文所以不会被拦。prepare s from a让 MySQL 把 a 里存的内容当作一条 SQL 去预编译。MySQL 在编译时会把 hex 解码成原始字符串得到完整的 select 语句。execute s执行编译好的语句结果回显到页面。这个思路的本质是把“过滤”的检测对象和“执行”的解析对象分离开来。过滤检测的是输入字符串表面内容而 MySQL 最终解析执行的是解码后的内容。你只要找一个黑名单没覆盖的命令作为中转把 select 变成编码形态送进去就能绕开正则。4.4 十六进制为什么能骗过正则正则匹配的工作对象是字节流。当它拿到0x73656c656374的时候看到的是字符0、x、7、3、6、5……里面并没有连续的select子串自然不会被命中。而 MySQL 在执行set a0x...时会自动把十六进制字面量转换成对应字符串赋值给变量这个过程发生在 SQL 解析阶段不受外部黑名单影响。这里要注意十六进制串的生成必须一字不差。常见错误是表名大小写写错或者漏了反引号。我习惯在本地用 Python 快速生成payload select * from FlagHere print(payload.encode().hex())把输出结果贴到 payload 里就行。如果你用查到的表名不一样比如叫flag就对应改掉然后再编码一次。5. 实操中的坑与排查技巧5.1 为什么注入后页面永远显示 words 表数据经常有人做完换表名操作之后发现页面似乎“没反应”或者还是显示原来的内容。其实不是没反应而是你改完表之后查询条件没构造对。原来 words 表里 id 字段的值可能是一串数字而新 words 表flag 表里唯一一行数据的 id 列由 flag 列改名而来内容是一段字符串比如flag{...}。此时你继续提交id1where 条件变成where id1匹配不到那行字符串数据自然没有回显。解决方法是把查询条件改成恒真比如1 or 11%23或者0 or 11让 where 条件不再依赖具体 id 值直接把第一行数据刷出来。这个细节属于“最后一步”的经典翻车点每次都得提醒自己检查。5.2 反引号写错导致的语法错误在堆叠注入里反引号的错误通常表现为两种一种是漏了反引号纯数字表名被当成数字常量直接报语法错误另一种是反引号用成了单引号MySQL 会认为你在引用字符串同样报错。遇到语法错误不要慌先把 payload 在本地 MySQL 环境或者在线 SQL 工具里跑一遍注意把#换成真正的注释符或者直接用--这样能快速定位是表名问题还是列名问题。很多时候本地能跑通线上跑不通原因就是表名大小写或者反引号细节不一致。5.3 预处理语句的字符集和字符串问题用 set prepare execute 时最常见的报错是You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ...。这个报错信息里的“near 后面的内容”非常关键它会把你出错位置的 SQL 片段显示出来直接用来看你的 hex 解码结果对不对。我遇到过的情况包括表名大小写不对比如实际是flag我编码成了FlagHere或者表名确实带反引号我却忘了编进去。确认表名的方式永远是在前面加一步1;show tables;#拿到准确的表名和大小写之后再重新生成 hex。5.4 这类题目的延展与防御思考刷完这两道题很容易陷入“绕过”的兴奋里但我建议你也从防御的角度把它们重新看一遍。为什么堆叠注入能成立因为代码用了多语句执行。为什么黑名单能被绕过因为过滤永远只能基于已知关键词而数据库的语法和命令远比你列出来的关键词丰富。换句话说这两道题最该记住的防御结论是参数化查询 / 预编译绑定变量比任何黑名单都可靠数据库账号最小权限能杜绝大量 DDL 和 DML 类攻击关闭多语句执行能从根上切断堆叠注入。把这些落到位黑名单长什么样根本不重要。6. 一些小经验和后续扩展从这两道题往后延伸你还会遇到很多“过滤变种”的注入题比如过滤了空格、过滤了逗号、过滤了注释符甚至过滤了数字。但核心思路是一致的先摸清过滤边界再寻找一个黑名单覆盖不到的执行通道。这两道题的 payload 本身并不复杂重要的是你理解每一步在做什么。我个人在实际操作中的体会是做这类题一定要有“本地复现”的习惯。把题目环境拉下来或者用 Docker 起一个 MySQL把 payload 先在本地跑通再拿去打线上环境。很多人卡住不是因为思路不对而是因为一个小细节没对上本地验证能极大加快定位速度。另外建议你把 handler、prepare、rename/alter 这几条命令整理成自己的速查笔记。它们不只是这两道题的答案在后续很多场景里都会反复出现比如日志脱库、UDF 提权前的信息收集、存储过程相关的题目等等。花点时间把它们吃透比死记十几个 payload 实用得多。
RELATED

相关推荐

MIPI/DVP CSI摄像头驱动移植

MIPI/DVP CSI摄像头驱动移植

1、原理图 1.1 dcmi port:代表硬件模块上的视频接口 DCMI 的 port:DCMI 控制器的接收口(SINK) OV5640 的 port:摄像头 sensor 的发送口(SOURCE) endpoint:port 上面的连接端子&#…

📅 2026/10/21 5:55:59
Spring Boot 整合 FISCO BCOS 联盟链实战:从设计到落地

Spring Boot 整合 FISCO BCOS 联盟链实战:从设计到落地

1. 为什么要在 Spring Boot 里整合 FISCO BCOS把联盟链节点接进一个常规的 Java 后端服务,这件事我在两三个项目里都做过。最早的一次是给一个供应链金融的存证模块做技术验证,当时团队的后端清一色是 Spring Boot 技术栈,谁也不想为了调链再…

📅 2026/10/21 5:55:59
差分数组详解:从区间修改到前缀和的高效算法

差分数组详解:从区间修改到前缀和的高效算法

派大星最近在啃算法题,翻来覆去就一句话:差分数组真是个又萌又猛的家伙。别看这名字唬人,背后的数学连小学一年级都算得过来——被减数减减数等于差;可一旦把它放到数组上,它就能把一段区间里一堆重复的加法操作&#…

📅 2026/10/21 5:55:59
MORE NEWS

更多资讯

📰

知识工作插件化:构建可编程、可组合的智能工作流底座

1. 项目概述:这不是一个插件,而是一套知识工作者的“操作系统增强层”“knowledge-work-plugins”这个标题乍看像某个开源仓库的命名,但拆开来看——knowledge work(知识工作)、plugins(插件)—…

📰

SQL题目面试避坑指南:从读题拆解到查询优化全链路

写SQL题目刷了几年,也帮不少同学备过面试、带过新人,我最大的感受是:多数人不是不会写SQL,而是根本不知道一道SQL题目到底在考察什么。同一个查询,有人三行搞定,有人磨叽半小时还漏了边界;同一张…

📰

知识工作插件:面向知识工作者的模块化增强协议

1. 项目概述:这不是一个插件,而是一套知识工作者的“操作系统增强层”“knowledge-work-plugins”——光看这个名字,很多人第一反应是“又一个浏览器插件合集”或者“某款笔记软件的扩展包”。但在我过去三年深度参与多个知识管理系统的搭建与…

📰

SchoolDB四表DDL全解析:数据库设计从字段选择到约束实践

最近给团队做内部培训,我又把一套用了很多年的教学示例库脚本翻了出来——SchoolDB。场景很简单:一个学校管理系统,只有4张表:学生、教师、课程、选课。很多人觉得写DDL语句没什么技术含量,敲几行CREATE TABLE谁不会&a…

📰

订单表慢SQL优化实战:联合索引、覆盖索引与深分页方案

1. 场景回顾:慢SQL是怎么暴露出来的做订单系统的同学应该都有体会:订单表往往是整个业务库里面数据量增长最快、查询模式最多样、索引也最容易“被用坏”的一张表。这个实战项目来自某电商平台的中台订单模块,核心表trade_order存放全渠道的订…

📰

Python实战英雄联盟胜率预测:从数据清洗到SHAP模型解释

简介:LeaguePredictor 是一套面向电子竞技数据分析初学者与机器学习实践者的 Python 项目源码,聚焦英雄联盟比赛胜率预测这一具体场景,帮助读者理解如何把分类模型落地到真实赛事数据上。资源包共 24 个文件,以 22 个 py 脚本为主…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬