资讯动态

DVWA靶场实战:字符型SQL注入手工测试完整链路解析

发布时间:2026/10/22 15:58:32 来源:尧图企业网站定制
2026年2月27日我花了大半个下午把DVWA的SQL Injection模块完整过了一遍重点是在Low级别手动跑通了字符型注入的完整链路。趁着流程和踩过的坑还很新鲜把整个过程整理成这篇记录从环境准备、闭合原理、手工注入的七个关键步骤到我在练习时遇到的各种报错和排查方式一口气写清楚。这篇适合刚接触Web安全、想把SQL注入原理彻底搞懂的朋友也适合在CTF入门阶段被注入题卡住的人。看完你能理解为什么一个单引号就能让SQL语句崩溃也能自己动手从探测到拖出数据完整走一遍而不是只会用工具咔咔跑。1. 准备工作与靶场环境搭建1.1 DVWA怎么快速跑起来DVWA是一套专门用来做漏洞练习的开源靶场默认集成了SQL注入、XSS、文件上传等多个常见漏洞模块。我的环境是本地一台笔记本Windows系统装的是PHPStudy套件把项目文件放到网站根目录后访问http://127.0.0.1/dvwa/就能进入安装和登录页面。启动之后必须先登录默认账号和密码在DVWA的配置里写好了一个是admin另一个是password。登录后建议先进入DVWA Security菜单把安全级别调整成Low。提示虽然叫Low但这里是学习漏洞原理的最佳起点。Medium和High级别加入了转义、过滤和多行限制一开始就看这些反而很难理解注入的本质。先把Low级别完全吃透再往更高等级走体验会非常清晰。搭建时最容易翻车的点有两个一是PHP版本过高导致旧版DVWA直接白屏建议使用PHP 5.x到7.x之间的版本二是数据库初始化失败进入页面后先去Setup菜单检查数据库状态确保勾选项都通过再开始操作。1.2 字符型注入和数字型注入到底差在哪DVWA的Low级别SQL注入模块后端把传入参数拼进了这么一条查询语句代码略作整理$id $_REQUEST[id]; $getid SELECT first_name, last_name FROM users WHERE user_id $id;;关键就在$id这里。用户传入的$id被一对单引号包裹这在SQL层面表示把它当成一个字符串值来处理。因为包裹的是单引号所以这种注入被称为字符型注入。数字型注入则是拼接时没有任何引号包裹类似这样$id $GET[id]; $query SELECT first_name, last_name FROM users WHERE user_id $id;;数字型只需要输入1 union select ...这类payload字符型的麻烦在于你得先考虑怎么把引号闭合掉再考虑怎么让后面的语句合法地接上。很多人一开始分不清这个区别看到字符型注入报错后手忙脚乱本质上就是没意识到后台代码里有那对单引号在作祟。打个比方SQL语句就像一句要填进表格的话原本表格上已经印好了引号表示“这里填一个字符串”。你输入的每一个字符都会被塞进引号里当普通数据。字符型注入要做的事情就是让你输入的内容里多出一个引号这个引号把原本印好的引号“前半个”闭合掉导致表格上引号后面的内容也能被你控制了。这就是后续一切操作的起点。2. 字符型注入为什么存在闭合原理拆解2.1 从后端代码看注入的本质下面这段是DVWA Low级别SQL注入模块的真实核心逻辑保留关键逻辑$getid SELECT first_name, last_name FROM users WHERE user_id $id;; $result mysqli_query($conn, $getid);问题出在用户输入和SQL语句的拼接方式上。开发者想让用户能指定查询某个用户ID于是在SQL语句里用单引号包裹了变量$id。这个想法本身没问题但$id来自用户请求中的参数开发者没做任何校验或转义直接把用户输入原封不动地拼进了SQL语句。这种情况下用户输入的内容已经不只是“数据”而是变成了“SQL代码的一部分”。用行话说叫代码与数据混淆。演示一下这个过程当用户正常输入参数id1时最终SQL语句是SELECT first_name, last_name FROM users WHERE user_id 1;这是完全正常的查询数据库会把user_id等于1的用户信息返回出来。可用户一旦输入id1最终SQL变成SELECT first_name, last_name FROM users WHERE user_id 1;数据库解析时会把第一个单引号和第二个单引号视为一对完整的字符串结束符然后发现后面又多了一个单引号语法直接崩掉报错信息就泄露出来了。所以我常说判断SQL注入最简单的信号就是输入单引号然后看页面是否出现数据库语法错误。出现语法错误证明你的输入真的进入了SQL语句的执行上下文这是最关键的信号。2.2 闭合、注释和万能条件背后的语法逻辑明白了单引号能让语句报错接下来就是要让报错变成可控的查询结果。字符型注入最核心的三步是闭合、拼接、注释。闭合的意思是我们输入的内容里要包含一个单引号让SQL语句原本配好的那一对单引号中的“前半个”被抵消掉。比如原语句是WHERE user_id $id;当我们输入1 AND 11实际拼接出的SQL是SELECT first_name, last_name FROM users WHERE user_id 1 AND 11;这里的WHERE user_id 1前面是一组完整查询等于选择了用户ID为1的记录后面拼上的AND 11是一个恒真条件因为1永远等于1所以结果跟原来的正常查询完全一致。页面就会正常返回数据。如果输入的是1 AND 12拼出的SQL变成SELECT first_name, last_name FROM users WHERE user_id 1 AND 12;因为12是恒假条件查询结果为空页面不会返回任何记录。这两种结果一对比就能确认我们的注入是成立的——我们输入的恒真恒假条件切实影响了最终查询结果。注释符的作用也很重要它能把SQL语句中后面无用的部分全部屏蔽掉。SQL语句末尾的分号、原有的条件都会因为注释符的存在而不再参与语法解析。这样即使拼接出来的SQL语句长度和结构变得奇怪也能被数据库正常执行。以MySQL为例注释符可以用#或--注意后面需要跟一个空格。实际手工注入时我在URL里经常把#写成%23否则浏览器会把它当作锚点处理用Burp抓包发送时就不用担心这个问题直接写#也可以不过为了不依赖工具我习惯统一用%23。一旦掌握了闭合和注释SQL注入的大门就算真正打开了你可以在WHERE条件后面任意追加自己想要执行的SQL语句块联合查询就是其中最有价值的一种利用手法。3. 从探测到拖库字符型注入的七步实操3.1 第一步判断注入点打开SQL Injection模块看到一个输入框和Submit按钮。先在输入框里填一个数字测试比如1点击提交页面正常返回两个字段First name和Surname内容对应的是用户ID为1的数据。再输入1点击提交页面立刻变成数据库报错页面出现了类似这样的提示You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near at line 1报错信息直接暴露了SQL语句的片段这说明我输入的内容已经进入了SQL语句的解析流程。单引号导致语法不匹配注入点确认存在。实操心得看到这个报错信息时别急着往下跑先把报错消息里的SQL片段截图或记下来。它往往能帮助你反推后端拼接SQL的完整结构比如在哪一个位置多了或少了一个引号。3.2 第二步验证闭合方式注入点有了下一步是验证闭合方式。我依次提交两条测试payload第一个输入1 AND 11页面正常返回了ID为1的记录。第二个输入1 AND 12页面返回空没有任何记录。这个对比实验能把闭合方式彻底确定当我们用AND 11这种恒真条件时查询依然正常用恒假条件时查询结果为空。说明我们输入的第一个单引号确实是用来闭合原SQL中user_id 的引号后面的AND 11已经被识别成SQL条件代码而不再只是普通字符串。这种手法的巧妙之处在于即使后端代码里还包含其他我们看不到的字符或逻辑只要预留的引号位置被正确接通后面的恒真条件就不会破坏SQL整体结构。3.3 第三步用ORDER BY探测字段数量在开始联合查询之前必须先知道原查询返回几个字段。联合查询对两边字段数有严格要求必须保证列数一致否则依然会报错。我提交1 ORDER BY 1#页面正常。提交1 ORDER BY 2#也正常。提交1 ORDER BY 3#这次页面报错了因为原查询结果只有两个字段不可能按第三列排序。由此确定当前SQL查询返回的字段数量是2。ORDER BY的原理是通过按第N列排序数据库能正确执行就说明第N列存在执行失败就说明N超出了结果集的列数上限非常直观而且不需要猜。注意事项有些环境会把空格转义或者ORDER BY和数字之间不能有多余空格。我习惯写1ORDER BY 2#或1 ORDER BY 2%23在DVWA Low环境下直接写带空格的版本没有问题。3.4 第四步用UNION SELECT定位显示位知道了当前查询返回2个字段现在可以用UNION SELECT来构造一个自己的查询结果。UNION查询的规则是把两个查询结果纵向拼接要求字段数一致。我们构造1 UNION SELECT 1, 2#页面不仅返回了ID为1的记录还额外多了一条数据First name显示为1Surname显示为2。这两个数字所在的位置就是当前页面可回显的“显示位”。显示位的意思非常直白你在UNION SELECT后面写的第几个字段最终就会把这个字段的值渲染在页面上的对应位置。通过显示位你能把数据库函数或者后续查询的数据填充到页面里实现数据带外回显。我把显示位搞清楚后习惯性地提交了1 UNION SELECT database(), 2#页面第一条记录的位置直接输出了dvwa。这是当前连接的数据库名。至此注入已经做到“想查什么就查什么”的地步了后面要做的就是靠information_schema慢慢把数据翻出来。3.5 第五步到第七步查库名、表名、字段名与数据确定了显示位后常规的拖库流程就非常机械了跟着下面顺序走就行。先查库名刚才已经顺手拿到了database()的返回值也就是当前数据库名dvwa。接着查表名构造1 UNION SELECT table_name, 2 FROM information_schema.tables WHERE table_schemadvwa#页面返回了该数据库下所有表的名称。information_schema是MySQL自带的元数据信息库记录了数据库、表、列、索引等所有结构信息。查询这个库权限通常不受限制所以这是SQL注入最常见的元数据来源。查到表名里有users下一步是查这张表的字段名1 UNION SELECT column_name, 2 FROM information_schema.columns WHERE table_schemadvwa AND table_nameusers#页面把users表的所有列名列了出来包括 user_id, first_name, last_name, user, password, avatar 等。我关注的自然是用户名和密码列。最后查询数据1 UNION SELECT user, password FROM users#页面直接列出了两张用户名和对应的密码哈希值。DVWA的密码是MD5格式的哈希值如果要还原成明文可以在本地用哈希计算工具跑一下字典不过这不影响我们理解整个注入流程。到这里一个从 输入一个单引号让页面报错 到 把users表整张拖出来 的完整字符型SQL注入链路已经全部走通。3.6 实操现场的payload速查我把这轮操作实际用到的payload整理成一张小表方便后续复习操作阶段Payload预期结果判断注入点1页面SQL语法报错验证闭合1 AND 11/1 AND 12前者返回数据后者为空探测列数1 ORDER BY 2#正常返回ORDER BY 3时报错定位显示位1 UNION SELECT 1,2#页面回显数字1和2查数据库名1 UNION SELECT database(),2#回显库名dvwa查表名1 UNION SELECT table_name,2 FROM information_schema.tables WHERE table_schemadvwa#回显所有表名查字段名1 UNION SELECT column_name,2 FROM information_schema.columns WHERE table_schemadvwa AND table_nameusers#回显所有列名拖取数据1 UNION SELECT user, password FROM users#回显所有用户名和密码哈希实操细节地址栏直接输入#时浏览器容易把它当作页面内锚点导致请求里丢失注释符。我实际测试时把#改写成%23之后一切都稳了。如果你也是直接在浏览器地址栏操作建议把表里的所有#都换成%23。4. 踩坑实录这些问题我基本都遇到过4.1 多一个引号或少一个引号字符型注入的报错信息能提供很多线索但经常也会让人绕晕。最常见的错误是payload里引号数量不对。比如写成1 AND 11这种写法拼接后会出现WHERE user_id 1 AND 11末尾多了一个单引号SQL解析器同样会报错。正确写法是让最后一个字符串的引号和SQL语句尾部配合好用注释符屏蔽掉原语句尾部的分号比如1 AND 11#搞不清楚的时候我的习惯做法是先在脑子里把最终SQL语句完整拼出来写完payload再落一行检查前引号是否闭合了原条件、逻辑条件是否正确闭合、字符串末尾是否多引号。4.2 注释符不生效在使用URL传递参数时最容易忽略的就是#被浏览器当作锚点处理。你以为注释符发出去了实际上服务器收到的SQL语句里根本没有注释符后面剩余代码依然存在导致查询结构错误页面一直报错。排查方式很直接用Burp Suite抓包看一眼实际发送的原始请求确认#有没有变成%23。或者干脆在payload里统一写成%23这是一个多环境稳定的方案。还有一类问题是数据库版本的注释符差异比如MySQL支持#和--两个短横线加空格但PostgreSQL和SQL Server只支持--。DVWA默认是MySQL环境我用#就没事但如果换靶场或换数据库需要灵活调整。4.3 空格被吞掉或编码出错DVWA Low级别自己没有过滤空格但浏览器地址栏和工具之间传递参数时加号会被当作空格空格会被解码成%20有些情况下双写会被服务端过滤。如果你直接把UNION SELECT写进地址栏经常出现的问题是参数值里有空格、加号导致后端接收的数据变了样子。所以我建议从第一次遇到乱账开始就切换到Burp等HTTP抓包工具里操作。在工具里把Raw请求截获后手动修改参数值再用Send发送这样能精确控制服务器收到的每一个字符。实际测试下来这比纠结地址栏的URL编码规则高效得多。4.4 UNION查询字段数不匹配如果ORDER BY确定有3列你写UNION SELECT 1,2时数据库会立刻报错。UNION两侧的字段数必须严格一致。反过来如果原SQL查询返回5列你只写UNION SELECT 1,2同样会报错。这个错误信息通常是The used SELECT statements have a different number of columns看到它不要慌说明列数探测步骤没过回头用ORDER BY慢慢试。4.5 Medium和High级别为什么不灵了Level调高后同样的payload直接失效这是很多新手卡住的地方。把DVWA的安全级别切到Medium后后端逻辑变成这样逻辑整理$id mysqli_real_escape_string($conn, $_REQUEST[id]);这个函数会对输入中的单引号、双引号、反斜杠等字符做转义单引号前面会被加一个反斜杠。于是我写的1到了SQL语句里就变成1\单引号不再是SQL语义里的闭合符而变成了普通字符串的一部分注入自然失败。High级别更进一步直接改写了SQL写法$query SELECT first_name, last_name FROM users WHERE user_id ? LIMIT 1;; // 使用参数化查询这个级别的SQL语句里已经没有了拼接动作用户输入以问号占位符的形式传给数据库再由数据库把占位符位置的数据当作纯数值处理。我之前的所有payload都拿不到任何操作空间。我整理了一个简单的对照表级别防护方式绕过思路Low无防护直接闭合引号联合查询Medium转义单引号尝试绕过转义如宽字节、二次注入High参数化查询限制返回一行不能通过拼接绕过只能寻找其他业务点Impossible参数化查询Token校验从头阻断注入Medium级别的转义虽然能挡住初学者Payload但它本质上还是“黑名单”思路只处理了已知的恶意字符。学完Low再去实践Medium你才会感受到“过滤永远不等于安全”这句话有多真实。比如在GBK字符集环境下拿%bf%27这类宽字节想办法让转义符号失效就是经典的绕过思路之一这也是后续值得继续研究的方向。5. 防御视角参数化查询为什么是解药5.1 Impossible级别到底做了什么事情当DVWA安全级别调到ImpossibleSQL查询部分变成了这样逻辑整理$stmt $conn-prepare(SELECT first_name, last_name FROM users WHERE user_id ? LIMIT 1;); $stmt-bind_param(i, $id); $stmt-execute();这段代码用了MySQL的预处理语句Prepared Statement。先把SQL骨架发给数据库数据库完成语法解析然后单独把用户输入绑定到?占位符上。也就是说数据库已经明确划定了边界占位符位置只能容纳一个值而不是一段代码。用户传来的任何内容都会被当作这个位置的“数据”不能改变SQL语句的结构。这跟“过滤”完全不是一个层次过滤还是在一个没有任何边界的文本上做删除和转义而预处理直接修改了SQL语句的生成方式让边界从一开始就存在。我在实际学习过程中最大的感受是SQL注入的问题不单是输入过滤不严而是“把用户数据拼接进SQL语句”这种做法本身就违反了安全边界原则。所以修复方案永远不是加几个黑名单函数而是彻底改成参数化查询。5.2 学完Low级别后下一步怎么走如果你和我一样刚开始接触Web安全不久我的建议是这样递进地练先把Low级别的注入流程做到不用看笔记就能写出payload能清楚说出每一步的目的和SQL语句最终的形态。然后切到Medium研究转义机制和宽字节注入。再去学POST型注入因为很多真实场景的注入点不在GET参数里而是藏匿在登录表单和搜索表单中。随后是盲注类题目页面没有报错也没有回显只能靠真假页面差异推断数据。学习和练习的范围务必控制在本地靶场或测试环境中不要对非授权的系统进行测试。这点说多少遍都不为过安全技能的价值不在于破坏而在于了解攻击手法后能真正写出无法被拆解的防御代码。最后再分享一个我自己的小习惯每做完一次手工注入就把用过的payload和SQL语句拼接结果抄在一个笔记里仔细复盘“这条语句到底长什么样数据库执行时会怎么走”。做满十几次之后你对SQL注入的理解会完全不一样。所谓手感其实就是对SQL语法结构和执行逻辑足够熟练。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