三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

SQL注入实战:从CTF题“随便注”解析堆叠查询与绕过技巧

SQL注入实战:从CTF题“随便注”解析堆叠查询与绕过技巧

1. 从一道CTF题说起:为什么“随便注”能火出圈?

如果你在网络安全圈子里混过一阵子,或者对CTF(Capture The Flag,夺旗赛)有点兴趣,那你大概率听说过“BUUCTF”这个平台。它就像是一个巨大的、永不关门的靶场,里面堆满了从新手到高手各个段位的题目,是无数安全爱好者练手、进阶的“圣地”。而“随便注”这道题,更是BUUCTF里一个现象级的存在。它最初可能只是2019年某次比赛里一道普通的Web题目,但几年过去了,它的名字依然频繁出现在各种讨论、教程和面试题里,甚至衍生出了“bp类型buuctf”、“buuctf命令拼接”这样的搜索热词。

这背后反映了一个很有意思的现象:一道好的CTF题,其价值远不止于解出flag那一刻的成就感。它更像是一个精心设计的“教学案例”,把现实中可能遇到的、教科书上又语焉不详的安全漏洞,浓缩在一个可控的环境里。你解这道题的过程,就是在模拟一次真实的渗透测试或代码审计。“随便注”这道题,就完美扮演了这个角色。它没有用那些花里胡哨、需要特定版本或冷门配置才能触发的漏洞,而是直指Web安全中最经典、也最危险的漏洞之一——SQL注入。更妙的是,它把注入过程中可能遇到的“障碍”和“技巧”层层递进地展现出来,让你不得不去思考、去尝试、去组合各种知识。

所以,今天我们不聊高深的零日漏洞,也不讲复杂的协议分析,就从一个安全从业者(或者说,一个CTF老鸟)的视角,来彻底拆解“随便注”这道题,以及它所代表的这一类SQL注入题的解题思路。你会发现,解题的过程,其实就是一次完整的、微型的安全评估实战。

2. 环境复现与第一层观察:目标到底在干什么?

在开始任何“攻击”之前,第一步永远是信息收集。对于CTF题,尤其是BUUCTF上的题目,我们通常能获得一个可访问的URL。假设我们拿到的题目地址是http://node4.buuoj.cn:port/(端口每次随机)。作为解题者,我们首先得搞清楚这个Web应用是干什么的。

打开页面,你可能会看到一个极其简单的界面,比如一个搜索框,旁边有个提交按钮,页面上或许还有一行提示:“输入查询内容”。这看起来像是一个数据库查询的前端。你的本能反应是什么?对,先试试常规输入。

2.1 基础功能探测与初步注入测试

你输入1,点击查询。页面返回了数据,比如:“ID: 1, Data: something”。输入2,返回另一条数据。输入abc,可能返回空或者错误。这个行为初步判断,后端逻辑可能是这样的:

SELECT * FROM some_table WHERE id = ‘用户输入’

这是一个典型的数字型或字符型查询。为了判断注入类型,我们会进行一些经典测试:

  1. 数字型判断:输入1 and 1=11 and 1=2。如果前者正常返回,后者返回空或错误,那么很大可能是数字型注入,因为输入被直接拼接到了SQL语句中,没有引号包裹。
  2. 字符型判断:输入1‘(一个单引号)。如果页面返回了SQL语法错误,比如You have an error in your SQL syntax...,那基本可以确定是字符型注入,因为我们的引号破坏了原语句的引号闭合。

在“随便注”这道题里,输入1‘大概率会报错。这就是我们的第一个突破口:存在SQL注入漏洞,且为字符型。错误信息本身也是宝贵的信息,它有时会泄露数据库类型(如MySQL, PostgreSQL)、表结构或查询语句片段。

2.2 信息收集的进阶手段

除了手动测试,我们通常会借助工具。这里就引出了热词中的“bp类型buuctf”。“bp”指的是 Burp Suite,一个渗透测试人员必备的集成平台。它的 Repeater(重放)和 Intruder(入侵者)模块在CTF中尤其有用。

  • Burp Suite Repeater:捕获到浏览器发送的查询请求后,可以在Repeater里手动修改参数,反复发送,观察响应。这比在浏览器地址栏或输入框里修改要方便、精确得多,尤其当参数需要URL编码或涉及POST请求时。
  • Burp Suite Intruder:当我们需要进行模糊测试(Fuzzing)时,比如猜解数据库名、表名、列名,Intruder可以自动化地替换Payload并记录响应。我们可以加载一个字典(如常见表名admin, user, flag等),让Intruder去暴力猜解。

所以,“bp类型buuctf”这个搜索词,反映的正是解题者利用Burp Suite这类专业工具来高效解决BUUCTF题目的普遍做法。它标志着解题从“手动碰运气”进入了“自动化、系统化”的阶段。

3. 核心攻击链构建:绕过过滤与获取数据

确认存在字符型注入后,下一步就是尝试获取数据。常规思路是利用UNION SELECT进行联合查询,直接读取我们想要的信息(比如表名、列名、数据)。你可能会构造这样的Payload:

1‘ union select 1, database() --+

