Dùng lệnh docker compose up -d để chạy các container. Kết quả sau khi chạy:
$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES475609f1d671 handsonsecurity/seed-ubuntu:large "/bin/sh -c /bin/bash" About a minute ago Up About a minute seed-attackere05c5d32b66e handsonsecurity/seed-ubuntu:large "bash -c ' /etc/init…" About a minute ago Up About a minute hostB-10.9.0.67d2a11f03dbb handsonsecurity/seed-ubuntu:large "bash -c ' /etc/init…" About a minute ago Up About a minute hostA-10.9.0.5
Với địa chỉ IP của các host là:
Attacker: 10.9.0.1
Host A: 10.9.0.5
Host B: 10.9.0.6
About the Attacker Container
Attacker container mount thư mục /volumes vào thư mục ./src của hosting machine (máy chạy Docker container):
volumes: - ./src:/volumes
Chúng ta sẽ viết code và đặt ở trong thư mục này.
Container được gắn vào một virtual switch nên nó chỉ thấy các traffic của chính nó. Để một container thấy được traffic của các container khác nhằm mục đích sniffing thì ta cần dùng host mode:
network_mode: host
Khi một container ở trong host mode, IP của nó sẽ trùng với IP của hosting machine và nó còn thấy được tất cả các network interface của hosting machine.
Getting the Network Interface Name
Các container được kết nối với nhau thông qua network sau:
$ docker network lsNETWORK ID NAME DRIVER SCOPE88ab64b587ba net-10.9.0.0 bridge local
Task 1: Using Scapy to Sniff and Spoof Packets
Scapy là một thư viện của Python dùng để thao tác với các gói tin. Cụ thể hơn, nó cho phép chúng ta đánh hơi (sniffing) và giả mạo (spoofing) các gói tin.
Khi mở kết nối Telnet từ một địa chỉ IP khác 10.9.0.5 chẳng hạn như 10.9.0.6 (host B) hoặc 10.9.0.1 (attacker container) thì sẽ không bắt được gói tin TCP.
Task 1.1C: Sniffing a Subnet
Bắt các gói tin đi vào hoặc đi ra subnet 192.168.1.0/24:
from scapy.all import *def print_pkt(pkt): pkt.show()pkt = sniff(iface='br-88ab64b587ba', filter='src net 192.168.1.0/24 or dst net 192.168.1.0/24', prn=print_pkt)
Ping từ attacker container đến 192.168.1.1 (gateway router) nhưng không bắt được gói tin nào. Nếu chuyển filter thành src net 10.9.0.0/24 or dst net 10.9.0.0/24 thì vẫn bắt được các gói tin ICMP từ các host trong mạng 10.9.0.0/24.
Important
Không thể bắt các gói tin ở đường mạng khác với đường mạng của interface đang lắng nghe.
Sử dụng interface eth0 để lắng nghe traffic trên đường mạng 192.168.1.0/24:
from scapy.all import *def print_pkt(pkt): pkt.show()pkt = sniff(iface='eth0', filter='src net 192.168.1.0/24 or dst net 192.168.1.0/24', prn=print_pkt)
Ping từ attacker container đến gateway router một lần nữa thì bắt được các gói tin ICMP có dạng như sau:
###[ Ethernet ]### dst = 5a:94:ef:e4:0c:dd src = 3e:d3:69:51:28:3d type = IPv4###[ IP ]### version = 4 ihl = 5 tos = 0x0 len = 84 id = 49925 flags = DF frag = 0 ttl = 64 proto = icmp chksum = 0xb44e src = 192.168.65.3 dst = 192.168.1.1 \options \###[ ICMP ]### type = echo-request code = 0 chksum = 0x5459 id = 0x61 seq = 0x1###[ Raw ]### load = '\x99#2f\x00\x00\x00\x00\x13\xe8\x05\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./01234567'
Với 192.168.65.3 là địa chỉ IP của attacker container trên interface eth0.
Task 1.2: Spoofing ICMP Packets
Scapy cho phép chúng ta xây dựng và gửi gói tin. Task này yêu cầu:
Xây dựng một gói tin echo request (ICMP).
Gửi gói tin echo request cho một host ở trong network.
Dùng Wireshark để theo dõi gói tin echo request.
Xây dựng và gửi gói tin echo request bằng script như sau:
Dòng ipPacket = IP tạo ra một instance của lớp IP đại diện cho một gói tin IP. Có thể dùng câu lệnh ls(ipPacket) hoặc ls(IP) để liệt kê các thuộc tính của lớp IP:
Dòng ipPacket.dst = '10.9.0.5' thiết lập địa chỉ IP đích đến. Nếu không thiết lập, giá trị mặc định sẽ là '127.0.0.1'.
Dòng icmpPacket = ICMP() tạo ra một instance của lớp ICMP đại diện cho một gói tin ICMP. Loại gói tin ICMP mặc định là echo request.
Dòng packet = ipPacket/icmpPacket ghép ipPacket và icmpPacket lại với nhau để tạo thành một gói tin ICMP hoàn chỉnh. Toán tử / đã được overload bởi lớp IP và được dùng để chồng các gói tin thuộc các tầng lại với nhau.
Sau khi chạy script thì ta bắt được gói tin echo-request và echo-reply bằng Scapy (do không có giao diện nên không thể dùng Wireshark) như sau:
root@docker-desktop:/volumes# python3 icmp_sniff.py###[ Ethernet ]### dst = 02:42:0a:09:00:05 src = 02:42:8d:86:fa:d3 type = IPv4###[ IP ]### version = 4 ihl = 5 tos = 0x0 len = 28 id = 1 flags = frag = 0 ttl = 64 proto = icmp chksum = 0x66c9 src = 10.9.0.1 dst = 10.9.0.5 \options \###[ ICMP ]### type = echo-request code = 0 chksum = 0xf7ff id = 0x0 seq = 0x0###[ Ethernet ]### dst = 02:42:8d:86:fa:d3 src = 02:42:0a:09:00:05 type = IPv4###[ IP ]### version = 4 ihl = 5 tos = 0x0 len = 28 id = 18465 flags = frag = 0 ttl = 64 proto = icmp chksum = 0x1ea9 src = 10.9.0.5 dst = 10.9.0.1 \options \###[ ICMP ]### type = echo-reply code = 0 chksum = 0x0 id = 0x0 seq = 0x0
Có thể thấy, Scapy bắt được gói tin echo request và gói tin echo reply tương ứng.
Task 1.3: Traceroute
Trong task này, chúng ta sẽ sử dụng Scapy để tìm ra các router giữa host nguồn và host đích (tương tự như Traceroute).
Ý tưởng như sau:
Xây dựng một gói tin ICMP với trường TTL (Time-To-Live) có giá trị là 1 và đích đến là host đích. Gói tin này sẽ được hủy bởi router đầu tiên và router đó cũng sẽ gửi lại cho ta gói tin ICMP error message. Khi đó, ta sẽ biết được địa chỉ IP của router đầu tiên trong đường đi từ host nguồn đến host đích.
Kế tiếp, ta tăng TTL lên thành 2 và gửi lại gói tin để nhận địa chỉ IP của router thứ 2.
Lặp lại quá trình này cho đến khi gói tin đến được host đích.
Dựa vào sơ đồ mạng của lab, ta thấy rằng giữa các host không có router nên trường TTL có thể có giá trị là 1. Xây dựng script như sau:
Sau khi chạy script thì công cụ hiển thị kết quả như sau:
Task 1.4: Sniffing and Spoofing
Task này yêu cầu ta viết script để gửi gói tin echo reply giả mạo bất cứ khi nào có một gói tin echo request được gửi từ các host trong LAN (trừ attacker container) đến một host nào đó, kể cả khi nó không tồn tại.
Có thể chạy các lệnh ping sau để thử nghiệm:
ping 1.2.3.4 # a non-existing host on the Internetping 10.9.0.99 # a non-existing host on the LANping 8.8.8.8 # an existing host on the Internet
Hint
Đề bài gợi ý rằng ta cần hiểu về giao thức ARP và định tuyến để giải thích các kết quả.
1.2.3.4
Thử dùng host A ping đến 1.2.3.4, ta thu được các gói tin sau trong Termshark của attacker container:
Với 10.9.0.5 và 02:42:0a:09:00:05 lần lượt là địa chỉ IP và địa chỉ MAC được gán cho interface eth0 của host A:
Có thể thấy, host A có gửi một gói tin ARP request để phân giải địa chỉ IP 10.9.0.1 (của attacker container) thành địa chỉ MAC 02:42:4b:60:b2:90. Ngoài ra, các gói tin echo request đều được gửi đến địa chỉ 1.2.3.4 thông qua địa chỉ MAC của attacker container. Như vậy, attacker container đóng vai trò như là default gateway của host A. Kiểm tra bảng định tuyến của host A thì quả thật như vậy:
$ docker exec -it hostA-10.9.0.5 ip routedefault via 10.9.0.1 dev eth010.9.0.0/24 dev eth0 proto kernel scope link src 10.9.0.5
Tuy nhiên, do IP 1.2.3.4 không tồn tại nên host A không nhận được gói tin echo reply nào.
Xây dựng script để lắng nghe và gửi gói tin echo reply giả mạo:
Gói tin echo reply bắt buộc phải chứa payload nếu gói tin echo request có. Trích từ RFC792:
The data received in the echo message must be returned in the echo reply message.
Như vậy, ta cần sao chép phần payload từ gói tin echo request vào gói tin echo reply để tạo thành một gói tin ICMP hoàn chỉnh. Ngoài ra, giá trị id và seq trong ICMP header của gói tin echo reply cũng phải khớp với gói tin echo request.
Chạy lại script và ping từ host A đến 1.2.3.4, ta thu được traffic sau trong Termshark:
Output của câu lệnh ping cho thấy các gói tin echo reply giả mạo đã được host A chấp nhận:
$ docker exec -it hostA-10.9.0.5 ping 1.2.3.4PING 1.2.3.4 (1.2.3.4) 56(84) bytes of data.64 bytes from 1.2.3.4: icmp_seq=1 ttl=64 time=121 ms64 bytes from 1.2.3.4: icmp_seq=2 ttl=64 time=50.9 ms64 bytes from 1.2.3.4: icmp_seq=3 ttl=64 time=38.0 ms64 bytes from 1.2.3.4: icmp_seq=4 ttl=64 time=36.8 ms
10.9.0.99
Dùng host A để ping đến 10.9.0.99, ta thu được các gói tin sau ở trong Termshark của attacker container:
Có thể thấy, host A liên tục gửi các gói tin ARP đến địa chỉ broadcast trong mạng nội bộ nhằm phân giải địa chỉ IP 10.9.0.99 nhưng không có phản hồi. Do không có địa chỉ MAC, gói tin ICMP sẽ không được gửi đi và do đó mà ta không thể bắt được ở trong Termshark.
Ta sẽ lắng nghe các gói tin ICMP ở promiscuous mode với read timeout là 1 giây. Interface mà ta lắng nghe được truyền vào thông qua đối số dòng lệnh. Nếu không có đối số, interface sẽ là br-88ab64b587ba.
Hàm compile_and_apply_filter() giúp biên dịch và áp dụng filter:
Thuộc tính ip->ip_src và ip->ip_dst tương ứng với IP nguồn và IP đích của gói tin. Hai thuộc tính này có kiểu struct in_addr thuộc netinet/in.h, có định nghĩa như sau1:
struct in_addr { unsigned long s_addr; // load with inet_aton()};
Để chuyển giá trị long về chuỗi IP có dấu chấm phân cách, ta cần dùng hàm inet_ntoa() của netinet/in.h:
Hàm trên sẽ liên tục in ra payload của các gói tin Telnet.
Chạy chương trình rồi mở kết nối Telnet từ host A đến host B:
$ docker exec -it hostA-10.9.0.5 telnet 10.9.0.6Trying 10.9.0.6...Connected to 10.9.0.6.Escape character is '^]'.Ubuntu 20.04.1 LTSe05c5d32b66e login: seedPassword: Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.15.146.1-microsoft-standard-WSL2 x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantageThis system has been minimized by removing packages and content that arenot required on a system that users do not log into.To restore this content, you can run the 'unminimize' command.Last login: Thu May 2 11:13:03 UTC 2024 from hostA-10.9.0.5.net-10.9.0.0 on pts/1seed@e05c5d32b66e:~$
Output của chương trình:
root@docker-desktop:/volumes# ./sniffer br-88ab64b587ba�������� ��!��"��'������ ��#��'�������!��"����#�� ����'���������� ��������Ubuntu 20.04.1 LTS�e05c5d32b66e login: sseeeeddPassword: deesWelcome to Ubuntu 20.04.1 LTS (GNU/Linux 5.15.146.1-microsoft-standard-WSL2 x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantageThis system has been minimized by removing packages and content that are�not required on a system that users do not log into.To restore this content, you can run the 'unminimize' command.Last login: Thu May 2 11:13:03 UTC 2024 from hostA-10.9.0.5.net-10.9.0.0 on pts/1
Có thể thấy, ta thu được username và password. Đối với username, do giá trị được hiển thị lại cho người dùng nên ta thấy các ký tự bị lặp lại.
Task 2.2: Spoofing
Khi một người dùng thông thường gửi một gói tin, hệ điều hành không cho phép người dùng thiết lập giá trị cho đa số các trường trong các header của các giao thức chẳng hạn như TCP, UDP và IP. Người dùng chỉ có thể thiết lập giá trị cho một vài trường chẳng hạn như địa chỉ IP đích, port đích, … Tuy nhiên, nếu như người dùng có đặc quyền root thì có thể thiết lập giá trị cho bất kỳ trường nào trong header của gói tin.
Nếu gói tin không có các header của Ethernet, ta sẽ không capture được gói tin echo request ở trong Termshark hoặc Scapy
Can you set the IP packet length field to an arbitrary value, regardless of how big the actual packet is?
Hàm tạo gói tin IP:
Packet make_ip_packet(char *src, char *dst, byte proto, Packet payload){ /* Create packet */ Packet ip_packet = { .data = malloc(sizeof(struct iphdr) + payload.len), .len = sizeof(struct iphdr) + payload.len}; /* Point to the IP header */ struct iphdr *iph = (struct iphdr *)ip_packet.data; /* Set IP header fields */ iph->version = 4; // version iph->ihl = 5; // IP header length (5 means 5 words, 1 word is 4 bytes, so it will be 5x4=20 bytes in total) iph->tos = 0; // priority iph->tot_len = htons(ip_packet.len); // total length (includes header and data) iph->id = htons(9999); // identification (anything is valid) iph->frag_off = 0; // fragment offset (0 means no fragmentation) iph->ttl = 64; // time to live iph->protocol = proto; // protocol iph->check = 0; // set to 0 for now (will be calculated later) iph->saddr = inet_addr(src); // source IP address iph->daddr = inet_addr(dst); // destination IP address iph->check = checksum(ip_packet.data, sizeof(struct iphdr)); // calculate checksum /* Copy packet payload */ memcpy(&ip_packet.data[sizeof(struct iphdr)], payload.data, payload.len); return ip_packet;}
Giả sử giá trị đúng của tot_len phải là 40. Có các trường hợp sau xảy ra:
Trường hợp đầu tiên, giá trị của tot_len nhỏ hơn 20: không gửi được gói tin echo request. Thay vào đó, sẽ có một gói tin IPv4 được gửi đi như sau:
Trường hợp thứ 2, giá trị của tot_len lớn hơn 40: có gói tin echo request được gửi đi nhưng không có gói tin echo reply trả về và Termshark thông báo “IPv4 total length exceeds packet length (40 bytes)“.
Trường hợp thứ 3, giá trị của tot_len nhỏ hơn 28: có gói tin echo request được gửi đi nhưng không có gói tin echo reply trả về và Termshark thông báo “Malformed Packet”.
Trường hợp thứ 4, giá trị của tot_len lớn hơn hoặc bằng 28 và nhỏ hơn 40: có gói tin echo request được gửi đi nhưng không có gói tin echo reply trả về.
Why do you need the root privilege to run the programs that use raw sockets? Where does the program fail if executed without the root privilege?
Do sử dụng đến những tính năng low-level nên ta cần dùng quyền root. Nếu không sử dụng thì sẽ gặp lỗi khi mở socket:
seed@docker-desktop:/volumes$ ./icmp_spooferError: socket() failed: Operation not permittedError: ioctl() failed: Bad file descriptorSource interface index: -1Error: ioctl() failed: Bad file descriptorSource MAC address: (null)Error: ioctl() failed: Bad file descriptorSource IP address: (null)Segmentation fault
Sau đó mở một raw socket ở layer 2 và lấy thông tin của interface:
int layer2_socket_descriptor = open_raw_socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); // Open layer 2 socketstruct ifreq if_req = initialize_ifreq(args); // Get layer 2 interface information
Kế đến, lấy ra các thông tin quan trọng của gói tin echo request:
char *src_mac = get_if_mac(layer2_socket_descriptor, if_req); // src_mac is of the sniffing machinechar *dst_mac = mac_str(eth->ether_shost); // dst_mac is of the machine that sent the packetchar *src_ip = ip_str(iph->daddr); // src_ip is the destination ip in captured packetchar *dst_ip = ip_str(iph->saddr); // dst_ip is the source ip in captured packetuint8_t icmp_type = icmph->type;unsigned long icmp_payload_len = ntohs(iph->tot_len) - sizeof(struct iphdr) - sizeof(struct icmphdr) - sizeof(struct timeval);
Ta bỏ qua các gói tin ICMP không phải là echo request:
if (icmp_type != ICMP_ECHO) return;
Xây dựng gói tin dựa trên các thông tin vừa lấy được ở trên:
printf("\nREPLY PACKET: \n");/* Make ICMP payload */Packet icmp_payload = make_icmp_payload(raw_packet, icmp_payload_len);/* Make ping packet */Packet ping_packet = make_ping_packet(icmp_payload, ICMP_ECHOREPLY, ntohs(icmph->un.echo.id), ntohs(icmph->un.echo.sequence));/* Make ip packet */Packet ip_packet = make_ip_packet(src_ip, dst_ip, IPPROTO_ICMP, ping_packet);/* Make ethernet frame */Packet ethernet_frame = make_ethernet_frame(src_mac, dst_mac, ip_packet);print_buff(ethernet_frame);
Hàm make_icmp_payload() giúp tạo ra payload của gói tin ICMP: