尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

强网杯SQL注入实战:绕过SELECT过滤的HANDLER与预编译技巧

强网杯SQL注入实战:绕过SELECT过滤的HANDLER与预编译技巧 1. 项目概述一次经典的SQL注入实战复盘最近在带新人做CTFCapture The Flag题目训练翻到了这道来自“强网杯 2019”的经典题目——“随便注”。这名字起得挺有意思听起来很随意但实际考察的却是SQL注入中非常核心且基础的知识点。对于刚接触Web安全的新手来说这道题是一个绝佳的“磨刀石”它能帮你把SQL注入的整个攻击链条从信息探测到最终利用完整地走一遍。很多朋友可能觉得SQL注入是老生常谈但真正能把每一步的原理、绕过技巧和利用手法都讲清楚、练扎实的并不多。今天我就以这道题为例带大家从头到尾拆解一遍不仅告诉你“怎么做”更重点剖析“为什么这么做”以及在实际渗透测试中你可能会遇到哪些变种和更复杂的防御手段。这道题的环境是一个典型的、存在SQL注入漏洞的Web查询接口。我们的目标就是利用这个漏洞获取数据库中的敏感信息也就是所谓的“Flag”。题目之所以经典在于它没有设置过于花哨的WAFWeb应用防火墙或过滤规则而是聚焦于最基础的联合查询注入、堆叠注入以及MySQL数据库特性利用。通过它我们可以清晰地理解如何手动构造Payload、如何判断注入点类型、如何一步步获取数据库结构并最终读取数据。下面我们就进入正题。2. 初探靶场与注入点判断2.1 目标界面与功能分析通常这类题目的前端是一个简单的输入框比如一个搜索框或查询框允许用户提交参数。我们假设目标URL是http://靶场地址/?inject参数的形式。第一步永远是信息收集。我们打开页面可能会看到一个提交查询的输入框。作为测试我们先输入一个数字1查看返回结果。页面返回了对应的数据记录这很正常。关键的第一步是探测这里是否存在SQL注入漏洞。最经典的方法是使用单引号‘来闭合SQL语句中的字符串。我们在输入框提交1‘。如果页面返回了与正常查询比如1不同的结果比如报错信息、空白页或者查询逻辑异常那么这里就可能存在注入点。在这道题中提交1‘后页面很可能会返回一个详细的SQL语法错误信息例如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 ‘‘1’’ at line 1这个报错信息是黄金线索。它明确告诉我们两件事第一后端数据库是MySQL第二我们的输入被直接拼接到了SQL语句中并且单引号破坏了语句结构导致了语法错误。这初步确认了存在字符型注入漏洞。2.2 确认注入类型与闭合方式知道有注入点后我们需要确定注入的类型和原SQL语句的闭合方式。常见的闭合方式有单引号‘、双引号“有时还会包含括号()。从上面的报错信息near ‘‘1’’可以看出我们输入的1‘在数据库中被处理成了‘1‘‘。这说明原语句大概是SELECT ... FROM ... WHERE id‘用户输入‘这样的结构。我们输入1‘后语句变成了SELECT ... FROM ... WHERE id‘1‘‘最后一个单引号成了多余的单引号导致语法错误。为了修复这个语法错误并让语句正确执行同时又能插入我们自己的SQL代码我们需要“注释掉”原语句后面的部分。在MySQL中注释符有两种#URL中需编码为%23和--注意后面有个空格。我们尝试构造Payload1‘ --。这个Payload的意思是1‘用于闭合前面的单引号--空格很重要用于注释掉后面所有的原始SQL代码。提交这个Payload后如果页面正常返回了id1的数据那么就证明我们成功控制了SQL语句并且确认了闭合方式是单引号注入类型为字符型注入。注意在浏览器URL中或表单提交时空格可能会被处理。--后面的空格有时会被忽略为了确保万无一失通常直接使用--来代替。在URL中表示空格。所以更常见的Payload是1‘ --。3. 信息获取与联合查询注入尝试3.1 确定字段数量在可以进行联合查询UNION SELECT的情况下第一步是确定当前查询语句返回的字段列数。我们使用ORDER BY子句来探测。ORDER BY用于根据指定的列索引对结果进行排序。如果索引超过了实际列数数据库就会报错。我们依次提交Payload1‘ order by 1 --(页面正常)1‘ order by 2 --(页面正常)1‘ order by 3 --(页面报错)当order by 3报错时说明当前查询结果只有2列。这是一个非常关键的结论意味着我们后续使用UNION SELECT时必须拼接两个字段。3.2 尝试联合查询并遭遇过滤知道了字段数接下来自然是想通过联合查询来获取数据。我们构造Payload-1‘ union select 1,2 --。这里将id设为-1一个不存在的值是为了让原查询结果为空从而确保页面显示的是我们union select出来的1和2。这两个数字是占位符用于测试哪个字段的内容会回显在页面上。然而提交这个Payload后页面返回了关键信息return preg_match(/select|update|delete|drop|insert|where|\./i,$inject);。这行代码是题目的核心过滤规则。它使用preg_match函数以不区分大小写的方式/i检测我们的输入中是否包含以下关键词select,update,delete,drop,insert,where以及点号.。这意味着我们想直接使用UNION SELECT来查询数据的常规路径被堵死了。select和union都被禁用了。同时禁止了点号也限制了我们使用database.table这样的形式进行跨库查询。这是一道典型的“代码层过滤”题目需要我们思考绕过方法。4. 堆叠注入与架构探秘4.1 什么是堆叠注入当常规的联合查询被禁用后我们就要考虑其他注入技术。这里题目留下了一个重要的“后门”堆叠查询注入。堆叠注入是指通过分号;将多条SQL语句分隔开并一次性提交执行。并非所有数据库或连接驱动都支持堆叠查询但MySQL在特定配置下是支持的。幸运的是这道题的环境支持堆叠注入。我们可以构造Payload1‘; show databases; --。这个Payload的执行逻辑是先执行原查询SELECT ... FROM ... WHERE id‘1‘然后分号开启一个新语句执行SHOW DATABASES;用于列出所有数据库名。提交后页面除了显示id1的查询结果很可能还会将数据库列表也显示出来。通过这一步我们验证了堆叠注入是可行的并且看到了系统中有哪些数据库比如information_schema,mysql,ctftraining, 以及一个可能存放flag的数据库。4.2 深入数据库与表结构既然show databases成功了我们就可以利用MySQL的SHOW命令来绕过对SELECT关键词的过滤一步步探查数据结构。查看当前数据库1‘; show tables; --这条命令会列出当前数据库中的所有表。假设我们看到的返回结果中有两个表1919810931114514和words。这里注意第一个表名是一串纯数字这很不寻常很可能就是存放flag的表。查看表结构接下来需要知道每个表里有哪些列。由于DESC 表名或SHOW COLUMNS FROM 表名内部可能也使用了SELECT在某些版本或解释中我们采用更直接的SHOW CREATE TABLE命令。这个命令会返回创建该表的完整SQL语句里面就包含了列定义。Payload forwords表1‘; show create table words; --Payload for 数字表1‘; show create table1919810931114514; --(注意纯数字的表名需要用反引号包裹否则会被解释为数字而语法错误)假设返回信息显示words表结构CREATE TABLE words (id int(10) NOT NULL, data varchar(20) NOT NULL)1919810931114514表结构CREATE TABLE 1919810931114514 (flag varchar(100) NOT NULL)至此我们完全摸清了数据库结构当前数据库下有两个表。words表有id和data两列这很可能就是前端查询默认操作的表。而1919810931114514这个表只有一列flag毫无疑问目标flag就存放在这里。5. 核心挑战绕过SELECT过滤读取数据现在我们面临核心难题我们知道flag在1919810931114514表的flag列里但过滤规则禁止使用SELECT来读取它。我们必须找到一种不使用SELECT关键词却能获取数据的方法。这里就需要一些MySQL的“骚操作”了。5.1 思路一使用HANDLER语句本题可用方案MySQL提供了一个相对冷门但很有用的命令HANDLER。它可以用于直接访问表的存储引擎接口实现类似游标的方式逐行读取数据而且语法中不包含SELECT。使用HANDLER的基本步骤如下打开表HANDLER 表名 OPEN;读取第一行HANDLER 表名 READ FIRST;读取下一行HANDLER 表名 READ NEXT;可重复执行以遍历数据关闭句柄HANDLER 表名 CLOSE;我们可以构造堆叠注入Payload来执行1‘; HANDLER1919810931114514OPEN; HANDLER1919810931114514READ FIRST; --执行后HANDLER ... READ FIRST语句会返回目标表的第一行数据也就是我们梦寐以求的flag。页面会将其内容显示出来。这是解决此题最直接、最优雅的方法之一。5.2 思路二预编译语句配合字符串拼接另一种强大绕过如果题目连HANDLER也过滤了虽然本题没有我们还可以祭出更高级的技巧预编译语句。其核心思想是将我们想要执行的SELECT语句拆分成字符串利用PREPARE准备和EXECUTE执行来动态执行SQL从而绕过对完整SELECT关键词的静态匹配。整个流程如下设置变量将查询语句的字符串形式存入一个变量。由于过滤了select我们可以用十六进制编码或concat函数来拼接。set sql concat(sel,ect flag from1919810931114514);这里用concat(sel,ect ...)巧妙地绕过了对完整单词select的匹配。预编译语句prepare stmt from sql;这条命令将变量sql中的字符串准备成一个可执行的SQL语句命名为stmt。执行语句execute stmt;执行预编译好的语句效果就等同于执行了SELECT flag FROM 1919810931114514。释放资源deallocate prepare stmt;我们可以将其组合成一个堆叠注入Payload1‘; set sqlconcat(sel,ect flag from1919810931114514); prepare stmt from sql; execute stmt; --这个方法非常强大因为它可以将被禁用的关键词拆解、编码或隐藏在变量中是绕过WAF和简单正则过滤的利器。5.3 思路三修改表结构“偷梁换柱”本题预期解还有一种非常巧妙的思路它不直接去读flag表而是改变前端查询的逻辑。我们回顾一下前端查询大概率是SELECT * FROM words WHERE id ‘用户输入‘。words表有id和data两列。1919810931114514表只有flag一列。如果我们能把1919810931114514这个表的名字改成words同时把它唯一的flag列改名为id或data那么当前端程序执行它固有的SELECT * FROM words ...查询时实际上查的就是我们改名后的、存放flag的表了具体操作步骤如下通过堆叠注入执行重命名原words表先把默认的words表改个名腾出位置。rename table words to any_other_name;重命名flag表为wordsrename table1919810931114514to words;修改新words表的结构现在名为words的表是原来的flag表它只有一列flag。我们需要给它增加列或者修改列名。但ALTER TABLE命令可能被过滤虽然本题没提且操作稍复杂。一个更简单的想法是我们不需要修改它只需要让前端查询能“碰到”flag就行。如果前端查询是SELECT id, data FROM words那么因为新表没有这些列会报错。但如果前端查询是SELECT * FROM words且flag列的类型是字符串那么查询1‘ or 11 --可能会把所有的flag行都显示出来。 实际上更精准的做法是修改列名。但MySQL中修改列名 (CHANGE COLUMN) 需要知道原列名和数据类型。我们已知原列名为flag。可以尝试alter table words change flag id varchar(100);这会把flag列改名为id。但这里有个问题原words表有两列现在新words表只有一列改名后的id查询SELECT *可能还是会出错。更常见的预期解是为flag表添加一个与words表结构一致的列。但考虑到步骤繁琐且题目环境可能不支持多次执行这种“偷梁换柱”的方法思路巧妙但在实际操作中可能不如HANDLER或预编译语句直接。不过它体现了在SQL注入中利用数据库本身的操作DML和DDL来改变应用程序上下文的高级思维。6. 实操过程与最终Payload构造综合以上分析最稳妥、最快捷的解法是使用HANDLER语句。下面我们模拟完整的攻击链探测与确认访问目标输入1‘看到MySQL报错确认字符型注入。输入1‘ --页面正常确认闭合方式与注释有效。绕过联合查询输入1‘ union select 1,2 --页面返回过滤规则得知select等关键词被禁。利用堆叠注入探查输入1‘; show databases; --查看所有数据库确认当前库。输入1‘; show tables; --发现words和1919810931114514两个表。输入1‘; show create table 1919810931114514; --确认该表存在flag列。最终读取数据构造最终Payload1‘; HANDLER 1919810931114514 OPEN; HANDLER 1919810931114514 READ FIRST; --提交Payload页面成功显示flag{th1s_1s_a_simp1e_f1ag}或类似内容。至此Flag获取成功。整个过程中我们绕过了对SELECT关键词的过滤通过堆叠注入执行SHOW和HANDLER命令完成了信息收集和数据读取。7. 总结与实战经验延伸这道“随便注”题目虽然基础但涵盖的知识点非常全面注入点判断、闭合方式确认、字段数探测、联合查询尝试、关键词过滤识别、堆叠注入利用、MySQL特有命令SHOW,HANDLER以及预编译语句的绕过思路。它完美地展示了当一条路UNION SELECT被堵死时一名渗透测试人员应该如何灵活运用已有的知识库寻找新的突破口。在实际的渗透测试或CTF比赛中以下几点经验尤为重要信息收集是基石不要一上来就想着用自动化工具梭哈。手动测试仔细阅读每一次的页面回显和报错信息。像本题中MySQL的详细报错就是最宝贵的线索。理解过滤逻辑看到过滤规则如preg_match不要慌仔细分析它过滤了哪些关键词、字符。思考这些过滤是否可以绕过是大小写绕过、双写绕过、编码绕过还是像本题一样寻找功能等效但关键词不同的替代命令如用SHOW代替部分SELECT功能用HANDLER代替SELECT读取数据善用数据库特性不同的数据库MySQL、PostgreSQL、SQL Server、Oracle有大量特有的系统表、内置函数和命令。熟练掌握这些特性往往能在关键时刻出奇制胜。比如MySQL的information_schema库、SHOW命令、HANDLER命令、PREPARE/EXECUTE语句等。堆叠注入的利用条件堆叠注入并非万能它取决于Web应用使用的数据库连接驱动如PHP中的mysqli_multi_query函数支持而mysqli_query或PDO::query默认不支持。在实战中如果发现分号;后提交的语句被执行了就要立刻想到堆叠注入的可能性。工具与手工结合Sqlmap等自动化工具很强大但在面对复杂过滤或非常规注入点时手工测试和构造Payload的能力不可或缺。这道题就是典型的手工优于自动化工具的场景因为工具可能无法自动识别并利用HANDLER这种非标准的注入方式。最后这道题也提醒我们开发人员防御SQL注入绝不能仅仅依赖简单的关键词黑名单。参数化查询预编译语句才是从根本上解决注入问题的黄金标准。同时要遵循最小权限原则数据库用户不应拥有执行DROP,RENAME,HANDLER等高危命令的权限。安全是一个攻防对抗、不断演进的过程只有深入理解攻击者的思路才能更好地构筑我们的防线。
返回列表