中文 English

密码都不用就能登root?PVE爆出严重认证绕过漏洞(CVE-2023-54391),一文教你把Proxmox安全彻底焊死!

发布时间: 2026-09-19 · 阅读量 --
Proxmox VE PVE 安全 Security 虚拟化 Virtualization 漏洞分析 Vulnerability 运维 Linux 自动化 Automation

先说结论:如果你曾经把 Proxmox VE 的 8006 管理端口暴露在公网上,且核心组件版本低于 8.0.4,请立刻、彻底掐断外网连接!

这绝不是那种需要复杂内存对齐或依赖特定网络时序的冷门理论漏洞,而是一个无需任何账号密码、甚至无需凭据,仅仅构造一行 HTTP 请求就能直接颁发 root@pam 超级管理员票据的逻辑认证绕过漏洞

更致命的是:受影响最深的是已经被官方宣告生命周期终结(EOL)的 Proxmox VE 7.x 系列以及未及时打补丁的早期 8.0 版本。在全球自动化爬虫和僵尸网络的疯狂扫描下,已有大量真实受害者报告遭遇 Rootkit 潜伏、隐蔽 SSH 后门植入、挖矿木马横行及虚拟机底层数据泄露

请务必记住:对于曾经暴露在公网且命中漏洞的机器,千万不要以为在终端敲一句 apt update 就万事大吉了!一旦失陷,必须按“已沦陷主机”思路做全盘重建与纯净恢复!

概念大图:Proxmox 虚拟化底层防御与绕过密钥解构


一、发生了什么?从“静默补丁”到全网狩猎的 CVE-2023-54391

在企业私有云、IDC 机房乃至全球数十万 Homelab 极客的机柜里,Proxmox Virtual Environment(简称 PVE) 凭借开源、轻量、集成 KVM/LXC 和强大 Web 管理界面的特性,成为了虚拟化平台的不二之选。然而,正是这个每天用来运筹帷幄的“总指挥部”,最近引爆了一场信任危机。

该漏洞官方安全公告追踪编号为 PSA-2026-00043-1,正式 CVE 编号为 CVE-2023-54391,CVSS v3.1 严重性评分高达 9.8(Critical)

1. 荒诞的“静默补丁间隙”(Silent Patch Gap)

很多人可能会好奇:为什么一个在 2026 年下半年引发大范围攻击的漏洞,编号开头却是 CVE-2023-

这涉及开源软件维护中经常出现的“静默补丁间隙”:

机理对比图:正常认证流程 vs CVE-2023-54391 绕过攻击流程


二、小学生也能秒懂:游乐园“VIP手环乌龙”通俗比喻

很多非安全背景的运维同学在看官方代码补丁时,面对一堆 Perl 脚本里的 $tfa_challenge$param->{'tfa-challenge'} 容易发懵。我们不妨用日常生活中的常见场景来做个生动的打比方。

假设有一座超大型游乐园(Proxmox VE 宿主机),游乐园的正门口设有严格的检票口(API 认证端点 /api2/json/access/ticket)。

1. 正常人进游乐园的规矩

2. 检票员脑子短路的“乌龙逻辑”

在易受攻击的旧版本检票系统里,检票员有一个极其离奇的“偷懒逻辑”:

  1. 如果某人走上前来,二话不说,直接伸出手腕冲检票员喊:“喂!我是来刷 VIP 手环第二道检验的!”(请求中附带了 tfa-challenge 参数)。
  2. 检票员脑子一热,擅自推断:“既然他已经在走第二道手环检验流程了,那第一道门票肯定早就由同事查验过了吧!所以门票我就不用看了!”——于是,密码校验被彻底跳过了!
  3. 紧接着,检票员翻开手里的名册核对:“等等,名册上写着,你(比如 root@pam 账户)根本就没办理过 VIP 手环啊!”
  4. 最令人瞠目结舌的一幕发生了:检票员非但没有报警抓人,反而心想:“既然你压根没办手环,那第二道手环校验当然也就不需要验了!既然门票‘已经验过’,手环‘不需要验’,那您请进总控室吧!
  5. 检票员毕恭毕敬地双手奉上乐园万能总钥匙(返回具备超级特权的 root Ticket 和 CSRF Token)!
黑客视角:
我不带任何门票(不用密码),
手腕上绑一根破塑料绳假装在刷手环(随便填一个 tfa-challenge=bypass),
大摇大摆从正门走进了总控室!

这也是为什么在这次安全事故中,提前开启了 2FA 双因素认证的用户反而意外幸免——因为名册上如果真记录了你办过手环,检票员就会较真地逼你出示正确的跳码,黑客的假塑料绳就当场露馅了!

通俗比喻图:游乐园 VIP 手环乌龙与保安致命逻辑漏洞


三、谁受影响?为什么这次漏洞致命到了骨髓?

1. 受影响版本矩阵

软件产品 受影响版本区间 安全版本水位线 官方生命周期状态 潜在风险级别
Proxmox VE 7.x PVE 7.0 至 7.4-17 (全部版本) 无原生安全版 (已 EOL) 已于 2024-07 停止维护 极度致命 (CRITICAL)
Proxmox VE 8.x PVE 8.0 初期版本 8.0.4 及以上 正常维护支持中 (LTS) 升级后免疫
核心包 libpve-access-control < 8.0.4 (如 7.0-7 ~ 8.0.3) >= 8.0.4 N/A 漏洞根源所在

2. 为什么危险?超乎想象的“虚拟化击穿”

如果在普通的单体应用(例如个人博客)里拿到 root,黑客顶多篡改个网页;但在作为**底层虚拟化底座(Hypervisor)**的 PVE 上拿到 root,破坏力是降维打击级别的:

  1. 瞬时接管全部业务系统:宿主机上的所有 Windows 虚拟机、Linux 生产容器(LXC)、软路由(iKuai/OpenWrt)、自建数据库,黑客均可在 Web 界面一键开启 VNC 终端无障碍进入。
  2. 底层存储直接脱库:ZFS 池、LVM-Thin 卷、Ceph 集群的原始镜像直接暴露在宿主机文件系统中,黑客可以静默拷贝整个虚拟硬盘(RAW/QCOW2),里面的企业源码、私钥凭据一览无余。
  3. 内网横向移动跳板:PVE 宿主机通常直连内网 Management 网段和 Trunk 交换机,一旦宿主机沦陷,黑客直接以特权网卡接入公司局域网,ARP 欺骗、内网嗅探、域控渗透畅通无阻。
  4. 勒索病毒全盘加密:黑客可以使用自动化脚本批量停止全部虚拟机,直接调用底层 qemu-img 加密虚拟磁盘并留下勒索信。

真实截图:curl 漏洞复现验证:零密码直取 root 全局凭证与 CSRF Token


四、我的机器中招了吗?极简自查与深度取证排查

想要知道自己的 PVE 节点是否处于危险水位,终端里的一行命令就能给出答案。

1. 黄金自查命令:核查核心组件版本

登录 PVE 节点的命令行(通过控制台或内网 SSH),执行以下命令:

dpkg-query -W libpve-access-control

真实截图:dpkg-query 软件包版本与 PVE 宿主机内核生命周期巡检

[!IMPORTANT] PVE 7 用户切忌掩耳盗铃! 很多运维人员会问:“我能不能在 PVE 7 上只升级打补丁的 libpve-access-control?” 官方给出的权威结论非常明确:Proxmox VE 7 已经全面 EOL,官方源早已停止维护,甚至底层的 Debian 11 (Bullseye) 也早已进入生命周期暮年。官方强烈要求所有生产环境尽快迁移升级至 Proxmox VE 8.x,永远停留在已经停止维护的旧系统上,躲得过初一躲不过十五。

2. 深度日志取证:看看黑客来过没有

如果你的机器版本低于 8.0.4,且 8006 曾经暴露过公网,立刻执行以下取证命令,翻查系统底层的 Web 访问日志:

grep "POST /api2/json/access/ticket" /var/log/pveproxy/access.log*

正常情况下,这里记录的应该是你本地内网 IP 发起的登录动作。如果你在输出中看到了陌生的公网 IP、境外 IP,或者连续密集的请求且 HTTP 状态码为 200,且后续紧跟着 termproxy(控制台终端代理)、qemulxc 等 API 调度痕迹,说明黑客已经借助自动化脚本完成了破门而入!

真实截图:pveproxy 访问日志取证审计:捕获境外扫描器利用痕迹


五、灵魂拷问:8006 端口到底能不能直接暴露到公网?

答案斩钉截铁:绝对不能!任何把 8006 端口直接做公网端口映射、放进路由器 DMZ、或者绑定公网原生 IP 的做法,都是极其危险的“自杀行为”!

很多用户图一时方便:“我在外面上班,家里装了软路由和 PVE,路由器上顺手做个端口映射 WAN:8006 -> PVE:8006,或者改个冷门高位端口比如 WAN:58006 -> PVE:8006,输入密码就能随时管机器,多省事啊!”

殊不知,在当今的网络空间对抗中,这种想法脆弱不堪:

  1. 高位端口掩耳盗铃:像 Shodan、Censys、ZoomEye 等全网测绘平台,根本不是靠猜端口,而是全量扫描所有 65535 个端口并抓取 SSL 握手证书。PVE 默认自签证书中明晃晃写着 pve-root-ca,扫网脚本一毫秒就能精准定位。
  2. pveproxy 架构定位:Proxmox 官方论坛管理团队在数百个安全求助帖中反复强调过:pveproxy 本质上是一个轻量级的管理接口代理,其设计初衷是工作在受信任的内部专网(Trusted Management Network)中。它没有经过像 Nginx/Cloudflare 那样历经万难的公网抗 DDoS、WAF 规则清洗和内存防御加固,直接把它扔到公网上裸奔,无异于开着纸糊的装甲车上战场。
  3. 零日与逻辑漏洞频发:即使修好了 CVE-2023-54391,未来谁能保证不会出现下一个类似 API 绕过、缓冲区溢出或拒绝服务漏洞?

拓扑对比图:8006 裸奔公网的高危隐患 vs 纵深防御三道门架构


六、纵深防御实操:怎样把 Proxmox 安全彻底焊死?

面对险象环生的网络环境,正确的姿势是构建**“四重防御纵深护城河”**。

第一重防线:零信任网络与端口隐身(Tailscale / WireGuard)

公网完全禁止对 8006 开放端口!远程管理统一收敛至虚拟专用网络:

第二重防线:Proxmox 数据中心原生防火墙配置

哪怕在局域网内,也要防范“内网横向穿透”。在 PVE 的 Datacenter -> Firewall 中配置白名单规则:

  1. 开启防火墙总开关(Input Policy 设为 DROP,Output Policy 设为 ACCEPT)。
  2. 添加规则仅允许特定的 VPN 网段(例如 10.0.X.0/24)访问 TCP 800622
  3. 默认丢弃所有来自 WAN 或非信任接口的入站连接。

真实截图:Proxmox VE 数据中心级防火墙策略配置实操

第三重防线:强制开启双因素认证(2FA / MFA)

本次漏洞中最神奇的“护身符”就是 2FA。只要开启了 2FA,黑客利用伪造挑战绕过第一道密码的阴谋就会彻底失效。

在 PVE Web 界面操作极其简便:

  1. 进入 Datacenter -> Two-Factor Authentication,点击 Add
  2. 推荐首选 WebAuthn / FIDO2(硬件级防钓鱼,直接插入 YubiKey 碰一下即可授权,安全等级最高)。
  3. 备选配置 TOTP(基于时间的一次性密码):使用手机上的 Microsoft Authenticator、1Password 或 Google Authenticator 扫描生成的二维码,输入 6 位跳码保存。
  4. 确保为关键超管账户 root@pam 以及所有普通管理员用户强制开启。

真实截图:Proxmox VE 双因素认证 (2FA / MFA) 配置与硬件密钥绑定

第四重防线:SSH 堡垒化加固

很多用户 PVE 的 8006 虽然防住了,但宿主机的 22 端口还在公网裸奔。 编辑 /etc/ssh/sshd_config

# 彻底禁用密码登录,仅允许高强度私钥认证
PasswordAuthentication no
PermitRootLogin prohibit-password
# 推荐改用 Ed25519 纯密钥对
PubkeyAuthentication yes

重启服务生效:systemctl restart sshd


七、科学更新策略:从 EOL 泥潭平滑升级到 PVE 8.x

对于还在使用 PVE 7.x 的用户,迁移到 PVE 8.x 是彻底消除历史包袱的唯一正道。

1. 配置免订阅软件源(pve-no-subscription)

对于家庭实验室或个人用户,如果没有购买企业级订阅 Key,默认的企业源会报 401 错误导致更新停滞。应切换为官方免订阅源:

# 注释掉默认企业源
sed -i 's/^deb/#deb/' /etc/apt/sources.list.d/pve-enterprise.list

# 添加官方无订阅稳定更新源 (以 Bookworm 为例)
cat << 'EOF' > /etc/apt/sources.list.d/pve-no-subscription.list
deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription
EOF

apt update

2. 运行官方升级预检工具

在跨大版本升级前,Proxmox 官方提供了专门的检查工具:

pve7to8 --full

根据工具提示,逐项消除所有的 WARNFAIL(例如清理废弃内核、检查集群 Corosync 状态、更新存储格式等),确认无误后再执行全量大版本升级。


八、已经被入侵怎么办?千万别“升个级就当无事发生”!

这是本文最核心、最严正的警告。

在现实案例排查中,笔者见到过太多这样的侥幸心理:

“哎呀,我查了下机器,确实是 7.4 老版本,日志里好像也有两条外网 IP 访问记录……那我赶紧敲个 apt update && apt upgrade -y 把核心包升级到最新,是不是就安全了?”

大错特错!这是典型的“鸵鸟政策”!

1. 真实黑客是怎么潜伏的?

黑客自动化攻击脚本在通过 CVE-2023-54391 获取 root 票据后的 30 秒内,绝不仅仅是逛一圈,而是会立即触发一连串的“后渗透持久化动作”:

  1. 植入 SSH 免密后门:自动向 /root/.ssh/authorized_keys 尾部追加攻击者的私有公钥。以后黑客连 Web 界面都不用看,直接从后门 SSH 畅通无阻登入。
  2. 注入用户态 Rootkit:修改 /etc/ld.so.preload,注入恶意共享动态链接库(如 libprocesshide.so)。此时,你在终端里执行 ps -eftoplsnetstat,看到的都是经过动态库过滤的“假象”,挖矿进程和外联连接被完美隐形!
  3. 系统定时任务持久化:在 /etc/cron.d//var/spool/cron/crontabs//etc/systemd/system/ 埋下看似合法的更新定时器,每隔十分钟从境外 C2 节点拉取最新的二进制 Payload。
  4. 横向渗透虚拟机:借助 PVE 的存储挂载点,直接往各虚拟机的虚拟磁盘文件里注入恶意启动脚本。

真实截图:失陷节点排查实录:SSH 后门、隐蔽 Cron 与 Rootkit 注入证据

2. 唯一可信处置思路:按已沦陷主机(Assumed Compromised)全盘重建!

在信息安全界有一条铁律:当敌方已经在你的操作系统内核层获得 root 权限并执行了任意指令后,这套操作系统的完整性即告永久归零。 任何在受染系统内部执行的“杀毒软件”和“软件包升级”都是自欺欺人。

正确的应急响应处置必须遵循以下标准 SOP(标准作业程序):

决策流程图:Proxmox VE 疑似入侵失陷应急响应决策树

  1. 物理断网隔离:立即拔掉受害宿主机的网线,或在交换机上将其所在的交换机端口打入黑洞 VLAN,切断黑客远端 C2 控制链路。
  2. 冷备份重要虚机磁盘:通过物理控制台进入系统,将重要的各业务 VM 原始磁盘文件导出至专用的离线只读存储介质(注意:切勿直接整机打包宿主机系统配置文件)。
  3. 全盘格式化擦除:使用干净的 Proxmox VE 8.x 官方安装介质引导服务器,格式化整块宿主系统盘,彻底清除所有可能潜伏在 MBR/GPT、引导区和动态库中的 Rootkit。
  4. 全新安装并封死端口:重装全新 PVE 系统,第一件事配置好本地防火墙白名单与 Tailscale 隧道,绝不让 8006 与公网发生任何接触。
  5. 从可信历史备份恢复:从入侵发生之前时间点的 Proxmox Backup Server(PBS) 增量备份中恢复虚拟机。
  6. 重置所有安全凭据:更换 root 登录强密码,重置并废弃所有旧的 SSH 密钥对,生成全新的 API Token。

