DVWA靶场实战:从SQL注入原理到手工注入与防御全解析
2026/9/17 5:34:19 网站建设 项目流程

DVWA这名字你可能听过,也可能刚在热搜词里刷到。DVWA(Damn Vulnerable Web Application)是我见过最适合新手练SQL注入的靶场,没有之一。它把漏洞按危险等级分成Low、Medium、High、Impossible四档,正好对应“代码写得随意→有点防护→防得不错→彻底安全”的进化过程。这篇文章就把DVWA里的SQL Injection模块完整走一遍:从搭建环境开始,到手工注入拿库名、表名、列名、账号密码,再看到Medium、High级别怎么绕过防护,最后看Impossible级别代码为什么注入不了。内容适合刚接触Web安全、想找个靶场练手的同学;如果你已经对SQL注入很熟,也可以对照看自己有没有漏掉关键细节。

1. 靶场准备:把DVWA正确跑起来

1.1 为什么新手练SQL注入首选DVWA

市面上的漏洞靶场不少,有Sqli-Labs、Pikachu、upload-labs、Vulhub这些,但DVWA依然是很多安全培训的第一站。原因很直接:它把同一个漏洞做成四个等级,你能直观看到同一个注入点在不同安全措施下是什么表现。这种“对比学习”的体验其他靶场很难替代。

更重要的是DVWA的代码量不大,每关源码都是完整可读的PHP文件。很多人在漏洞页面里试了半天不知道原理,打开源码一眼就明白了。练DVWA不只是练“能不能打进去”,更是练“能不能看懂代码为什么错”。SQL注入这个模块尤其适合这么学,因为SQL语句的拼接、过滤、预处理在源码里看得一清二楚。

1.2 搭建方式怎么选:Docker、集成环境、源码手动部署

搭DVWA常见有三种方式,我直接给你一个选型建议。

方式上手难度坑点适合人群
Docker镜像最低需要装Docker,镜像可能较老想最快跑起来的人
集成环境(PHPStudy等)PHP版本兼容、MySQL账号密码要配Windows用户、想本地改源码调试的人
手动部署LAMP/LNMP环境问题多,但最能锻炼排查能力熟悉Linux的人,或想顺便练环境搭建的人

我自己最早是用集成环境装的,后来图省事改成了Docker。如果你只是想过SQL注入这个模块,直接Docker拉镜像最稳,后续重启、还原环境都方便。

1.3 初始化三步:配置文件、数据库、重置数据

不管用哪种方式装,DVWA都有一个绕不开的初始化动作。以源码部署为例,下载完解压到Web根目录后,进config目录,把config.inc.php.dist复制一份改成config.inc.php,然后打开改数据库账号密码:

$_DVWA[ 'db_server' ] = '127.0.0.1'; $_DVWA[ 'db_database' ] = 'dvwa'; $_DVWA[ 'db_user' ] = 'root'; $_DVWA[ 'db_password' ] = '你自己的MySQL密码';

这里有个很蠢但常见的坑:很多人改完配置文件发现不生效,一看是文件权限不对,或者文件名还带.dist后缀没有去掉。改完配置后,浏览器访问http://你的地址/DVWA/setup.php,点一下Create / Reset Database,看到提示成功就说明数据库初始化好了。

Docker方式更简单,启动后直接访问映射出来的端口,按页面提示点一次重置即可。默认登录账号是admin,密码是password,登录后点左边DVWA Security,把难度切到Low,然后进SQL Injection模块,就可以开打了。

1.4 启动失败的常见原因与排查顺序

“靶场启动失败”这个热搜词说明大家都卡在环境上过。我见过的启动失败,绝大多数不是DVWA本身的问题,而是环境冲突。

常见情况按排查顺序排一下:先看端口有没有被占,80端口经常被Nginx、Apache、其他集成环境的组件抢掉;再看MySQL有没有起来,很多集成环境默认不启动MySQL,DVWA连不上数据库就会白屏;最后看PHP版本和扩展,老版本DVWA在PHP 7以上容易报一堆警告,Docker镜像反而不会有这个问题。

我的建议是:环境折腾超过半小时就直接换Docker,不要在配环境上耗太多时间。靶场是拿来练漏洞的,不是拿来练运维的。

2. SQL注入原理:先搞懂漏洞为什么存在,再动手打

2.1 漏洞本质:SQL语句被“拼接”了

SQL注入能发生,根源只有一个:程序把用户输入直接拼进了SQL语句。看DVWA Low级别的核心代码就一行:

$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";

这里的$id就是你在输入框里填的内容。如果填的是正常数字1,SQL就是:

SELECT first_name, last_name FROM users WHERE user_id = '1';

但如果你填的是1' UNION SELECT user, password FROM users#,拼接之后SQL就变成了:

