BWAPP靶场SQL注入通关指南:原理、手工注入与绕过实战
2026/9/16 19:31:16 网站建设 项目流程

很多刚开始接触Web安全的朋友,第一个真正产生“我在挖洞”感觉的靶场,不是DVWA就是BWAPP。我自己是先刷完DVWA的SQL注入关卡,再转头打开BWAPP时,明显感觉到这个靶场更贴近真实业务——它有登录框、有搜索框、有User-Agent头,甚至还有XML数据传输,注入点藏在各种你想不到的地方。今天这篇就专门聊BWAPP里的SQL注入通关,从原理到实操,把每一关怎么过、报错怎么排、绕过怎么写,全部分享一遍。

如果你正在刷靶场、准备面试,或者只是想搞懂“SQL注入到底是怎么发生的”,这篇文章适合你完整读一遍。我会按我自己的通关顺序来讲:先讲BWAPP里SQL注入的关卡分布和难易梯度,再逐个拆解GET注入、POST登录绕过、搜索型注入、盲注和过滤绕过,最后把我在实战里踩过的坑整理成一份排查清单。内容尽量写得像我在旁边手把手带你打,而不是教科书复读。

1. BWAPP是什么,为什么拿它练SQL注入

1.1 靶场定位与适合人群

BWAPP全称是Buggy Web Application,直译过来就是“故意写满Bug的Web应用”。它的定位是给安全测试人员、开发人员和学生提供一个合法的、可重复练习的攻击环境,覆盖OWASP Top 10里的大部分漏洞类型,SQL注入只是其中一个模块。和DVWA相比,BWAPP的题目更密集,一个问题会拆出多个场景,比如同一个SQL注入,会分GET型、POST型、搜索型、盲注、XML型,还会故意加各种过滤规则。这种“同一种漏洞换着花样考”的设计,特别适合把注入基础打扎实。

从难度上看,BWAPP每一关都可以设置安全级别,我主要用的是low和medium。low级别基本是纯裸奔的拼接SQL,适合理解注入原理;medium级别会加一些简单的过滤或转义,适合练习绕过技巧。这个梯度安排和DVWA很像,但BWAPP的关卡数量更多,题型更杂,刷完一遍再去做CTF里的SQL注入题,比如ctfshow的web入门,明显会觉得思路开阔了很多。

1.2 通关前需要准备的环境

我这边用的是PHP集成环境加BWAPP源码包,几分钟就能搭好。如果你之前装过DVWA,那路径很相似:把下载好的BWAPP解压到Web根目录,访问install.php初始化数据库,它会自动建好库和表,还会提示默认账号密码。整个安装过程不涉及任何复杂的配置,比较省心。

另外建议准备两个工具:一个是Burp Suite,用来抓包改包,很多注入点在请求头里(比如User-Agent、Referer),光靠浏览器地址栏是不够的;另一个是浏览器的开发者工具,看请求参数、看页面源码,用来观察注入点的回显位置。数据库端我喜欢用MySQL命令行配合查询,方便直接验证SQL语句的正确性。后面讲到的所有注入语句,我都默认是MySQL 5.x和MariaDB环境,这也是BWAPP默认的数据库。

注意:所有注入测试请务必在本地靶场或获得明确授权的环境里操作,别拿这些语句去测别人的网站。安全测试的基础是合规,能力再强也要守住这条线。

2. 先把原理讲透:SQL注入到底是怎么发生的

2.1 注入的根因:查询语句被拼进去了

SQL注入说穿了就一句话——程序把用户输入的内容,没有经过任何处理,直接拼接进了SQL查询语句。比如BWAPP里的SQL Injection(GET)这关,后端代码大概是这样的:

$sql = "SELECT * FROM movies WHERE id = " . $_GET['id'];

你访问id=1的时候,查询是正常的:

SELECT * FROM movies WHERE id = 1

但如果id的值是1' or '1'='1,拼接出来的语句就变成了:

SELECT * FROM movies WHERE id = 1' or '1'='1

此时前面的id=1'虽然会报错,但后面的or '1'='1是永真条件,整个查询就会把movies表里所有记录都返回出来。这就是万能密码和万能参数的核心原理。

