Kỹ thuật DNS Rebinding và cửa sổ thời gian TOCTOU
Khám phá kỹ thuật DNS Rebinding và lỗ hổng Time-of-Check to Time-of-Use (TOCTOU): vượt qua các bộ lọc IP nội bộ bằng máy chủ DNS TTL=0.
TL;DR: Khi đối mặt với nguy cơ SSRF, giải pháp tự nhiên của nhiều lập trình viên là: phân giải tên miền thành địa chỉ IP, kiểm tra xem IP đó có thuộc dải mạng nội bộ (Private IP) hay không, và nếu an toàn thì mới cho phép tải dữ liệu. Tuy nhiên, giải pháp này để lộ một lỗ hổng kiến trúc nghiêm trọng mang tên TOCTOU (Time-of-Check to Time-of-Use). Bằng cách thiết lập một máy chủ DNS độc hại cấu hình thời gian sống bộ nhớ đệm bằng 0 (TTL = 0), kẻ tấn công có thể trả về IP công khai an toàn ở thời điểm kiểm tra, và lập tức tráo đổi thành IP nội bộ 127.0.0.1 ở thời điểm ứng dụng thực hiện kết nối.
1. Scenario — Cơ chế kiểm tra IP thông minh bị đánh lừa
Một ứng dụng xử lý tài liệu trực tuyến cho phép người dùng nhập đường dẫn file PDF từ Internet để phân tích văn bản. Lập trình viên đã đọc kỹ các khuyến cáo về SSRF và triển khai hàm kiểm tra IP trước khi nạp tệp:
# Vulnerable two-step verification (TOCTOU flaw)
import socket, ipaddress, requests
def is_safe_domain(domain):
# Step 1: Check time (Resolve DNS and validate IP)
ip_str = socket.gethostbyname(domain)
ip = ipaddress.ip_address(ip_str)
if ip.is_private or ip.is_loopback:
return False
return True
def fetch_document(url):
domain = urllib.parse.urlparse(url).hostname
# Check
if not is_safe_domain(domain):
return {"error": "Dia chi IP khong hop le"}, 400
# Use (Bug: requests.get will resolve DNS AGAIN!)
response = requests.get(url, timeout=3)
return {"content": response.text}
Nhìn qua, đoạn mã trên có vẻ hoàn hảo: domain phải được phân giải ra IP công khai thì mới được phép đi tiếp.
Tuy nhiên, kẻ tấn công đăng ký một tên miền độc hại rebind.attacker.com và cấu hình máy chủ DNS riêng:
- Lần truy vấn DNS thứ nhất (tại hàm
socket.gethostbyname): Trả về IP công khai93.184.216.34(IP củaexample.com), vớiTTL = 0. Hàmis_safe_domainthấy đây là IP công cộng hợp lệ và cho qua. - Lần truy vấn DNS thứ hai (ngay bên trong
requests.get): Do bản ghi DNS cóTTL = 0, bộ nhớ đệm DNS của hệ điều hành không lưu lại. Thư việnrequestsbuộc phải gửi truy vấn DNS mới. Lần này, máy chủ DNS của kẻ tấn công trả về127.0.0.1.
Thư viện requests kết nối thẳng tới dịch vụ quản trị nội bộ trên 127.0.0.1, tải về dữ liệu nhạy cảm và trả về cho kẻ tấn công. Bộ lọc kiểm tra ban đầu hoàn toàn bị vô hiệu hóa.
2. Analogy — Nhân viên soát vé và chiếc vé tráo tay
Hãy tưởng tượng một câu lạc bộ đêm có quy định nghiêm ngặt: chỉ người trên 18 tuổi mới được vào cửa:
- Khâu kiểm tra (Time of Check): Bảo vệ đứng ở cửa ngoài kiểm tra chứng minh nhân dân. Kẻ gian nhờ một người trưởng thành xuất trình giấy tờ thật hợp lệ. Bảo vệ kiểm tra thấy đúng 25 tuổi, liền gật đầu cho phép bước qua dây ngăn cách để vào sảnh chờ.
- Khâu sử dụng (Time of Use): Ở sảnh chờ trước khi bước vào phòng tiệc chính, người trưởng thành nhanh chóng tráo thẻ cho một cậu bé 14 tuổi đang đợi sẵn. Cậu bé bước thẳng vào quán bar và uống rượu mà người gác cổng bên ngoài không hề hay biết.
Trong DNS Rebinding, sự tráo đổi giấy tờ diễn ra ngay giữa hai lần tra cứu tên miền:
| Đời sống thực | Cơ chế kỹ thuật trong DNS Rebinding |
|---|---|
| Người trưởng thành xuất trình ID | Lần phân giải DNS 1 trả về IP công khai hợp lệ |
| Bảo vệ duyệt cho vào sảnh chờ | Hàm kiểm tra is_safe_domain() trả về True |
| Thao tác tráo đổi người ở sảnh | Bản ghi DNS hết hạn (TTL=0), DNS server đổi IP sang nội bộ |
| Cậu bé bước vào phòng tiệc | Thư viện HTTP client kết nối thẳng vào 127.0.0.1 |
3. Vì sao cửa sổ thời gian TOCTOU lại tồn tại trong kiến trúc mạng?