SELECT first_name, last_name FROM users WHERE user_id = '1' UNION SELECT user, password FROM users#';

这个变化很关键:单引号把原来user_id = '1'的引号闭合了,UNION把后面一整段查询结果拼了进来,#号又把后面原本的单引号和分号注释掉了。整条SQL完全按攻击者写的逻辑执行。这就是“万能密码绕过”背后的原理——本质上都是通过闭合引号、注释尾巴,改变整个WHERE判断逻辑。

2.2 手工注入的五步法

很多人一上来就开sqlmap,这样反而学不到东西。我建议任何一个注入点都先走一遍手工五步法:

  1. 判断是否存在注入,以及是字符型还是数字型。
  2. ORDER BY探测当前SELECT语句的列数。
  3. UNION SELECT找页面的回显位。
  4. information_schema库拿库名、表名、列名。
  5. 根据列名拖数据,拿到账号密码后进一步利用。

这五步就是SQL注入从入门到“脱裤”的标准流程。后面Low级别通关就是严格按照这个顺序走的。

2.3 工具准备:Burp Suite、浏览器、HackBar

手工注入至少要准备两个工具。一个是Burp Suite Community,免费版就够用,抓包、改包、重放请求都能搞定,Medium级别和High级别都离不开它。另一个是浏览器里的HackBar这类插件,方便在URL里改参数,不用每次在页面上点提交。

浏览器本身的开发者工具也很重要,尤其是Network面板。看到请求方式是GET还是POST、请求头带什么Cookie、响应里有没有报错信息,这些信息对判断注入类型帮助很大。

2.4 高频关键词一次说清楚:information_schema、注释符、所有数据库名

information_schema是MySQL自带的“数据库的数据库”,它里面存了所有数据库、表、列的元信息。SQL注入拿到它,就相当于拿到了一张整库的结构地图。常用的两个表是tablescolumns

注释符这块也很基础但容易踩坑。MySQL里常用#--,注意--后面必须带一个空格,否则不生效。在浏览器地址栏里直接构造URL时,#会被浏览器当成锚点吃掉,所以要把#编码成%23,空格可以写成+%20。这个细节在High级别会非常明显。

另外,热搜词里问到“如何通过SQL注入获取所有的数据库名”,其实就是查schema_name

1' UNION SELECT schema_name, 2 FROM information_schema.schemata#

只要当前数据库用户有权限,这一条就能把所有库名列出来。

3. Low级别通关:字符型联合查询完整演示

3.1 第一件事:验证注入点

把DVWA Security切到Low,进入SQL Injection页面,这就是一个输入ID查用户名的功能。先正常输入1,页面返回ID: 1First name: admin, Surname: admin,说明查询正常。

接下来是验证注入点的关键操作:输入1',注意结尾带单引号。如果页面直接报SQL语法错误,提示类似:

You have an error in your SQL syntax; check the manual...

这说明你输入的内容已经被拼进SQL语句,而且数据库把错误信息直接吐出来了。这个报错既证明了存在注入,也告诉你是字符型注入——因为程序用单引号包住了输入,你提交的单引号打破了原本的闭合关系。

3.2 探测列数和回显位

确认注入点后,下一步要知道原SELECT查了几列。最常用的方法是用ORDER BY,从1开始试:

1' ORDER BY 1# 1' ORDER BY 2# 1' ORDER BY 3#

前两条正常返回,第三条报Unknown column '3' in 'order clause',说明当前查询只有2列。

知道列数后,用UNION SELECT找回显位。UNION合并两个查询要求两边列数一致,既然原查询是2列,就构造:

1' UNION SELECT 1,2#

页面如果显示First name: 1, Surname: 2,说明第1个字段回显在“First name”位置,第2个字段回显在“Surname”位置。这两个位置就是后面放数据的位置。

3.3 通过information_schema一步步拿库、表、列

拿到回显位后,信息收集就顺理成章了。先看当前库名和当前用户:

1' UNION SELECT database(), user()#

我用DVWA默认配置跑出来是dvwaroot@localhost。知道当前库名之后,查这个库里有哪些表:

1' UNION SELECT table_name, table_schema FROM information_schema.tables WHERE table_schema='dvwa'#

结果里能看到users表,这就是存储账号密码的地方。接着查这张表有哪些列:

1' UNION SELECT column_name, column_type FROM information_schema.columns WHERE table_name='users'#

这里能看到user_idfirst_namelast_nameuserpasswordavatar等字段,重点记住userpassword

3.4 拖数据、解出密码、尝试登录

拿到字段名之后,直接查数据:

1' UNION SELECT user, password FROM users#

