网络攻防无处不在:我的RustDesk服务竟成了DDoS的一环

brooke_zb 2026-07-05 16:50 1


本文首发于个人博客: 网络攻防无处不在:我的RustDesk服务竟成了DDoS的一环 | 灵感空间站 同步过来抛砖引玉




突如其来的欠费


前几日下午,照常在工位上的我突然收到云服务商的欠费通知,不由得警觉起来。笔者在云上只有一个公网流量按量付费的服务器,虽然预留的余额不多,但是服务器上平时也没什么流量,按理不会用的这么快。于是我赶紧登录上去看看是什么情况。


一看费用账单,果真都是公网流量费用,半天消耗了快10GB,一看监控,外网出入带宽居高不下。



但是一时半会还找不到原因,故先紧急止血:临时降低服务器带宽,避免继续快速消耗流量。


初步定位:RustDesk UDP 端口流量激增


遇到这种突发状况,我才发现自己对服务器运维知之甚少,不知道到底是哪个进程、哪个端口消耗了流量,也无从下手,只好赶紧求助运维同事。该说不说不愧是运维大哥,安装好 iftop 工具,三下五除二就定位到如下链路(均已脱敏):


172.16.0.7:21116   => ip-69-33-x-a.iad.megapath.net:25565       1.10Mb  1.25Mb   696Kb
<= 0b 0b 0b
172.16.0.7:21116 => ip-69-33-x-b.iad.megapath.net:25565 1.21Mb 635Kb 605Kb
<= 0b 0b 0b
172.16.0.7:21116 => ip-69-33-x-c.iad.megapath.net:25565 1.03Mb 577Kb 563Kb
<= 0b 0b 0b

iftop 工具中,右侧三列分别为最近2秒、10秒、40秒的平均速率,可以看出,服务器21116端口在不断往 69.33.x.x/24 网段的25565端口发送数据。


21116是 RustDesk 服务端(一个开源的远程桌面服务)hbbs 程序使用到的一个核心端口。RustDesk 服务端部署分为 hbbshbbr 两个程序,前者用于 ID 注册、心跳、P2P 打洞等核心功能,后者用于 P2P 打洞失败时走服务器中继转发。我通过智能插座 + 电脑通电自启 + 自部署 RustDesk 服务端这套组合,实现了随时随地访问家里电脑的能力。


至于对方的25565端口我可太熟了,Minecraft 服务端的默认端口就是25565。于是我在云安全组中封禁了这个网段的出入口,观察云服务监控,外网出带宽恢复正常了,但是外网入带宽、内网出带宽以及 iftop 监控发现21116端口仍在不断尝试向上述 IP 发包,即使重启 RustDesk 服务也无济于事。


这看起来有些矛盾:既然安全组已经封禁,为什么服务器里还能看到发包?原因是安全组是在云厂商网络出口侧生效。也就是说,包在本机网络栈里已经生成,但在云厂商出口处被丢弃,因此账单侧出站流量恢复正常。


结合前面21116端口与 69.33.x.x/24 网段之间只有出口流量没有入口流量,说明该网段并不是攻击者,而是受害者,没准是一个正在遭受 DDoS 攻击的 Minecraft 服务器(笑)。不过既然出口流量已经封住了,接下来就可以慢慢研究了。


抓包分析:还原攻击链路


接下来,需要搞清楚真正的攻击来源,以及为什么 hbbs 程序会向受害者端口大量发包。询问 ChatGPT 之后,我尝试用 tcpdump 监控 hbbs 程序的网络出入包,发现大量如下特征的链路:


14:57:58.432264 veth90f352c Out IP 194.247.x.x.46403 > 172.18.0.2.21116: UDP, length 548
14:57:58.432393 veth90f352c P IP 172.18.0.2.21116 > 69.33.x.x.25565: UDP, length 643

上文只列举了一个,实际上有大量不同的国外 IP 不断往21116端口发包,这下就解释得通了:并不是 hbbs 程序无故往外网大量发包,而是 多个攻击者向 hbbs 发包,hbbs 再往同一个目标发包。这个特征很符合 UDP 反射放大攻击的特征。


为了了解是 hbbs 中哪个协议被用来进行反射攻击,我继续用 tcpdump 抓包,获取完整 UDP payload:


tcpdump -ni any \
'udp and src host 194.247.x.x and dst port 21116' \
-c 5 -s 0 -vvv -XX

然后将抓到的 payload 贴到 ChatGPT 中分析。不得不说,AI 在逆向/数据分析这块还是太权威了:其思考链中通过浏览 rustdesk-server 源码仓库,查找 protobuf 消息格式,编写代码解码,到最终解析出该 UDP 包对应的 RustDesk 消息如下:


