Note: Chia sẻ được lấy từ sự kiện DDoS – Tư duy phòng thủ
Introduction
Khi nói đến an ninh mạng, phần lớn chúng ta sẽ nghĩ ngay đến những Hacker với những pha tấn công “nghẹt thở” kèm theo những sự ngưỡng mộ nhất định. Tuy nhiên, bên kia chiến tuyến, câu chuyện của những “blue team”, người bảo vệ hệ thống cũng ly kỳ không kém nhưng lại rất ít được biết đến. Với những “red team”, họ có thể tấn công 1000 lần và chỉ cần 1 lần thành công thì được xem là thành công. Còn với “blue team”, họ có thể thành công chống lại 1000 cuộc tấn công, nhưng chỉ cần thất bại 1 lần thì bị xem là thất bại. It’s true!
Vậy nên, hôm nay tôi sẽ chia sẻ câu chuyện từ góc nhìn của 1 người phòng thủ khi đối mặt với các cuộc tấn công, hy vọng các bạn sẽ hiểu hơn về công việc của những người bảo vệ hệ thống. Câu chuyện được tổng hợp từ những đợt chống tấn công DDoS của tôi và các attacker – tôi đặt tên là Mr.Tèo (vì cứ gặp hắn thì có khả năng “tèo” cao), các tình tiết trong câu chuyện đều có thật và các kỹ thuật áp dụng cũng đều là thực tế, các bạn có thể áp dụng cho hệ thống của mình.
Part 1: DDoS Protection – From Inside View
NGÀY THỨ NHẤT: CHÀO SÂN
Cạch cạch cạch … enter – Mr.Tèo nhập tên miền của tôi vào tool tấn công DDoS và nhấn nút khai hoả. Sau vài lần F5, hắn nhận thấy website bắt đầu phản hồi chậm dần, lỗi “502 Bad Gateway” bắt đầu xuất hiện nhiều hơn. Vài phút sau, hắn nhoẻn miệng cười khi server đã không còn phản hồi. Mr. Tèo đã ghi bàn thắng đầu tiên, tỉ số là 1-0.
Bên kia chiến tuyến, tôi – anh IT culi kiêm quản trị server – nhận được điện thoại của sếp: “Em à, khách hàng báo website không truy cập được, kiểm tra gấp cho anh”. Công nhận, khách hàng là tool monitor trên cả realtime, còn ông sếp là công cụ notify được tích hợp AI xịn sò nhất cái xứ Đông Lào này. Tôi móc điện thoại truy cập vào website, mọi thứ cứ xoay tròn trong vô vọng và không có tín hiệu trả lời, giống như cái cách mà tôi nhắn tin cho crush vậy! Xác định hệ thống có vấn đề, tôi lật đật SSH vào server để kiểm tra.
Việc SSH vào server lúc này cũng khá lag, từng dòng chữ hiện ra chậm chạp như xem phim full HD bằng mạng GPRS vậy. Chuyện quái gì đang xảy ra vậy – tôi thầm nghĩ? Màn hình command line đã load xong và sẵn sàng chạy lệnh, tôi thực hiện ngay các thao tác kiểm tra tài nguyên hệ thống để có cái nhìn tổng quan:
- Tải (Load Average) của server đang cao ngút trời.
- CPU: tất cả các core đang chạy 100%
- RAM: Đã sử dụng toàn bộ, swap cũng đã được sử dụng.
- Disk IO: tăng cao bất thường.
Vậy đã rõ, hệ thống bị quá tải dẫn đến không thể xử lý request (truy cập) của người dùng. Vậy điều gì đã gây ra tình trạng này? Tôi chạy lệnh “top” để kiểm tra danh sách process đang chạy trên server. Thật kỳ lạ, tại sao lại có quá nhiều process “httpd” (process của web server Apache) đang chạy thế này? Và mỗi process đang sử dụng 100% CPU. Rõ ràng, đây chính là nguyên nhân khiến hệ thống bị quá tải. Không biết, web server đang xử lý cái gì nhỉ – tôi băn khoăn.
Process httpd được sinh ra để xử lý request của người dùng, vì thế khi hệ thống có nhiều process httpd chạy cùng lúc đồng nghĩa đang có lượng truy cập lớn đổ về website. Tôi kiểm tra ngay access_log của server để xác định các truy cập đó là gì.
Hừm, log truy cập quá nhiều, nhìn hoa cả mắt nhưng rõ ràng có sự bất thường:
- Các truy cập đến từ rất nhiều IP khác nhau, đa phần là IP nước ngoài.
- Cùng truy cập vào duy nhất trang chủ.
- Các IP chỉ access vào trang chủ và không tải các static resource (như javascript, CSS, image …), điều này cho thấy đây không phải là các client bình thường và không được truy cập bằng browser.
Đến đây, tôi nhận ra mình đang là nạn nhân của một cuộc tấn công DDoS nhắm vào tầng ứng dụng (Layer 7 – Application Layer). Mục tiêu của kẻ tấn công là tạo thật nhiều request khiến server xử lý đến cạn kiệt tài nguyên. Do đó, các client hợp lệ sẽ không sử dụng được dịch vụ trên server.
Với kiểu tấn công ở tầng ứng dụng, yêu cầu các IP tham gia tấn công phải là client thật, nghĩa là chúng phải hoàn thành được bắt tay 3 bước (3 way handshake) ở tầng TCP mới có thể gửi payload lên tầng Application. Do đó, số lượng IP tấn công là hữu hạn. Nếu số lượng IP tấn công ít thì yêu cầu tần số tấn công trên mỗi IP phải cao mới đủ sức làm quá tải server. Ngược lại, nếu số lượng IP đủ nhiều mới có thể cho phép tần số tấn công trên mỗi IP thấp mà vẫn hạ gục được server. Vậy nên, với tấn công ở Layer 7, rate limit (giới hạn tần số) trên mỗi IP là một phương pháp có thể áp dụng để giảm thiểu tác hại của DDoS và lọc ra danh sách botnet.
Nghĩ là làm, việc quan trọng nhất cần ưu tiên bây giờ là nhanh chóng đưa website trở lại hoạt động. Tôi áp giới hạn tần số truy cập của từng IP lên trang chủ, nếu IP nào truy cập vượt quá tần số quy định thì sẽ từ chối phục vụ, đồng thời lưu log lại IP vi phạm. Nếu IP nào vi phạm từ 3 lần trở lên (nghĩa là cố tình vi phạm – khả năng là bot cao) thì đưa vào Firewall để block luôn IP, giảm tải cho tầng ứng dụng. Thời gian đầu, server vẫn còn phải gồng mình phục vụ các request vì IP chưa đạt tới ngưỡng quy định, nhưng dần dần server đã bắt được nhiều IP botnet cho vào firewall và ổn định dần. Sau tầm 5p, website đã có thể truy cập trở lại, mặc dù vẫn phải thở oxy 1 chút.
Website truy cập lại được phần nào giúp tôi giảm áp lực với sếp. Lúc này tôi mới có nhiều thời gian phân tích kỹ hơn đặc điểm của cuộc tấn công. Tôi capture 10,000 raw packet để nhìn rõ hơn mặt mũi của bọn chúng:
tcpudmp -i any -nn -c 10000 port 80
Ồ, phát hiện thêm một đặc điểm mới của đám botnet này. Request của chúng phần lớn mang theo các header như:
- X-Forwarded-For
- X-Real-IP
- X-Via
HTTP Header chứa X-Forwarded-For mang theo IP của máy chạy tool DDoS
Các header này cho thấy Mr.Tèo đã sử dụng các proxy public làm trung gian tấn công hệ thống. Ngoài ra, giá trị IP nằm trong các header kia cũng chính là IP của Mr.Tèo.
Dựa vào đặc điểm này, tôi tiến hành cấu hình server siết chặt hơn các truy cập có mang các header proxy. Người dùng bình thường nếu sử dụng mạng công ty có cấu hình proxy thì request của họ cũng có thể mang các header trên. Nên việc chặn hoàn toàn các request mang header proxy sẽ dễ chặn nhầm người dùng thật.
Do đó, tôi chỉ giới hạn tần số của chúng mà không chặn hẳn. Ngoài ra, tôi còn giới hạn sâu hơn 1 chút:
- Nếu IP là IP quốc tế và mang header proxy: giới hạn chỉ cho phép truy cập 2 lần/giây
- Nếu IP là IP Việt Nam và mang header proxy: giới hạn cho phép truy cập 5 lần/giây.
Pseudo code:
if (request == “GET /”) {
if ( IP not in VN AND proxy is in http.headers ) {
limit_rate 2/s;
if (request_rate > limit_rate) {
log_ip(“attack_ip.log");
}
}
if ( IP is in VN AND proxy is in http.headers ) {
limit_rate 5/s;
if (request_rate > limit_rate) {
log_ip(“attack_ip.log”);
}
}
}
list_attack_ip = read(“attack_ip.log");
foreach ip in list_attack_ip {
if ( count(ip) > 3) {
firewall_block(ip);
}
}
Vì phần lớn IP tấn công là IP quốc tế nên rule như trên đã cản lọc gần như toàn bộ cuộc tấn công. Và tôi cũng không phải quá lo lắng chặn nhầm người dùng thật vì khách hàng của tôi phần lớn là ở Việt Nam, và IP Việt Nam nếu có sử dụng proxy thì cũng được limit tương đối thoải mái, đủ để truy cập bình thường!
Sau khi áp rule, tải của server đang giảm dần, danh sách IP proxy bị đưa vào Firewall càng ngày càng tăng lên rồi dừng hẳn. Có lẽ, toàn bộ IP proxy đã bị nhận diện và block bởi firewall. Website đã truy cập ổn định lại. Tôi thở phào nhẹ nhõm vì đã vượt qua thử thách đầu tiên, tuy nhiên tôi biết rằng cuộc chiến chỉ vừa mới bắt đầu, càng về sau trận chiến sẽ càng ác liệt hơn.
Tổng kết ngày 1
Tấn công:
HTTP Flood sử dụng public proxy. Mục tiêu làm tiêu hao tài nguyên (CPU, RAM, Disk …) của máy chủ, quá tải web server.
Phòng thủ:
Dấu hiệu nhận biết: tài nguyên bị cạn kiệt bởi các process liên quan đến xử lý web request. access_log tràn ngập các log entry với pattern tương tự nhau.
- Giới hạn tần số truy cập vào URL bị tấn công.
- Phân tích kỹ header của request để tìm dấu hiệu bất thường.
- Phát hiện tấn công ở Layer 7 nhưng ghi log lại để đẩy dần xuống chặn IP ở Layer 3 cho tối ưu.
Tỉ số tạm thời là 1-1 nghiêng về 2 đội. Bên kia chiến tuyến, Mr.Tèo đang bày binh bố trận cho trận đánh tiếp theo! À mà Mr.Tèo, khi dùng tool DDoS sử dụng public proxy, các proxy sẽ Forward IP máy chạy tool của ông cho tôi đấy nhé, hãy cẩn thận!
Part 2: DDoS Protection – From Inside View
NGÀY THỨ HAI: CHỌT KHE GHI BÀN
Bài hôm qua được các bác share nhiều nên đã đến tay Mr.Tèo. Hắn đọc ngấu nghiến rồi giật mình khi thấy đoạn bị lộ IP. “Đã thế bố mày không dùng proxy nữa, cho mày bắt vào mắt” – Mr.Tèo said.
Căn bản, IP của các proxy server là cố định, phía server tôi chỉ cần dựa vào header và lọc ra đủ IP proxy, đưa vào Firewall thì hầu như cách đánh này sẽ không còn tác dụng (bạn nào chưa rõ chỗ này thì vui lòng xem lại Ngày thứ nhất). Mr. Tèo đã hiểu rõ điều này. Các header proxy như: X-Forwared-For, X-Real-IP, X-Via … được các public proxy tự động chèn vào trước khi gửi đến server của tôi. Mr. Tèo không control các server proxy nên không thể can thiệp cấu hình để loại bỏ header này. Hắn biết tôi dựa vào các header proxy để chống lại hắn nên đã sử dụng chiêu thức khác: dùng botnet để DDoS, loại bỏ hoàn toàn proxy header. Và tất nhiên, các rule limit rate dựa trên proxy header của tôi lúc này không còn tác dụng nữa. Một pha “chọt khe” ngọan mục của Mr. Tèo, tỉ số được nâng lên 2-1. Con bot notify tích hợp AI mang tên “Sếp” lại gọi tên tôi một lần nữa…
SSH vào server kiểm tra, tình trạng quá tải y hệt như hôm qua: tràn ngập process httpd, full CPU, RAM, Disk IO … Kiểm tra access_log thì pattern vẫn y hệt hôm qua, không khác một chút gì: đa phần là IP nước ngoài, GET vào trang chủ, không load các static resources, User-Agent và Referer hợp lệ… Đúng là lần này bắt vào mắt thật! Rule phòng thủ hôm qua vẫn còn nằm đấy mà giờ nó lại lọt hết vào trong thế này thì toang rồi. Một vài ý tưởng hiện lên trong đầu:
- Không limit theo proxy header mà limit hẳn tần số truy cập từ các IP quốc tế.
- Áp limit rate trên từng IP, không quan tâm các vấn đề khác.
- Bật testcookie để challenge javascript
Tất cả các phương án đều khả thi và có thể ngay lập tức giải quyết vấn đề. Tuy nhiên, tư tưởng của tôi khi chống DDoS là phải làm sao giảm tối thiểu ảnh hưởng đến trải nghiệm của các khách hàng hợp lệ, cũng như các bot tốt như Google Bot, Facebook bot … Các giải pháp phía trên tuy tốt nhưng nó thuộc dạng giải pháp tổng quan, khi áp dụng ít nhiều sẽ ảnh hưởng đến khách hàng hợp lệ của mình.
Còn thở là còn gỡ, giờ đang tắt thở thì gỡ kiểu gì? Tôi triển khai áp limit rate cho IP quốc tế trước để lấy chút oxy cho server rồi tiến hành phân tích sâu hơn. Capture 10,000 raw packet về xem chi tiết:
tcpdump -i any -nn -c 10000
HTTP Header lần này đẹp lung linh, không còn chứa proxy header nữa. Các tham số khác như User-Agent, Referer cũng đều đúng theo tiêu chuẩn và hợp lý. Không có đặc điểm nào để phân biệt giữa bot và người dùng hợp lệ nữa rồi. Mr.Tèo chọt khe cú này hơi chí mạng. Làm sao bây giờ?
Khi các dữ kiện ở Layer 7 không còn giúp tôi phân biệt giữa người và bot, tôi phân tích sâu hơn về các layer phía dưới, cụ thể là ở layer 4 – Transport layer. Điều thú vị bắt đầu lộ diện:
- [1] Quá trình bắt tay 3 bước của TCP diễn ra hoàn toàn hợp lệ
- [2] Sau khi bắt tay, client ngay lập tức push 1 packet với cờ [PSH, ACK] và length lên đến 1308 bytes.
- [3] Payload chứa đến 4 cú GET và trang chủ giống hệt nhau.
À, thì ra là mày … thăm ngàn, thăm ngàn. Đây là 1 dấu hiệu quá rõ ràng để nhận diện đám botnet. Tại sao ư? Bởi vì sau khi bắt tay ba bước hoàn tất, một client thông thường sẽ gửi 1 cú GET đến trang chủ để lấy nội dung HTML. Sau đó, dựa vào nội dung HTML trả về client mới biết cần request những gì tiếp theo. Việc ngay lập tức gửi nhiều cú GET ngay sau khi bắt tay là điều không tự nhiên, chưa kể lại là request giống nhau. Nhìn vào phần số [3] chúng ta sẽ thấy rõ, payload của gói tin là 4 cái “Hypertext Transfer Protocol”.
HTTP là một stateless protocol, thông thường, mỗi request sẽ được gửi trong một socket riêng biệt và socket sẽ được đóng sau khi hoàn tất nhận phản hồi từ server. Mr. Tèo đã lợi dụng cơ chế Keep-Alive, một cơ chế của web server cho phép client gửi nhiều HTTP trong cùng một TCP socket mà không cần phải mở socket mới. Với packet trên, thay vì phải mở 4 connection để gửi 4 cú GET, hắn chỉ cần mở 1 connection mà thôi. Tại sao hắn lại làm như thế?
Có vài lý do:
- Để bypass các Network Firewall. Các network firewall hoạt động ở layer 3 & 4, đối tượng xem xét của chúng là các địa chỉ IP và TCP/UDP header. Các network firewall không quan tâm đến application payload. Với cách đánh như trên, các network firewall chỉ ghi nhận 1 connection, nhưng phía tầng application, web server ghi nhận 4 request. Hắn hoàn toàn có thể nhét hàng chục request hoặc hàng trăm request vào một connection như vậy và bypass được cơ chế detect/limit ở các Network Firewall.
- Để tăng sát thương. Nếu mỗi request được gửi riêng lẻ, giữa chúng sẽ có những độ trễ, web server sẽ có những khoảng trống để “thở”. Nhưng nếu nhét vào gửi 1 cục như trên thì các request sẽ đến với web server cùng lúc, áp lực lúc này sẽ được gia tăng.
Tên khoa học chính thức của thủ đoạn này là: “Multiple Verb – Single Request”. Mọi người có thể tìm hiểu thêm.
Nhận diện được hành vi của botnet, tôi tiến hành viết rule ngăn chặn theo logic:
- Xét các gói tin thứ 3-5 của kết nối đến port 80, nếu packet length lớn hơn 600 bytes bắt đầu xem xét vào payload.
- Round 1: Nếu bytes 40-52 chứa string “GET / HTTP” thì chuyển kết nối sang round 2.
- Round 2: Nếu bytes 350-362 tiếp tục chứa string “GET / HTTP” thì chuyển kết nối sang round 3.
- Round 3: Nếu bytes 650-672 tiếp tục chứa string “GET / HTTP” thì đưa IP vào BLACKLIST, block 30 phút.
- IP nào thỏa tất cả các điều kiện trên mới đến được tới Round 3 và chắc chắn là botnet rồi. Các request tiếp theo của chúng trong 30 phút tới sẽ bị chặn.
# Tạo ipset tên botnet chứa các IP vi phạm
ipset create botnet hash:ip
# Tạo chain ROUND_1
iptables -N ROUND_1
iptables -I ROUND_1 -m string --algo kmp --from 40 --to 52 --string "GET / HTTP" -j ROUND_2
# Tạo chain ROUND_2
iptables -N ROUND_2
iptables -A ROUND_2 -m string --algo kmp --from 350 --to 362 --string "GET / HTTP" -j ROUND_3
# Tạo chain ROUND_3
iptables -N ROUND_3
iptables -A ROUND_3 -m string --algo kmp --from 650 --to 672 --string "GET / HTTP" -j SET --add-set botnet src --timeout 1800 --exist
# Khi nhận được packet ở chain INPUT, nếu các đó là request đến dịch vụ web (port 80), xét gói tin thứ 3-5 của kết nối, nếu có chiều dài từ 600-1400 bytes thì cho vào ROUND_1 để check payload
iptables -I INPUT -p tcp --dport 80 -m connbytes --connbytes 3:5 --connbytes-dir original --connbytes-mode packets -m length --length 600:1400 -j ROUND_1
# Khi server vừa nhận được gói tin ở table raw - chain PREROUTING, nếu IP nằm trong danh sách botnet thì DROP kết nối
iptables -t raw -I PREROUTING -p tcp --dport 80 -m set --match-set botnet src -j DROP
Sau khi apply rule, tôi thử xem rule đã hoạt động đúng hay chưa. Móc python ra code 1 script đơn giản giả lập lại kiểu tấn công và check counter của iptables:
Perfect, các round đã hoạt đúng theo kịch bản, IP cũng đã bị đưa vào danh sách botnet. Tôi deploy lên server chính và chỉ trong vòng 30s, hơn 10,000 ip botnet đã bị nhận diện và đưa vào Firewall. Server lại mượt mà trở lại. Tỉ số được cân bằng 2-2.
Tổng kết ngày 2
Tấn công:
- Sử dụng phương thức: Multiple Verb – Single Request
- Gửi nhiều HTTP request vào một packet với cờ [PSH, ACK]
- Log ở layer 7 hợp lệ, không có dấu hiệu nhận diện.
- Ở Layer 4, do tạo ra ít kết nối nên bypass được hệ thống Network Firewall limit theo số lượng kết nối hoặc tần số kết nối trên mỗi IP.
Phòng thủ:
- Dựa vào các đặc điểm của packet ở Layer 4: packet length, packet payload.
- Kiểm tra bằng nhiều tầng (round 1, round 2, round 3) để hạn chế tối đa việc chặn nhầm user hợp lệ, bắt chính xác botnet.
- Khi detect botnet, đưa vào blacklist và “silent” chúng trong 30 phút. Làm như vậy để “lỡ xui” có chặn nhầm user thật thì 30 phút sau sẽ tự động được unblock.
Về chống DDoS, căn bản là việc tìm cách phân biệt đâu là bot, đâu là người để chặn. Mr.Tèo đã tận dụng khéo léo Keep-Alive để qua mặt Firewall và gom nhiều request vào cùng một payload để gia tăng sát thương.Tuy nhiên, lần này có vẻ Mr.Tèo đã hơi over-optimize rồi.
Part 3,4,5: DDoS Protection – From Inside View
Tham khảo: DDoS Protection – From Inside View (Day 3,4,5)
Trích: SinhVienIT, 7onez, Vietnix, Quantrilinux, Chongluadao