这里,--+是注释符(在MySQL中,--后面需要一个空格,+在URL中常被解释为空格),用于注释掉原查询后面的引号和语句,避免语法错误。database()函数用于获取当前数据库名。

但是,在“随便注”这道题里,你很可能会碰壁。页面可能返回一个奇怪的提示,或者直接过滤了unionselect等关键词。这就是题目设置的“障碍”,也是其精髓所在。它模拟了现实应用中简单的WAF(Web应用防火墙)或代码层的关键字过滤。

3.1 关键词过滤与绕过技巧

unionselect被过滤时,我们就需要绕过了。这里体现了CTF的趣味性和知识性。常见的绕过方法有:

  • 大小写绕过UnIoN SeLeCt。有些简单的过滤是大小写敏感的。
  • 双写绕过uniunionon selselectect。如果过滤逻辑是简单地删除关键词,那么双写后,删除中间的union,剩下的部分正好又组成了union
  • 内联注释绕过/*!union*/ select。在MySQL中,/*!...*/中的内容会被当作代码执行,可以用来包裹关键词。
  • 编码绕过:URL编码、十六进制编码等。例如,select的十六进制是0x73656c656374,在有些地方可以直接使用。
  • 使用等价函数或语法:如果只是过滤了select,但没过滤handler(MySQL的一个读取表的接口),或者可以通过报错注入、时间盲注等其他方式。

在“随便注”这道题中,经过测试,你会发现它可能采用了正则表达式过滤,unionselect无论大小写、双写都可能被拦截。这时,解题思路就需要转变。

3.2 堆叠查询(Stacked Queries)的利用

如果union select的路走不通,我们就要观察其他注入点。一个重要的测试是尝试执行多条SQL语句,即堆叠查询。Payload如下:

1‘; show databases; --+

注意这里的分号;。如果后端使用的是支持多语句查询的数据库驱动(如PHP中的mysqli_multi_query),那么这个分号会让数据库执行完SELECT ... WHERE id=‘1‘后,继续执行show databases;

如果页面返回了数据库列表,那么恭喜你,找到了新的突破口!堆叠查询给了我们巨大的操作空间,因为我们可以执行任何SQL语句,而不仅仅是查询。这也就是“buuctf命令拼接”这个热词的由来——我们通过注入点“拼接”上了新的SQL“命令”。

3.3 信息获取的替代路径

既然可以执行任意语句,我们就不需要union select了。我们可以:

  1. show databases;查看所有数据库。
  2. use 数据库名; show tables;切换到目标数据库,查看所有表。
  3. show columns from 表名;desc 表名;查看某个表的结构(列名)。

通过这一步,你可能会发现两个表:一个叫words,结构大概是(id int, data varchar(100)),这很可能就是前台查询的那个表;另一个表名字可能叫1919810931114514(一个纯数字的表名,非常可疑)或者flag。查看这个可疑表的结构,发现它只有一个列,比如flag varchar(100)

问题来了:我们如何读取1919810931114514这个表里的数据?直接select * from 1919810931114514吗?在堆叠查询中当然可以。但题目可能在这里又设置了障碍:即使开启了堆叠查询,程序默认返回的仍然是第一条查询语句的结果集。也就是说,我们执行1‘; select * from 1919810931114514; --+,页面显示的很可能还是words表中 id=1 的结果,而不是flag表的内容。

4. 终极技巧:巧用预处理语句与表重命名

这就是“随便注”这道题最精彩、最考验思维灵活性的地方。我们需要让程序把flag表的内容“吐”到它原本预期显示words表内容的地方。怎么实现?一个非常巧妙的思路是:修改表结构,瞒天过海

4.1 理解程序的数据流

我们推测程序的后端逻辑固定如下:

  1. 执行一个形如SELECT * FROM words WHERE id = ‘$id‘的查询。
  2. 将查询结果的第一行(或全部)的某个字段(比如data列)输出到网页上。

我们的目标是让这个固定的查询,去查询1919810931114514表,并且把flag列的内容放到data列的位置上返回。

4.2 预处理语句(PREPARE)的作用

由于select可能被过滤,我们无法直接构造一个select语句让程序执行。但是,我们可以利用堆叠查询的权限,预先准备好(PREPARE)一个SQL语句,然后执行(EXECUTE)它。预处理语句是数据库的一个特性,它允许我们将SQL语句的模板和参数分开。在这里,我们可以用它来“绕过”代码层对select的过滤,因为select这个词是作为字符串存储在数据库端的,而不是由我们的输入直接拼接的。

我们可以构造如下Payload:1‘; PREPARE stmt FROM CONCAT(‘s‘,‘elect‘, ‘ * from1919810931114514‘); EXECUTE stmt; --+

这里,CONCAT(‘s‘,‘elect‘, ‘...‘)将字符串拼接成select * from1919810931114514``。PREPARE stmt FROM将这个字符串定义为一条预处理语句stmt,然后EXECUTE stmt执行它。这样,我们成功执行了select查询,但输入流里并没有出现完整的select关键词,可能绕过了过滤。

4.3 表重命名(RENAME)的妙用