九、全维度纵深防御矩阵:构建牢不可破的虚拟化底座

真正的安全不是一两个孤立的开关,而是一套环环相扣的工程化体系。以下是针对 Proxmox VE 虚拟化底盘的七层纵深防御矩阵:

架构矩阵图:Proxmox VE 七层纵深防御安全矩阵架构


十、跨平台全自动一键审计脚本:Windows 11 / Ubuntu 26.04 / macOS 26

为了让各位读者无需繁琐的手动敲命令排查,本文特别编写了一套跨平台的全自动 PVE 安全体检与健康评分工具包(PVE Security Audit Toolkit)

工具包完全采用系统原生能力开发,不依赖任何第三方依赖库

此外,工具包原生提供了 人工交互模式AI Agent 无人值守模式(JSON 格式直接喂给智能体)

真实截图:跨平台自动化 PVE 安全审计脚本执行实况与健康评分

[!TIP] 你可以直接从博客下载打包好的全套工具脚本:下载 pve-security-toolkit.zip

1. 方案 A:Ubuntu 26.04 / Linux 平台自动化脚本

可以直接在 PVE 宿主机本地运行,或者在 Linux 运维工作站上远程审计:

#!/usr/bin/env bash
# ==============================================================================
# Proxmox VE Automated Security Audit & Hardening Toolkit (Ubuntu 26.04 / Linux)
# ==============================================================================
# Focus: CVE-2023-54391 Auth Bypass Detection, Port 8006 Audit & Backdoor Hunting
# Zero third-party dependencies. Supports Human CLI and Headless AI Agent modes.
# ==============================================================================
set -euo pipefail

TARGET_HOST=""
TARGET_USER="root"
TARGET_PORT="22"
TARGET_KEY=""
LOCAL_MODE=false
FORMAT="text"

while [[ $# -gt 0 ]]; do
    case "$1" in
        --host) TARGET_HOST="$2"; shift 2 ;;
        --user) TARGET_USER="$2"; shift 2 ;;
        --port) TARGET_PORT="$2"; shift 2 ;;
        --key)  TARGET_KEY="$2"; shift 2 ;;
        --local) LOCAL_MODE=true; shift ;;
        --format) FORMAT="$2"; shift 2 ;;
        --agent) FORMAT="json"; shift ;;
        *) echo "Unknown argument: $1" >&2; exit 1 ;;
    esac
done

if [[ "$LOCAL_MODE" == false && -z "$TARGET_HOST" ]]; then
    echo "[ERROR] Either --host <target> or --local must be specified." >&2
    exit 1
fi

run_cmd() {
    local cmd="$1"
    if [[ "$LOCAL_MODE" == true ]]; then
        eval "$cmd" 2>/dev/null || true
    else
        local ssh_opts=("-p" "$TARGET_PORT" "-o" "BatchMode=yes" "-o" "StrictHostKeyChecking=accept-new" "-o" "ConnectTimeout=8")
        [[ -n "$TARGET_KEY" ]] && ssh_opts+=("-i" "$TARGET_KEY")
        ssh "${ssh_opts[@]}" "${TARGET_USER}@${TARGET_HOST}" "$cmd" 2>/dev/null || true
    fi
}

