用内网穿透安全地部署 LLM 服务

First Post:
Last Update:
Word Count:
4.1k
Read Time:
20 min
Page View: loading...

写在前面:vllm 潜在的高危漏洞

先说结论:不要直接把 LLM 服务(vLLM、Ollama、Jupyter 等)通过 FRP 的 tcp 模式暴露到公网

  1. vLLM 默认无认证:只要能连上端口就能调 API。
  2. vLLM 历史 RCE 漏洞:CVE-2025-62164、CVE-2025-9141、CVE-2025-66448 等,CVSS 都在 8.0+,通过 prompt embedding、tool call、auto_map 都能 RCE。
  3. FRP tcp 模式直接在公网开端口:扫描器几小时内就能扫到 OpenAI 兼容 API。

任何一个单独存在都还能扛,三个叠在一起就是灾难。


整体架构

我们用 FRP 的 stcp(secret tcp) 模式,而不是 tcp 模式。区别:

模式 公网端口 谁能访问 安全性
tcp 中转服务器开放固定端口(如 10001) 任何能扫到这个端口的人 ❌ 等于裸奔
stcp 不开任何公网端口 必须持有 secretKey 的 visitor ✅ 公网零暴露

最终的访问链路:

1
2
3
4
5
6
7
8
9
10
11
[你的笔记本/工作机]
└─ frpc visitor (持有 secretKey)
│ 通过中转服务器建立加密隧道

[中转服务器 frps] ← 只开 SSH (22) 和 FRP 控制端口 (7000),无业务端口

│ vLLM 主机主动连出
[GPU 主机 vLLM]
└─ frpc (持有同样的 secretKey)
└─ vLLM 监听 127.0.0.1:8888 (不对外)
└─ vLLM 启用 API key

整条路径上有四把锁:

  1. SSH 密钥登录(防爆破)
  2. FRP token(防恶意客户端注册)
  3. stcp secretKey(防未授权访问 LLM)
  4. vLLM API key(即便前面都漏了,API 层还有一道)

一、客户端部署脚本(GPU 主机)

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#!/bin/bash
set -euo pipefail

# ============================================================
# 安全的 FRP 客户端部署脚本(stcp 模式 + 多重加固)
# 用法:
# 1. 把下面三个变量从环境变量或外部文件读入,不要硬编码到脚本里
# export FRP_SERVER_ADDR="your.server.ip"
# export FRP_AUTH_TOKEN="..." # openssl rand -hex 32
# export FRP_STCP_SECRET="..." # openssl rand -hex 32
# 2. bash deploy_frpc.sh
# ============================================================

# ---------- 1. 校验必需的环境变量 ----------
: "${FRP_SERVER_ADDR:?need FRP_SERVER_ADDR}"
: "${FRP_AUTH_TOKEN:?need FRP_AUTH_TOKEN (openssl rand -hex 32)}"
: "${FRP_STCP_SECRET:?need FRP_STCP_SECRET (openssl rand -hex 32)}"

# ---------- 2. FRP 版本与下载校验 ----------
FRP_VERSION="0.61.0" # 用较新版本,老版本有 CVE
FRP_TAR_FILE="frp_${FRP_VERSION}_linux_amd64.tar.gz"
FRP_DIR="frp_${FRP_VERSION}_linux_amd64"
DOWNLOAD_URL="https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_TAR_FILE}"
# 从 GitHub Release 页面 hashes 文件里复制对应版本的 sha256,避免供应链劫持
EXPECTED_SHA256="<paste-sha256-from-github-release-here>"

# ---------- 3. FRP 服务端配置 ----------
SERVER_PORT=7000

# ---------- 4. 转发的本地 LLM 服务 ----------
PROXY_NAME="llm-service"
LOCAL_IP="127.0.0.1" # 关键:vLLM 必须只监听 127.0.0.1
LOCAL_PORT=8000

# ---------- 5. 下载 + 校验 ----------
if [ ! -d "$FRP_DIR" ]; then
echo "[+] Downloading FRP ${FRP_VERSION}..."
wget -q --show-progress "$DOWNLOAD_URL"

