用 frp + 三层加固,把 Windows 远程桌面安全地暴露到公网
实践环境:一台华为云 Linux 云服务器(Debian 12)+ 本地一台 Windows 10 主机
文中所有端口、IP、路径、实例规格均为示例,请替换成你自己的。
背景
我需要从手机和临时设备远程连回家里的 Windows。可选方案有几类:
- 公网直连 RDP:要有固定公网 IP,运营商大多不给
- 商业远控(向日葵、ToDesk 等):能用,但数据过第三方
- 虚拟组网(WireGuard / ZeroTier):优雅,但每台设备都要装客户端,临时设备上很麻烦
- frp 反向代理:只需要一台有公网 IP 的机器,任意设备用一个 App 就能连
我选了第四条。下面是我实际踩过的坑和三处加固。
一、拓扑
手机 / 笔记本 ──(公网)──> 云服务器:38521 ──frps──(长连接)──> 家里 Windows 的 frpc ──> 127.0.0.1:3389
└─ 控制口 :7000
云服务器上跑 frps,家里 Windows 上跑 frpc。frpc 主动连出去,所以家里不需要公网 IP。
二、最小配置
服务端 frps.toml:
bindAddr = "0.0.0.0"
bindPort = 7000
auth.method = "token"
auth.token = "<换成你的强 token>"
# 关键:限制可映射的端口范围,防止这台机器被人当跳板乱开端口
allowPorts = [
{ start = 38521, end = 38521 }
]
# 管理面板只监听本地,用 SSH 隧道去看
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "<强密码>"
客户端 frpc.toml:
serverAddr = "<你的服务器公网 IP>"
serverPort = 7000
auth.method = "token"
auth.token = "<和上面一致>"
[[proxies]]
name = "rdp-tcp-38521"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3389
remotePort = 38521
frps 只在 frpc 注册之后才会监听 38521。这一点很容易误判:你在安全组里放行了端口,但 telnet 依然不通,原因不是安全组,而是没有 frpc 在跑。我就是在这里卡了一轮。
安全组入方向放行 TCP 7000 和 TCP 38521。
三、三层加固
裸奔的 RDP 是勒索软件的头号目标。我做了三层,逐层说明它到底挡住什么。
第 1 层:换掉那个"教程默认端口"
网上所有 frp + RDP 教程几乎都用 3389 -> 13389。扫描器的字典里就有这个组合。
换成 38521 这类随机高位端口。代价为零,收益是躲开绝大多数无差别扫描。
但要说清楚它的局限:全端口扫描(65535 个端口,几分钟跑完)照样能找到它。所以这层只是"降低噪音",不是安全边界。
而且别把真实端口写进公开文章——包括这一篇。本文的 38521 是我随手编的示意值。
第 2 层:按源 IP 限速(最有效的一层)
扫描被挡住之后,剩下的威胁是定向爆破。RDP 爆破的唯一目的就是猜密码,而猜密码需要大量新建连接。限速直接掐断这个前提。
Debian 12 用 nftables。我用一个独立的 systemd 单元,避免污染 Docker 的规则集:
cat > /etc/frpguard/frpguard.nft <<'EOF'
table inet frpguard {
set rdp_src {
type ipv4_addr
size 65535
flags dynamic, timeout
timeout 5m
}
set ctl_src {
type ipv4_addr
size 65535
flags dynamic, timeout
timeout 5m
}
chain input {
type filter hook input priority -10; policy accept;
tcp dport 38521 ct state new update @rdp_src { ip saddr limit rate over 10/minute } drop
tcp dport 7000 ct state new update @ctl_src { ip saddr limit rate over 20/minute } drop
}
}
EOF
配套单元:
[Unit]
Description=frp port per-source rate limit (nftables)
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStartPre=-/usr/sbin/nft delete table inet frpguard
ExecStart=/usr/sbin/nft -f /etc/frpguard/frpguard.nft
ExecStop=/usr/sbin/nft delete table inet frpguard
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now frpguard.service
实测:连续发起 15 个连接,放行 7 个、丢弃 8 个;集合里能看到源 IP 被登记、5 分钟后自动过期。
nft list set inet frpguard rdp_src
# elements = { 127.0.0.1 limit rate over 10/minute timeout 5m }
正常一次远程桌面会话只产生 1~2 个新连接,10/minute 完全够用,不会误伤自己。
7000 也要限。控制口同样是公网可达的,token 爆破一样是威胁,只是没人提。
第 3 层:为什么我没有做账户锁定
很多教程会推荐「5 次密码错误 → 锁定 30 分钟」。我一开始也准备这么做,后来放弃了,原因是它有个反作用。
Windows 的账户锁定计数器是账户级的,不是「RDP 级」的。这意味着:
- 攻击者连发 5 个错误密码,你的管理员账户就被锁 30 分钟
- 锁定期间你自己也进不去,坐在机器前按 Ctrl+Alt+Del 一样被拒
- 攻击者只要每 30 分钟再来 5 次,就能永久把你挡在门外
这叫 lockout DoS。它防不住定向爆破(对方本来也不指望 5 次猜中),只能防无脑扫描——而无脑扫描已经被第 1、2 层挡在门外了。
边际收益很低,边际风险很高。 如果确实想加,建议放宽到 10~20 次 / 15 分钟,只当最后一道保险。
另外确认 NLA(网络级别身份验证)保持开启:
(Get-ItemProperty 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp').UserAuthentication
# 1 = 开启,不要关
四、验证方法:不要只测端口通不通
「端口能连」不等于「服务正常」。我用的验证方式是直接发一个 RDP X.224 Connection Request,看它回什么。
import socket
CR = bytes.fromhex("030000130ee00000000000010008000300000000")
def probe(host, port=3389):
s = socket.socket(); s.settimeout(8)
s.connect((host, port))
s.sendall(CR)
d = s.recv(64)
s.close()
if not d:
return "EOF(没有服务应答)"
# TPKT 头 03 00 xx xx,第 6 字节 0xd0 = X.224 Connection Confirm
return "X224_CONFIRM(RDP 正常)" if d[5] == 0xD0 else d.hex()
print(probe("127.0.0.1", 3389)) # 本机环回,先确认 RDP 本身活着
print(probe("<公网IP>", 38521)) # 走完 frps -> frpc -> RDP 全链路
两条都返回 X224_CONFIRM,才算真的通了:
127.0.0.1:3389 -> X224_CONFIRM 030000130ed00000123400021f080002000000
<公网IP>:38521 -> X224_CONFIRM 同上
先验证本机环回,再验证公网。顺序反了的话,出问题你分不清是 RDP 没开还是隧道没通。
五、两个容易忽略的运维细节
frpc 的自启动身份
我用 HKCU\...\Run 启动过一版,结果发现一个死循环式的坑:
用户没登录 → frpc 不启动 → 你连不进去 → 所以没人能登录。
正确做法是注册成开机启动的系统级任务:
schtasks /create /tn frpc /tr "C:\frp\frpc.exe -c C:\frp\frpc.toml" `
/sc onstart /ru SYSTEM /rl HIGHEST /f
注册完记得删掉 HKCU 里的那条,否则会双开。
带宽
如果这台机器的带宽只有个位数 Mbit/s(入门级云服务器常见),RDP 能用,但必须在客户端关掉:
- 桌面背景
- 字体平滑
- 视觉样式 / 动画
否则会卡成幻灯片。另外带宽小还有个副作用:爆破洪水能把整条管道打满,那时你自己也连不上——这也是第 2 层限速除了防爆破之外的价值。
六、更安全但更麻烦的方案:stcp
frp 支持 stcp 类型:服务端不监听任何公网端口,数据复用 7000 那条长连接,访问方持 secretKey 跑一个 visitor。
| 类型 | 公网端口 | 攻击面 | 访问端要求 |
|---|---|---|---|
tcp |
有 | 全网可达 | 直接用 RDP 客户端 |
stcp |
无 | 只有持 key 的人 | 每台设备装 frpc + 配 visitor |
从安全角度 stcp 明显更好。但从可用性角度它有个硬伤:手机、公司电脑、临时借的设备,每台都要装一次客户端、填一次 key。用两天你就会放弃,然后回去用 tcp。
工程上正确的不是「理论上最安全」,是「你会一直用下去的那个」。 所以我最终选了 tcp + 三层加固,stcp 配好备用。
小结
| 层 | 措施 | 挡住什么 | 副作用 |
|---|---|---|---|
| 1 | 端口换随机高位 | 95% 无脑扫描 | 无 |
| 2 | 按源 IP 限速 10/min | 几乎全部自动化爆破 | 无 |
| 3 | 账户锁定 | 剩余扫描 | 可能把自己锁死,故不做 |
真正的安全边界是第 2 层 + 强密码 + NLA,不是端口号,也不是账户锁定。
环境:华为云 Linux 云服务器(Debian 12,frp 0.71)+ Windows 10 客户端(frp 0.71)
- 点赞
- 收藏
- 关注作者
评论(0)