log() {
    local level="$1"; shift
    [[ "$FORMAT" != "json" ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"
}

log "INFO" "Starting Proxmox VE Security Audit on ${TARGET_HOST:-localhost}..."

# 1. Audit libpve-access-control version
PKG_VER="$(run_cmd "dpkg-query -W -f='\${Version}' libpve-access-control" | tr -d ' \r\n')"
if [[ -z "$PKG_VER" ]]; then
    PKG_VER="not-installed"; VER_STATUS="UNKNOWN"
else
    if dpkg --compare-versions "$PKG_VER" "ge" "8.0.4" 2>/dev/null; then
        VER_STATUS="PASS"
    else
        VER_STATUS="CRITICAL_VULNERABLE"
    fi
fi

# 2. Check PVE Release & EOL Status
PVE_MGR_VER="$(run_cmd "pveversion" | head -n 1 | tr -d '\r\n')"
if echo "$PVE_MGR_VER" | grep -q "pve-manager/7\."; then
    RELEASE_STATUS="EOL_WARNING"
elif echo "$PVE_MGR_VER" | grep -q "pve-manager/8\."; then
    RELEASE_STATUS="SUPPORTED"
else
    RELEASE_STATUS="UNKNOWN"
fi

# 3. Check Port 8006 Exposure
LISTEN_8006="$(run_cmd "ss -tulpn | grep -E ':8006\\b'" || true)"
PORT_BIND="RESTRICTED_OR_ABSENT"
echo "$LISTEN_8006" | grep -q "0.0.0.0:8006" && PORT_BIND="ALL_INTERFACES"

# 4. Check 2FA Status for root@pam
TFA_CHECK="$(run_cmd "grep -E '^user:root@pam:' /etc/pve/user.cfg" || true)"
TFA_STATUS="DISABLED"
echo "$TFA_CHECK" | grep -q "tfa=" && TFA_STATUS="ACTIVE"

# 5. Check pveproxy access log for exploit attempts
LOG_EXPLOIT_HITS="$(run_cmd "grep -c 'POST /api2/json/access/ticket' /var/log/pveproxy/access.log 2>/dev/null" || echo "0")"
LOG_EXPLOIT_HITS="${LOG_EXPLOIT_HITS//[^0-9]/}"
LOG_EXPLOIT_HITS="${LOG_EXPLOIT_HITS:-0}"

# 6. Check Persistence: authorized_keys, crontabs, ld.so.preload
AUTH_KEYS_COUNT="$(run_cmd "wc -l < /root/.ssh/authorized_keys 2>/dev/null" || echo "0")"
AUTH_KEYS_COUNT="${AUTH_KEYS_COUNT//[^0-9]/}"
AUTH_KEYS_COUNT="${AUTH_KEYS_COUNT:-0}"

CRON_SUSPICIOUS="$(run_cmd "grep -rnE '(curl|wget|bash|sh|python|\.sh)' /etc/cron* /var/spool/cron/crontabs/ 2>/dev/null | grep -vE '(pve|proxmox|certbot)' | wc -l" || echo "0")"
CRON_SUSPICIOUS="${CRON_SUSPICIOUS//[^0-9]/}"
CRON_SUSPICIOUS="${CRON_SUSPICIOUS:-0}"

LD_PRELOAD_EXISTS=false
run_cmd "test -f /etc/ld.so.preload" >/dev/null 2>&1 && LD_PRELOAD_EXISTS=true

# Compute Overall Health Score (0 - 100)
SCORE=100
[[ "$VER_STATUS" == "CRITICAL_VULNERABLE" ]] && SCORE=$((SCORE - 45))
[[ "$RELEASE_STATUS" == "EOL_WARNING" ]] && SCORE=$((SCORE - 20))
[[ "$TFA_STATUS" == "DISABLED" ]] && SCORE=$((SCORE - 15))
[[ "$PORT_BIND" == "ALL_INTERFACES" ]] && SCORE=$((SCORE - 10))
[[ "$LD_PRELOAD_EXISTS" == true ]] && SCORE=$((SCORE - 50))
(( CRON_SUSPICIOUS > 0 )) && SCORE=$((SCORE - 20))
(( SCORE < 0 )) && SCORE=0

GRADE="A"
if (( SCORE < 50 )); then GRADE="D (CRITICAL)"
elif (( SCORE < 70 )); then GRADE="C (RISK)"
elif (( SCORE < 85 )); then GRADE="B (WARNING)"
elif (( SCORE == 100 )); then GRADE="S (HARDENED)"
fi

if [[ "$FORMAT" == "json" ]]; then
    cat << JSON
{
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "target": "${TARGET_HOST:-localhost}",
  "score": $SCORE,
  "grade": "$GRADE",
  "audit": {
    "libpve_access_control_version": "$PKG_VER",
    "version_status": "$VER_STATUS",
    "pve_release": "$PVE_MGR_VER",
    "release_status": "$RELEASE_STATUS",
    "port_8006_binding": "$PORT_BIND",
    "root_2fa_status": "$TFA_STATUS",
    "pveproxy_ticket_requests": $LOG_EXPLOIT_HITS,
    "authorized_keys_count": $AUTH_KEYS_COUNT,
    "suspicious_crontabs": $CRON_SUSPICIOUS,
    "ld_so_preload_detected": $LD_PRELOAD_EXISTS
  }
}
JSON
else
    echo "================================================================================"
    echo "PROXMOX VE SECURITY AUDIT REPORT: CVE-2023-54391 & HARDENING ASSESSMENT"
    echo "================================================================================"
    echo "Target Host             : ${TARGET_HOST:-localhost} (Port: $TARGET_PORT, User: $TARGET_USER)"
    echo "Overall Score           : $SCORE / 100 [Grade: $GRADE]"
    echo "--------------------------------------------------------------------------------"
    echo "1. libpve-access-control: $PKG_VER [$VER_STATUS]"
    echo "2. Proxmox OS Release   : $PVE_MGR_VER [$RELEASE_STATUS]"
    echo "3. Port 8006 Listener   : $PORT_BIND"
    echo "4. root@pam 2FA Status  : $TFA_STATUS"
    echo "5. pveproxy Ticket Hits : $LOG_EXPLOIT_HITS records"
    echo "6. root SSH Keys        : $AUTH_KEYS_COUNT key(s) installed"
    echo "7. Suspicious Cron Jobs : $CRON_SUSPICIOUS item(s)"
    echo "8. ld.so.preload Hook   : $( [[ "$LD_PRELOAD_EXISTS" == true ]] && echo "DETECTED (DANGER!)" || echo "Clean" )"
    echo "================================================================================"
fi

人工执行方式:

# 本地在 PVE 节点上执行:
sudo bash pve_security_toolkit_ubuntu2604.sh --local

# 从 Ubuntu 客户端远程扫描:
bash pve_security_toolkit_ubuntu2604.sh --host 192.168.X.X --user root

AI Agent 自动配置/执行方式:

# Agent 静默调用获取结构化 JSON 报告,判定是否需触发自动化防御流程:
AGENT_REPORT=$(bash pve_security_toolkit_ubuntu2604.sh --host 192.168.X.X --agent)
echo "$AGENT_REPORT" | jq '.score'

2. 方案 B:macOS 26 平台自动化脚本

在 Mac 终端原生运行,通过 SSH 远程并发审计多台 PVE 机器:

#!/usr/bin/env zsh
# ==============================================================================
# Proxmox VE Automated Security Audit & Hardening Toolkit (macOS 26 / Zsh)
# ==============================================================================
# Focus: CVE-2023-54391 Remote Inspection, Port 8006 Probing & Compromise Audit
# Zero third-party dependencies. Compatible with Apple Silicon and Intel Macs.
# ==============================================================================
set -eu

TARGET_HOST=""
TARGET_USER="root"
TARGET_PORT="22"
TARGET_KEY=""
FORMAT="text"

while [[ $# -gt 0 ]]; do
    case "$1" in
        --host)   TARGET_HOST="$2"; shift 2 ;;
        --user)   TARGET_USER="$2"; shift 2 ;;
        --port)   TARGET_PORT="$2"; shift 2 ;;
        --key)    TARGET_KEY="$2"; shift 2 ;;
        --format) FORMAT="$2"; shift 2 ;;
        --agent)  FORMAT="json"; shift ;;
        *) echo "Unknown argument: $1" >&2; exit 1 ;;
    esac