if [ "$EXPECTED_SHA256" != "<paste-sha256-from-github-release-here>" ]; then
echo "[+] Verifying SHA256..."
echo "${EXPECTED_SHA256} ${FRP_TAR_FILE}" | sha256sum -c -
else
echo "[!] SHA256 not set. Strongly recommend pinning EXPECTED_SHA256."
fi

tar -zxf "$FRP_TAR_FILE"
else
echo "[=] FRP directory already exists, skipping download."
fi

cd "$FRP_DIR"

# ---------- 6. 生成 frpc.toml(stcp 模式)----------
echo "[+] Generating frpc.toml..."
cat > frpc.toml <<EOF
serverAddr = "${FRP_SERVER_ADDR}"
serverPort = ${SERVER_PORT}

auth.method = "token"
auth.token = "${FRP_AUTH_TOKEN}"

# 强制 TLS,加密 frpc <-> frps 的控制信道
transport.tls.enable = true
transport.tls.disableCustomTLSFirstByte = false

# 日志(限制级别和大小,避免被攻击者翻历史请求 / 被日志炸盘)
log.to = "./frpc.log"
log.level = "info"
log.maxDays = 7

[[proxies]]
name = "${PROXY_NAME}"
# 关键:用 stcp(secret tcp)而不是 tcp
# 公网不会有任何端口开放,只有持有 secretKey 的 visitor 能连上
type = "stcp"
secretKey = "${FRP_STCP_SECRET}"
localIP = "${LOCAL_IP}"
localPort = ${LOCAL_PORT}
EOF

# 配置文件含密钥,限制权限
chmod 600 frpc.toml

# ---------- 7. 用 systemd 托管,不要 nohup ----------
# nohup 启动的进程没有重启策略、没有日志规范、没有用户隔离
# 强烈建议改成 systemd 服务(见下文 systemd 单元文件示例)
echo "[+] Stopping existing frpc instances..."
pkill -f "frpc -c ./frpc.toml" 2>/dev/null || true

echo "[+] Starting frpc..."
nohup ./frpc -c ./frpc.toml > /dev/null 2>&1 &

cat <<EOF
------------------------------------------------------
Deployment complete (stcp mode).

The service is NOT exposed on any public port.
To access it from another machine, run an frpc visitor
with the same secretKey on that client.

Local logs: $(pwd)/frpc.log
Config (600): $(pwd)/frpc.toml
------------------------------------------------------
EOF

几个关键变化:

  • secret 不再硬编码到脚本里,从环境变量读取。脚本可以放进 Git,密钥不会泄露。
  • FRP 版本升级到 0.61.0,并加 SHA256 校验防止下载被劫持。
  • type = "tcp"type = "stcp",删除了 remotePort,公网零端口暴露。
  • 强制 TLS,加密控制信道。
  • localIP 必须配合 vLLM 的 --host 127.0.0.1,否则等于白配。

二、访问端的 visitor 配置(你的工作机/笔记本)

stcp 模式需要在访问 LLM 的那台机器上跑一个 visitor。这是新增的、原来没有的步骤。

一键部署脚本

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/bin/bash
set -euo pipefail

# ============================================================
# FRP Visitor 部署脚本(工作机/笔记本)
# 用法:
# export FRP_SERVER_ADDR="your.server.ip"
# export FRP_AUTH_TOKEN="..." # 和 GPU 主机相同的 token
# export FRP_STCP_SECRET="..." # 和 GPU 主机相同的 secret
# export LOCAL_BIND_PORT=8888 # 可选,默认 8888
# bash deploy_frpc_visitor.sh
# ============================================================

: "${FRP_SERVER_ADDR:?need FRP_SERVER_ADDR}"
: "${FRP_AUTH_TOKEN:?need FRP_AUTH_TOKEN}"
: "${FRP_STCP_SECRET:?need FRP_STCP_SECRET}"
LOCAL_BIND_PORT="${LOCAL_BIND_PORT:-8888}"

# ---------- 1. 下载配置 ----------
FRP_VERSION="0.61.0"
FRP_TAR_FILE="frp_${FRP_VERSION}_linux_amd64.tar.gz"
DOWNLOAD_URL="https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_TAR_FILE}"
# 从 https://github.com/fatedier/frp/releases/tag/v${FRP_VERSION} 的 sha256sums 文件里复制
EXPECTED_SHA256="<paste-sha256-from-github-release-here>"