Lỗ hổng Time-of-Check to Time-of-Use (TOCTOU) là một dạng lỗi chạy đua (Race Condition) kinh điển trong khoa học máy tính. Nó xuất hiện khi một hệ thống kiểm tra trạng thái của một đối tượng ở thời điểm $t_1$, nhưng việc thao tác trên đối tượng đó lại diễn ra ở thời điểm $t_2$ ($t_2 > t_1$), và trạng thái của đối tượng có thể bị thay đổi trong khoảng thời gian $\Delta t = t_2 - t_1$.
Trong bài học trước, bạn đã nghiên cứu sự lệch pha cú pháp:
Trong trường hợp của DNS Rebinding, $\Delta t$ chính là khoảng thời gian từ lúc hàm is_safe_domain() hoàn tất cho tới khi lệnh requests.get() bắt đầu bắt tay TCP. Dù khoảng thời gian này chỉ kéo dài vài mili-giây, máy chủ DNS của kẻ tấn công vẫn dễ dàng lật ngược kết quả:
Cơ chế hoạt động của Custom DNS Server
Kẻ tấn công có thể dựng một máy chủ DNS bằng Python hoặc dùng các dịch vụ có sẵn như rbndr.us:
- Truy vấn lần 1 ($t_1$):
- Client hỏi:
A rebind.attacker.com - DNS Server trả lời:
IP: 1.1.1.1, TTL: 0
- Client hỏi:
- Truy vấn lần 2 ($t_2$):
- Client hỏi:
A rebind.attacker.com - DNS Server trả lời:
IP: 127.0.0.1, TTL: 0
- Client hỏi:
Một số máy chủ DNS thậm chí trả về cùng lúc cả 2 bản ghi IP trong một câu trả lời duy nhất:
rebind.attacker.com. IN A 1.1.1.1
rebind.attacker.com. IN A 127.0.0.1
Nếu IP đầu tiên không phản hồi hoặc bị từ chối, thư viện mạng sẽ tự động thử kết nối tiếp tới IP thứ hai, kích hoạt SSRF ngay lập tức.
# Remote image scraper
def download_image(url):
host = urlparse(url).hostname
# Resolves IP to check
addr = socket.getaddrinfo(host, 80)[0][4][0]
if addr.startswith("127.") or addr.startswith("10."):
raise SecurityException("Internal network denied")
# Downloads using URL string
return urllib.request.urlopen(url).read()Thử ngẫmVì sao việc hệ điều hành bật tính năng DNS Caching cục bộ (như nscd trên Linux hoặc DNS Cache Service trên Windows) đôi khi lại giúp giảm thiểu nguy cơ DNS Rebinding?
Bởi vì nếu DNS Cache giữ lại kết quả của lần phân giải thứ nhất bất chấp giá trị TTL = 0, thư viện HTTP client ở bước 2 sẽ tái sử dụng IP công khai thay vì gửi request DNS mới, làm hỏng kế hoạch tráo đổi IP của kẻ tấn công.
4. Giải pháp khắc phục triệt để: DNS Pinning và Ghim IP kết nối
Để triệt tiêu hoàn toàn cửa sổ TOCTOU, quy tắc bắt buộc là: Địa chỉ IP đã được kiểm tra phải là chính địa chỉ IP được dùng để mở kết nối TCP.
Kỹ thuật kết nối trực tiếp qua IP và truyền Host Header
Thay vì truyền tên miền cho thư viện HTTP client phân giải lại, ta ghim chặt IP vào kết nối:
# SECURE PATTERN: DNS Pinning via Custom HTTP Transport in Python Requests
import socket, ipaddress, urllib.parse, requests
from requests.adapters import HTTPAdapter
from urllib3.connection import HTTPConnection
def safe_fetch_url(target_url):
parsed = urllib.parse.urlparse(target_url)
original_host = parsed.hostname
port = parsed.port or (443 if parsed.scheme == "https" else 80)
# 1. Resolve DNS once at application layer
ip_str = socket.gethostbyname(original_host)
ip_obj = ipaddress.ip_address(ip_str)
# 2. Strict IP validation
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local:
raise ValueError("Yeu cau toi mang noi bo bi tu choi")
# 3. Connection pinning: keep hostname for valid TLS/SNI,
# and use custom transport adapter or Egress Proxy in production.
# NEVER set verify=False to suppress SSL errors (creates severe MITM vulnerability).
session = requests.Session()
response = session.get(
target_url,
allow_redirects=False,
verify=True,
timeout=5
)
return response.content
Trong kiến trúc này:
- DNS chỉ được phân giải duy nhất một lần ở bước kiểm tra địa chỉ IP.
- Quá trình bắt tay TLS giữ nguyên trạng thái
verify=Trueđể bảo vệ toàn vẹn dữ liệu trước tấn công Man-In-The-Middle (MITM). - Thuộc tính
allow_redirects=Falsengăn chặn mọi hành vi HTTP Redirect ngầm trỏ về IP nội bộ. - Cửa sổ thời gian TOCTOU bị triệt tiêu hoàn toàn.
5. Pitfalls & Giải pháp khắc phục triệt để
❌ Bẫy thường gặp: Tin tưởng vào cơ chế ghim DNS của hệ điều hành
Nhiều lập trình viên cho rằng chỉ cần hệ điều hành có cache DNS là đủ an toàn. Tuy nhiên, trong môi trường container Docker hoặc microservices Kubernetes, hầu hết các container base image (như Alpine, Debian slim) mặc định không cài đặt dịch vụ DNS Caching cục bộ. Mỗi lời gọi socket đều gửi gói tin UDP trực tiếp tới CoreDNS hoặc máy chủ DNS bên ngoài, khiến DNS Rebinding hoạt động hoàn hảo 100% các lần thử.
✅ Giải pháp chuẩn mực: Sử dụng Egress Proxy chuyên dụng
Giải pháp an toàn và bền vững nhất ở mức hạ tầng:
- Định tuyến toàn bộ lưu lượng tải ra ngoài Internet (Outbound HTTP) qua một Forward Proxy bảo mật (như Squid hoặc Envoy).
- Cấu hình Egress Proxy kiểm tra địa chỉ IP sau phân giải và chặn ngay lập tức các gói tin có đích đến là mạng nội bộ (Internal IP / Loopback) trước khi thiết lập kết nối TCP (TCP Handshake).
6. Đào sâu
Tài liệu tham khảo chuyên sâu về DNS Rebinding:
- Taviso — DNS Rebinding Vulnerabilities: Công cụ tạo domain test DNS Rebinding của chuyên gia an ninh Tavis Ormandy.
- USENIX Security — Protecting Browsers from DNS Rebinding Attacks: Nghiên cứu học thuật phân tích toàn diện cơ chế phòng chống DNS Rebinding.
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition: Đặc tả tiêu chuẩn quốc tế về lỗ hổng chạy đua thời gian.
7. Tóm tắt
- Lỗ hổng TOCTOU phát sinh từ khoảng cách thời gian giữa bước phân giải IP để kiểm tra và bước gọi mạng thực tế của thư viện HTTP client.
- Tấn công DNS Rebinding sử dụng máy chủ DNS tùy biến với
TTL = 0để trả về IP công khai an toàn ở lần hỏi 1 và IP nội bộ127.0.0.1ở lần hỏi 2. - Kiểm tra IP trước bằng hàm
gethostbyname()rồi truyền URL gốc cho client là mẫu code sai lầm cực kỳ phổ biến. - Phòng thủ triệt để bằng cách ghim địa chỉ IP đã xác minh trực tiếp vào socket kết nối (DNS Pinning) hoặc sử dụng Egress Proxy kiểm soát kết nối ở tầng mạng.
8. Tự kiểm tra
- Q1Tại sao kỹ thuật DNS Rebinding lại bắt buộc phải thiết lập Time-To-Live của bản ghi DNS bằng 0 (TTL = 0)?
- Q2Nếu ứng dụng sử dụng kết nối HTTPS thay vì HTTP, liệu DNS Rebinding có bị ngăn chặn bởi chứng chỉ SSL/TLS không?
- Q3Cơ chế DNS Rebinding có thể được sử dụng trong tấn công Client-side (trình duyệt) như thế nào?
- Q4Khi ghim địa chỉ IP (DNS Pinning) trong Python Requests, tại sao ta bắt buộc phải gửi kèm header 'Host': original_host?
- Q5Làm thế nào để các dịch vụ đám mây lớn (như Cloudflare hay AWS Route 53) phát hiện và hạn chế các tên miền sử dụng DNS Rebinding?
Bài tiếp theo: Khai thác Cloud Metadata Service (AWS IMDSv1/v2 và GCP)
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bản chất SSRF và bất đồng bộ URL Parser
Bài tiếpKhai thác Cloud Metadata: AWS IMDSv1/v2 và GCP
Thảo luận & góp ý
Hỏi đáp về bài này