done

if [[ -z "$TARGET_HOST" ]]; then
    echo "[ERROR] Target host must be specified via --host <IP/Host>." >&2
    exit 1
fi

run_ssh() {
    local cmd="$1"
    local ssh_opts=(-p "$TARGET_PORT" -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=8)
    [[ -n "$TARGET_KEY" ]] && ssh_opts+=(-i "$TARGET_KEY")
    ssh "${ssh_opts[@]}" "${TARGET_USER}@${TARGET_HOST}" "$cmd" 2>/dev/null || true
}

log() {
    local level="$1"; shift
    [[ "$FORMAT" != "json" ]] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"
}

log "INFO" "Probing target Proxmox host: $TARGET_HOST (Port: $TARGET_PORT)..."

# 1. Check libpve-access-control
PKG_VER="$(run_ssh "dpkg-query -W -f='\${Version}' libpve-access-control" | tr -d ' \r\n')"
if [[ -z "$PKG_VER" ]]; then
    PKG_VER="unknown"; VER_STATUS="NOT_DETECTED"
else
    if [[ "$PKG_VER" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
        V_MAJ="${match[1]}"; V_MIN="${match[2]}"; V_REV="${match[3]}"
        if (( V_MAJ > 8 )) || (( V_MAJ == 8 && V_MIN > 0 )) || (( V_MAJ == 8 && V_MIN == 0 && V_REV >= 4 )); then
            VER_STATUS="PASS"
        else
            VER_STATUS="CRITICAL_VULNERABLE"
        fi
    elif [[ "$PKG_VER" =~ ^7\. ]]; then
        VER_STATUS="CRITICAL_VULNERABLE"
    else
        VER_STATUS="PASS"
    fi
fi

# 2. Check PVE Release
PVE_MGR="$(run_ssh "pveversion" | head -n 1 | tr -d '\r\n')"
RELEASE_STATUS="UNKNOWN"
[[ "$PVE_MGR" == *"pve-manager/7."* ]] && RELEASE_STATUS="EOL_WARNING"
[[ "$PVE_MGR" == *"pve-manager/8."* ]] && RELEASE_STATUS="SUPPORTED"

# 3. Check 2FA
TFA_CHECK="$(run_ssh "grep -E '^user:root@pam:' /etc/pve/user.cfg" || true)"
TFA_STATUS="DISABLED"
[[ "$TFA_CHECK" == *"tfa="* ]] && TFA_STATUS="ACTIVE"

# 4. Check Access Logs
LOG_HITS="$(run_ssh "grep -c 'POST /api2/json/access/ticket' /var/log/pveproxy/access.log 2>/dev/null" || echo "0")"
LOG_HITS="${LOG_HITS//[^0-9]/}"
LOG_HITS="${LOG_HITS:-0}"

# 5. Check Persistence
AUTH_KEYS="$(run_ssh "wc -l < /root/.ssh/authorized_keys 2>/dev/null" || echo "0")"
AUTH_KEYS="${AUTH_KEYS//[^0-9]/}"
AUTH_KEYS="${AUTH_KEYS:-0}"

LD_PRELOAD=false
run_ssh "test -f /etc/ld.so.preload" >/dev/null 2>&1 && LD_PRELOAD=true

# Scorecard
SCORE=100
[[ "$VER_STATUS" == "CRITICAL_VULNERABLE" ]] && (( SCORE -= 45 ))
[[ "$RELEASE_STATUS" == "EOL_WARNING" ]] && (( SCORE -= 20 ))
[[ "$TFA_STATUS" == "DISABLED" ]] && (( SCORE -= 15 ))
[[ "$LD_PRELOAD" == true ]] && (( SCORE -= 50 ))
(( SCORE < 0 )) && SCORE=0

GRADE="A"
if (( SCORE < 50 )); then GRADE="D (CRITICAL)"
elif (( SCORE < 70 )); then GRADE="C (RISK)"
elif (( SCORE < 85 )); then GRADE="B (WARNING)"
elif (( SCORE == 100 )); then GRADE="S (HARDENED)"
fi

if [[ "$FORMAT" == "json" ]]; then
    cat << JSON
{
  "timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "target": "$TARGET_HOST",
  "score": $SCORE,
  "grade": "$GRADE",
  "audit": {
    "libpve_access_control_version": "$PKG_VER",
    "version_status": "$VER_STATUS",
    "pve_release": "$PVE_MGR",
    "release_status": "$RELEASE_STATUS",
    "root_2fa_status": "$TFA_STATUS",
    "pveproxy_ticket_requests": $LOG_HITS,
    "authorized_keys_count": $AUTH_KEYS,
    "ld_so_preload_detected": $LD_PRELOAD
  }
}
JSON
else
    echo "================================================================================"
    echo "PROXMOX VE SECURITY AUDIT REPORT: macOS 26 Remote Inspection"
    echo "================================================================================"
    echo "Target Host             : $TARGET_HOST (Port: $TARGET_PORT, User: $TARGET_USER)"
    echo "Overall Security Score  : $SCORE / 100 [Grade: $GRADE]"
    echo "--------------------------------------------------------------------------------"
    echo "1. libpve-access-control: $PKG_VER [$VER_STATUS]"
    echo "2. Proxmox OS Release   : $PVE_MGR [$RELEASE_STATUS]"
    echo "3. root@pam 2FA Status  : $TFA_STATUS"
    echo "4. pveproxy Ticket Hits : $LOG_HITS records"
    echo "5. root SSH Keys Count  : $AUTH_KEYS key(s)"
    echo "6. ld.so.preload Hook   : $( [[ "$LD_PRELOAD" == true ]] && echo "DETECTED (DANGER!)" || echo "Clean" )"
    echo "================================================================================"
fi

人工执行方式:

zsh pve_security_toolkit_macos26.zsh --host pve-node.lan --user root

AI Agent 自动配置/执行方式:

# Agent 自动抓取审计结果并判断是否通过:
zsh pve_security_toolkit_macos26.zsh --host pve-node.lan --agent | jq -r '.grade'

3. 方案 C:Windows 11 平台自动化脚本

适用于 Windows 11 环境下的 PowerShell 7 或原生 Windows Terminal:

# ==============================================================================
# Proxmox VE Automated Security Audit & Hardening Toolkit (Windows 11 / PowerShell)
# ==============================================================================
# Focus: CVE-2023-54391 Remote Inspection, Port 8006 Auditing & Backdoor Hunting
# Zero third-party dependencies. Compatible with Windows 11 Windows Terminal / PS 7.x / 5.1.
# ==============================================================================
[CmdletBinding()]
param (
    [Parameter(Mandatory = $true, Position = 0)]
    [string]$HostName,

    [Parameter(Mandatory = $false)]
    [string]$User = "root",

    [Parameter(Mandatory = $false)]
    [int]$Port = 22,

    [Parameter(Mandatory = $false)]
    [string]$KeyPath = "",

    [Parameter(Mandatory = $false)]
    [ValidateSet("text", "json")]
    [string]$Format = "text",

    [Parameter(Mandatory = $false)]
    [switch]$Agent
)

if ($Agent) { $Format = "json" }

function Invoke-RemotePveCommand {
    param([string]$Command)
    $sshArgs = @("-p", $Port, "-o", "BatchMode=yes", "-o", "StrictHostKeyChecking=accept-new", "-o", "ConnectTimeout=8")
    if ($KeyPath -and (Test-Path $KeyPath)) { $sshArgs += @("-i", $KeyPath) }
    $sshArgs += @("$User@$HostName", $Command)
    try {
        $res = & ssh.exe $sshArgs 2>$null
        return ($res -join "`n").Trim()
    } catch { return "" }
}

if ($Format -ne "json") {
    Write-Host "[INFO] Initiating Proxmox VE Security Audit for $HostName (Port $Port)..." -ForegroundColor Cyan
}

# 1. Check libpve-access-control
$pkgVer = Invoke-RemotePveCommand "dpkg-query -W -f='`${Version}' libpve-access-control"
$verStatus = "UNKNOWN"
if ($pkgVer) {
    if ($pkgVer -match '^([0-9]+)\.([0-9]+)\.([0-9]+)') {
        $maj = [int]$Matches[1]; $min = [int]$Matches[2]; $rev = [int]$Matches[3]
        if (($maj -gt 8) -or ($maj -eq 8 -and $min -gt 0) -or ($maj -eq 8 -and $min -eq 0 -and $rev -ge 4)) {
            $verStatus = "PASS"
        } else { $verStatus = "CRITICAL_VULNERABLE" }
    } elseif ($pkgVer -like "7.*") { $verStatus = "CRITICAL_VULNERABLE" }
    else { $verStatus = "PASS" }
}

# 2. Check PVE Release
$pveMgr = Invoke-RemotePveCommand "pveversion"
$releaseStatus = "UNKNOWN"
if ($pveMgr -like "*pve-manager/7.*") { $releaseStatus = "EOL_WARNING" }
elseif ($pveMgr -like "*pve-manager/8.*") { $releaseStatus = "SUPPORTED" }

# 3. Check 2FA
$tfaRaw = Invoke-RemotePveCommand "grep -E '^user:root@pam:' /etc/pve/user.cfg"
$tfaStatus = if ($tfaRaw -like "*tfa=*") { "ACTIVE" } else { "DISABLED" }

# 4. Check Access Logs
$logHits = Invoke-RemotePveCommand "grep -c 'POST /api2/json/access/ticket' /var/log/pveproxy/access.log 2>/dev/null"
$logHits = ($logHits -replace '[^0-9]', '')
if (-not $logHits) { $logHits = "0" }

# 5. Check Persistence
$authKeys = Invoke-RemotePveCommand "wc -l < /root/.ssh/authorized_keys 2>/dev/null"
$authKeys = ($authKeys -replace '[^0-9]', '')
if (-not $authKeys) { $authKeys = "0" }

$ldPreload = Invoke-RemotePveCommand "test -f /etc/ld.so.preload && echo 'YES' || echo 'NO'"
$ldPreloadExists = ($ldPreload -eq "YES")

# Scorecard
$score = 100
if ($verStatus -eq "CRITICAL_VULNERABLE") { $score -= 45 }
if ($releaseStatus -eq "EOL_WARNING") { $score -= 20 }
if ($tfaStatus -eq "DISABLED") { $score -= 15 }
if ($ldPreloadExists) { $score -= 50 }
if ($score -lt 0) { $score = 0 }

$grade = "A"
if ($score -lt 50) { $grade = "D (CRITICAL)" }
elseif ($score -lt 70) { $grade = "C (RISK)" }
elseif ($score -lt 85) { $grade = "B (WARNING)" }
elseif ($score -eq 100) { $grade = "S (HARDENED)" }

if ($Format -eq "json") {
    [PSCustomObject]@{
        timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
        target = $HostName
        score = $score
        grade = $grade
        audit = [PSCustomObject]@{
            libpve_access_control_version = $pkgVer
            version_status = $verStatus
            pve_release = $pveMgr
            release_status = $releaseStatus
            root_2fa_status = $tfaStatus
            pveproxy_ticket_requests = [int]$logHits
            authorized_keys_count = [int]$authKeys
            ld_so_preload_detected = $ldPreloadExists
        }
    } | ConvertTo-Json -Depth 5
} else {
    Write-Host "================================================================================" -ForegroundColor Gray
    Write-Host "PROXMOX VE SECURITY AUDIT REPORT: Windows 11 PowerShell Diagnostic" -ForegroundColor White
    Write-Host "================================================================================" -ForegroundColor Gray
    Write-Host "Target Host             : $HostName (Port: $Port, User: $User)"
    Write-Host "Overall Security Score  : $score / 100 [Grade: $grade]"
    Write-Host "--------------------------------------------------------------------------------" -ForegroundColor Gray
    Write-Host "1. libpve-access-control: $pkgVer [$verStatus]"
    Write-Host "2. Proxmox OS Release   : $pveMgr [$releaseStatus]"
    Write-Host "3. root@pam 2FA Status  : $tfaStatus"
    Write-Host "4. pveproxy Ticket Hits : $logHits records"
    Write-Host "5. root SSH Keys Count  : $authKeys key(s)"
    Write-Host "6. ld.so.preload Hook   : $(if ($ldPreloadExists) { 'DETECTED (DANGER!)' } else { 'Clean' })"
    Write-Host "================================================================================" -ForegroundColor Gray
}

人工执行方式:

.\pve_security_toolkit_windows11.ps1 -HostName "192.168.X.X" -User "root"

AI Agent 自动配置/执行方式:

# PowerShell 环境下 Agent 管道调用:
$report = .\pve_security_toolkit_windows11.ps1 -HostName "192.168.X.X" -Agent | ConvertFrom-Json
if ($report.score -lt 85) { Write-Warning "Agent Alert: PVE security posture requires remediation!" }

十一、高频疑难 Q&A

Q1:我的 PVE 从没暴露过公网,一直在纯内网运行,是不是百分之百安全?

答:相对安全,但绝不能高枕无忧! 虽然黑客的自动化全网爬虫无法直接穿透内网探测你的 8006 端口,但一旦内网中有任何一台设备失陷(例如局域网内的 Windows 电脑中了钓鱼木马、或者通过内网穿透工具暴露的轻量容器被提权),黑客就可以在局域网内发起横向探测。只要探测到你未修补的 8006 端口,同样能通过该漏洞拿下你的虚拟化总机。因此,内网节点也应尽快升级核心包并开启 2FA 双因素认证

Q2:我给 PVE 配置了 Nginx 反向代理,并启用了自定义域名和 HTTPS,能防住这个漏洞吗?

答:防不住! 只要 Nginx 把外部对 /api2/json/access/ticket 的请求透明转发给后端的 pveproxy,黑客构造的 tfa-challenge 恶意参数就会原封不动送达易受攻击的代码段。反向代理如果仅仅做了 SSL 卸载和域名解析,并不提供 WAF 级别的逻辑阻断。唯一的例外是:你在反向代理层配置了 HTTP Basic Auth、客户端证书双向认证(mTLS)、或者 Cloudflare Access 身份网关。

Q3:为什么说重装 PVE 系统比排查木马更划算?

答:因为排查高级 Rootkit 的人力成本远高于重装。 现代 Linux 内核态木马(LKM Rootkit)可以直接挂钩(Hook)内核系统调用表。它们可以从内核层面抹去指定的进程 PID、隐藏指定的端口监听、并在执行 cat 时动态过滤掉恶意命令。你花费几天时间用常规指令查得干干净净,实际上黑客依然拥有内核最高权限。而 PVE 的架构优势在于宿主机系统与虚拟机虚拟磁盘完全分离——格式化宿主机并重装 PVE 8 只需不到 20 分钟,从物理层面杜绝一切潜伏后患。

Q4:PVE 8.x 升级之后,我的虚拟机和容器需要重做吗?

答:完全不需要。 PVE 的大版本升级主要更新宿主机内核(如 Linux Kernel 6.8)、QEMU 模拟器引擎和 WebUI 组件。虚拟机内部的操作系统(如 Windows Server、Ubuntu、Debian 等)及其虚拟磁盘(ZFS/LVM-Thin)均保持独立。升级后启动虚拟机即可原样恢复运行。


总结:安全从无侥幸,把虚拟化底座焊死在铜墙铁壁中

回顾本次 CVE-2023-54391 的爆发历程,我们可以清晰地看到一条安全规律:最可怕的安全隐患,往往不是来自于复杂的密码学碰撞,而是来自于看似微小的业务逻辑判断疏漏与运维习惯中的“图一时方便”。

  1. 守住端口底线:严禁 8006 管理端口暴露公网,全面拥抱 Tailscale 等零信任隧道;
  2. 抓紧告别 EOL:尽快脱离已经终止支持的 PVE 7.x,跟进 PVE 8.x 长期支持分支;
  3. 点亮 2FA 护身符:为关键超管账户全面普及 WebAuthn 硬件密钥或 TOTP 动态码;
  4. 恪守失陷原则:一旦确认系统在易受攻击状态下暴露过公网,果断执行格式化重建,绝不存有一丝侥幸心理。

虚拟化平台是整个数字世界的地基。唯有以如履薄冰的敬畏之心筑牢每一道防线,才能让承载于其上的业务与数据真正稳如磐石!

本文阅读量 --