我在给刚入门的朋友讲的时候,经常用“往快递单里塞私货”来打比方:系统本来要打印一张收件人信息,结果你在收件人一栏里填了“张三 AND 给所有人发短信”,快递系统一看这串指令,就真的照做了。SQL注入不是SQL本身的漏洞,是整个“把外部输入当成代码执行”的流程出了问题。

2.2 按参数位置与返回结果给注入分类

BWAPP里的SQL注入关卡很多,如果不分类,很容易打着打着就乱了。我习惯按两个维度分类:注入点在哪,以及服务器回不回应你的查询结果。

按注入点分:

  • GET型:参数写在URL里,比如?id=1,适合用浏览器直接改。
  • POST型:参数在请求体里,比如登录表单的username、password,需要抓包或者用开发者工具修改。
  • 搜索型:参数会被拼进LIKE语句中,比如WHERE title LIKE '%$search%',闭合引号和通配符要格外小心。
  • Header型:注入点藏在User-Agent、Referer等HTTP头里,无法直接看到参数,必须靠Burp Suite抓包修改。
  • 其他特殊类型:比如XML注入、JSON注入,参数以结构化数据格式传递,BWAPP也有对应关卡。

按返回结果分:

  • 回显注入:查询结果会直接显示在页面上,你可以在页面里看到数据字段的位置。这种最轻松,用union select就可以控制显示内容。
  • 盲注:页面不会直接展示查询结果,只能靠页面返回“正常”还是“异常”,或者响应时间的快慢来判断。BWAPP里的Blind关卡就是典型的例子,需要花更多耐心逐字符猜数据。

理解了这两种分类,后面的实操逻辑就很清晰了:先探测注入点属于哪一类,再选择对应的攻击手法。BWAPP的设计恰好把这些类型都覆盖了一遍,所以我强烈建议按我下面的顺序从第一关开始打,不要跳关。

3. BWAPP手工注入过关实录

3.1 GET注入关卡:从探测到拿数据完整走一遍

最经典的起点是页面左侧菜单里的SQL Injection(GET/Search)。我先演示GET型那一关,因为它的回显最直观,适合建立完整的注入流程。

第一步,探测有没有注入点。访问类似下面的地址:

http://127.0.0.1/bwapp/sqli_1.php?title=Iron&action=search

页面正常显示搜索结果。接着在title参数后面加一个单引号:

http://127.0.0.1/bwapp/sqli_1.php?title=Iron'&action=search

如果页面出现数据库报错,比如You have an error in your SQL syntax,说明单引号被拼进了SQL语句,这里十有八九有注入。如果页面没有报错,也不代表一定安全,可能是开发者做了转义,也可能是盲注场景,后面再讲。

第二步,确定字段数量。用order by逐个尝试:

http://127.0.0.1/bwapp/sqli_1.php?title=Iron' order by 1-- -&action=search

这里的-- -是MySQL的注释符号,用来把后面可能残留的SQL语句全部注释掉。逐个尝试order by 1、order by 2……直到页面开始报错。比如说order by 7正常、order by 8报错,那就说明原查询有7列。这一步非常关键,因为后面union select的字段数量必须和原查询一致,否则怎么试都出不来。

第三步,找回显位。把id参数或title参数改成不存在的值,再用union select占位:

http://127.0.0.1/bwapp/sqli_1.php?title=' union select 1,2,3,4,5,6,7-- -&action=search

页面上会显示数字2、3、5之类的回显位,这些位置就是我们可以控制输出的地方,后面拿数据库名、表名、字段名都往这里放。

第四步,拿当前数据库名和版本:

http://127.0.0.1/bwapp/sqli_1.php?title=' union select 1,database(),version(),4,5,6,7-- -&action=search

看到页面上显示数据库名和版本号之后,注入点就是你的“内部人员身份”了。后续用information_schema继续取表名和字段名,我放在第4.3节专门讲。

实操心得:GET注入最重要的一个习惯是“每次修改URL之前,先想清楚当前语句拼出来是什么样”。我见过太多新手在union select这里卡半天,原因是忘了注释符后面要加空格,或者忘记把前面的参数改成不存在的值,导致页面只显示第一行原始数据,看不到自己的注入结果。

3.2 POST登录绕过与万能密码:不用密码也能进后台

