尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

DVWA靶场实战:从SQL注入原理到防御的完整攻防演练

DVWA靶场实战:从SQL注入原理到防御的完整攻防演练 1. 项目概述为什么从DVWA和SQL注入开始如果你刚接触网络安全或者想从理论真正走向实践那么“DVWA靶场”和“SQL注入”绝对是你的最佳起点。我见过太多新手一上来就啃厚厚的理论书或者对着复杂的真实环境无从下手最后热情被消磨殆尽。而DVWADamn Vulnerable Web Application这个“该死的脆弱Web应用”就是为解决这个问题而生的。它是一个专门设计用来练习安全渗透测试的PHP/MySQL应用把各种常见的Web漏洞比如我们今天要深挖的SQL注入集成在一起并且贴心地设置了从低到高的安全等级。为什么是SQL注入在OWASP开放Web应用安全项目发布的Top 10安全风险榜单中注入类漏洞其中SQL注入是绝对主力常年位居前三甚至多次登顶。它不仅仅是“在登录框输入‘ or ‘1’’1”那么简单其背后是应用程序对用户输入数据的不信任和不当处理这一根本性问题。通过亲手在DVWA中利用SQL注入漏洞你能最直观地理解“为什么一个简单的查询会变成灾难”以及“开发者应该如何从代码层面堵上这个洞”。这比看一百篇理论文章都管用。所以这篇内容的目标很明确我们不空谈直接动手。我会带你从零开始在DVWA的SQL注入模块里从最低安全级别Low一路“打”到最高级别High剖析每一层防御机制的弱点并还原攻击者的完整思考路径和操作手法。在这个过程中你会自然而然地理解OWASP Top 10中关于注入漏洞的核心要义。准备好了吗我们开始实战。2. DVWA环境快速搭建与初始化工欲善其事必先利其器。虽然网上有很多在线靶场但为了学习过程的彻底和自由我强烈建议你在本地搭建一个DVWA环境。这样你可以随意修改代码、查看数据库、重置数据学习效果最好。2.1 一站式环境部署XAMPP方案对于绝大多数初学者最省心的方法是使用集成环境包比如XAMPP。它把ApacheWeb服务器、MySQL数据库、PHP和phpMyAdmin数据库管理工具打包在一起一键安装。下载与安装前往Apache Friends官网下载对应你操作系统Windows/macOS/Linux的XAMPP版本。安装过程基本就是一路“Next”建议安装路径不要有中文和空格。启动服务安装完成后打开XAMPP控制面板。点击Apache和MySQL模块旁边的“Start”按钮。当它们旁边的状态变成绿色并显示PID进程ID时表示服务已成功启动。部署DVWA从DVWA的GitHub官方仓库github.com/digininja/DVWA下载ZIP包并解压。将解压后的整个DVWA-master文件夹复制到XAMPP的网站根目录下。这个目录通常是C:\xampp\htdocs\Windows或/Applications/XAMPP/htdocs/macOS。为了方便访问可以把文件夹重命名为dvwa。配置文件修改找到dvwa/config目录将其中的config.inc.php.dist文件复制一份并重命名为config.inc.php。用文本编辑器打开这个新文件找到数据库配置部分$_DVWA[ db_server ] 127.0.0.1; $_DVWA[ db_database ] dvwa; $_DVWA[ db_user ] root; $_DVWA[ db_password ] pssw0rd;默认的数据库密码是pssw0rd。如果你的XAMPP中MySQL的root密码是空的默认情况你需要将‘pssw0rd’改为‘’两个单引号中间为空。这是第一个常见的坑。访问与初始化打开浏览器访问http://localhost/dvwa/。首次访问会跳转到设置页面setup.php。点击页面底部的“Create / Reset Database”按钮。如果一切顺利页面会提示数据库创建成功并显示“Setup Check”全部为绿色勾。如果出现红色警告请根据提示检查上一步的配置文件特别是数据库密码和PHP函数允许情况如allow_url_include。登录初始化成功后使用默认账号admin和密码password登录。注意永远不要在公网服务器上部署未经安全加固的DVWA因为它本身充满漏洞。仅在本地或隔离的虚拟环境中使用。2.2 安全等级设置与理解登录后在左侧菜单找到“DVWA Security”并点击。这里就是控制整个应用漏洞难度的核心。安全级别Low完全没有防护。代码直接拼接用户输入是学习漏洞原理的“沙盒”。Medium引入了简单的防护措施比如mysql_real_escape_string()函数但存在绕过的可能。这是理解“不完全防护”的典型场景。High采用了更严格的防护例如使用预编译语句Prepared Statement但可能在其他逻辑上存在瑕疵需要更精巧的利用。Impossible代表当前最佳实践的安全代码通常无法被直接攻破用于学习防御方案。在开始SQL注入实战前请务必将安全级别设置为“Low”。我们将从这里起步逐步升级挑战。3. SQL注入核心原理深度拆解在动手之前我们必须把原理吃透。很多人在注入时只是机械地输入Payload攻击载荷却不明白为什么这个Payload能生效换一个场景就懵了。让我们抛开术语用最直白的方式理解。想象一个简单的用户登录场景。后端PHP代码可能是这样的Low级别$id $_GET[id]; // 直接从URL获取用户输入的id $query SELECT first_name, last_name FROM users WHERE user_id $id; $result mysqli_query($connection, $query);这段代码的意图是根据用户输入的id从users表中查询对应的用户名。正常情况用户输入1。代码拼接出的SQL语句是SELECT ... WHERE user_id 1。完美执行。攻击情况用户输入1 OR 11。代码拼接出的语句变成了SELECT first_name, last_name FROM users WHERE user_id 1 OR 11看这里的关键在于那个单引号‘。它提前闭合了原本用于包裹用户输入的引号使得‘1’1‘这个恒真条件被引入到了WHERE子句中。由于OR的逻辑整个条件永远为真。这条语句的含义就变成了“查询users表中所有满足user_id1或者11的记录”。11永远成立所以它会返回users表中的所有记录。这就是SQL注入最本质的原理通过插入特殊字符如单引号、分号改变原始SQL语句的语法结构将用户输入的数据“注入”并“解释”为代码的一部分来执行。在DVWA的SQL注入模块中虽然场景不是登录而是根据ID查询用户信息但原理完全相通。我们输入的不是用户名密码而是一个用户ID后端程序同样会把它拼接到查询语句里。我们的目标就是通过精心构造的输入让这个查询语句执行我们想要的“额外动作”比如查看到其他用户的数据、数据库结构甚至执行系统命令取决于数据库权限。4. 实战演练DVWA SQL注入Low级别手把手通关将DVWA安全级别设为Low然后点击左侧“SQL Injection”。你会看到一个简单的输入框提示你输入User ID。4.1 第一步漏洞探测与确认不要一上来就用复杂的Payload。规范的渗透测试第一步是确认漏洞存在。基础探测在输入框输入一个简单的单引号‘然后提交。预期结果页面返回了SQL语法错误信息例如“You have an error in your SQL syntax; check the manual...”。原理分析我们输入的单引号破坏了原SQL语句的语法。假设原语句是SELECT ... WHERE id$_GET[id]‘输入‘后语句变成... WHERE id‘’多了一个单引号导致语法错误。这强烈暗示此处存在SQL注入漏洞并且程序开启了错误回显这为我们后续的“报错注入”提供了便利。逻辑测试输入1‘ and ‘1’1和1‘ and ‘1’2。输入1‘ and ‘1’1语句变为... WHERE id1‘ and ‘1’1‘。这是一个“与”操作id1且11真所以应正常返回ID为1的用户信息。输入1‘ and ‘1’2语句变为... WHERE id1‘ and ‘1’2‘。id1为真但12为假“真与假”结果为假所以应该查询不到任何结果页面可能显示空白或“User ID missing from the database”。结论如果两种输入返回了不同的页面状态则进一步确认了注入点的存在以及我们可以通过布尔逻辑真/假来控制查询结果。4.2 第二步信息收集——判断字段数与可显示位在联合查询注入Union-Based Injection中我们需要知道原始查询语句SELECT了多少列并且找出哪些列的内容会在页面上显示出来称为“回显位”。判断查询列数使用ORDER BY子句。ORDER BY n表示根据第n列进行排序。如果n超过了实际的列数数据库就会报错。输入1‘ order by 1 --注意--后面有个空格这是SQL的单行注释符用于注释掉原查询后面可能存在的其他语句比如后面的单引号页面正常返回。输入1‘ order by 2 --页面正常返回。输入1‘ order by 3 --页面报错。结论原始查询语句一共查询了2列。因为ORDER BY 3时超出了列数范围导致错误。寻找回显位使用UNION SELECT语句。UNION用于合并两个SELECT的结果集要求两个SELECT的列数必须相同。我们已经知道是2列。输入1‘ union select 1,2 --这个Payload的意思是先执行原查询id‘1’再联合一个我们自定义的查询select 1,2。如果页面在原本显示“名”First name和“姓”Surname的地方变成了显示数字“1”和“2”那么就说明这两个位置是回显位。实际操作在DVWA Low级别下输入上述Payload后页面很可能会在“First name”处显示“1”在“Surname”处显示“2”。这就意味着我们可以把任何我们想查询的信息放在select 1,2的这两个位置上结果就会显示在页面上。4.3 第三步数据库信息提取现在我们有了“武器”注入点和“输出窗口”回显位可以开始提取敏感信息了。我们将利用数据库的系统表在MySQL中信息模式库information_schema存储了所有元数据。获取当前数据库名输入1‘ union select database(), version() --database()函数返回当前数据库名version()返回数据库版本。我们会看到在回显位显示了数据库名很可能是dvwa和MySQL版本号。获取所有表名输入1‘ union select table_name, null from information_schema.tables where table_schemadatabase() --这里我们从information_schema.tables表中查询属于当前数据库table_schemadatabase()的所有表名table_name。null是为了占位因为我们只需要一个回显位来显示表名。执行后页面会列出dvwa数据库中的所有表例如users,guestbook等。我们的目标显然是users表。获取目标表users的列名输入1‘ union select column_name, null from information_schema.columns where table_schemadatabase() and table_name‘users’ --我们从information_schema.columns中查询users表的所有列名。执行后会看到诸如user_id,first_name,last_name,user,password,avatar等列名。其中user和password是我们的终极目标。4.4 第四步终极目标——拖取用户凭证现在表名和列名都知道了我们可以直接查询数据了。输入1‘ union select user, password from users --这个Payload会从users表中查询出所有用户的登录名和密码哈希值。在DVWA中密码是用MD5算法哈希后存储的。你会看到类似admin-5f4dcc3b5aa765d61d8327deb882cf99这是“password”的MD5值这样的结果。实操心得在Low级别整个过程行云流水因为没有任何过滤。这完美展示了“将用户输入直接拼接到SQL语句中”所带来的毁灭性后果。攻击者可以轻松获取整个数据库的权限。作为开发者看到这里应该已经脊背发凉了。5. 中级挑战突破Medium级别的过滤将DVWA安全级别切换到Medium。你会发现输入框变成了一个下拉菜单只能选择数字ID。这似乎防住了我们手动输入单引号别急这只是前端限制。我们可以用Burp Suite这类代理工具拦截并修改请求或者直接分析表单用其他方式发送请求。5.1 漏洞点变化与绕过思路在Medium级别查看源码vulnerabilities/sqli/source/medium.php你会发现关键变化$id $_POST[id]; // 从POST请求获取数据 $id mysql_real_escape_string($id); // 使用了转义函数 $getid SELECT first_name, last_name FROM users WHERE user_id $id;两点重要变化数据来源从$_GET变成了$_POST所以攻击点从URL参数变成了表单POST数据。使用了mysql_real_escape_string()函数对输入进行转义。这个函数会在特殊字符如单引号‘、双引号“、反斜杠\等前添加反斜杠进行转义使其失去特殊含义变成普通字符。那么我们之前的Payload1‘ union select 1,2 --会被转义成1\‘ union select 1,2 --注入就失败了。但是仔细看拼接的SQL语句WHERE user_id $id。变量$id没有被单引号包裹在Low级别语句是WHERE user_id ‘$id‘。这是Medium级别一个“弄巧成拙”的致命错误。因为没有了引号我们注入时就不再需要关心单引号的闭合问题。mysql_real_escape_string()转义单引号是为了防止它破坏引号边界但现在根本没有边界需要破坏。我们的注入可以直接基于数字和SQL语法进行。5.2 实战攻击流程由于是POST请求我们无法直接在浏览器地址栏操作。有两个方法方法A使用HackBar等浏览器插件如果DVWA集成了的话。方法B使用Python脚本或curl命令模拟POST请求。这里为了直观我们假设可以修改请求。攻击步骤判断列数因为不需要单引号Payload更简洁。发送POST数据id1 order by 2如果失败试试id1 order by 2#用#注释正常。尝试id1 order by 3报错。确认列数为2。联合查询发送POST数据id1 union select 1,2此时后端语句是WHERE user_id 1 union select 1,2语法正确。页面应显示回显位1和2。提取信息后续步骤与Low级别类似只是去掉单引号。获取数据库名id1 union select database(), version()获取表名id1 union select table_name, null from information_schema.tables where table_schemadatabase()注意查询users表时因为table_name‘users’这里有个引号会被转义。所以我们需要将字符串‘users’转换为不带引号的16进制形式来绕过。在MySQL中0x7573657273就是字符串“users”的16进制表示‘u’75, ‘s’73, ‘e’65, ‘r’72, ‘s’73。Payload变为id1 union select column_name, null from information_schema.columns where table_schemadatabase() and table_name0x7573657273拖取数据id1 union select user, password from users注意事项Medium级别的案例非常经典。它告诉我们前端限制形同虚设安全必须依赖服务端验证。错误的安全感开发者以为用了转义函数就安全了却因为漏写了引号导致防护完全失效。这对应了OWASP Top 10中关于“安全配置错误”和“注入”的混合风险。绕过技巧当引号被过滤时可以使用16进制编码、CHAR()函数等方式来表示字符串。6. 高级对抗High级别的隔离挑战与时间盲注将安全级别切换到High。查看源码你会发现代码结构发生了较大变化。High级别通常会采用更接近真实安全实践的代码例如使用预编译语句Prepared Statements但DVWA的High级别SQL注入模拟了另一种情况将用户输入限制在单次、隔离的查询中。查看source/high.php你可能看到类似这样的逻辑具体实现可能随版本变化用户输入被限制在一个简单的查询中且后续操作如显示结果可能与初始查询分离。可能使用了mysql_real_escape_string()且引号包裹正确。关键点错误信息被关闭页面对于SQL错误只会返回统一、模糊的提示如“用户不存在”而不会泄露具体语法错误。这就是“盲注”Blind SQL Injection的典型环境。当没有错误回显也没有直接的数据回显时我们如何判断注入是否存在并提取数据答案是通过观察页面行为的差异或者引入时间延迟。6.1 布尔盲注Boolean-Based Blind Injection原理通过构造SQL语句让页面根据我们查询条件的真True或假False返回两种不同的、可区分的状态例如返回内容“存在”与“不存在”或者HTTP响应状态码不同。攻击思路确认盲注点输入1‘ and 11 --和1‘ and 12 --。观察页面返回内容是否有细微差别比如一个显示了用户信息一个显示“用户不存在”。如果有说明我们可以通过布尔逻辑控制页面输出布尔盲注可行。逐位猜解数据例如我们要猜解当前数据库名的第一个字母。数据库名长度可以用1‘ and length(database())1 --,2,3... 来猜直到页面返回“真”的状态。假设长度是4。猜第一个字符的ASCII码1‘ and ascii(substring(database(),1,1))100 --。如果页面返回“真”状态说明ASCII码大于100。然后我们再用二分法150, 125...一步步缩小范围最终确定准确的ASCII码再转换为字符如97是‘a’。重复此过程对substring(database(),2,1),substring(database(),3,1)... 进行猜解最终拼出完整的数据库名dvwa。这个过程极其繁琐必须借助自动化工具如SQLmap。6.2 时间盲注Time-Based Blind Injection如果页面对于真假查询都返回一模一样的内容布尔盲注就失效了。这时可以用时间盲注。原理通过SQL语句控制数据库执行时间延迟然后根据页面响应时间的长短来判断查询条件的真假。攻击思路确认时间注入点输入1‘ and sleep(5) --。如果页面响应大约延迟了5秒才返回说明sleep()函数被执行了时间注入漏洞存在。结合条件进行猜解1‘ and if(ascii(substring(database(),1,1))100, sleep(5), 0) --。这个Payload的意思是如果当前数据库名第一个字母的ASCII码大于100那么让数据库睡眠5秒否则立即返回。我们观察页面响应时间如果等了5秒说明条件为真ASCII100如果立即返回说明条件为假。同样通过二分法利用响应时间的差异我们可以逐位猜解出所有数据。实操心得High级别的实战手动操作几乎是不可能的任务因为它涉及成千上万次请求。这正体现了自动化渗透测试工具如SQLmap的价值。同时这也说明了为什么“不返回详细错误信息”是一种有效的纵深防御措施它能极大提高攻击门槛和成本。作为开发者除了使用预编译语句关闭错误回显也是上线前必须做的安全配置。7. 防御之道从Impossible级别看最佳实践最后将安全级别切换到Impossible并查看其源码。这里展示了当前公认的、最有效的SQL注入防御方案使用预编译语句Prepared Statements与参数化查询这是根治SQL注入的“银弹”。原理将SQL语句的结构模板与数据参数分开发送和解析。数据库先编译语句模板如SELECT * FROM users WHERE id ?这个模板是固定的。随后传入的参数如1会被严格视为数据而不会被当作代码解析。无论参数里包含什么特殊字符都无法改变原语句的语义。代码示例PHP PDO$stmt $pdo-prepare(SELECT first_name, last_name FROM users WHERE user_id :id); $stmt-bindParam(‘:id‘, $id, PDO::PARAM_INT); $stmt-execute();:id是一个占位符bindParam方法将变量$id绑定到这个占位符并明确指定其为整数类型PDO::PARAM_INT。这样即使$id是1‘ OR ‘1’’1它也会被当作一个完整的字符串值去匹配user_id字段而不会去解析其中的OR。严格的输入验证与类型转换在Impossible级别的代码中你还会看到对输入$id进行了严格的检查if( is_numeric( $id ) )。确保输入是预期的类型这里是数字对于非数字输入直接拒绝处理。最小权限原则用于连接数据库的账户不应具有DROP,FILE,EXECUTE等高危权限仅授予其应用所需的最小权限如SELECT,INSERT,UPDATE这样即使发生注入危害也能被限制。关闭错误回显避免将数据库的详细错误信息暴露给前端用户防止攻击者利用这些信息进行推理。总结一下防御体系首选且必须在所有数据库操作中使用参数化查询预编译语句。辅助加固进行严格的输入验证白名单原则最佳和输出编码。安全配置遵循最小权限原则配置数据库账户并关闭生产环境的详细错误回显。纵深防御使用Web应用防火墙WAF作为网络层的补充防护。通过DVWA四个级别的实战我们完整经历了SQL注入的攻击链从毫无防护的肆意利用Low到绕过不完全的防御Medium再到对抗无回显的艰难盲注High最后学习如何从根源上构建坚固的防线Impossible。这个过程正是理解OWASP Top 10中“注入”漏洞从存在、利用到修复的绝佳缩影。真正的安全不是靠一两个函数而是一套从编码习惯到架构设计的完整体系。
返回列表