然而,即使执行了查询,程序可能也不会显示其结果。这时,另一个更直接、更经典的解法登场了:表重命名

我们知道程序固定查询words表。那我们能不能把words表改个名,然后把1919810931114514这个表改名叫words,同时把这个表的flag列改名叫data呢?这样,当程序执行SELECT * FROM words WHERE id = ‘1‘时,实际上查询的就是我们改造后的、存放flag的表,并且返回的列名也是程序期望的data

具体步骤如下(通过堆叠查询执行):

  1. 备份原表rename table words to words_backup;(先把原words表改名备份,防止丢失)。
  2. 改造目标表rename table1919810931114514to words;(将flag表改名为words)。
  3. 修改列名:光改表名不够,因为1919810931114514表里只有flag列,而程序期望查询words表有iddata列。我们需要修改列名。但ALTER TABLE CHANGE语句可能也被过滤。另一个巧思是,我们可以利用创建新表的方式。但更简单的是,我们注意到程序可能只根据列的位置取数据。我们让flag列“扮演”data列,同时需要有一个id列。我们可以:
    • alter table words change flag data varchar(100);(将flag列改名为data)。
    • 但还需要一个id列。我们可以添加:alter table words add id int not null auto_increment primary key;。或者,更粗暴地,如果程序只是简单地按顺序取字段,我们可以构造一个查询,让flag值出现在第二个字段位置。但经过测试,最稳妥的方式是直接修改表结构。

实际上,在“随便注”这道题的经典解法中,通常结合了预处理语句和查询技巧。一个常见的最终Payload序列是:

1‘; rename table words to words1; rename table `1919810931114514` to words; alter table words change flag id varchar(50); --+

或者,更简洁地,利用show语句和预处理语句直接查询:

1‘; show columns from `1919810914514`; --+ //先查看列名,假设列名为flag 1‘; use supersqli; set @sql = concat(‘s‘,‘elect `flag` from `1919810931114514`‘); PREPARE stmt from @sql; EXECUTE stmt; --+

执行成功后,我们只需要再访问一下首页,或者提交一个普通的查询(比如11‘ or 1=1 --+),程序查询words表时,实际上查的就是我们重命名后的、包含flag的表,flag就会在原本显示data的位置被展示出来。

5. 举一反三:从“随便注”到通用注入方法论

解完一道题,收获不应该只是一个flag字符串。更重要的是提炼出方法论。“随便注”这道题几乎涵盖了SQL注入从入门到进阶的多个关键点:

  1. 注入点探测:数字型、字符型的判断,报错信息的利用。
  2. 信息收集show databases/tables/columns在堆叠注入中的核心作用。这对应了真实环境中获取数据库架构的能力。
  3. 绕过技巧:面对关键词过滤,思维不能僵化。大小写、双写、注释、编码、等价替换、预处理语句,这些都是武器库里的工具。现实中的WAF绕过也是如此,需要不断测试和组合。
  4. 堆叠注入的利用与限制:认识到堆叠注入的强大(执行任意SQL)和局限(默认返回第一个结果集)。这要求我们理解后端代码的执行流程。
  5. 创造性思维:当直接查询不行时,如何“曲线救国”?表/列重命名这个思路,体现了对程序逻辑和数据流的深度理解。在真实漏洞利用中,这种“利用应用程序原有逻辑来实现攻击目标”的思维至关重要,比如在文件上传中利用本地文件包含,在反序列化中利用已有的类属性进行POP链构造。

回到那些网络热词:

  • buuctf rot:这可能指另一道涉及ROT编码的题目,或者是“随便注”解题过程中某个步骤需要ROT解码。这提醒我们,CTF中密码学(编码)和Web安全经常结合。
  • buuctf black watch 入群题:BUUCTF的“Black Watch”小组入群题通常有一定难度,可能涉及更复杂的漏洞组合或新颖的考点。“随便注”作为一道经典题,其解题思维对于解决这类题目是很好的基础训练。
  • buuctf php:很多BUUCTF的Web题,包括“随便注”,后端都是PHP。理解PHP中mysql(i)_querymysql(i)_multi_query的区别,对于判断是否支持堆叠注入至关重要。
  • buuctf snake:这可能是另一道独立题目。但这也说明BUUCTF题库的丰富性,各种类型的漏洞(如反序列化、XXE、SSTI等)都有涵盖。

对于想深入Web安全的朋友,我的建议是:不要只满足于复制Payload拿到flag。一定要自己动手搭环境(可以用Docker快速搭建LAMP/ LNMP + 题目源码),从黑盒测试开始,用Burp Suite抓包改包,一步步猜测后端逻辑,尝试每一种可能的绕过。遇到过滤,就本地写个简单的过滤脚本,自己尝试各种绕过方法。理解“为什么这个Payload能工作”以及“为什么那个Payload不行”,比记住十个Payload更有价值。

“随便注”之所以经典,就是因为它像一把钥匙,打开了一扇门,门后是SQL注入这个庞大而深邃的领域。它告诉你,安全研究有时候就像解谜,需要耐心、知识和一点点打破常规的创造力。

← 返回列表