Giải phẫu & Khai thác Lỗ hổng Ứng dụng
17/21
Bài 17 / 21~13 phútSSRF & Cloud PivotingMiễn phí… lượt xem

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:

Python
# 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 khai 93.184.216.34 (IP của example.com), với TTL = 0. Hàm is_safe_domain thấ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ện requests buộ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ựcCơ chế kỹ thuật trong DNS Rebinding
Người trưởng thành xuất trình IDLầ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ảnhBả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ệcThư 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?

Mô hình DNS Rebinding & Cửa sổ TOCTOU

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:

Nhớ lại —

Ôn lại nguyên lý hoạt động của các bộ phân tích URL và các kỹ thuật mã hóa địa chỉ IP.

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:

  1. 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
  2. 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

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.

🐛 Tìm bug trong code này
# 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:

Python
# 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:

  1. DNS chỉ được phân giải duy nhất một lần ở bước kiểm tra địa chỉ IP.
  2. 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).
  3. Thuộc tính allow_redirects=False ngăn chặn mọi hành vi HTTP Redirect ngầm trỏ về IP nội bộ.
  4. 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:

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

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Tạ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)?
  2. Q2
    Nế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?
  3. Q3
    Cơ chế DNS Rebinding có thể được sử dụng trong tấn công Client-side (trình duyệt) như thế nào?
  4. Q4
    Khi 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?
  5. Q5
    Là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.

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Khai thác Cloud Metadata: AWS IMDSv1/v2 và GCP