Apa itu SSRF?
Server-Side Request Forgery (SSRF) adalah serangan dimana attacker memaksa server (bukan browser) untuk membuat HTTP request ke tujuan yang dipilih attacker. Yang berbahaya: server biasanya ada di jaringan internal yang tidak bisa diakses dari luar — metadata cloud, database, admin dashboard.
Skenario Klasik
// Fitur: user paste URL gambar, server fetch untuk generate preview
app.post("/fetch-preview", async (req, res) => {
const url = req.body.url;
const response = await fetch(url); // ← SSRF sink!
const buffer = await response.buffer();
res.send(buffer);
});
// Attacker mengirim:
// url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
// Server menarik AWS credentials dari instance metadata!
Target Populer
- Cloud metadata service — AWS/GCP/Azure expose sensitive data di
169.254.169.254 - Internal services — Redis, Elasticsearch, databases di port localhost
- Admin panels — Kubernetes API, Docker socket, CI/CD endpoints
- Port scanning — Tebak service yang jalan di internal network
- file:// scheme — Baca file lokal (
file:///etc/passwd) - gopher:// — Craft arbitrary TCP packets (exploit Redis tanpa auth)
Kasus Nyata: Capital One (2019)
Attacker mengeksploitasi SSRF di WAF misconfigured → hit EC2 metadata → curi temporary credentials → akses S3 → download 106 juta record nasabah. Kerugian: $190 juta. Root cause: SSRF + IMDSv1 yang tidak butuh auth token.
AWS IMDSv1 vs IMDSv2
# IMDSv1 (lama, vulnerable)
curl http://169.254.169.254/latest/meta-data/
# → langsung dapat data, cukup satu GET
# IMDSv2 (baru, aman)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/
# Kenapa IMDSv2 aman? Butuh PUT dulu (SSRF biasa hanya bisa GET),
# token memiliki TTL, header X-aws-ec2-metadata-token susah dipalsukan
Pencegahan
1. Allowlist Hostname (Paling Efektif)
const ALLOWED = ["images.cdn.com", "storage.googleapis.com"];
function isAllowed(url) {
try {
const u = new URL(url);
if (u.protocol !== "https:") return false;
return ALLOWED.includes(u.hostname);
} catch {
return false;
}
}
app.post("/fetch-preview", async (req, res) => {
if (!isAllowed(req.body.url)) return res.status(400).send("URL not allowed");
// ... lanjutkan fetch
});
2. Block Private IP Ranges
// Resolve hostname → IP → cek apakah private
const dns = require("dns").promises;
const net = require("net");
async function isPrivate(host) {
const addrs = await dns.resolve(host);
return addrs.some(ip => {
if (ip.startsWith("10.") || ip.startsWith("172.16.") || ip.startsWith("192.168.")) return true;
if (ip === "127.0.0.1" || ip === "::1") return true;
if (ip.startsWith("169.254.")) return true; // cloud metadata
return false;
});
}
Catatan: ada DNS rebinding attack — hostname yang awalnya resolve ke IP publik bisa re-resolve ke IP internal setelah validasi. Mitigasi: resolve sekali, pin IP untuk request aktual.
3. Disable Redirect
// fetch bisa di-redirect ke URL internal setelah validasi lolos
const response = await fetch(url, { redirect: "error" });
4. Network Egress Control
# Di AWS: security group / VPC route table
# Hanya izinkan outbound ke internet publik, block RFC1918
# Di Kubernetes: NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
spec:
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 169.254.0.0/16
5. Pakai IMDSv2
# Force IMDSv2 di EC2 launch template
aws ec2 modify-instance-metadata-options \
--instance-id i-xxx \
--http-tokens required
Checklist SSRF
- Apakah ada endpoint yang fetch URL dari user? (webhook, avatar URL, preview, OAuth redirect)
- Apakah input URL divalidasi dengan allowlist hostname?
- Apakah redirect di-disable?
- Apakah IMDSv2 aktif di EC2?
- Apakah egress firewall memblokir RFC1918 dan 169.254/16?
- Apakah scheme dibatasi ke
http(s)://saja?