Tutorial Mitigasi Serangan SSRF & DNS Rebinding Backend Pakai Smokescreen Proxy Go
Poin Kunci Artikel Ini:
- HTTP client membuka socket connection TCP langsung ke IP internal tersebut dan membocorkan credential internal atau kredensial IAM cloud ke penyerang.
- [ ] Service Smokescreen terpasang dan dikelola oleh Systemd dengan kredensial user non-root.
- [ ] Aturan IPTables / Network Security Group aktif memblokir akses langsung TCP port 80/443 dari proses aplikasi.
Ancaman SSRF dan DNS Rebinding di Arsitektur Microservices
Server-Side Request Forgery (SSRF) merupakan kerentanan keamanan di mana penyerang memanipulasi aplikasi backend untuk melakukan HTTP request ke lokasi yang tidak terisolasi. Dalam arsitektur microservices, aplikasi sering kali harus berinteraksi dengan layanan pihak ketiga melalui webhook, fetching metadata Open Graph, pembuatan dokumen PDF dari URL, atau pemrosesan gambar. Jika backend menerima URL dari pengguna tanpa validasi ketat, penyerang dapat mengeksploitasi endpoint internal seperti AWS EC2 Metadata Service (http://169.254.169.254/), Google Cloud Metadata (http://metadata.google.internal/), Kubernetes API Server, basis data internal, maupun service mesh internal.
Strategi pertahanan paling umumβseperti memeriksa skema URL atau memvalidasi nama domain menggunakan daftar hitam (blacklist)βsering kali gagal menghadapi teknik serangan DNS Rebinding. Serangan DNS Rebinding memanipulasi mekanika resolusi nama domain (Domain Name System) dengan memanfaatkan kondisi pacuan antara pemeriksaan awal (Time-of-Check) dan pembuatan koneksi TCP aktual (Time-of-Use).
Anatomi Serangan DNS Rebinding dan Kegagalan Validasi Layer Aplikasi
Banyak pengembang mencoba menangani SSRF dengan menyelesaikan alamat IP domain terlebih dahulu di kode aplikasi sebelum mengirimkan HTTP request. Pendekatan ini secara mendasar cacat karena ketidakcocokan antara proses resolusi DNS aplikasi dan pembuatan socket TCP oleh runtime HTTP client bawaan.
Proses eksploitasi DNS Rebinding terjadi dalam urutan berikut:
- Penyerang mengonfigurasi DNS server kustom untuk domain
malicious.attacker.comdengan nilai TTL (Time-To-Live) 0 detik. - Saat aplikasi backend memverifikasi URL, DNS resolver mengembalikan alamat IP publik yang sah (misal
93.184.216.34). Aplikasi menganggap URL aman karena IP tidak berada dalam range privat. - Beberapa milidetik kemudian, HTTP client backend mengeksekusi request HTTP aktual. Karena TTL bernilai 0, cache DNS kadaluarsa seketika. HTTP client melakukan query DNS kedua ke server penyerang.
- DNS server penyerang merespons query kedua dengan alamat IP internal (misal
169.254.169.254atau127.0.0.1). - HTTP client membuka socket connection TCP langsung ke IP internal tersebut dan membocorkan credential internal atau kredensial IAM cloud ke penyerang.
Pemeriksaan berbasis aplikasi juga rentan terhadap trik penyembunyian IP seperti representasi desimal IP (http://2852039166/), octal (http://0254.0372.0254.0372/), hexadecimal (http://0xa9fea9fe/), IPv6 mapped IPv4 (http://[::ffff:169.254.169.254]/), serta pengalihan HTTP (302 Redirect) ke endpoint internal.
Arsitektur Pertahanan Smokescreen Proxy
Smokescreen adalah HTTP CONNECT proxy berbasis bahasa Go yang dikembangkan oleh Stripe untuk menyelesaikan masalah SSRF secara mendasar pada tingkat infrastruktur. Berbeda dari proxy HTTP standar, Smokescreen dirancang khusus untuk mematahkan serangan DNS Rebinding pada lapisan transport (Layer 4).
Cara kerja Smokescreen dalam mengamankan trafik egress:
- Single-point DNS Resolution: Smokescreen melakukan resolusi DNS nama host tujuan saat mendengarkan permintaan HTTP CONNECT atau proxy request.
- Atomic IP Validation: IP hasil resolusi langsung diverifikasi terhadap daftar CIDR yang dilarang (
deny_ranges). Jika IP tergolong subnet internal, koneksi ditolak seketika sebelum socket TCP dibuat. - Direct IP Dialing: Jika IP dinyatakan aman, Smokescreen langsung membuka koneksi TCP ke alamat IP yang telah dipatenkan tersebut (bukan ke nama host), sehingga menghilangkan potensi query DNS kedua yang berbahaya.
- Enforced TLS Tunneling: Mendukung penguncian enkripsi HTTPS melalui terowongan CONNECT tanpa melakukan pembongkaran sertifikat (no TLS termination), menjaga privasi data backend.
Setup dan Konfigurasi Smokescreen Outbound Proxy di Linux VPS
Berikut adalah langkah-langkah komprehensif menginstal dan mengonfigurasi Smokescreen pada Linux VPS (Ubuntu 22.04 LTS / Debian 12).
Langkah 1: Kompilasi dan Instalasi Binary Smokescreen
Pastikan environment Go (versi 1.21+) sudah terpasang, atau unduh binary resmi dari repositori GitHub.
# Opsi A: Kompilasi langsung menggunakan Go
go install github.com/stripe/smokescreen@latest
sudo cp $(go env GOPATH)/bin/smokescreen /usr/local/bin/
# Opsi B: Unduh release binary
export SMOKESCREEN_VERSION="0.0.5"
wget https://github.com/stripe/smokescreen/releases/download/v${SMOKESCREEN_VERSION}/smokescreen_Linux_x86_64.tar.gz
tar -xvf smokescreen_Linux_x86_64.tar.gz
sudo mv smokescreen /usr/local/bin/
sudo chmod +x /usr/local/bin/smokescreen
Verifikasi biner telah terpasang dengan benar:
smokescreen --version
Langkah 2: Membuat Konfigurasi YAML Egress Filtering
Buat pengguna sistem khusus tanpa hak akses root untuk menjalankan daemon proxy demi prinsip least privilege:
sudo useradd --system --no-create-home --shell /bin/false smokescreen
sudo mkdir -p /etc/smokescreen
Buat file konfigurasi di /etc/smokescreen/config.yaml. Daftar deny_ranges mencakup seluruh range IP privat RFC 1918, Link-Local RFC 3927, Loopback, CGNAT RFC 6598, serta range IPv6 privat.
port: 4750
ip: "127.0.0.1"
deny_ranges:
# IPv4 Private Subnets (RFC 1918)
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
# IPv4 Loopback & Link-Local / Cloud Metadata
- "127.0.0.0/8"
- "169.254.0.0/16"
- "0.0.0.0/8"
# Carrier Grade NAT (RFC 6598)
- "100.64.0.0/10"
# Multicast & Reserved Range
- "224.0.0.0/4"
- "240.0.0.0/4"
# IPv6 Unique Local Address & Loopback / Link-Local
- "::1/128"
- "fc00::/7"
- "fe80::/10"
- "::/128"
- "::ffff:0:0/96"
allow_ranges: []
connect_timeout: 5s
idle_timeout: 30s
log_level: "info"
log_json: true
Atur hak akses file konfigurasi:
sudo chown -R smokescreen:smokescreen /etc/smokescreen
sudo chmod 640 /etc/smokescreen/config.yaml
Langkah 3: Konfigurasi Service Systemd
Buat unit file service Systemd di /etc/systemd/system/smokescreen.service untuk memastikan service berjalan otomatis dan terisolasi.
[Unit]
Description=Smokescreen Outbound SSRF Proxy
After=network.target
[Service]
Type=simple
User=smokescreen
Group=smokescreen
ExecStart=/usr/local/bin/smokescreen --config /etc/smokescreen/config.yaml
Restart=always
RestartSec=3
LimitNOFILE=65536
# Hardening Security Directives
ProtectSystem=strict
ProtectHome=true
NoNewPrivileges=true
PrivateTmp=true
CapabilityBoundingSet=
ReadWritePaths=/etc/smokescreen
[Install]
WantedBy=multi-user.target
Muat ulang konfigurasi Systemd dan jalankan service:
sudo systemctl daemon-reload
sudo systemctl enable --now smokescreen
sudo systemctl status smokescreen
Integrasi HTTP Client Backend dengan Smokescreen
Agar perlindungan berfungsi penuh, seluruh HTTP request outbound dari aplikasi backend harus dilewatkan melalui Smokescreen Proxy port 4750.
1. Implementasi pada Aplikasi Go
Dalam runtime Go, atur http.Transport menggunakan properti Proxy secara eksplisit:
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
func NewSecureClient(proxyAddr string) (*http.Client, error) {
proxyURL, err := url.Parse(proxyAddr)
if err != nil {
return nil, fmt.Errorf("invalid proxy address: %w", err)
}
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
}
return &http.Client{
Transport: transport,
Timeout: 15 * time.Second,
}, nil
}
func main() {
client, err := NewSecureClient("http://127.0.0.1:4750")
if err != nil {
panic(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", "https://httpbin.org/ip", nil)
if err != nil {
panic(err)
}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("Request blocked or failed: %v\n", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Response status: %d\nBody: %s\n", resp.StatusCode, string(body))
}
2. Implementasi pada Node.js (Axios & Fetch)
Gunakan pustaka https-proxy-agent untuk meruteksi koneksi HTTP/HTTPS melalui proxy:
const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');
const PROXY_URL = 'http://127.0.0.1:4750';
const httpsAgent = new HttpsProxyAgent(PROXY_URL);
const secureAxios = axios.create({
httpsAgent: httpsAgent,
httpAgent: httpsAgent,
timeout: 10000,
maxRedirects: 3 // Membatasi redirect untuk mencegah redirect loop
});
async function fetchExternalUrl(targetUrl) {
try {
const response = await secureAxios.get(targetUrl);
console.log(`Status Code: ${response.status}`);
return response.data;
} catch (error) {
if (error.response) {
console.error(`Blocked by Proxy - Status: ${error.response.status}`);
} else {
console.error(`Network Error: ${error.message}`);
}
throw error;
}
}
// Contoh Eksekusi
fetchExternalUrl('https://example.com');
3. Implementasi pada Python (Requests)
import requests
from requests.exceptions import RequestException
PROXIES = {
'http': 'http://127.0.0.1:4750',
'https': 'http://127.0.0.1:4750',
}
def safe_fetch(url: str):
try:
response = requests.get(
url,
proxies=PROXIES,
timeout=10,
allow_redirects=True
)
response.raise_for_status()
return response.text
except RequestException as e:
print(f"[SECURITY BLOCK] Request to {url} failed: {e}")
return None
if __name__ == "__main__":
# Tes domain aman
safe_fetch("https://httpbin.org/get")
# Tes domain internal (akan diblokir Smokescreen)
safe_fetch("http://169.254.169.254/latest/meta-data/")
Mengunci Outbound Traffic Menggunakan IPTables
Mengonfigurasi aplikasi agar menggunakan proxy belum cukup jika pengembang masih bisa mengabaikan konfigurasi proxy di kode mereka. Untuk memaksakan kepatuhan, kunci jaringan outbound menggunakan aturan iptables di tingkat kernel Linux.
Skenario ini mengizinkan trafik keluar langsung (port 80 dan 443) HANYA untuk proses yang dijalankan oleh user smokescreen, sementara proses lain (seperti user aplikasi www-data atau node) dipaksa melalui proxy 127.0.0.1:4750.
# 1. Bersihkan aturan lama (Opsional - sesuaikan dengan infrastruktur yang ada)
sudo iptables -F OUTPUT
# 2. Izinkan trafik Loopback lokal secara penuh
sudo iptables -A OUTPUT -o lo -j ACCEPT
# 3. Izinkan koneksi terbuat (ESTABLISHED, RELATED)
sudo iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# 4. Izinkan user 'smokescreen' membuat koneksi TCP langsung ke internet (Port 80 & 443)
sudo iptables -A OUTPUT -p tcp -m owner --uid-owner smokescreen --dport 80 -j ACCEPT
sudo iptables -A OUTPUT -p tcp -m owner --uid-owner smokescreen --dport 443 -j ACCEPT
# 5. Izinkan query DNS internal server (Port 53 UDP/TCP) jika diperlukan resolver lokal
sudo iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
# 6. Izinkan seluruh aplikasi internal mengakses Smokescreen Proxy lokal di port 4750
sudo iptables -A OUTPUT -p tcp --dport 4750 -j ACCEPT
# 7. Drop seluruh koneksi outbound TCP baru langsung dari user lain ke internet/IP internal
sudo iptables -A OUTPUT -p tcp -m state --state NEW -j DROP
Simpan aturan IPTables agar tetap persisten setelah reboot:
sudo apt-get install -y iptables-persistent
sudo netfilter-persistent save
Monitoring Log Canonical dan Troubleshooting
Smokescreen menghasilkan struktur log berformat JSON yang mendetail. Log ini mengkategorikan setiap permintaan outbound lengkap dengan alasan pemblokiran dan IP teresolusi.
Contoh Log Pemblokiran SSRF (Canonical Log Line)
{
"allow": false,
"content_length": 0,
"decided_by": "deny_ranges",
"domain": "instance-data.ec2.internal",
"host": "169.254.169.254:80",
"id": "req_ch9102ks81h2",
"level": "info",
"msg": "CANONICAL-LOG-LINE",
"reason": "requested host resolved to denied IP range",
"requested_host": "169.254.169.254",
"resolved_ips": ["169.254.169.254"],
"start_time": "2024-10-24T08:12:01Z",
"status_code": 403,
"user_agent": "Go-http-client/1.1"
}
Matriks Troubleshooting Masalah Umum
| Gejala Masalah | Penyebab Utama | Langkah Solusi Technical |
|---|---|---|
HTTP Client menerima status 403 Forbidden saat mengakses API publik valid. |
Alamat IP dari domain publik tersebut masuk ke dalam range yang diblokir atau terjadi False Positive pada penyedia CDN. | Gunakan dig +short <domain> untuk mengecek IP hasil resolusi. Jika IP tersebut aman namun terblokir oleh CIDR agresif, masukkan IP/subnet spesifik ke allow_ranges di config.yaml. |
Client mengalami Connection Refused ke port 4750. |
Daemon Smokescreen mati atau opsi ip pada konfigurasi diset ke interface yang salah. |
Periksa status service dengan systemctl status smokescreen. Pastikan parameter ip di YAML bernilai 127.0.0.1 atau 0.0.0.0. |
| Kegagalan TLS Handshake saat melakukan proxy HTTPS request. | Aplikasi mencoba melakukan Terminasi TLS di level proxy tanpa tunneling CONNECT. | Pastikan client HTTP backend menggunakan metode HTTP CONNECT tunnel standard, bukan mentransfer raw HTTP plaintext ke port HTTPS proxy. |
Checklist Audit Keamanan Egress Microservices
- [ ] Service Smokescreen terpasang dan dikelola oleh Systemd dengan kredensial user non-root.
- [ ] File konfigurasi
/etc/smokescreen/config.yamlmenutup seluruh range RFC 1918, Link-Local (169.254.0.0/16), dan IPv6 loopback. - [ ] Aturan IPTables / Network Security Group aktif memblokir akses langsung TCP port 80/443 dari proses aplikasi.
- [ ] Pustaka HTTP Client di aplikasi (Go, Node.js, Python) dikonfigurasi menggunakan proxy address
127.0.0.1:4750. - [ ] Structured JSON logs dari Smokescreen di-forward ke SIEM / Log Aggregator (Datadog, Grafana Loki, atau ELK) untuk alert real-time.
Kesimpulan
Mengamankan komunikasi outbound microservices membutuhkan pendekatan pertahanan berlapis (defense-in-depth). Pengecekan IP di layer aplikasi terbukti tidak efektif menghalau serangan DNS Rebinding dan manipulasi skema IP. Dengan mengintegrasikan Smokescreen Proxy Go di tingkat infrastruktur dan membatasi akses jaringan melalui IPTables, pemutusan Time-of-Check to Time-of-Use (TOCTOU) dapat dicapai. Pendekatan ini menjamin bahwa seluruh koneksi outbound diperiksa secara eksplisit pada saat socket TCP dibentuk, menjaga infrastruktur internal backend tetap terisolasi dari eksploitasi SSRF.