FRP_DIR="${HOME}/frp"
BIN_DIR="${FRP_DIR}/bin"
CONFIG_FILE="${FRP_DIR}/frpc-visitor.toml"
LOG_FILE="${FRP_DIR}/frpc-visitor.log"

mkdir -p "${BIN_DIR}"

# ---------- 2. 下载 + 校验 ----------
if [ ! -f "${BIN_DIR}/frpc" ]; then
echo "[+] Downloading FRP ${FRP_VERSION}..."
wget -q --show-progress -O "/tmp/${FRP_TAR_FILE}" "${DOWNLOAD_URL}"

if [ "${EXPECTED_SHA256}" != "<paste-sha256-from-github-release-here>" ]; then
echo "[+] Verifying SHA256..."
echo "${EXPECTED_SHA256} /tmp/${FRP_TAR_FILE}" | sha256sum -c -
else
echo "[!] SHA256 not set — strongly recommend pinning EXPECTED_SHA256."
fi

tar -zxf "/tmp/${FRP_TAR_FILE}" -C /tmp
cp "/tmp/frp_${FRP_VERSION}_linux_amd64/frpc" "${BIN_DIR}/frpc"
chmod 755 "${BIN_DIR}/frpc"
rm -rf "/tmp/frp_${FRP_VERSION}_linux_amd64" "/tmp/${FRP_TAR_FILE}"
echo "[+] frpc installed to ${BIN_DIR}/frpc"
else
echo "[=] frpc already at ${BIN_DIR}/frpc, skipping download."
fi

# ---------- 3. 生成 frpc-visitor.toml ----------
echo "[+] Generating ${CONFIG_FILE}..."
cat > "${CONFIG_FILE}" <<EOF
serverAddr = "${FRP_SERVER_ADDR}"
serverPort = 7000

auth.method = "token"
auth.token = "${FRP_AUTH_TOKEN}"

transport.tls.enable = true

log.to = "${LOG_FILE}"
log.level = "info"
log.maxDays = 7

[[visitors]]
name = "llm-visitor"
type = "stcp"
serverName = "llm-service" # 必须和 GPU 主机 proxy name 一致
secretKey = "${FRP_STCP_SECRET}"
bindAddr = "127.0.0.1"
bindPort = ${LOCAL_BIND_PORT}
EOF

chmod 600 "${CONFIG_FILE}"

# ---------- 4. 安装为 systemd 用户服务(推荐)----------
# systemd 用户服务不需要 root,随用户登录自动启动
SYSTEMD_USER_DIR="${HOME}/.config/systemd/user"
mkdir -p "${SYSTEMD_USER_DIR}"

cat > "${SYSTEMD_USER_DIR}/frpc-visitor.service" <<EOF
[Unit]
Description=FRP Client Visitor (stcp → llm-service)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=${BIN_DIR}/frpc -c ${CONFIG_FILE}
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target
EOF

# 让 systemd 用户实例随开机启动(即使没有登录 GUI)
loginctl enable-linger "$(whoami)" 2>/dev/null || true

systemctl --user daemon-reload
systemctl --user enable --now frpc-visitor

cat <<EOF
------------------------------------------------------
Visitor 部署完成。

访问 LLM: http://127.0.0.1:${LOCAL_BIND_PORT}
配置文件: ${CONFIG_FILE}
日志: ${LOG_FILE}

常用命令:
systemctl --user status frpc-visitor
systemctl --user restart frpc-visitor
journalctl --user -u frpc-visitor -f
------------------------------------------------------
EOF

最终生成的 ~/frp/frpc-visitor.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
serverAddr = "your.server.ip"
serverPort = 7000

auth.method = "token"
auth.token = "和 GPU 主机相同的 FRP_AUTH_TOKEN"

transport.tls.enable = true

log.to = "/home/you/frp/frpc-visitor.log"
log.level = "info"
log.maxDays = 7

[[visitors]]
name = "llm-visitor"
type = "stcp"
serverName = "llm-service" # 要和 GPU 主机的 proxy name 一致
secretKey = "和 GPU 主机相同的 FRP_STCP_SECRET"
bindAddr = "127.0.0.1"
bindPort = 8888 # 你在本机用哪个端口访问