BWAPP的Login页面是练习POST注入的好地方。这里需要打开Burp Suite,拦截登录请求,看到请求体里的username和password参数。

用一个经典万能密码试试:

username: admin' or '1'='1' -- - password: 随便填

后端的SQL如果长这样:

$sql = "SELECT * FROM users WHERE login='" . $_POST['username'] . "' AND password='" . $_POST['password'] . "'";

拼接后就是:

SELECT * FROM users WHERE login='admin' or '1'='1' -- -' AND password='xxx'

-- -把后面校验密码的部分全部注释掉了,前面的条件是login='admin' or '1'='1',恒为真,系统就认为你已经是admin用户了。利用这个思路,很多登录框都能用一个简单的恒真条件直接绕过。这也解释了为什么程序里绝不能明文拼接SQL,哪怕是一个简单的登录查询,都可能被人用两个引号和一句注释打得妈都不认识。

如果这关在medium安全级别下,单引号会被转义成\',万能密码直接失效。这时候可以观察报文格式,部分情况下还能用宽字节注入绕过,但更常见的思路是如果登录框不好打,就转攻同一个页面的其他参数,或者换一个注入点。BWAPP这种多关卡设计本身就告诉我们:真实的Web应用不可能只有一个入口,上一个入口堵死了,总会有下一个入口没堵。

3.3 搜索型注入与LIKE通配符的坑

搜索框是另一个高频注入点。BWAPP的SQL Injection(Search)这关,后端语句大概是:

$sql = "SELECT * FROM movies WHERE title LIKE '%" . $_GET['title'] . "%'";

普通人看到的是搜索框,但SQL看到的是LIKE '%xxx%'。这里注入最大的坑是:你要先把自己从两个%中“闭合”出来,才能写完整的注入语句。

推荐的payload是:

%' union select 1,2,3,4,5,6,7-- -

别看这个payload长得奇怪,它其实做了三件事:前导的%匹配了LIKE语句中后面的%,中间的单引号闭合了字符串的字面量,然后才开始写union语句,最后的注释符把末尾残留的%'注释掉。任何一个部分少了,语句都闭合不上。

我做这道题时最开始直接套用GET注入的payload,结果页面死活不显示数据,后来才明白是忘记考虑百分号。这个坑在真实漏洞里也很常见,凡是输入框被拼进LIKE查询的,注入点闭合方式都和普通where条件不一样。所以看到搜索框,不要下意识用'开头的payload,先想想前后有没有通配符。

3.4 盲注场景:没有回显怎么拿数据

BWAPP菜单里有一关叫Blind SQL Injection,页面只知道查询是否成功,不会把数据库内容显示出来。这关的逻辑是:提交一个ID,页面会告诉你“存在这个用户”或者“不存在”。这就构成了布尔盲注的条件。

布尔盲注的核心是:用条件判断逐步猜测数据,一次只猜一个字符。比如判断当前数据库名的第一个字符是不是'a':

http://127.0.0.1/bwapp/blind_sqli_1.php?id=1 AND SUBSTRING(database(),1,1)='a'

如果页面返回“存在”,说明当前库名的第一个字符就是a;如果返回“不存在”,就换下一个字符。整个过程比较慢,但原理非常清晰。

如果布尔盲注页面根本没有差异,可以考虑时间盲注。比如判断条件成立时让数据库睡眠3秒:

http://127.0.0.1/bwapp/blind_sqli_1.php?id=1 AND IF(SUBSTRING(database(),1,1)='b',SLEEP(3),0)

用计时器观察页面响应时间,3秒左右就是条件成立,否则就是条件不成立。时间盲注比布尔盲注更稳,但也更慢,我通常在布尔页面没有区分度时才切换到时间盲注。在真实环境的排查中,时间盲注还能用于绕过一些逻辑上“无回显”的查询场景,比如INSERT、UPDATE语句里如果带着SLEEP函数,可以根据耗时确认注入点是否有效。

想提高手工盲注效率,我建议学一下二分法:不要从'a'开始一个个猜,而是先判断字符的ASCII码是大还是小,比如ASCII(SUBSTRING(database(),1,1))>100,然后逐步缩小范围,每个字符从最多26次尝试降到8次左右。手工刷题练的就是这种判断思路,等熟练之后再用工具脚本批量跑,你会发现自己对盲注的理解完全不一样。