RendezvousMessage {
punch_hole_sent {
socket_addr: <11 bytes> # 解码后为 69.33.x.x:25565
id: "szpontnet"
relay_server: "ymfZoVOLVAjokGhkMbhuA2MJg71BOdqVqfEwEnAwBHn8Q7JyYk..." # 超长随机字符串,约 511 bytes
version: "1.3.0"
}
}

这个过程如果让没有相关经验的我来做,压根一点思路也没有。


解出了包的内容,再去源码里比对逻辑就清晰得多了,根据 rustdesk-server源码 ,PunchHoleSent 消息是客户端 P2P 连接过程中的一环,这个消息会进入 handle_hole_sent 方法,执行以下逻辑:



  1. 从攻击包里的 socket_addr 解出目标地址

  2. 构造 PunchHoleResponse

  3. 把攻击包里的 relay_server 原样复制进去

  4. 发给 socket_addr 指定的目标


附带 RustDesk 客户端间 P2P 连接的完整过程方便理解(A, B 为客户端,hbbs 为服务端):



  1. 前提:客户端 AB 已注册到 hbbs

  2. Ahbbs : PunchHoleRequest(A 想连接 B,先发起打洞请求)

  3. hbbsB : PunchHole(hbbsA 的地址告诉 B

  4. BA : UDP punch packet(B 收到 PunchHole,向 A 打洞)

  5. Bhbbs : PunchHoleSent(B 通知 hbbsB 已经向 A 发起打洞了)

  6. hbbsA : PunchHoleResponse(hbbs 再通知 AB已准备好,可以尝试连接 B


如此一来,真相大白。这是一次基于 UDP 的反射型 DDoS 滥用,攻击者并没有攻破我的服务器,而是利用 RustDesk 服务端协议的安全漏洞,伪造大量 PunchHoleSent 消息,诱导 RustDesk 服务端向伪造的目标地址发送 PunchHoleResponse,间接对目标地址进行 DDoS 攻击。


这类攻击的高明之处,在于只需要少量的 IP 向大量暴露在公网的 RustDesk 服务发送伪造消息,就能利用这些公开服务器对目标地址造成 DDoS攻击;同时 PunchHoleResponse 响应包略大于 PunchHoleSent 请求包(643 / 548 ≈ 1.17),因此也带有一定放大效果。


缓解方案


清楚了攻击链路,接下来就要着手解决方案,防止后续其他攻击者利用这个漏洞攻击别的目标。但是细想下来,这防御措施却并不好做。21116端口承载了 RustDesk 的核心功能,直接关掉这个端口会导致客户端无法注册到服务端,而攻击者利用的又是 RustDesk 标准协议内容,除了包体较大之外,没有能明显区分的特征。


限制入口IP


考虑到攻击方 IP 都是国外的,于是我先使用 iptables + ipset 将 21116/UDP 的访问白名单限制在国内 IP 中。


首先创建 ipset 集合,作为白名单:


ipset create cn_ips hash:net family inet -exist

然后找一个包含所有国内 IP 网段的 CIDR 网段合集,这里使用的是 IPDeny 提供的文件,将其下载到临时目录中:


curl -fsSL https://www.ipdeny.com/ipblocks/data/countries/cn.zone -o /tmp/cn.zone

然后导入刚刚创建的合集,这里使用 awk 将下载下来的 zone 文件预处理为 ipset restore 格式,这样导入比按行切分 zone 文件再一个个 add 进去要快很多:


SET_NAME=cn_ips
ZONE_FILE=/tmp/cn.zone

awk -v set="$SET_NAME" '
/^[[:space:]]*$/ { next }
/^[[:space:]]*#/ { next }
{ print "add " set " " $1 }
' "$ZONE_FILE" | ipset restore -exist

完成之后验证一下导入的数量:


ipset list cn_ips | grep 'Number of entries'

现在将这个 ipset 挂到 iptables 中,首先创建一个规则链,然后将 cn_ips 这个白名单添加到链中:


# 创建规则链
iptables -N RUSTDESK_UDP_GEO 2>/dev/null || true
iptables -F RUSTDESK_UDP_GEO

# 挂载白名单
iptables -A RUSTDESK_UDP_GEO -m set --match-set cn_ips src -j RETURN

# 可选:记录被拦截来源,限速防止刷日志
iptables -A RUSTDESK_UDP_GEO -m limit --limit 5/min --limit-burst 10 \
-j LOG --log-prefix "rustdesk_udp21116_drop: " --log-level 4

# 其他全部丢弃
iptables -A RUSTDESK_UDP_GEO -j DROP

接下来根据服务的部署方式挂载这个规则链,笔者这里是用 Docker 部署的 RustDesk 服务端,故将规则链挂在 DOCKER-USER 前面:


iptables -C DOCKER-USER -p udp --dport 21116 -j RUSTDESK_UDP_GEO 2>/dev/null || \
iptables -I DOCKER-USER 1 -p udp --dport 21116 -j RUSTDESK_UDP_GEO

然后验证规则是否生效:


# 看 DROP 规则的 packet / bytes 是否增长
iptables -L DOCKER-USER -n -v --line-numbers
iptables -L RUSTDESK_UDP_GEO -n -v --line-numbers

# 或者查看日志
dmesg -T | grep rustdesk_udp21116_drop

限制出口速率


为了防止流量被刷,我们还可以限制 21116/UDP 端口的出口速率,这里依旧用 iptables 实现。


依旧先添加一个规则链:


iptables -N RUSTDESK_UDP_OUT_LIMIT 2>/dev/null || true
iptables -F RUSTDESK_UDP_OUT_LIMIT

然后挂上限速规则,将网络包速率限制在15/s,允许突发50个包:


iptables -A RUSTDESK_UDP_OUT_LIMIT \
-m hashlimit \
--hashlimit-name rustdesk_21116_udp_out \
--hashlimit-above 15/second \
--hashlimit-burst 50 \
--hashlimit-mode srcip,srcport \
-j DROP
iptables -A RUSTDESK_UDP_OUT_LIMIT -j RETURN

最后将其挂在 DOCKER-USER 前,限制 21116/UDP 出口方向:


iptables -I DOCKER-USER 1 \
-p udp --sport 21116 \
-j RUSTDESK_UDP_OUT_LIMIT

最后记得 iptables 规则是内存生效的,需要加上持久化和恢复机制,避免重启丢失规则。


复盘总结


需要说明的是,以上规则只是缓解措施,不是根治方案。入口白名单可以减少被国外来源利用的概率,但无法防止国内攻击源;出口限速可以控制最坏影响,但也不能修复协议本身的问题。真正的修复仍然需要 rustdesk-server 在协议层对 PunchHoleSent 增加上下文校验或者身份认证。Github 上已有人提了 issue,可以关注后续进展。


这次事件的整个处理流程相对还是比较稳健的,从调整带宽的紧急止血措施,到安全组封禁目标网段,再到保留现场抓包分析,并实施缓解方案防止后续同类型的攻击,每一步都见效显著。当然,这次事件也带来了几个收获,在此分享一下:


网络攻防无处不在


不要觉得你的网站没什么流量,部署的服务也是私人用途,就可以放松警惕,笔者历来对安全比较上心,仅开放必要的端口,并将各类密钥设置得尽量复杂,但也没想到会在服务器没被攻破的情况下被别人利用,成为了 DDoS 攻击的一环。


这也说明,服务器安全不只是防止被入侵。即使系统没有被攻破,一个正常开放的服务端口,也可能因为协议行为被滥用,成为攻击链路的一部分。


监控告警很重要


必须建立主动监控和告警,这次是因为公网流量把余额花光了触发了欠费通知,我才知道服务器被攻击了,而此时距离攻击开始时间点已经过去小半天了。所谓吃一堑,长一智,过后我立刻补齐了相关监控,包括出口流量异常增高,以及费用异常增高等监控,并设置告警,避免异常情况下无法及时得知。


对自建服务来说,安全组、防火墙、出口限制、流量监控和费用告警都应该作为基础设施的一部分,而不是出事之后才临时补救。


不要预留过多余额(确信)


因为我只预留了个位数的余额在云服务厂商中,所以损失还不算大。如果预留个3位数余额,哪怕是直到攻击结束了我还不知道,只能过后看这月度账单干瞪眼。攻击者没准也是考虑到这一点,才选择7月1日发起攻击。

最新回复 (3)
  • 未末 07-05 16:56
    1

    我今天刚把 rustdesk 服务部署在国内的 vps 上^-^,看完后有点怕

  • F_0x57 07-05 16:57
    2

    很奇怪,我怀疑是内部有定期激励用户消费计划 ^-^,无脑 DDOS ,我自己的按量付费,跟那种折扣购买的机器,公网流量能在一瞬间给我跑满,我去申请工单,售后也是草草了事,让我不太相信这些服务器,现在都是 FRP 其他方式穿透,有点恶心

  • KTMan 07-05 17:36
    3

    rustdesk 你部署好了之后,如果不建立服务的话,它也会消耗流量吗?

* 帖子来源Linux.do
返回