启动后,本地访问 http://127.0.0.1:8888 就等于访问 GPU 主机的 vLLM。整条链路对公网完全不可见。


三、服务端(中转 VPS)

一键部署脚本

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/bin/bash
set -euo pipefail

# ============================================================
# FRP 服务端部署脚本(中转 VPS,需要 root)
# 用法:
# export FRP_AUTH_TOKEN="..." # openssl rand -hex 32
# export FRP_DASHBOARD_PASSWORD="..." # openssl rand -hex 16
# export ALLOWED_GPU_IP="x.x.x.x" # GPU 主机出口 IP(可选,留空则不限)
# export ALLOWED_SSH_IP="x.x.x.x" # 你的家庭 IP(可选,留空则不限)
# sudo bash deploy_frps.sh
# ============================================================

: "${FRP_AUTH_TOKEN:?need FRP_AUTH_TOKEN (openssl rand -hex 32)}"
: "${FRP_DASHBOARD_PASSWORD:?need FRP_DASHBOARD_PASSWORD (openssl rand -hex 16)}"
ALLOWED_GPU_IP="${ALLOWED_GPU_IP:-}"
ALLOWED_SSH_IP="${ALLOWED_SSH_IP:-}"

# ---------- 1. 下载配置 ----------
FRP_VERSION="0.61.0"
FRP_TAR_FILE="frp_${FRP_VERSION}_linux_amd64.tar.gz"
DOWNLOAD_URL="https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/${FRP_TAR_FILE}"
# 从 https://github.com/fatedier/frp/releases/tag/v${FRP_VERSION} 的 sha256sums 文件里复制
EXPECTED_SHA256="<paste-sha256-from-github-release-here>"
INSTALL_DIR="/opt/frp"

# ---------- 2. 下载 + 校验 ----------
if [ ! -f "${INSTALL_DIR}/frps" ]; then
echo "[+] Downloading FRP ${FRP_VERSION}..."
wget -q --show-progress -O "/tmp/${FRP_TAR_FILE}" "${DOWNLOAD_URL}"

if [ "${EXPECTED_SHA256}" != "<paste-sha256-from-github-release-here>" ]; then
echo "[+] Verifying SHA256..."
echo "${EXPECTED_SHA256} /tmp/${FRP_TAR_FILE}" | sha256sum -c -
else
echo "[!] SHA256 not set — strongly recommend pinning EXPECTED_SHA256."
fi

tar -zxf "/tmp/${FRP_TAR_FILE}" -C /tmp
mkdir -p "${INSTALL_DIR}"
cp "/tmp/frp_${FRP_VERSION}_linux_amd64/frps" "${INSTALL_DIR}/frps"
chmod 755 "${INSTALL_DIR}/frps"
rm -rf "/tmp/frp_${FRP_VERSION}_linux_amd64" "/tmp/${FRP_TAR_FILE}"
echo "[+] frps installed to ${INSTALL_DIR}/frps"
else
echo "[=] frps already at ${INSTALL_DIR}/frps, skipping download."
fi

# ---------- 3. 创建专用低权限用户 ----------
if ! id frps &>/dev/null; then
echo "[+] Creating frps system user..."
useradd -r -s /usr/sbin/nologin -d /nonexistent frps
fi

# ---------- 4. 生成 frps.toml ----------
echo "[+] Generating ${INSTALL_DIR}/frps.toml..."
cat > "${INSTALL_DIR}/frps.toml" <<EOF
bindPort = 7000

auth.method = "token"
auth.token = "${FRP_AUTH_TOKEN}"

# 拒绝非 TLS 连接
transport.tls.force = true

# stcp 不需要 allowPorts,保留以防万一以后加 tcp 代理
allowPorts = [
{ start = 10000, end = 10010 }
]

# Dashboard 只监听本地,通过 SSH 隧道访问
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "${FRP_DASHBOARD_PASSWORD}"

log.to = "/var/log/frps.log"
log.level = "info"
log.maxDays = 30
EOF

