零基础玩转bWAPP靶场(十五):SQL 注入(POST/搜索型)
摘要本文是 bWAPP 靶场系列的第十五篇聚焦于 SQL Injection (POST/Search)POST 型搜索框 SQL 注入漏洞。文章从零基础出发首先讲清楚 POST 型注入和 GET 型注入的核心区别——一个在 URL 里看得见一个在请求体里看不见然后按照 Low、Medium、High 三个安全级别逐一分析源码、演示通关。一、前言上一篇文章我们讲了SQL Injection (GET/Select)也就是下拉菜单选择电影的那个关卡。今天我们要看的这个关卡页面长得和第十三篇GET/Search几乎一模一样——都是一个搜索框输入电影名点搜索显示结果。那区别在哪区别不在页面上在请求方式上。第十三篇用的是GET请求——你搜的东西会出现在 URL 里比如?titleIronMan。今天这篇用的是POST请求——你搜的东西不会出现在 URL 里而是藏在 HTTP 请求的 body 里。就这点区别。但别小看这点区别它决定了你的攻击方式完全不同——GET 型你直接在地址栏改参数就行POST 型你得用Burp Suite抓包改或者用浏览器开发者工具。说白了GET 型是“明着来”POST 型是“暗着来”。二、GET 型 vs POST 型到底有什么区别先帮大家把这两个概念彻底搞清楚。GET 请求参数跟在 URL 后面比如http://xxx.com/search.php?titleavengers浏览器地址栏里看得见你可以直接在地址栏里改参数然后回车适合搜索、翻页这种“不改变数据”的操作POST 请求参数放在 HTTP 请求的 body 里浏览器地址栏里看不见你得用 Burp Suite 抓包或者 F12 打开开发者工具才能看到和修改适合登录、提交表单这种“会改变数据”的操作对我们做注入来说GET 型注入很简单——直接在浏览器地址栏改参数就行。POST 型注入稍微麻烦一点——你得拦截请求在请求体里修改参数然后再放行。具体怎么操作等会儿通关的时候手把手教。三、源码分析老规矩先看源码再动手。if(isset($_POST[title])) { $title $_POST[title]; $sql SELECT * FROM movies WHERE title LIKE % . sqli($title) . %; $recordset mysql_query($sql, $link); // ... 显示结果 ... }这段代码做了几件事检查有没有收到 POST 请求里的title参数如果有把$title传给sqli()函数处理拼接到 SQL 语句里SELECT * FROM movies WHERE title LIKE %...%执行查询显示结果关键点sqli()函数怎么处理用户输入取决于当前的安全级别。再看看sqli()函数的定义function sqli($data) { switch($_COOKIE[security_level]) { case 0 : // Low $data no_check($data); break; case 1 : // Medium $data sqli_check_1($data); // addslashes() break; case 2 : // High $data sqli_check_2($data); // mysql_real_escape_string() break; } return $data; }级别用的函数做了什么Lowno_check()什么都不做原样返回Mediumaddslashes()在引号、反斜杠前加\Highmysql_real_escape_string()MySQL 专用转义函数和第十三篇GET/Search的代码几乎一模一样唯一的区别就是第十三篇从$_GET[title]拿数据这篇从$_POST[title]拿数据就这么简单。四、Low 安全级别4.1 准备工作抓包工具POST 型注入不能直接在地址栏改参数你得拦截请求。方法一Burp Suite推荐打开 Burp开启 Proxy 拦截Intercept 设为 On在浏览器搜索框里输入内容点 SearchBurp 会拦截到这个 POST 请求在请求体里修改title参数的值点击 Forward 放行方法二浏览器开发者工具简单场景可用F12 打开开发者工具 → Network 标签在搜索框输入内容点 Search找到对应的 POST 请求右键 → Edit and Resend修改请求体里的title参数发送这篇教程我们用 Burp Suite 演示。4.2 正常使用功能先在搜索框里随便输个电影名比如Iron Man点 Search。页面正常显示搜索结果没什么特别的。4.3 注入经典 payload——验证漏洞打开 Burp Suite开启拦截。在搜索框里输入a点 Search。Burp 拦截到请求后把请求体里的titlea改成title OR 11然后 Forward。发生了什么后端执行的 SQL 变成了SELECT * FROM movies WHERE title LIKE % OR 11%11永远为真所以返回了所有电影。4.4 使用 UNION 联合查询窃取数据先用ORDER BY测试列数title ORDER BY 7 --没报错。title ORDER BY 8 --报错了。说明返回 7 列。然后获取数据库信息title UNION SELECT 1,database(),3,4,5,6,7 --页面会显示当前数据库名。获取表名title UNION SELECT 1,table_name,3,4,5,6,7 FROM information_schema.tables WHERE table_schemadatabase() --获取字段名title UNION SELECT 1,column_name,3,4,5,6,7 FROM information_schema.columns WHERE table_schemadatabase() and table_nameusers --获取用户数据title UNION SELECT 1,login,password,4,5,6,7 FROM users --4.5 源码分析Low 级别调用了no_check()function no_check($data) { return $data; }啥也没干直接返回。所以你的 payload 原样拼进了 SQL 里注入成功。五、Medium 安全级别5.1 尝试注入把安全级别切到 Medium重复上面的操作。Burp 拦截请求把titlea改成title OR 11Forward。结果注入失败页面显示“No movies were found!”。5.2 为什么失败了Medium 级别调用了sqli_check_1()也就是addslashes()function sqli_check_1($data) { return addslashes($data); }addslashes()会在单引号前面加反斜杠。你的 OR 11变成了\ OR \1\\1。拼到 SQL 里之后引号被转义了不再起字符串边界的作用所以注入失败。但是——addslashes()不是万能的。它只转义引号、反斜杠和 NULL 字节。如果注入点本身不用引号比如数字型注入它就完全没用。5.3 Medium 级别的评价在这个关卡里addslashes()确实挡住了最简单的注入。但官方文档明确说了不要用 addslashes() 来做安全防护。它不是专门为 SQL 设计的不同数据库的转义规则不一样而且存在绕过方法比如宽字节注入。六、High 安全级别6.1 尝试注入切到 High重复操作。Burp 拦截注入 OR 11。结果依然失败。6.2 为什么High 级别调用了sqli_check_2()也就是mysql_real_escape_string()function sqli_check_2($data) { return mysql_real_escape_string($data); }这是 PHP 中专为 MySQL 设计的转义函数它会考虑数据库的字符集比addslashes()更安全。在这个关卡里它有效地防御了注入。6.3 但是——这仍然不是最佳方案mysql_real_escape_string()虽然比addslashes()好但仍然不是最安全的做法。最安全的方法是参数化查询Prepared Statements——把 SQL 语句的结构和用户输入的数据分开数据库知道哪部分是代码、哪部分是数据从根本上杜绝了注入的可能。简单来说转义函数是“防守”——试图把危险字符堵住参数化查询是“隔离”——直接把用户输入隔离到数据区永远进不了代码区哪个更安全显然是后者。七、三种安全级别对比级别用的函数能不能防住这个关卡值不值得信赖Lowno_check()不能完全不可信Mediumaddslashes()能这个场景不推荐有绕过方法Highmysql_real_escape_string()能这个场景还行但不如参数化查询八、现实世界中SQL 注入还在发生吗很多人觉得 SQL 注入是“老漏洞”了现在应该没了吧事实是依然大量存在。2024 年用友 NC Cloud被曝出 SQL 注入漏洞攻击者可以通过未授权访问注入恶意 SQL 语句从而控制服务器。同样是 2024 年SAP Global Label Management也被发现存在 SQL 注入漏洞攻击者可利用该漏洞窃取数据库敏感数据。2025 年osTicket的搜索功能被曝出 SQL 注入漏洞允许已认证的攻击者通过keywords和topic_id参数执行任意 SQL 命令。还有WordPress 的 Team 插件CVE-2025-14124未认证的用户可以通过 AJAX 接口的search参数进行 SQL 注入。甚至Google Cloud 的 Security Command Center在 2026 年 7 月也被曝出了 SQL 注入漏洞。看到了吗从企业级 ERP用友、SAP到开源工单系统osTicket从 WordPress 插件到 Google Cloud——SQL 注入从来没有消失过。它只是从“新漏洞”变成了“老漏洞”而“老漏洞”最大的问题就是大家觉得它已经不存在了放松了警惕。九、防御到底该怎么防说了这么多到底该怎么防 SQL 注入第一参数化查询Prepared Statements是最佳实践。不管你用 PHP、Java、Python 还是 Go不管你是用 MySQL、PostgreSQL 还是 SQL Server参数化查询是通用的、最有效的防御手段。// 危险的做法千万不要这样写 $sql SELECT * FROM movies WHERE title LIKE % . $_POST[title] . %; // 安全的做法参数化查询 $stmt $link-prepare(SELECT * FROM movies WHERE title LIKE CONCAT(%, ?, %)); $stmt-bind_param(s, $_POST[title]); $stmt-execute();第二输入验证作为辅助。对用户输入做格式校验比如邮箱就检查是不是邮箱格式数字就检查是不是真的是数字。但不要把输入验证当作唯一的防线——攻击者总能想出你没想到的绕过方法。第三最小权限原则。数据库连接账号不要用 root只给最少的权限——只需要 SELECT 就别给 INSERT只需要查这张表就别让它碰别的表。就算注入了也偷不走多少东西。十、总结本文分析了bWAPP中SQL Injection (POST/Search)漏洞该漏洞与GET方式的SQL注入本质一致仅请求方式存在区别GET注入可直接修改浏览器URL参数POST注入则需要借助抓包工具修改请求数据包。靶场三个安全等级防护效果各有差异Low等级未设置任何输入过滤可直接实施SQL注入攻击Medium等级采用addslashes()进行特殊字符转义在当前场景下能够起到防护作用但该函数存在安全局限不建议作为可靠防御手段High等级使用mysql_real_escape_string()防护能力优于addslashes()但依旧不是最优解决方案。本次实验得出核心结论切勿直接将用户可控输入拼接至SQL语句中无论是addslashes()还是mysql_real_escape_string()这类转义函数都存在被绕过的风险防御SQL注入最根本、最可靠的方式是采用预编译参数化查询。从真实安全事件来看SQL注入并非过时的历史漏洞2024至2026年间用友、SAP、osTicket、谷歌云等众多厂商的产品仍持续爆出相关高危漏洞长期具备极高的安全威胁。记住一句话用户输入是数据不是代码。永远不要把数据当代码用。重要声明本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。如果这篇文章帮你解决了实操上的困惑别忘记点击点赞、分享也可以留言告诉我你遇到的其它问题我会尽快回复。你的关注是我坚持原创和细节共享的力量来源谢谢大家。