页面会把所有用户的用户名和密码哈希都列出来。老版本DVWA默认密码是MD5格式,例如5f4dcc3b5aa765d61d8327deb882cf99,这就是password的MD5值,在线MD5查询网站或本地用hashcat都能解出来。新版本DVWA改用了更安全的哈希格式,解不出来也不要慌,你可以直接翻源码或在数据库里看明文逻辑。

到这里,Low级别的SQL注入就算打通了。你拿到了所有账号密码哈希,还可以去登录页面尝试用admin用户登录后台,整个利用链闭环。

4. Medium级别通关:转义函数拦不住数字型注入

4.1 先看Medium源码改了什么

把DVWA Security切到Medium,再进入SQL Injection。先别急着输入,打开这一关的源码对比一下。Medium级别主要改了两处:一是请求方式从GET变成了POST,二是对输入用了mysqli_real_escape_string做转义。

4.2 GET变POST:请求方式的变化才是第一个坑

Medium级别页面的表单改成了POST提交,这是一个非常容易忽略的细节。如果你还像Low级别一样,直接在URL地址栏里拼参数,会发现怎么拼都没反应,页面始终不显示查询结果。

正确的做法是打开Burp Suite抓包,或者用HackBar把请求方式改成POST,然后在请求体里提交参数:

id=1 Submit=Submit

为什么GET变POST会影响这么大?因为原来的漏洞利用语句全是放在URL里的,现在URL里根本没有id这个参数,服务器自然接收不到。这一步不搞清楚,后面注入写得再漂亮也没用。

4.3 数字型注入的完整利用流程

在POST请求里提交1 OR 1=1,页面会返回所有用户的数据。这说明Medium级别存在数字型注入,原来的SQL语句大概是:

SELECT first_name, last_name FROM users WHERE user_id = 1 OR 1=1;

这里没有引号闭合问题,因为Medium源码里SQL是直接拼数字的,不需要用单引号包住输入。联合查询也一样可以打:

id=1 UNION SELECT user, password FROM users

请求体里提交这串,页面同样能把整个users表的数据回显出来。整个利用流程和Low级别一样,唯一区别就是请求方式从GET变成POST。

4.4 为什么转义挡不住这次攻击

很多人会问:mysqli_real_escape_string不是专门转义SQL特殊字符的吗,怎么还是被注入了?原因在于它转义的是单引号、双引号这类字符。当你提交1 OR 1=1这种纯数字和逻辑判断的组合时,里面根本没有引号,转义函数找不到可以转义的对象。

这也解释了为什么很多老系统过滤了引号、过滤了关键字,却还是被数字型注入打穿。字符串输入需要关注闭合引号的问题,但数字型输入必须做严格类型校验,比如is_numeric或者强制(int)转换。只靠转义函数,挡得住字符串型,挡不住数字型。

5. High级别通关:LIMIT 1和CSRF Token的限制怎么绕

5.1 High级别的加固点拆解

切到High级别后再看SQL Injection,这个关卡在经典版本里的核心变化有两个:SQL语句末尾加了LIMIT 1,同时整个页面加入了CSRF Token校验,每次提交都需要带上当前Session对应的user_token

还有一个容易被忽略的点:High级别仍然存在字符型注入,源码里还是把用户输入直接拼进了带引号的SQL语句,所以单引号闭合的思路依然成立。

5.2 注入语句的变形:#注释、URL编码、token

既然还是字符型注入,那经典的联合查询语句依然能用,但因为页面只回显第一行,所以需要一些处理。注入语句可以这样写:

999' UNION SELECT user, password FROM users#

用一个不存在的ID值999,让原本的查询结果为空,这样UNION合并后的数据自然就排到了第一位,页面就能直接显示users表里的账号密码。

这里有个很大的坑是#号。如果你在浏览器地址栏里直接输入带#的URL,浏览器会把#后面的内容当成页面锚点,根本不发给服务器。所以要么用Burp Repeater直接发原始请求,要么把#编码成%23再放URL里。

5.3 当查询结果被LIMIT限制时怎么让数据正常显示

LIMIT 1导致页面只能显示一行数据,Union注入本身能返回多行,但这种场景下多行数据显示不出来。除了用上面那种“XX不存在”的方式让UNION结果排在最前面,还可以对查询结果做排序控制,比如按你需要的那条数据排第一。不过实战中最高效的还是把原始查询条件改成一个空结果,让注入的查询结果成为唯一结果。

High级别还有一个CSRF Token问题。每次打开页面,源码里都有一个隐藏的user_token字段,提交时必须带上最新的token。用Burp抓包会发现,直接复制之前的token重放会报CSRF错误。解决办法很简单:用Burp的Repeater之前,先在浏览器里重新加载一下页面,从响应HTML里把最新的token抠出来填进请求。这也是High级别比Medium多出来的一个实际难点。手动注入走到这一步,基本功已经比较扎实了。