# ---------- 5. 设置文件权限 ----------
chown -R frps:frps "${INSTALL_DIR}"
chmod 600 "${INSTALL_DIR}/frps.toml"
touch /var/log/frps.log
chown frps:frps /var/log/frps.log

# ---------- 6. 安装 systemd 服务 ----------
echo "[+] Installing /etc/systemd/system/frps.service..."
cat > /etc/systemd/system/frps.service <<EOF
[Unit]
Description=FRP Server
After=network.target

[Service]
Type=simple
User=frps
Group=frps
WorkingDirectory=${INSTALL_DIR}
ExecStart=${INSTALL_DIR}/frps -c ${INSTALL_DIR}/frps.toml
Restart=always
RestartSec=3

# 安全加固
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now frps

# ---------- 7. 防火墙(UFW)----------
if command -v ufw &>/dev/null; then
echo "[+] Configuring UFW..."
ufw default deny incoming
ufw default allow outgoing

# SSH
if [ -n "${ALLOWED_SSH_IP}" ]; then
ufw allow from "${ALLOWED_SSH_IP}" to any port 22 proto tcp
else
ufw allow 22/tcp
fi

# FRP 控制端口(只允许 GPU 主机 IP 连接,最小暴露面)
if [ -n "${ALLOWED_GPU_IP}" ]; then
ufw allow from "${ALLOWED_GPU_IP}" to any port 7000 proto tcp
else
echo "[!] ALLOWED_GPU_IP not set — opening port 7000 to all (not ideal)."
ufw allow 7000/tcp
fi

ufw --force enable
echo "[+] UFW rules applied."
else
echo "[!] ufw not found — please configure your firewall manually."
echo " Open ports: 22/tcp (SSH), 7000/tcp (FRP control)"
fi

cat <<EOF
------------------------------------------------------
FRP Server 部署完成。

状态检查:
systemctl status frps
tail -f /var/log/frps.log

Dashboard(本地只读,需先建 SSH 隧道):
ssh -L 7500:127.0.0.1:7500 user@<your-server-ip>
然后访问 http://127.0.0.1:7500

配置文件(chmod 600):${INSTALL_DIR}/frps.toml
------------------------------------------------------
EOF

最终生成的 /opt/frp/frps.toml

frps.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bindPort = 7000

auth.method = "token"
auth.token = "和客户端相同的 FRP_AUTH_TOKEN" # openssl rand -hex 32

# 强制 TLS,拒绝非 TLS 的 frpc 连入
transport.tls.force = true

# 限制可分配端口范围;stcp 用不到,但万一以后加 tcp 代理时也有约束
allowPorts = [
{ start = 10000, end = 10010 }
]

# Dashboard 仅本地可访问,需要时通过 SSH 隧道看
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "<another-strong-random-password>"

# 日志
log.to = "/var/log/frps.log"
log.level = "info"
log.maxDays = 30

注意点:

  • auth.token 必须用 openssl rand -hex 32 生成的真随机串,不要用 your_secure_token 这种占位符(我之前就是这么挂的)。
  • webServer.addr = "127.0.0.1":dashboard 只听本地,如果你想看,本地起 SSH 隧道:ssh -L 7500:127.0.0.1:7500 user@your.server.ip
  • transport.tls.force = true:拒绝任何明文连接。

最终生成的 /etc/systemd/system/frps.service

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
# /etc/systemd/system/frps.service
[Unit]
Description=FRP Server
After=network.target

[Service]
Type=simple
User=frps # 专用低权限用户,不要用 root
Group=frps
WorkingDirectory=/opt/frp
ExecStart=/opt/frp/frps -c /opt/frp/frps.toml
Restart=always
RestartSec=3

# 安全加固
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

以上所有配置均由上方的部署脚本自动生成,手动调整时参考这两份文件。

中转服务器防火墙(关键!)

1
2
3
4
5
6
7
8
9
10
11
# 默认拒绝所有入站
sudo ufw default deny incoming
sudo ufw default allow outgoing

# 只放 SSH(建议加源 IP 限制,<your-home-ip> 改成你的固定出口 IP)
sudo ufw allow from <your-home-ip> to any port 22

# FRP 控制端口(如果你的 GPU 主机出口 IP 固定,也加 from 限制)
sudo ufw allow from <gpu-host-ip> to any port 7000