4. 进阶关卡:过滤、编码与绕过技巧

4.1 字符过滤后的手工注入测试

BWAPP的medium级别会模拟开发者的简单过滤,比如把常见的关键字替换成空字符串,或者对单引号做转义。这一节对应很多人查过的“sql过滤字符后手工注入漏洞测试”,也希望讲清楚绕过过滤器时到底该想什么。

先做一个简单的测试场景:后端把selectunionand这些词直接替换成空字符串。这个时候,一个直接的union select是打不进去的,因为语句拼出来之后关键字已经没了。

常见的绕过思路是双写,比如:

ununionion selselectect

如果后端只做了一次空替换,union中的union被删掉之后,剩下的还是union,最终拼接出来的语句反而变成了合法的union select。这种手法有点“以子之矛攻子之盾”的意思,在CTF题目里特别常见。

另外还可以用注释符代替空格。很多过滤规则会把空格过滤掉,但SQL解析器支持用/**/代替空格,比如:

1'/**/union/**/select/**/1,2,3-- -

如果unionselect没被过滤,把空格换成注释符号往往能绕过只针对空格的过滤。除此之外,MySQL还支持反引号包裹字段名、大小写混写等方式。具体的过滤规则决定具体的绕过方式,没有一招通吃的万能payload,所以关键是先摸清对方到底过滤了什么。

4.2 常见过滤规则与绕过手段整理

我把BWAPP和实际测试中常见的过滤规则整理成一个速查表,方便你刷题时对照排查:

过滤内容表现常用绕过思路
过滤空格空格被替换为空使用/**/%09%0a等空白字符
过滤关键字select、union等被替换为空双写关键字,如ununionionselselectect
过滤单引号单引号被转义或删除尝试宽字节注入、改用整型参数、用十六进制编码表示字符串
过滤等号=被替换为空like替代=,比如where name like 'admin'
过滤注释符--#被过滤尝试用/*结尾、利用闭合引号或者%00截断
过滤逗号,被替换为空join替代部分union场景,或者用from重排

表格里最后一项容易被忽略:有些过滤会删除逗号,导致substr(database(),1,1)这种函数没法用。此时可以用substr(database() from 1 for 1)这种写法替代,因为MySQL的substr支持FROM ... FOR ...语法,这个语法不需要逗号。类似的知识点需要平时多积累,到现场才不会慌。

需要提醒的是,绕过过滤器时一定要“先验证再继续”。每绕过一步,就用一个简单的布尔条件测试,比如先确认and 1=1能正常显示,再确认and 1=2能正常报错或隐藏,最后才开始跑数据。如果跳过验证直接上复杂payload,很容易在语句嵌套很深时迷失方向。

4.3 从SQL注入到获取所有数据库名的完整语句

很多人搜过“mysql数据库如何通过sql注入获取所有的数据库名”,这里我单独写一下。拿到了union回显位之后,获取所有数据库名的语句非常固定:

union select 1,group_concat(schema_name),3,4,5,6,7 from information_schema.schemata-- -

information_schema是MySQL自带的“数据库的数据库”,存放所有元数据。schemata表里的schema_name字段,就是所有数据库的名字。用group_concat把多行结果拼成一行,主要是为了方便在有限的页面回显位置里一次性看完。

把这条语句放到前面GET注入关卡的title参数里,实际URL就是:

http://127.0.0.1/bwapp/sqli_1.php?title=' union select 1,group_concat(schema_name),3,4,5,6,7 from information_schema.schemata-- -&action=search

页面上会显示一串库名,比如bwapp、information_schema、mysql、performance_schema。接下来如果想知道某个库里有哪几张表,就把范围缩小到目标库,语句变成:

union select 1,group_concat(table_name),3,4,5,6,7 from information_schema.tables where table_schema='bwapp'-- -

想查某张表里的字段,就换成columns表:

union select 1,group_concat(column_name),3,4,5,6,7 from information_schema.columns where table_schema='bwapp' and table_name='users'-- -

