news 2026/9/24 18:30:22

DVWA靶场SQL注入实战:从手工注入到PDO预编译防御全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DVWA靶场SQL注入实战:从手工注入到PDO预编译防御全解析

聊起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'='11' 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'#

返回结果里能看到userpassword两个关键字段名。

第七步:拿数据。直接查用户名和密码哈希:

1' UNION SELECT user, password FROM users#

页面把所有用户名和MD5加密的密码哈希都返回出来了。密码哈希虽然不能直接登录,但可以通过彩虹表或在线MD5解密还原出明文。

提示:在URL里输入#时,浏览器会把#当成锚点,不发送给服务器,所以提交时要用%23代替,或者在Burp Suite的Repeat功能里改包发送。

3.3 Low级别的几个实操细节

Low级别看起来简单,但实操中我第一次还是卡了几分钟。有个细节是:在输入框里直接提交时,DVWA用的请求参数是idSubmit,页面是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#

把最后一条输入进去,所有用户的userpassword字段直接暴露。

注意:在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声明
其他机器访问不到DVWAApache只监听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" --batch

sqlmap会自动判断注入类型、获取库名表名列名数据,甚至能直接尝试读文件、写WebShell。但我建议最好是在手工注入打通一条完整流程之后,再让sqlmap来做速度补充,这样出问题时你知道它在干什么,不会两眼一抹黑。

最后分享一个我个人的练手习惯:每次打完一个靶场模块,我会把四个级别的源码依次截图放进笔记里,在旁边写下“这个防护为什么能被绕过”或者“这个修复方案为什么有效”。时间久了,这些笔记就是一份特别宝贵的安全编码素材库。DVWA的好处就是它把所有答案都摆在你面前,只要肯花时间,SQL注入这个坑,你一定能彻底趟平。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 18:28:54

微电网调度实战:差分进化算法与Matlab代码全流程解析

先说个有意思的事。我把这个题目挂到技术社区之后&#xff0c;后台收到最多的私信不是问“差分进化算法怎么调参”&#xff0c;也不是问“微电网模型怎么建”&#xff0c;而是一堆人在问“兄弟&#xff0c;那个Matlab代码能不能发我一份”。这其实反映了一个很现实的情况&#…

作者头像 李华
网站建设 2026/9/24 18:28:44

Windows系统基础安全加固指南:从账户权限到日志审计

装好Windows之后&#xff0c;我一般不会马上把软件装齐&#xff0c;而是先把系统安全相关的东西过一遍。这个习惯是几年前帮人处理一台被勒索病毒加密的电脑之后养成的&#xff0c;那台机器所有文档都变成了奇怪后缀&#xff0c;系统里还开着默认的管理员共享、防火墙几乎等于没…

作者头像 李华
网站建设 2026/9/24 18:28:05

CSS 3D翻页时钟实战:从原理到动效打磨的完整指南

第一次在手头的项目里看到翻页时钟的效果时&#xff0c;我盯着那个数字“啪”地翻过去的过程看了很久。数字时钟本身没什么稀奇的&#xff0c;但那个“翻”的动作&#xff0c;像老式火车站时刻表一样&#xff0c;带着一种机械的仪式感&#xff0c;让时间流动变得肉眼可见。后来…

作者头像 李华
网站建设 2026/9/24 18:27:53

基于YOLOv8的电梯电瓶车闯入报警系统实战:从训练到部署

简介&#xff1a;基于YOLOv8的电梯内电瓶车闯入报警系统&#xff0c;面向计算机相关专业学生、老师及企业员工&#xff0c;尤其适合毕业设计、课程设计、大作业或项目初期立项演示&#xff0c;用于解决电梯场景中电瓶车违规进入的实时检测与自动报警问题。资源包共8个文件&…

作者头像 李华
网站建设 2026/9/24 18:27:30

2026年AI生成网站全攻略:低成本上线企业官网的实操指南

2026年了&#xff0c;如果你还想花上万块找人做企业官网&#xff0c;我建议你先停下来看看AI生成网站这条路的成熟度。现在的情况是&#xff1a;一个纯展示型官网&#xff0c;从文案、页面设计到域名上线&#xff0c;AI可以把整个流程压缩到一两天&#xff0c;成本能压到一两百…

作者头像 李华
网站建设 2026/9/24 18:27:25

资金流预测实战:基于时间序列模型与特征工程的现金流管理指南

身边很多做财务、运营和做独立产品的朋友&#xff0c;都有过这样一个让人抓狂的经历&#xff1a;月初看账面资金还够用&#xff0c;结果月底突然发现要借钱周转。问题的根源往往不是业务不行&#xff0c;而是资金流入流出根本没被当成一件可以预测、可以管理的事。我之前在给一…

作者头像 李华