sudo ufw enable

stcp 模式下不需要开放业务端口,这是它最大的安全优势。


四、配套的 vLLM 启动方式

FRP 只是网络层。LLM 服务本身的安全配置同样关键,否则前面的加固都白费:

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 用稳定版(截至 2026-05,至少 0.11.1+,避免已知 RCE)
pip install --upgrade vllm

# 2. 用专用低权限用户启动,不要 root
sudo useradd -m -s /bin/bash vllmuser
sudo -u vllmuser bash -c '
python -m vllm.entrypoints.openai.api_server \
--model your_model \
--host 127.0.0.1 \
--port 8000 \
--api-key "$(openssl rand -hex 32 | tee ~/.vllm_api_key)"
'

三个要点:

  1. --host 127.0.0.1:只监听本地,所有外部访问必须经过 FRP,多一道控制。
  2. --api-key:vLLM 默认无认证,必须显式启用。
  3. 专用用户启动:万一 vLLM 出新 CVE 被 RCE,攻击者拿到的也只是 vllmuser 权限,不是 root。

五、安全隐患说明

如果用 tcp 模式,每个隐患列出来对比参考:

隐患 1:type = "tcp" 直接在公网开端口

如果用 tcp 模式,中转服务器会监听 remotePort = 10001。互联网上的端口扫描器(Shodan、Censys、各种自动化爬虫)几小时内就能发现这个端口。一旦识别出是 OpenAI 兼容 API,就会被自动化工具尝试漏洞利用。

修复:改用 stcp,公网零端口。

隐患 2:vLLM 自身无认证 + 已知 RCE

vLLM 默认不需要任何认证。即使你信任所有能扫到端口的人不会乱用算力,也防不住已知漏洞:

  • CVE-2025-62164(CVSS 8.8):通过 prompt embedding 触发 torch.load() RCE,影响 0.10.2 ~ 0.11.0
  • CVE-2025-9141:tool call 反序列化 RCE,影响 0.10.0 ~ 0.10.1.1
  • CVE-2025-66448auto_map 配置 RCE,影响所有 < 0.11.1
  • ShadowMQ 系列:ZMQ + pickle 反序列化 RCE

修复:升级到 ≥ 0.11.1,启用 --api-key,绑定 127.0.0.1,用低权限用户跑。

隐患 3:硬编码的弱 token

如果用 AUTH_TOKEN="your_secure_token" 这种占位符,任何人都能注册 frpc 客户端连到你的 frps。

修复:从环境变量读取,openssl rand -hex 32 生成强随机串,配置文件 chmod 600

隐患 4:明文 TCP 控制信道

如果用默认配置不启用 TLS,frpc 和 frps 之间的控制信道明文传输,中间人可以嗅探 token、看到代理元数据。

修复:客户端 transport.tls.enable = true,服务端 transport.tls.force = true

隐患 5:nohup 启动 + root 跑

如果用 nohup ./frpc &,问题:进程崩了不会重启、日志无管理、跟 shell 生命周期耦合、通常是以当前用户(可能是 root)跑。

修复:systemd 服务 + 专用低权限用户 + 资源/能力限制。

隐患 6:FRP 版本过旧

如果用旧版本(如 0.56.0),FRP 本身偶尔也出安全更新。

修复:跟进新版本,定期 wget 新 release + SHA256 校验。

隐患 7:中转服务器无防火墙

如果中转 VPS 上还有别的服务(数据库管理面板、redis、什么测试 API),它们也跟 frps 一起暴露在公网。

修复:默认 deny incoming,只白名单放行必要端口。


总结:四道锁的成本

一次配好可以用很久:

加固项 一次性成本 防住的事
stcp 模式 改几行配置 + 客户端配 visitor 所有公网扫描和未授权访问
FRP TLS 改两行配置 token 嗅探、中间人
vLLM API key + 127.0.0.1 启动加两个参数 API 层未授权调用
vLLM 低权限用户 创建一个用户 RCE 时不直接 root
systemd + 防火墙 几分钟 进程稳定性、其他服务暴露
If you find this helpful, please give my project a ⭐on GitHub! =w=/