6. Impossible级别与真实防御:从“能打”到“防得住”

6.1 Impossible代码用了什么手段

把安全级别切到Impossible,再看源码,会看到它用的是PDO预处理加参数绑定:

$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' ); $data->bindParam( ':id', $id, PDO::PARAM_INT ); $data->execute();

bindParam还指定了PDO::PARAM_INT,意思是这个参数只接受整数类型。无论你提交1' UNION SELECT...还是1 OR 1=1,它都会被当成一个纯粹的“值”传递,而不是SQL代码的一部分。

6.2 参数化查询为什么能彻底解决问题

用一个生活类比来理解:预处理像是你写好一张带空格的答题卡,SQL语句结构已经定死,用户输入只是往空格里填内容。不管填什么,都不会改变答题卡上印好的题目。而拼接SQL就像直接让你把用户的话原封不动念出来,如果用户说的是带指令性质的大段台词,你可能连自己念了什么都不知道就执行了。

这就是为什么业界一直强调:参数化查询/预编译语句是防SQL注入的根本手段。过滤、转义、WAF这些都只是辅助和兜底,只要某条SQL还是拼接出来的,就可能有绕过的办法。

6.3 真实项目里的SQL注入防御清单

过了Impossible级别之后,可以把防御思路沉淀成一张检查清单:

  • 所有数据库操作优先使用参数化查询或预编译语句。
  • 必须在SQL里动态拼接表名、列名时,用白名单校验,不让用户直接传字段名。
  • 应用连数据库不要用root等高权限账号,按业务分配最小权限。
  • 关闭生产环境的详细报错,把数据库异常信息记到日志而不是回显给页面。
  • WAF和关键字过滤可以加,但只能作为第二道防线,不能替代参数化查询。
  • 定期做代码审计,重点关注SQL查询是否全部经过预处理,依赖的框架和数据库驱动也要及时更新。

7. 通关之后的经验复盘与常见问题

7.1 我踩过的坑和排查思路

整个DVWA SQL注入模块打下来,有几个坑是新手大概率会踩的。

第一个坑是版本差异带来的困惑。网上很多教程写的是老版本DVWA,SQL注入模块还是GET传参,但新版可能改成POST或加了额外校验。遇到这种情况不要慌,直接去看当前版本的源码,看它到底怎么取参、怎么拼SQL、有没有做过滤,版本差异就全清楚了。

第二个坑是请求方式变化。Low级别在URL里注入很顺手,到了Medium突然失效,其实不是注入语句有问题,而是POST和GET的通道不一样。以后遇到任何注入点,先抓包确认请求方式,再谈Payload。

第三个坑是浏览器对URL的自动处理。#会被当成锚点,空格会被当成URL分隔符,导致很多Payload在地址栏里敲进去是坏的。一旦发现地址栏里输入和Burp里输入结果不一样,优先怀疑URL编码问题。

7.2 常见问题速查表

现象可能原因解决办法
靶场页面白屏或连不上数据库MySQL未启动或配置文件密码不对检查MySQL状态,核对config.inc.php中的db_password
默认账号admin登录失败密码不是password检查是否改过密码,或重置数据库后再试
输入1'后没有SQL报错错误回显被关闭或做了过滤看源码确认过滤方式,改用盲注或报错注入
Medium提交后页面无变化请求方式已改为POST用Burp改包,不要在URL直接拼参数
High提交后提示CSRF错误user_token过期或缺失重新加载页面,从HTML里取最新的token
UNION注入报列数不匹配原查询列数判断错误用ORDER BY重新探测列数
URL里#后面的内容消失浏览器把#当锚点处理将#编码为%23,或使用Burp Repeater
密码哈希解不出来版本升级改用了bcrypt等强哈希用哈希识别工具确认算法,或直接在数据库改逻辑

7.3 下一步往哪练:从入门到进阶路线

DVWA通关之后,下一步可以按顺序练几个靶场:Sqli-Labs是专门为SQL注入设计的靶场,关卡多、类型全,报错注入、盲注、堆叠注入都能练到;Pikachu是中文靶场,漏洞类型覆盖广,适合系统过一遍Web漏洞;upload-labs专攻文件上传,和SQL注入思路完全不一样但很实用;再往后可以打CTF平台上的Web题,像CTFshow的SQL注入入门系列就不错。

每个靶场都建议先手工打一遍,再考虑用sqlmap这类工具去验证结果。第一次通DVWA,尽量把每一条UNION语句自己敲进去,不要复制粘贴,等你能不看笔记写出来,说明SQL注入的语法和思路已经真正进脑子了。到那时候再碰自动化工具,你会发现sqlmap只是加速器,不是救命稻草。学漏洞利用和学防御是一个硬币的两面,靶场提供的就是这个受控环境,好好利用它。

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

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

立即咨询