1. 靶场环境与核心思路解析拿到一个名为“easysql”的CTF题目尤其是来自“极客大挑战”这类赛事第一反应往往是“这题应该不会太难”。但经验告诉我名字越简单陷阱可能越深。这道题的核心是考察对SQL注入中一个非常经典且容易被忽略的细节处理堆叠注入Stacked Queries与查询结果返回机制的结合利用。很多新手在遇到有回显的注入点时会习惯性地使用union select来获取数据但这道题的环境设计巧妙地限制了这一常规路径迫使你思考查询语句本身的执行逻辑和结果返回的原始形式。题目通常提供一个简单的登录框或搜索框输入单引号‘测试可能会看到报错信息这确认了存在SQL注入漏洞。然而当你尝试使用1 union select 1,2,3#这类payload时却发现页面没有任何变化或者只返回了正常查询的结果。这并不是union被过滤了而是题目后端处理查询结果的方式可能比较“原始”——它可能只简单地输出了第一条查询语句的结果集。如果你的union查询没有改变第一条查询的结果例如原查询因为条件不成立而返回空union查询返回了数据那么页面显示的内容就会发生变化。但如果第一条查询本身就能返回数据那么union查询的结果就会被忽略在某些数据库配置或PHP的mysql_query()函数行为下它只获取第一个结果集。这时就需要用到堆叠注入用分号;分隔执行一条全新的、能直接控制输出内容的查询语句。所以这道题的解题思路轴心是利用堆叠注入绕过对union或特定函数可能存在的限制直接执行一个select查询并将我们想要的数据作为唯一的查询结果返回给页面。关键在于构造一个原查询不返回数据或返回可控数据而我们的注入查询能独占输出流的payload。2. 关键步骤与Payload构造详解2.1 初步探测与注入点确认首先我们需要一个标准的探测流程。假设靶场是一个搜索框提示“请输入查询内容”。基础测试输入1页面可能显示与“1”相关的某条记录。输入1‘如果页面返回数据库错误如“You have an error in your SQL syntax...”则明确存在字符型注入。输入1‘ and ‘1’‘1和1‘ and ‘1’‘2观察页面返回是否不同这是判断注入是否可用的经典方法。判断列数使用order by语句。输入1‘ order by 1#1‘ order by 2#1‘ order by 3#... 直到页面返回错误或显示异常假设在order by 4时出错说明当前查询的列数为3。尝试联合查询输入1‘ union select 1,2,3#。这是一个关键的分水岭。如果页面上原本显示数据的位置变成了数字“2”或“3”说明这些位置可以回显数据那么题目可能就很简单了直接通过union select爆库、表、列名即可。但根据题目“easysql”的套路更可能的情况是页面毫无变化依然显示着输入1时的正常结果。注意这里“毫无变化”是一个重要信号。它可能意味着后端代码逻辑是执行SQL语句获取结果集然后直接echo或print结果集中的第一行数据或者循环输出所有行。当使用union时如果第一个select即原查询1‘因为引号未闭合而执行失败那么整个查询失败无输出。但如果我们构造1‘ and 12 union select 1,2,3#让原查询结果为空那么union后的查询结果就会成为唯一输出此时页面应显示2或3。如果这样做了页面仍无变化或者只显示1那就要高度怀疑后端是否只处理第一个结果集。2.2 引入堆叠注入Stacked Queries当联合查询看似无效时堆叠注入是下一个尝试方向。堆叠注入允许我们在一个数据库连接中执行多条SQL语句语句之间用分号;分隔。并非所有数据库或驱动都支持但在CTF的MySQL环境中很常见。探测堆叠注入是否可用输入1‘; select 1;#。注意这里原查询1‘是一个未闭合的、会报错的查询但分号结束了它。如果堆叠注入可用数据库会继续执行select 1;。页面可能有两种反应直接显示一个数字1。这说明后端不仅支持堆叠注入而且会输出最后一条或所有执行语句的结果。这是最理想的情况。页面空白或显示错误。这可能是因为原查询1‘的语法错误导致整个执行中断或者后端代码捕获了错误。我们需要构造一个不会让原查询报错的payload。更稳妥的堆叠注入测试payload1‘; select 1;--或1‘; select 1;#。这里我们通过注释符--或#将原查询后面的单引号注释掉使得1‘成为一个完整的、合法的查询查询内容为字符串1。这样第一条查询正常执行并返回结果可能返回1相关的数据紧接着执行第二条查询select 1。如果页面在显示正常数据之外还额外显示了数字1或者完全被数字1覆盖都说明堆叠注入成功。2.3 构造核心Payload获取Flag假设我们通过上述步骤确认1注入点为字符型2联合查询回显位置不明显或不可控3堆叠注入可用且执行select语句的结果会直接输出到页面上。那么最终获取flag的payload思路就清晰了让第一条查询返回空结果或无干扰结果让第二条查询直接查询包含flag的数据。一个经典的payload构造如下1‘; show databases;--这个payload会先执行查询1‘返回关于“1”的数据然后执行show databases;列出所有数据库名。如果页面显示了数据库列表那么我们就成功迈出了第一步。接下来我们需要查看当前数据库、表、列。但更直接的方式是猜测flag可能存放在一个常见的表里比如flag、flags、geek等。CTF题目为了简化经常把flag放在一个单独的表中。因此我们可以尝试1‘; select * from flag;--或者如果不知道表名需要先获取表名1‘; show tables;--假设返回的表名中有flag。接下来获取列名1‘; show columns from flag;--或者使用desc flag;--假设列名是flag。那么最终的payload就是1‘; select flag from flag;--输入这个payload如果一切顺利flag就会直接显示在页面上。2.4 Payload的变种与细节处理在实际操作中可能会遇到一些细微的障碍需要调整payload注释符的选择MySQL中--后面有个空格和#都是单行注释符。但在URL传输或表单提交时#常被当作锚点处理不会被发送到服务器。因此在Web注入中使用--在URL中代表空格或%23#的URL编码更为可靠。所以payload可能演变为1‘; select flag from flag;--或1‘%23但分号后的语句需要另起所以更常用--。让原查询“静默”为了让页面只显示我们注入查询的结果最好让第一条查询不返回任何数据。可以构造-1‘; select flag from flag;--。-1通常是不存在的ID原查询返回空。或者使用永假条件1‘ and 12; select flag from flag;--。处理可能的过滤题目名“easysql”可能意味着没有过滤但也不能掉以轻心。如果select、union等关键词被过滤可能需要尝试大小写绕过、双写绕过selselectect或内联注释/*!select*/。不过在这道题中通常不需要。结果集处理如果执行1‘; select 1,2,3;--页面显示1 2 3说明多个列都能回显。那么获取flag时可以select 1,flag,3 from flag从第二个位置读取flag。3. 完整解题流程实录与思考下面我模拟一次完整的解题过程记录下每个步骤的输入、观察和决策。第一步信息收集与注入点确认访问靶场看到一个输入框提示“请输入查询”。我首先输入1页面返回一行信息“ID: 1, Data: something...”。输入1‘页面返回SQL语法错误。确认是字符型注入。第二步试探列数与联合查询输入1‘ order by 3--页面正常。输入1‘ order by 4--页面报错。确认列数为3。 输入1‘ union select 1,2,3--页面依然显示“ID: 1, Data: something...”没有任何变化。输入-1‘ union select 1,2,3--页面显示变成了“ID: 1, Data: 2”。很好这说明当原查询-1‘结果为空时union查询的结果得到了显示并且第2列是可回显位。这说明union是可用的。但为什么第一步的union没效果因为原查询1‘有结果页面只展示了第一个结果集的第一行。这印证了我们最初的猜测。第三步尝试堆叠注入虽然union可用但为了练习和确认环境我尝试堆叠注入。输入1‘; select 1,2,3--。观察页面显示发生了变化不再是“ID: 1, Data: something...”而是直接显示了“1 2 3”。这说明后端支持堆叠注入并且在执行多条语句后似乎只输出了最后一条select语句的结果或者覆盖了之前的输出。这是一个非常重要的行为特征。第四步利用堆叠注入进行信息收集输入1‘; show databases;--。页面显示了一系列数据库名其中有一个名为geek的数据库很显眼。 输入1‘; use geek; show tables;--。这里我试图切换数据库。但注意在堆叠注入中use database是有效的。页面显示了geek数据库中的表有一个表名叫flag。 输入1‘; use geek; show columns from flag;--。页面显示该表只有一个列列名为flag。第五步获取Flag构造最终payload-1‘; use geek; select flag from flag;--输入与结果输入payload后页面干净利落地显示了一串字符串格式通常为flag{xxxx-xxxx-xxxx-xxxx}或SUCTF{...}。复制提交解题成功。复盘与思考 这道题的关键教学点在于不要被“有回显”和“联合查询可用”所迷惑必须仔细观察页面行为细节。当发现union查询在有数据时被“忽略”而在无数据时正常显示就应该立刻联想到后端可能使用了mysql_query()这类默认只获取第一个结果集的函数。此时堆叠注入提供了一条更直接的通道通过分号隔断原查询执行我们自己的select语句并利用后端直接输出执行结果的特性一举拿到flag。它考察了对不同注入技术适用场景的理解以及灵活切换利用手法的能力。4. 常见问题、防御与拓展4.1 解题过程中可能遇到的“坑”分号被过滤或转义这是堆叠注入最大的敌人。如果分号;被过滤堆叠注入就无法实现。此时需要回归到union注入并精心构造原查询为假如-1‘确保union查询结果被展示。或者尝试其他分隔符在MySQL中很少见。多语句执行被禁用数据库连接配置可能设置了PDO::MYSQL_ATTR_MULTI_STATEMENTS false对于PDO或使用了mysqli_multi_query的相反配置。这会从根本上禁止堆叠注入。这种情况下只能依靠union或盲注。无回显如果无论输入什么页面都没有直接的数据回显那么这道题就可能是一道盲注题。需要利用时间盲注sleep()或布尔盲注根据页面内容差异来逐位获取数据。但题目名为“easysql”通常不会这么设计。Flag不在预期位置可能表名不是flag而是Flag、fl4g等或者flag在某个表的某个列里。这就需要通过information_schema数据库系统表来遍历。堆叠注入payload可以这样写-1‘; select group_concat(table_name) from information_schema.tables where table_schemadatabase();--来爆表名再用group_concat(column_name)爆列名。4.2 从攻击到防御如何避免此类漏洞作为开发者理解攻击手法才能更好地防御。针对这道题体现的漏洞防御措施包括使用预处理语句Prepared Statements这是防止SQL注入最有效、最根本的方法。无论是使用PDO还是MySQLi预处理都能确保用户输入的数据始终被当作数据处理而非SQL代码的一部分。它从根本上消除了拼接SQL语句的风险。// PDO 示例 $stmt $pdo-prepare(SELECT * FROM users WHERE id ?); $stmt-execute([$user_input]);禁用多语句查询如果业务确实不需要执行多条SQL应在数据库连接时明确禁用此功能。// PDO 示例 $pdo new PDO($dsn, $user, $pass, [PDO::MYSQL_ATTR_MULTI_STATEMENTS false]);最小权限原则给Web应用使用的数据库账户分配最小的必要权限。通常查询操作只需要SELECT权限。绝对不要使用root或具有FILE、DROP、CREATE等高级权限的账户连接数据库。这样即使发生注入攻击者能造成的破坏也有限。输入验证与过滤虽然不能作为主要防御手段但可以对输入进行严格的类型检查例如ID字段必须是整数和白名单过滤只允许特定字符集。切记黑名单过滤过滤select、union等关键词很容易被绕过不可依赖。错误信息处理生产环境必须关闭数据库错误信息的直接回显。自定义统一的错误页面避免将数据库结构、字段名等敏感信息泄露给攻击者。这道题中如果关闭了错误回显探测注入点会稍微困难一些。4.3 技术拓展堆叠注入的更多可能性堆叠注入的威力远不止于select数据。因为它可以执行任意SQL语句在权限允许的情况下攻击者可以增删改数据; INSERT INTO ... ;,; UPDATE ... ;,; DELETE FROM ... ;修改数据库结构; ALTER TABLE ... ;,; DROP TABLE ... ;读写文件; SELECT ... INTO OUTFILE /tmp/backdoor.php ;需FILE权限或; LOAD_FILE(/etc/passwd) ;执行系统命令在某些特定配置和数据库版本下通过; SELECT sys_exec(whoami) ;等方式非常罕见且依赖数据库特定功能和外挂组件。因此堆叠注入的危害等级通常比普通的联合查询注入要高得多。在CTF中它往往是一道题目的“题眼”也是区分选手对注入技术理解深度的一个标志。通过这道“easysql”我们不仅学会了一个payload更理解了不同注入技术背后的数据库交互原理这才是最重要的收获。在真实渗透测试中遇到支持堆叠注入的点一定要保持高度警惕并谨慎评估其潜在影响范围。
CTF中SQL堆叠注入实战:从原理到绕过联合查询限制
发布时间:2026/6/26 3:43:23
1. 靶场环境与核心思路解析拿到一个名为“easysql”的CTF题目尤其是来自“极客大挑战”这类赛事第一反应往往是“这题应该不会太难”。但经验告诉我名字越简单陷阱可能越深。这道题的核心是考察对SQL注入中一个非常经典且容易被忽略的细节处理堆叠注入Stacked Queries与查询结果返回机制的结合利用。很多新手在遇到有回显的注入点时会习惯性地使用union select来获取数据但这道题的环境设计巧妙地限制了这一常规路径迫使你思考查询语句本身的执行逻辑和结果返回的原始形式。题目通常提供一个简单的登录框或搜索框输入单引号‘测试可能会看到报错信息这确认了存在SQL注入漏洞。然而当你尝试使用1 union select 1,2,3#这类payload时却发现页面没有任何变化或者只返回了正常查询的结果。这并不是union被过滤了而是题目后端处理查询结果的方式可能比较“原始”——它可能只简单地输出了第一条查询语句的结果集。如果你的union查询没有改变第一条查询的结果例如原查询因为条件不成立而返回空union查询返回了数据那么页面显示的内容就会发生变化。但如果第一条查询本身就能返回数据那么union查询的结果就会被忽略在某些数据库配置或PHP的mysql_query()函数行为下它只获取第一个结果集。这时就需要用到堆叠注入用分号;分隔执行一条全新的、能直接控制输出内容的查询语句。所以这道题的解题思路轴心是利用堆叠注入绕过对union或特定函数可能存在的限制直接执行一个select查询并将我们想要的数据作为唯一的查询结果返回给页面。关键在于构造一个原查询不返回数据或返回可控数据而我们的注入查询能独占输出流的payload。2. 关键步骤与Payload构造详解2.1 初步探测与注入点确认首先我们需要一个标准的探测流程。假设靶场是一个搜索框提示“请输入查询内容”。基础测试输入1页面可能显示与“1”相关的某条记录。输入1‘如果页面返回数据库错误如“You have an error in your SQL syntax...”则明确存在字符型注入。输入1‘ and ‘1’‘1和1‘ and ‘1’‘2观察页面返回是否不同这是判断注入是否可用的经典方法。判断列数使用order by语句。输入1‘ order by 1#1‘ order by 2#1‘ order by 3#... 直到页面返回错误或显示异常假设在order by 4时出错说明当前查询的列数为3。尝试联合查询输入1‘ union select 1,2,3#。这是一个关键的分水岭。如果页面上原本显示数据的位置变成了数字“2”或“3”说明这些位置可以回显数据那么题目可能就很简单了直接通过union select爆库、表、列名即可。但根据题目“easysql”的套路更可能的情况是页面毫无变化依然显示着输入1时的正常结果。注意这里“毫无变化”是一个重要信号。它可能意味着后端代码逻辑是执行SQL语句获取结果集然后直接echo或print结果集中的第一行数据或者循环输出所有行。当使用union时如果第一个select即原查询1‘因为引号未闭合而执行失败那么整个查询失败无输出。但如果我们构造1‘ and 12 union select 1,2,3#让原查询结果为空那么union后的查询结果就会成为唯一输出此时页面应显示2或3。如果这样做了页面仍无变化或者只显示1那就要高度怀疑后端是否只处理第一个结果集。2.2 引入堆叠注入Stacked Queries当联合查询看似无效时堆叠注入是下一个尝试方向。堆叠注入允许我们在一个数据库连接中执行多条SQL语句语句之间用分号;分隔。并非所有数据库或驱动都支持但在CTF的MySQL环境中很常见。探测堆叠注入是否可用输入1‘; select 1;#。注意这里原查询1‘是一个未闭合的、会报错的查询但分号结束了它。如果堆叠注入可用数据库会继续执行select 1;。页面可能有两种反应直接显示一个数字1。这说明后端不仅支持堆叠注入而且会输出最后一条或所有执行语句的结果。这是最理想的情况。页面空白或显示错误。这可能是因为原查询1‘的语法错误导致整个执行中断或者后端代码捕获了错误。我们需要构造一个不会让原查询报错的payload。更稳妥的堆叠注入测试payload1‘; select 1;--或1‘; select 1;#。这里我们通过注释符--或#将原查询后面的单引号注释掉使得1‘成为一个完整的、合法的查询查询内容为字符串1。这样第一条查询正常执行并返回结果可能返回1相关的数据紧接着执行第二条查询select 1。如果页面在显示正常数据之外还额外显示了数字1或者完全被数字1覆盖都说明堆叠注入成功。2.3 构造核心Payload获取Flag假设我们通过上述步骤确认1注入点为字符型2联合查询回显位置不明显或不可控3堆叠注入可用且执行select语句的结果会直接输出到页面上。那么最终获取flag的payload思路就清晰了让第一条查询返回空结果或无干扰结果让第二条查询直接查询包含flag的数据。一个经典的payload构造如下1‘; show databases;--这个payload会先执行查询1‘返回关于“1”的数据然后执行show databases;列出所有数据库名。如果页面显示了数据库列表那么我们就成功迈出了第一步。接下来我们需要查看当前数据库、表、列。但更直接的方式是猜测flag可能存放在一个常见的表里比如flag、flags、geek等。CTF题目为了简化经常把flag放在一个单独的表中。因此我们可以尝试1‘; select * from flag;--或者如果不知道表名需要先获取表名1‘; show tables;--假设返回的表名中有flag。接下来获取列名1‘; show columns from flag;--或者使用desc flag;--假设列名是flag。那么最终的payload就是1‘; select flag from flag;--输入这个payload如果一切顺利flag就会直接显示在页面上。2.4 Payload的变种与细节处理在实际操作中可能会遇到一些细微的障碍需要调整payload注释符的选择MySQL中--后面有个空格和#都是单行注释符。但在URL传输或表单提交时#常被当作锚点处理不会被发送到服务器。因此在Web注入中使用--在URL中代表空格或%23#的URL编码更为可靠。所以payload可能演变为1‘; select flag from flag;--或1‘%23但分号后的语句需要另起所以更常用--。让原查询“静默”为了让页面只显示我们注入查询的结果最好让第一条查询不返回任何数据。可以构造-1‘; select flag from flag;--。-1通常是不存在的ID原查询返回空。或者使用永假条件1‘ and 12; select flag from flag;--。处理可能的过滤题目名“easysql”可能意味着没有过滤但也不能掉以轻心。如果select、union等关键词被过滤可能需要尝试大小写绕过、双写绕过selselectect或内联注释/*!select*/。不过在这道题中通常不需要。结果集处理如果执行1‘; select 1,2,3;--页面显示1 2 3说明多个列都能回显。那么获取flag时可以select 1,flag,3 from flag从第二个位置读取flag。3. 完整解题流程实录与思考下面我模拟一次完整的解题过程记录下每个步骤的输入、观察和决策。第一步信息收集与注入点确认访问靶场看到一个输入框提示“请输入查询”。我首先输入1页面返回一行信息“ID: 1, Data: something...”。输入1‘页面返回SQL语法错误。确认是字符型注入。第二步试探列数与联合查询输入1‘ order by 3--页面正常。输入1‘ order by 4--页面报错。确认列数为3。 输入1‘ union select 1,2,3--页面依然显示“ID: 1, Data: something...”没有任何变化。输入-1‘ union select 1,2,3--页面显示变成了“ID: 1, Data: 2”。很好这说明当原查询-1‘结果为空时union查询的结果得到了显示并且第2列是可回显位。这说明union是可用的。但为什么第一步的union没效果因为原查询1‘有结果页面只展示了第一个结果集的第一行。这印证了我们最初的猜测。第三步尝试堆叠注入虽然union可用但为了练习和确认环境我尝试堆叠注入。输入1‘; select 1,2,3--。观察页面显示发生了变化不再是“ID: 1, Data: something...”而是直接显示了“1 2 3”。这说明后端支持堆叠注入并且在执行多条语句后似乎只输出了最后一条select语句的结果或者覆盖了之前的输出。这是一个非常重要的行为特征。第四步利用堆叠注入进行信息收集输入1‘; show databases;--。页面显示了一系列数据库名其中有一个名为geek的数据库很显眼。 输入1‘; use geek; show tables;--。这里我试图切换数据库。但注意在堆叠注入中use database是有效的。页面显示了geek数据库中的表有一个表名叫flag。 输入1‘; use geek; show columns from flag;--。页面显示该表只有一个列列名为flag。第五步获取Flag构造最终payload-1‘; use geek; select flag from flag;--输入与结果输入payload后页面干净利落地显示了一串字符串格式通常为flag{xxxx-xxxx-xxxx-xxxx}或SUCTF{...}。复制提交解题成功。复盘与思考 这道题的关键教学点在于不要被“有回显”和“联合查询可用”所迷惑必须仔细观察页面行为细节。当发现union查询在有数据时被“忽略”而在无数据时正常显示就应该立刻联想到后端可能使用了mysql_query()这类默认只获取第一个结果集的函数。此时堆叠注入提供了一条更直接的通道通过分号隔断原查询执行我们自己的select语句并利用后端直接输出执行结果的特性一举拿到flag。它考察了对不同注入技术适用场景的理解以及灵活切换利用手法的能力。4. 常见问题、防御与拓展4.1 解题过程中可能遇到的“坑”分号被过滤或转义这是堆叠注入最大的敌人。如果分号;被过滤堆叠注入就无法实现。此时需要回归到union注入并精心构造原查询为假如-1‘确保union查询结果被展示。或者尝试其他分隔符在MySQL中很少见。多语句执行被禁用数据库连接配置可能设置了PDO::MYSQL_ATTR_MULTI_STATEMENTS false对于PDO或使用了mysqli_multi_query的相反配置。这会从根本上禁止堆叠注入。这种情况下只能依靠union或盲注。无回显如果无论输入什么页面都没有直接的数据回显那么这道题就可能是一道盲注题。需要利用时间盲注sleep()或布尔盲注根据页面内容差异来逐位获取数据。但题目名为“easysql”通常不会这么设计。Flag不在预期位置可能表名不是flag而是Flag、fl4g等或者flag在某个表的某个列里。这就需要通过information_schema数据库系统表来遍历。堆叠注入payload可以这样写-1‘; select group_concat(table_name) from information_schema.tables where table_schemadatabase();--来爆表名再用group_concat(column_name)爆列名。4.2 从攻击到防御如何避免此类漏洞作为开发者理解攻击手法才能更好地防御。针对这道题体现的漏洞防御措施包括使用预处理语句Prepared Statements这是防止SQL注入最有效、最根本的方法。无论是使用PDO还是MySQLi预处理都能确保用户输入的数据始终被当作数据处理而非SQL代码的一部分。它从根本上消除了拼接SQL语句的风险。// PDO 示例 $stmt $pdo-prepare(SELECT * FROM users WHERE id ?); $stmt-execute([$user_input]);禁用多语句查询如果业务确实不需要执行多条SQL应在数据库连接时明确禁用此功能。// PDO 示例 $pdo new PDO($dsn, $user, $pass, [PDO::MYSQL_ATTR_MULTI_STATEMENTS false]);最小权限原则给Web应用使用的数据库账户分配最小的必要权限。通常查询操作只需要SELECT权限。绝对不要使用root或具有FILE、DROP、CREATE等高级权限的账户连接数据库。这样即使发生注入攻击者能造成的破坏也有限。输入验证与过滤虽然不能作为主要防御手段但可以对输入进行严格的类型检查例如ID字段必须是整数和白名单过滤只允许特定字符集。切记黑名单过滤过滤select、union等关键词很容易被绕过不可依赖。错误信息处理生产环境必须关闭数据库错误信息的直接回显。自定义统一的错误页面避免将数据库结构、字段名等敏感信息泄露给攻击者。这道题中如果关闭了错误回显探测注入点会稍微困难一些。4.3 技术拓展堆叠注入的更多可能性堆叠注入的威力远不止于select数据。因为它可以执行任意SQL语句在权限允许的情况下攻击者可以增删改数据; INSERT INTO ... ;,; UPDATE ... ;,; DELETE FROM ... ;修改数据库结构; ALTER TABLE ... ;,; DROP TABLE ... ;读写文件; SELECT ... INTO OUTFILE /tmp/backdoor.php ;需FILE权限或; LOAD_FILE(/etc/passwd) ;执行系统命令在某些特定配置和数据库版本下通过; SELECT sys_exec(whoami) ;等方式非常罕见且依赖数据库特定功能和外挂组件。因此堆叠注入的危害等级通常比普通的联合查询注入要高得多。在CTF中它往往是一道题目的“题眼”也是区分选手对注入技术理解深度的一个标志。通过这道“easysql”我们不仅学会了一个payload更理解了不同注入技术背后的数据库交互原理这才是最重要的收获。在真实渗透测试中遇到支持堆叠注入的点一定要保持高度警惕并谨慎评估其潜在影响范围。