最后再回填到原始的查询中,就能把users表的账号密码字段全部读出来。整个过程就是一条链:库名 -> 表名 -> 字段名 -> 数据。熟练之后你会发现,只要回显位控制得住,所有元数据信息都能通过information_schema查出来。这也是为什么我把“获取所有数据库名”单独拎出来讲,因为它是整个复制数据库流程里的第一步,也是最重要的一步。

5. 通关过程中踩过的坑:常见问题与排查技巧

5.1 经典报错与解决思路

刷BWAPP时我遇到过不少报错,有些问题第一次看完全没有头绪,后来复盘才发现答案特别简单。这里挑几个典型的列出来。

先说“order by 一直不报错”。这种情况通常不是语句写错了,而是数据太多,每列都有值,所以报错被淹没了。建议先把查询条件收窄,比如title=''让结果集为空,再用order by测试,报错会更明显。

再说“union select回显不出来”。排查顺序一般是:字段数对不对、注释符后面有没有空格、前面的参数值是不是不存在的随机字符串。我见过最离谱的一次,是因为走了代理,浏览器自动把单引号转成了%27,后端解出来又正常了,但Burp里看起来却带编码,导致我一直以为语句被截断了。后来统一在Burp的Repeater里发包,才彻底避开浏览器的干扰。

还有“页面中文乱码”的问题。这本身不影响注入判断,但看着很干扰思路。我通常在浏览器端把编码切到UTF-8,或者用Burp的Response渲染功能直接看渲染后的页面。掌握这些基本功,实际刷题时会舒服很多。

5.2 手工注入与工具结合的取舍

很多新手刷题到一半就想着用sqlmap一把梭。我的看法是:工具能用,但不能在没理解原理的时候用。BWAPP这种靶场价值就在于训练手工思路,如果每一关都直接丢给sqlmap,你对SQL语句拼接、闭合方式、回显位置的敏感度很难建立起来。等手工刷完一遍,再回头用sqlmap做验证或者处理一些重复性高的盲注,会非常顺。

我在实际项目中通常是“手工定思路、工具提效率”:先用浏览器和Burp确认注入点和闭合方式,再用sqlmap跑数据,工具跑出来之后还要回到手工语句里验证一遍,确保数据真实可靠。这种配合方式的前提是工具操作者知道自己在干什么,而不是只会运行命令。

5.3 SQL注入的防御基线

练完了攻击,最后说两句防御。SQL注入的根源是“将外部输入拼接进SQL语句”,所以最有效的防御就是切断拼接这条路。现代开发中推荐的做法是用预编译参数化查询,也就是先让数据库“知道”这是一条带参数的模板语句,再传入用户输入值,数据库会把输入当作纯数据,而不是SQL代码的一部分。

以PHP的PDO为例:

$stmt = $pdo->prepare("SELECT * FROM users WHERE login = ? AND password = ?"); $stmt->execute([$username, $password]);

只要用了这种方式,就算传入admin' or '1'='1,它也只是被当成一个奇怪的字符串去匹配,无法改变查询结构。除此之外,对输入做白名单校验、数据库账号用最小权限、Web应用防火墙做兜底,都是纵深防御的一部分。不过我一直强调:过滤和校验是辅助,参数化查询才是根本。想靠过滤黑名单防注入的人,迟早会在某种编码或双写手法面前翻车。

6. 结尾还想再多说几句

BWAPP的SQL注入关卡我刷了好几遍,每次重新打都有新发现。第一次是照着别人的payload抄,第二次是自己推语句闭合,第三次开始尝试在medium级别下手工绕过,到后来再去打Pikachu和ctfshow的SQL注入题,明显感觉很多套路是相通的。所谓“通关”,并不是把所有关卡跑一遍就完事,而是真正理解每一关背后那个“为什么”。你在BWAPP里看到的' or '1'='1,到真实漏洞里可能换成时间盲注,但底层都是同一个拼接缺陷,换的只是闭合姿势。

最后分享一个个人习惯:每次打完一个注入点,我会把完整的URL和SQL语句抄在本地笔记里,旁边再写一行“为什么这个payload有效”的注释。时间久了,这本笔记就成了我自己的SQL注入速查手册。如果你也想在Web安全这条路上走远一点,建议也养成这种记录习惯,这比存一堆别人的payload库有用得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询