DVWA靶场搭建实战:Docker+WSL2一键部署渗透测试环境 1. 为什么 DVWA 是渗透测试新手绕不开的第一道门刚接触渗透测试的朋友常会陷入一个误区一上来就猛啃《Web 应用安全权威指南》或者直接翻 Kali Linux 的工具手册结果学了两周连“SQL 注入到底在哪输命令”都搞不清。我带过不少转行学员90% 的人卡在同一个地方——没有真实可操作、可验证、可反复试错的环境。你对着靶场截图照着敲命令报错堆栈全是英文连哪一行出问题都定位不了你按教程装完环境浏览器打开却显示 502 Bad Gateway查日志发现是 PHP 版本不兼容但又不知道该降级还是升级……这些不是技术门槛高而是缺一个“能让你亲手把漏洞打出来”的起点。DVWADamn Vulnerable Web Application就是这个起点。它不是模拟器也不是教学动画而是一个真实运行在你本地的 PHPMySQL Web 应用里面预置了 SQL 注入、XSS、CSRF、文件上传、命令执行等 12 类经典漏洞且每类漏洞都按“低/中/高/不可能”四级难度做了隔离设计。更重要的是它的代码极度透明——你随时可以打开/var/www/dvwa/vulnerabilities/下的 PHP 文件看到$_GET[id]是怎么没过滤就拼进 SQL 语句的看到echo $_GET[name]是怎么直接输出造成 XSS 的。这种“漏洞即代码、攻击即调试”的闭环体验是任何在线靶场或视频教程都无法替代的。关键词“dvwa靶场搭建”背后其实藏着三个真实需求第一要能一次装成功不被 Apache 配置、PHP 模块缺失、MySQL 权限这些底层细节卡住第二要清楚知道每个配置项改了什么、为什么必须这么改而不是复制粘贴完就跑第三要为后续实战留出扩展空间——比如以后想加 Burp Suite 抓包分析或者集成 Nikto 扫描环境得支持无缝接入。这篇教程就是按这三个需求来写的不跳步、不省略、不假设你知道“systemctl 是什么”从下载 ISO 镜像开始到浏览器里点开第一个漏洞页面为止每一步都告诉你“为什么这步不能少”“如果失败了最可能卡在哪”。适合零基础但愿意动手的人也适合教别人时当备课材料用。2. 环境选型与底层逻辑为什么推荐 Docker Windows WSL2 组合很多人一搜“DVWA 搭建”出来的教程全是“Windows 下装 XAMPP → 解压 DVWA → 改 config.inc.php → 启动 Apache”。这套流程看似简单实则埋了至少五个雷XAMPP 自带的 MySQL 8.0 默认启用严格模式DVWA 的旧版 SQL 语句会直接报错PHP 8.x 移除了mysql_*函数而 DVWA 1.10 及之前版本仍依赖它Apache 的.htaccess重写规则在 XAMPP 中默认关闭导致 DVWA 的路由跳转失效Windows 防火墙偶尔会拦截 80 端口但错误提示却是“Connection refused”更麻烦的是一旦装崩重装 XAMPP 得手动删注册表、清服务、卸载残留耗时半小时起步。我试过七种环境组合最终锁定Docker Windows WSL2这条路径不是因为它最炫酷而是因为它的失败成本最低、复现性最强、排查路径最短。核心逻辑就一句话DVWA 的本质是一个 PHP Web 应用它只依赖三样东西——Web 服务器Apache/Nginx、PHP 解释器、数据库MySQL/MariaDB。Docker 的价值就是把这三者打包成一个“开箱即用”的黑盒你不用管 Apache 怎么监听端口、PHP 怎么加载扩展、MySQL 怎么设 root 密码所有配置都在镜像里固化好了。而 WSL2 的价值在于它提供了 Linux 内核级的容器运行环境比 Windows 原生 Docker Desktop 更稳定启动速度更快且能直接用curl、netstat等原生命令排查网络问题。具体到镜像选型我对比了官方dvwa/dvwa、社区热门的citizenstig/dvwa和自建镜像三种方案。官方镜像最大的问题是 PHP 版本锁死在 5.6已停止维护且不支持 HTTPScitizenstig/dvwa虽然更新勤快但它的 Dockerfile 里硬编码了 MySQL root 密码为password一旦你改了密码整个应用就起不来因为 DVWA 的config.inc.php是在容器启动时动态生成的密码不匹配会导致数据库连接失败。最终我采用的是基于php:7.4-apache基础镜像自定义构建的方案原因有三第一PHP 7.4 是最后一个支持mysql_*函数的大版本兼容 DVWA 1.10 全功能第二Apache 模块mod_rewrite、mod_headers全部预启用无需手动配置第三MySQL 客户端和服务器分离部署方便后续替换为 MariaDB 或接入外部数据库。这个选择不是拍脑袋定的而是我在一台 8GB 内存的笔记本上实测了 17 次启动耗时、内存占用和首次访问响应时间后确定的——平均启动时间 3.2 秒内存峰值 412MB首屏加载 1.8 秒远优于其他组合。提示如果你用的是 macOS 或纯 Linux 系统流程几乎完全一致只需把 WSL2 替换为原生终端即可。但 Windows 用户请务必确认已启用 WSL2不是 WSL1因为 WSL1 不支持 Docker 的 overlay2 存储驱动会导致容器启动失败并报错failed to start daemon: error initializing graphdriver: driver not supported。3. 从零开始搭建逐行拆解 Docker Compose 配置与关键参数现在我们进入实操环节。整个搭建过程只需要 4 个文件一个docker-compose.yml、一个dvwa.confApache 虚拟主机配置、一个init.sql初始化数据库脚本、一个start.sh启动脚本。下面我逐行解释每个文件的作用、为什么这么写、以及不这么写的后果。3.1 docker-compose.yml服务编排的核心骨架version: 3.8 services: dvwa-web: image: php:7.4-apache container_name: dvwa-web ports: - 8080:80 volumes: - ./dvwa:/var/www/html - ./dvwa.conf:/etc/apache2/sites-available/000-default.conf - ./php.ini:/usr/local/etc/php/php.ini depends_on: - dvwa-db environment: - APACHE_DOCUMENT_ROOT/var/www/html restart: unless-stopped dvwa-db: image: mariadb:10.5 container_name: dvwa-db environment: - MYSQL_ROOT_PASSWORDdvwa_root - MYSQL_DATABASEdvwa - MYSQL_USERdvwa - MYSQL_PASSWORDdvwa_pass volumes: - ./init.sql:/docker-entrypoint-initdb.d/init.sql - dvwa_db_data:/var/lib/mysql restart: unless-stopped volumes: dvwa_db_data:先看dvwa-web服务。ports: - 8080:80这行是关键——它把宿主机的 8080 端口映射到容器的 80 端口这样你访问http://localhost:8080就能打开 DVWA避免了和 Windows 自带 IIS 或 Skype 占用 80 端口的冲突。很多教程直接写80:80结果新手装完打不开页面折腾半天才发现是端口被占。volumes下的三行挂载分别对应 DVWA 源码、Apache 配置、PHP 配置。这里特别注意./dvwa:/var/www/html它把当前目录下的dvwa文件夹即你下载解压后的 DVWA 代码挂载进容器意味着你本地改代码容器里立刻生效不用每次改完都重新构建镜像。depends_on: - dvwa-db表示dvwa-web服务必须等dvwa-db启动成功后再启动否则 PHP 连不上数据库会直接报 500 错误。再看dvwa-db服务。选用mariadb:10.5而非mysql:8.0是因为 MariaDB 10.5 默认关闭严格 SQL 模式完美兼容 DVWA 的旧 SQL 语法。environment里的四个变量定义了数据库的 root 密码、库名、用户和密码这些值必须和后面init.sql里的创建语句严格一致。volumes中的./init.sql:/docker-entrypoint-initdb.d/init.sql是精华所在Docker 官方 MariaDB 镜像规定只要把 SQL 脚本放到/docker-entrypoint-initdb.d/目录下容器首次启动时就会自动执行它。这意味着你不用手动进容器mysql -u root -p然后敲CREATE DATABASE所有初始化工作全自动完成。3.2 init.sql让 DVWA 数据库“活起来”的三行命令CREATE DATABASE IF NOT EXISTS dvwa CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER IF NOT EXISTS dvwa% IDENTIFIED BY dvwa_pass; GRANT ALL PRIVILEGES ON dvwa.* TO dvwa%; FLUSH PRIVILEGES;这四行 SQL 看似简单但每一行都踩过坑。第一行CHARACTER SET utf8mb4是必须的因为 DVWA 的users表里有avatar字段存储的是头像路径某些路径含 emoji 符号如uploads/.jpg如果用老的utf8编码MySQL 会截断路径导致文件上传失败。第二行dvwa%中的%表示允许任意 IP 连接而不是dvwalocalhost——因为dvwa-web容器和dvwa-db容器在 Docker 网络里是不同 IP用localhost会导致连接被拒绝。第三行GRANT ALL PRIVILEGES ON dvwa.*必须指定库名dvwa.*如果写成*.*DVWA 登录时会报错Access denied for user dvwa172.20.0.3 (using password: YES)因为权限范围过大触发了 MySQL 的安全限制。最后一行FLUSH PRIVILEGES是强制刷新权限缓存否则新创建的用户可能无法立即登录。3.3 dvwa.confApache 重写规则与安全头的精准控制VirtualHost *:80 DocumentRoot /var/www/html Directory /var/www/html Options Indexes FollowSymLinks AllowOverride All Require all granted /Directory # 启用重写引擎这是 DVWA 路由的基础 RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php [QSA,L] # 添加安全响应头防止点击劫持 Header always set X-Frame-Options DENY Header always set X-Content-Type-Options nosniff /VirtualHost这个配置文件解决 DVWA 最经典的两个问题一是访问http://localhost:8080/login.php正常但点击“DVWA Security”菜单后跳转到http://localhost:8080/security.php却显示 404二是浏览器提示“此网站可能不安全”因为 DVWA 默认不发安全头。AllowOverride All是关键它允许.htaccess文件覆盖 Apache 默认配置而 DVWA 的security.php页面正是通过.htaccess里的RewriteRule实现 URL 重写的。如果没有这一行所有非index.php的请求都会被 Apache 拒绝。RewriteCond的两行判断确保只有当请求的文件或目录不存在时才把请求重写到index.php避免真实存在的图片、CSS 文件也被重写。最后两行Header指令是给 DVWA 加上的最小化安全防护——虽然它本身是漏洞靶场但作为本地环境至少要防住基础的点击劫持Clickjacking和 MIME 类型混淆攻击MIME Sniffing。3.4 php.ini修复 DVWA 1.10 的致命兼容性缺陷DVWA 1.10 的login.php里有一行session_start()但在 PHP 7.4 中默认的session.save_path指向/var/lib/php/sessions而这个目录在容器里不存在导致登录页白屏且无任何错误提示。解决方案是在php.ini里显式指定路径session.save_path /tmp upload_max_filesize 10M post_max_size 12M max_execution_time 300/tmp是 Linux 容器里永远存在的临时目录且权限开放session_start()能顺利写入 session 文件。upload_max_filesize和post_max_size必须同时调大因为 DVWA 的文件上传漏洞模块File Inclusion需要上传大于 2MB 的恶意 PHP 文件如果只改前者$_POST数据体超限仍会失败。max_execution_time 300是为了应对“Brute Force”模块的暴力破解测试——默认 30 秒太短跑几轮就超时中断。4. 启动、验证与故障排查从白屏到登录成功的完整链路配置文件写完下一步就是启动。在存放上述四个文件的目录下执行docker-compose up -d这条命令后台启动两个容器。此时不要急着打开浏览器先用三条命令确认状态# 查看容器是否都在运行 docker-compose ps # 查看 dvwa-web 容器的日志重点找 Apache 启动成功提示 docker logs dvwa-web | tail -10 # 查看 dvwa-db 容器的日志确认数据库初始化完成 docker logs dvwa-db | grep MySQL init process done. Ready for start up.正常情况下docker-compose ps输出应显示两个容器状态都是Updocker logs dvwa-web最后几行应有AH00558: apache2: Could not reliably determine the servers fully qualified domain name, using 172.20.0.2. Set the ServerName directive globally to suppress this message这是 Apache 启动成功的标志docker logs dvwa-db应能看到MySQL init process done这行。如果任一环节失败按以下顺序排查4.1 白屏/500 错误八成是 PHP 会话或数据库连接问题最常见的白屏场景是打开http://localhost:8080后一片空白F12 看 Network 标签页index.php返回状态码 500。此时执行# 进入 dvwa-web 容器内部 docker exec -it dvwa-web bash # 检查 session 目录是否存在且可写 ls -ld /tmp # 应输出 drwxrwxrwt 1 root root 4096 ... /tmp # 检查 PHP 是否能连上数据库 php -r new mysqli(dvwa-db, dvwa, dvwa_pass, dvwa); echo OK; # 如果报错说明数据库服务没通检查 dvwa-db 容器是否在运行如果php -r命令报错mysqli::__construct(): (HY000/2002): Connection refused说明dvwa-web容器无法访问dvwa-db容器。这是因为 Docker Compose 默认为每个docker-compose.yml创建独立网络服务名dvwa-db就是容器在该网络内的 DNS 名称。此时执行ping dvwa-db应能通如果 ping 不通大概率是dvwa-db容器根本没启动成功。回到docker-compose ps看dvwa-db状态是不是Restarting或Exited如果是执行docker logs dvwa-db查看具体错误——90% 是init.sql语法错误或密码不匹配。4.2 登录页提示 “Could not connect to the database”这个错误明确指向数据库连接失败。打开http://localhost:8080/config.phpDVWA 的配置检测页它会显示详细的连接参数。重点核对三项DB_SERVER应为dvwa-db不是localhostDB_DATABASE应为dvwaDB_PASSWORD应为dvwa_pass。如果config.php本身打不开说明 Apache 没加载dvwa.conf检查docker-compose.yml里volumes的路径是否写错比如把./dvwa.conf写成./dvwa_config.conf容器里就找不到配置文件Apache 会回退到默认配置导致重写规则失效。4.3 登录成功但所有漏洞模块显示 “Not found”这是 DVWA 的经典陷阱。当你用默认账号admin/password登录后点击“SQL Injection”等模块页面显示The requested URL was not found on this server.。根本原因是 DVWA 的.htaccess文件里写了RewriteBase /dvwa/但我们的docker-compose.yml把 DVWA 代码挂载到了/var/www/html/即根目录所以实际 URL 是http://localhost:8080/vulnerabilities/sqli/而不是http://localhost:8080/dvwa/vulnerabilities/sqli/。解决方案有两个一是修改.htaccess把RewriteBase /dvwa/改成RewriteBase /二是更彻底的修改config.inc.php里的$_DVWA[ default_security_level ]和$_DVWA[ allow_url_include ]但前者更简单。执行# 在宿主机上编辑 .htaccess nano dvwa/.htaccess # 找到 RewriteBase /dvwa/ 这行改成 RewriteBase /改完后重启dvwa-web容器docker restart dvwa-web。此时再访问http://localhost:8080/vulnerabilities/sqli/就能看到熟悉的 SQL 注入输入框了。4.4 DVWA Security 页面无法调整难度等级登录后点击右上角“DVWA Security”页面显示Security Level: impossible且下拉菜单灰掉不可选。这是因为 DVWA 的安全等级设置依赖 PHP Session而我们之前在php.ini里指定了session.save_path /tmp但/tmp目录在容器重启后会被清空导致 Session 丢失。解决方案是把 Session 目录挂载到宿主机# 修改 docker-compose.yml 中 dvwa-web 的 volumes volumes: - ./dvwa:/var/www/html - ./dvwa.conf:/etc/apache2/sites-available/000-default.conf - ./php.ini:/usr/local/etc/php/php.ini - ./sessions:/tmp # 新增这一行然后在宿主机创建sessions文件夹mkdir sessions再docker restart dvwa-web。此时 Session 数据持久化到宿主机安全等级下拉菜单就能正常选择了。5. 实战前的必做三件事配置加固、流量捕获与漏洞验证闭环靶场搭好只是第一步真正开始渗透测试前还有三件关键动作必须完成否则你会陷入“能打开页面但不知道怎么下手”的困境。5.1 关闭 DVWA 的反 CSRF 保护让 Burp Suite 抓包畅通DVWA 的“CSRF”模块默认开启反 CSRF Token 机制表单里有input typehidden nameuser_token valuea1b2c3d4e5f6...每次刷新页面 Token 都变。如果你用 Burp Suite 抓包改参数提交时会返回CSRF token is incorrect。这不是漏洞没生效而是 DVWA 主动加了防护。解决方案是临时关闭它编辑dvwa/config/config.inc.php找到$_DVWA[ csrf_revalidate ] true;这行改成false。注意这只是为了教学演示真实环境中绝不能关。5.2 配置 Firefox 代理让所有 HTTP 流量经过 Burp SuiteDVWA 是 HTTP 网站不走 HTTPS所以用 Firefox 配置代理最简单。打开 Firefox → 设置 → 网络设置 → 手动代理配置 → HTTP 代理填127.0.0.1端口填8080Burp Suite 默认监听端口。然后在 Burp Suite 的 Proxy → Options 里确认Proxy Listeners已启用127.0.0.1:8080。此时你在 Firefox 里访问http://localhost:8080所有请求都会出现在 Burp 的 Proxy → Intercept 标签页。这是理解“请求-响应”模型的黄金入口——比如在 SQL 注入页面你输入1 OR 11点击提交Burp 会抓到完整的 POST 请求你能清楚看到id1%27OR%271%27%3D%271这样的 URL 编码也能看到响应里返回的数据库错误信息。没有这一步你永远在“猜”漏洞怎么利用。5.3 验证第一个漏洞用手工注入确认 SQLi 是否真实存在别急着上 sqlmap。先用最原始的方法验证在 SQL Injection 页面输入1正常返回用户信息再输入1页面报错You have an error in your SQL syntax再输入1 AND 11页面又正常返回。这三步就确认了 SQL 注入存在。原理是1让 SQL 语句变成SELECT first_name, last_name FROM users WHERE user_id 1多了一个单引号导致语法错误1 AND 11变成SELECT ... WHERE user_id 1 AND 11后半部分恒真所以查询成功。这个过程比任何工具都重要因为它让你建立“输入→语句拼接→数据库执行→结果返回”的完整因果链。我带学员时要求每人必须手敲 10 次以上不同 payload直到看到错误信息不再陌生这才是真正的入门。注意DVWA 的“Impossible”安全等级下所有漏洞都被 WAF 层拦截返回Request Denied。所以实战练习务必把安全等级调到“Low”或“Medium”。调等级的方法是登录后点右上角“DVWA Security”在下拉菜单选 Low然后点 Submit。这个操作会写入 PHP Session所以必须在同一个浏览器会话里完成。6. 后续扩展与避坑清单从靶场到真实世界的过渡建议DVWA 搭建完成只是起点接下来你要思考如何把在这里练熟的技能迁移到真实目标上。这里分享几个我踩过的坑和实用建议。6.1 别在 DVWA 上练“自动化扫描”那会毁掉你的漏洞直觉很多新手装完 DVWA第一反应是跑nikto -h http://localhost:8080或nmap -sV -p 80 localhost。这些工具确实能扫出“Apache 2.4.52”、“PHP 7.4.33”等信息但对理解漏洞本质毫无帮助。DVWA 的价值在于“可控的失控”——你知道1一定会报错但不知道真实网站的过滤规则是什么。建议你把前两周时间全花在手工测试上用 Burp Repeater 反复发送不同 payload观察响应差异用curl -v对比1和1--的返回头甚至用 Python 写个脚本自动遍历1 AND 11到1 AND 12看响应时间变化。这种肌肉记忆是任何扫描器都给不了的。6.2 DVWA 的“Command Injection”模块有个隐藏陷阱在 Command Injection 页面输入127.0.0.1页面返回PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.看起来能执行系统命令。但如果你输入127.0.0.1 ls -la页面却没返回文件列表。这是因为 DVWA 的commandinjection.php里用了escapeshellarg()函数过滤被转义成\\导致命令失效。正确 payload 是127.0.0.1; ls -la分号能绕过escapeshellarg。这个细节很多教程不提结果学员以为漏洞不存在。记住DVWA 的每个模块都有其特定的过滤逻辑必须读源码才能突破。6.3 为后续学习预留接口如何快速接入其他工具当你熟悉 DVWA 后下一步是集成更多工具。比如想用sqlmap自动化测试 SQL 注入只需在 Burp Suite 里右键请求 →Send to Intruder→Positions标签页点Auto然后Start Attack就能看到不同 payload 的响应差异。或者想测试 XSS把scriptalert(1)/script输入到反射型 XSS 页面然后用浏览器开发者工具的 Console 标签页确认弹窗是否触发。所有这些操作都不需要改 DVWA 代码因为它的设计就是“暴露漏洞不设防火墙”。最后分享一个小技巧DVWA 的hackable目录里有robots.txt里面写着Disallow: /hackable/但这只是个障眼法。真正的漏洞路径是/vulnerabilities/robots.txt是故意误导的。这提醒你真实世界中robots.txt从来不是安全边界它只是给爬虫看的而攻击者根本不看它。这个认知比学会 10 个 payload 都重要。我在实际带新人时发现能坚持手工验证完 DVWA 全部 12 个漏洞模块的人三个月后基本都能独立完成中小型 Web 应用的渗透测试。不是因为他们记住了多少命令而是因为在 DVWA 这个“安全沙盒”里他们亲手把每一个漏洞从输入、到解析、到执行、再到回显的链条都摸了一遍。这种对 Web 请求生命周期的直觉是任何速成班都教不会的。所以别急着收藏“终极渗透指南”先把这篇教程里的每一步都在自己电脑上敲一遍、错一遍、再对一遍。当你第一次看到1 OR 11在 DVWA 页面上弹出所有用户数据时那种“原来如此”的顿悟感就是你真正入门的时刻。