SQL注入实战:搜索型注入漏洞的探测、利用与防御

发布时间:2026/8/21 12:40:00
SQL注入实战:搜索型注入漏洞的探测、利用与防御 1. 先搞清楚“搜索型注入”和“字符型闭合”到底在说什么如果你刚接触SQL注入看到“搜索型注入”、“字符型闭合”、“联合注入”这些词可能会有点懵。这其实是在描述一个非常经典的场景一个带搜索框的网页比如让你搜用户名或者商品你输入一个关键词它就在数据库里帮你找。而“字符型闭合”指的是这个搜索功能在处理你的输入时是用单引号把你的关键词包起来的。举个例子你在搜索框输入admin后台执行的SQL语句可能是这样的SELECT * FROM users WHERE username LIKE %admin%这里的%admin%就是字符型。单引号是SQL用来标记字符串的开始和结束的。所谓“闭合”就是我们要想办法构造一个输入让原本的单引号提前“关门”然后我们就能在后面拼接自己的恶意SQL代码了。Pikachu靶场的“搜索型注入”关卡就是模拟这种场景。它比普通的“字符型注入”多了一个%通配符这会让我们的闭合方式有一点点不同但核心思路完全一样。这个关卡的价值在于它非常贴近真实开发中“模糊查询”功能的实现很多新手甚至老手都可能在这里写出有漏洞的代码。所以这篇文章解决的就是在一个用单引号和百分号包裹用户输入的搜索功能里如何一步步判断注入点、确定闭合方式、使用联合查询UNION来获取数据库信息。如果你对SQL注入的基本原理比如什么是UNION SELECT还不熟建议先了解一下再来看这篇会更容易跟上。2. 环境准备与靶场启动别在第一步卡住动手之前环境得先跑起来。Pikachu是一个用PHP写的、集成了多种Web漏洞的靶场非常适合学习和练习。2.1 获取与部署PikachuPikachu的部署不算复杂但有几个点容易踩坑。1. 下载与解压你可以从它的GitHub仓库或一些开源镜像站下载。下载后得到一个压缩包解压到你的Web服务器根目录下。比如如果你用XAMPP就解压到xampp/htdocs/下用PHPStudy就解压到phpstudy_pro/WWW/下。解压后的文件夹名字建议改成简单的pikachu方便访问。2. 数据库初始化这是最关键的一步。解压后浏览器访问http://localhost/pikachu具体路径根据你的文件夹名调整。第一次访问页面很可能会提示你“数据库连接错误”或者直接显示一个安装引导页面。如果有引导页面按照页面提示点击“初始化安装”按钮即可。这个按钮会自动执行pikachu目录下的inc/config.inc.php脚本创建数据库和所需的数据表。如果报错或空白页大概率是数据库配置不对。你需要手动检查并修改配置文件。配置文件路径通常是pikachu/inc/config.inc.php。 用文本编辑器打开这个文件找到数据库连接部分通常长这样$dbuserroot; //数据库账号 $dbpassroot; //数据库密码 $dbnamepikachu; //数据库名 $hostlocalhost; //数据库地址把你的数据库账号通常是root和密码XAMPP默认空密码PHPStudy可能是root填进去。注意很多人在这一步出错就是因为密码没填对。XAMPP的MySQL默认密码为空所以$dbpass;。改完后保存文件再刷新浏览器页面应该就能看到初始化按钮了。3. 验证安装初始化成功后页面会显示“安装成功”之类的提示并且会出现Pikachu漏洞平台的主菜单。左侧导航栏找到“SQL-Inject”-“搜索型注入”点击进入我们的目标关卡就准备好了。2.2 理解关卡界面进入“搜索型注入”关卡你会看到一个非常简单的页面一个输入框一个提交按钮。页面上可能已经有了一些提示比如“试试在搜索框输入一些信息吧”。我们的任务就是在这个输入框里做文章。在开始“攻击”之前我建议你先以正常用户的身份玩一下输入kobe点击搜索。看看返回什么结果。输入test或者一个不存在的名字再搜索。看看返回什么。这个步骤很重要它能帮你建立“正常行为”的基准。你会看到输入kobe后页面返回了关于“kobe”的用户信息邮箱、身份证号等。输入不存在的词则可能返回“未找到”或空白。这告诉我们后端确实在执行一个搜索查询并且会将结果展示在页面上——这对我们后续使用UNION注入至关重要因为UNION注入的前提就是页面会“回显”数据库查询结果。3. 注入点探测与闭合方式确认现在我们从一个攻击者的视角出发。面对一个搜索框我们不知道后端代码怎么写第一步永远是试探。3.1 基础探测判断是否存在注入点我们输入一些“特殊”的字符观察页面反应。输入单引号这是最经典的测试。输入然后搜索。如果页面报错显示SQL语法错误如You have an error in your SQL syntax...那几乎可以肯定存在SQL注入漏洞并且很可能是字符型注入。因为我们的单引号破坏了原SQL语句的字符串闭合导致语法错误。如果页面正常显示未找到或空白也不代表没漏洞可能被过滤或处理了需要进一步测试。 在Pikachu的搜索型注入里输入通常会报错。这给了我们第一个强烈信号。输入永真条件尝试为了进一步确认我们尝试构造一个逻辑上永远为“真”的条件看看是否会影响结果。 假设后端SQL是SELECT ... FROM ... WHERE username LIKE %$input%我们输入 or 11 --这里解释一下闭合前面的单引号。or 11添加一个“或”条件11永远为真。如果这个条件被拼接到SQL中整个WHERE子句就会变成WHERE username LIKE %% or 11这意味着条件永远满足可能会返回所有数据。--或#这是SQL的单行注释符。--后面要跟一个空格在URL或表单提交时常被用来表示空格。它的作用是注释掉原SQL语句中我们输入后面的部分比如后面可能还有的单引号或其他代码避免再次破坏语法。提交 or 11 --后如果页面返回了所有用户的数据而不是只返回kobe或空白那就铁证如山注入点存在并且我们可以控制SQL逻辑。在Pikachu中你可能会发现输入 or 11 --后返回的结果和输入kobe时一样并没有返回所有数据。这是第一个关键点因为这是“搜索型”注入原语句中有LIKE %$input%。我们输入 or 11 --后完整的语句可能是SELECT ... FROM ... WHERE username LIKE % or 11 --%由于LIKE %%这个条件本身就会匹配所有内容百分号是通配符所以即使没有or 11这个查询也可能返回大量数据。但靶场为了简化可能只处理了第一条。不过or 11能执行且不报错已经证明了注入点的存在。3.2 关键步骤确定准确的闭合方式在普通字符型注入中闭合通常就是$input。但这里是“搜索型”提示我们可能有LIKE %$input%。所以闭合方式可能是%$input%。我们需要验证。我们尝试一个会引发错误的闭合方式来反推原语句结构。输入和 第二个是单引号百分号单引号 观察报错信息。更系统的方法是尝试闭合并注释输入 --如果页面正常可能无结果说明成功闭合了开头的引号--注释掉了后面的%和可能的其他代码。这暗示原结构可能是%$input%。尝试包含百分号闭合输入% --如果页面也正常那原结构是%$input%的可能性就更大了。因为我们用%闭合了前面的%再用--注释掉后面。在Pikachu靶场中经过测试你会发现输入kobe% --得到的搜索结果和输入kobe是一样的。这就基本确定了后端的SQL语句结构是WHERE ... LIKE %$input%。所以我们的注入payload攻击载荷需要以%来闭合前面的%而不是一个简单的。为什么这一步如此重要因为如果闭合方式判断错误你精心构造的UNION语句就无法正确嵌入到原SQL中会导致语法错误注入失败。很多新手在这里折戟就是因为没耐心做这个探测。4. 使用UNION联合查询获取数据确定了注入点存在和闭合方式%后我们就可以开始利用UNION SELECT来获取数据库信息了。UNION操作符用于合并两个或多个SELECT语句的结果集。前提是两个SELECT语句必须拥有相同数量的列且列的数据类型也要相似。我们的攻击思路是把原本查询数据如用户信息的语句拼接上一个我们自定义的、查询数据库元数据如库名、表名、字段名的语句。4.1 第一步判断查询列数这是使用UNION的前提。我们不知道原查询SELECT了多少列。我们需要通过ORDER BY子句来试探。ORDER BY n表示按第n列排序。如果n超过了实际列数数据库就会报错。 我们从ORDER BY 1开始尝试直到报错为止。构造Payloadkobe% order by 1 --(页面正常)kobe% order by 2 --(页面正常)kobe% order by 3 --(页面正常)kobe% order by 4 --(页面正常)kobe% order by 5 --(页面可能开始报错或显示异常)在Pikachu这个关卡中当你测试到order by 4时正常order by 5时页面出错或内容消失那么就说明原查询语句返回的列数是4列。4.2 第二步确定回显点我们知道有4列了但页面上的数据是从哪几列显示出来的呢我们需要找出在页面中“可见”的列也就是“回显点”。使用UNION SELECT 1,2,3,4来测试。为了让UNION前面的查询结果为空从而确保页面显示的是我们UNION后面的结果我们通常要构造一个必然不成立的条件。构造Payloadxxx% union select 1,2,3,4 --这里xxx是一个数据库中肯定不存在的用户名比如asdfghjkl这样原查询LIKE %asdfghjkl%结果为空页面就会显示我们union select的1,2,3,4提交后观察页面。原本显示用户名、邮箱的地方现在可能变成了数字2和3具体哪个位置显示哪个数字因靶场前端设计而异。这说明第2列和第3列的内容会被显示在页面上。1和4的位置可能不显示或用于其他用途。记住这两个数字比如2和3我们之后就要把想查询的信息放在这两个位置上。4.3 第三步获取数据库信息现在我们可以把2和3替换成我们想要查询的数据库函数了。1. 查询当前数据库名和用户Payloadxxx% union select 1, database(), user(), 4 --database(): 返回当前操作的数据库名称。user(): 返回当前执行查询的数据库用户。提交后在页面的回显点刚才显示2和3的位置你应该能看到数据库名如pikachu和用户名如rootlocalhost。2. 查询所有数据库名在MySQL中information_schema.schemata表存储了所有数据库的信息。 Payloadxxx% union select 1, group_concat(schema_name), 3, 4 from information_schema.schemata --group_concat(): 将多行结果合并成一个字符串用逗号分隔便于在一个回显点查看。information_schema.schemata: 系统表。schema_name: 该表中的列存放数据库名。执行后你会在一个回显点看到一串数据库名如information_schema,mysql,performance_schema,pikachu,test。我们的目标库pikachu就在其中。3. 查询pikachu数据库中的所有表名information_schema.tables表存储了所有表的信息。我们通过table_schema字段来筛选特定数据库。 Payloadxxx% union select 1, group_concat(table_name), 3, 4 from information_schema.tables where table_schemapikachu --执行后你会看到pikachu库下的所有表可能包括httpinfo,member,message,users,xssblind等等。我们需要找到存放用户信息的表看起来users和member比较可疑。4. 查询users表的所有字段名information_schema.columns表存储了所有列字段的信息。 Payloadxxx% union select 1, group_concat(column_name), 3, 4 from information_schema.columns where table_schemapikachu and table_nameusers --执行后你会得到users表的所有字段名例如id,username,password,level。4.4 第四步拖取最终数据——用户名和密码现在表名users和字段名username, password都知道了我们就可以直接查询敏感数据了。Payloadxxx% union select 1, username, password, 4 from users --提交后页面上就会清晰地显示所有用户的用户名和密码通常是经过MD5等哈希加密的字符串。至此一次完整的、基于UNION的搜索型字符注入攻击就完成了。你成功绕过了前端的搜索功能直接窃取了数据库核心表中的所有用户凭证。5. 深度复盘、防御与排查思考通关一个靶场关卡不是终点理解漏洞成因、掌握防御方法、建立排查思路才是真正提升安全能力的关键。5.1 漏洞根源为什么会有搜索型注入我们来回溯一下漏洞代码可能长什么样这是原理示意非Pikachu真实源码$username $_GET[name]; // 直接获取用户输入未过滤 $sql SELECT * FROM users WHERE username LIKE %$username%; $result mysqli_query($conn, $sql);问题一目了然未过滤输入直接将用户输入的$username拼接进SQL字符串。不当使用字符串拼接用单引号和百分号包裹变量构成了%$input%的结构。攻击者通过输入%提前闭合字符串后面的union select... --就被当作SQL命令执行了。核心教训永远不要信任用户输入。任何来自客户端浏览器、API请求的数据在进入SQL查询前都必须被视为潜在的威胁。5.2 如何防御从开发层面堵住漏洞防御SQL注入尤其是这种搜索型注入有几种成熟的方法按推荐顺序排列1. 使用参数化查询预编译语句这是最有效、最根本的防御手段。它让SQL语句的“结构”和“数据”分离。数据库先编译SQL语句的模板如SELECT * FROM users WHERE username LIKE ?然后再将用户输入的数据作为“参数”传入。此时即使用户输入包含、%、union等特殊字符数据库也会将其严格视为“数据”的一部分而不是“命令”的一部分。PHP (PDO)示例$stmt $pdo-prepare(SELECT * FROM users WHERE username LIKE ?); $searchTerm % . $username . %; // 在代码层面添加通配符 $stmt-execute([$searchTerm]);PHP (MySQLi)示例$stmt $conn-prepare(SELECT * FROM users WHERE username LIKE ?); $searchTerm % . $username . %; $stmt-bind_param(s, $searchTerm); $stmt-execute();注意通配符%是在代码中拼接好然后作为一个完整的参数传给SQL的而不是写在SQL模板里。2. 对输入进行严格的转义和过滤如果因为历史遗留问题无法使用参数化查询极不推荐新项目如此则必须进行转义。使用mysqli_real_escape_string()这个函数会在特殊字符如单引号前添加反斜杠使其失去特殊含义变成普通字符。但要注意它不能防御所有情况且必须与正确的字符集设置一起使用。$username mysqli_real_escape_string($conn, $_GET[name]); $sql SELECT * FROM users WHERE username LIKE %$username%;对于我们的搜索型注入如果用户输入% union select 1,2,3 --经过转义后会变成%\ union select 1,2,3 --单引号被转义无法闭合从而防御成功。3. 最小权限原则用于连接数据库的账号不应该拥有root或DBA权限。应该创建一个仅对特定数据库有必要权限如SELECT,UPDATE且仅限于业务所需的表的账号。这样即使发生注入攻击者能造成的破坏也有限比如无法执行DROP TABLE,SELECT * FROM mysql.user等危险操作。4. 白名单验证如果适用对于某些固定类别的搜索如按“状态”搜索状态只有“启用”、“禁用”两种可以使用白名单。只允许输入预定义的值其他一律拒绝。$allowed_status [active, inactive]; if (!in_array($input_status, $allowed_status)) { // 处理非法输入如返回错误或默认值 $input_status active; }5.3 渗透测试排查清单当你面对一个搜索框作为一名安全测试人员或开发者自查面对一个搜索功能可以按以下顺序排查基础探测输入、、\等特殊字符观察是否有SQL错误信息直接回显到页面这是最明显的漏洞迹象。逻辑测试输入 or 11、 or 11 --等永真条件看是否返回异常多的数据或所有数据。闭合测试尝试 --、 #、) --、% --等多种闭合方式配合or 11观察页面变化。盲注测试如果页面没有明显回显不显示数据只显示“找到/未找到”则需要尝试基于布尔或时间的盲注。例如输入 and sleep(5) --观察页面响应是否延迟5秒。工具辅助在授权范围内可以使用sqlmap等自动化工具进行深度检测。但手动理解原理永远是基础。代码审计如果可能直接审查后端处理搜索的代码检查是否使用了字符串拼接是否使用了参数化查询或正确的转义函数。5.4 从靶场到实战的思维延伸Pikachu靶场环境是理想的、无干扰的。实战中会更复杂WAFWeb应用防火墙可能会拦截union、select、information_schema等关键词。需要尝试大小写混淆、编码、注释符分割等绕过技巧。错误信息屏蔽生产环境通常会关闭数据库错误回显使注入不再直接报错需要转向盲注。输入过滤可能过滤了空格用/**/或代替、union用ununionion双写绕过、select等。搜索逻辑复杂可能涉及多表联查、多个搜索条件闭合方式可能更复杂如%$input% and status1需要更耐心的探测。给开发者的最终建议在新项目开始时就强制使用参数化查询PDO/MySQLi预处理。对老项目进行安全审计时将搜索、登录、订单查询等所有涉及用户输入拼接SQL的地方作为最高优先级进行修复。安全不是功能选项而是开发的基石。一次成功的UNION注入足以拖走整张用户表其后果远比一个功能BUG严重得多。