Arsitektur Navigasi Webring dan Vektor Kerentanan
Webring modern mengarahkan trafik antar-situs anggota secara desentralisasi atau semi-terpusat via endpoint relai seperti /next dan /prev. Pola navigasi ini memperkenalkan tiga kegagalan struktural utama saat node anggota bermasalah:
- Infinite Redirect Loop: Terjadi jika node mengembalikan redirect ke relai utama tanpa memajukan pointer rotasi, atau dua node saling me-redirect satu sama lain secara sirkular.
- Dead Node Blocking (Cascading 504): Relai mencoba menghubungi peer secara synchronous saat melayani permintaan pengguna. Jika peer mengalami downtime, relai memicu timeout berantai.
- Open Redirect dan SSRF (Server-Side Request Forgery): Penggunaan metadata peer yang tidak tervalidasi memungkinkan aktor jahat mendaftarkan domain internal (seperti
169.254.169.254atau subnet RFC 1918) atau memanfaatkan DNS Rebinding untuk memindai infrastruktur privat relai saat proses verifikasi peer.
1. Mitigasi Redirect Loop dengan HMAC Traversal Token
Pencegahan cycle secara stateless dilakukan dengan menyematkan token bertanda tangan kriptografis pada parameter query redirect HTTP 302. Relai tidak perlu menyimpan state sesi di server.
Token membawa payload minimum: identitas pemohon awal (origin), counter hop (hop), dan batas kedaluwarsa (exp). Format payload: origin:hop:exp yang di-hash menggunakan HMAC-SHA256.
// Pseudocode struktur token
token_payload = origin_domain + ":" + hop_count + ":" + unix_expiry
signature = hmac_sha256(secret_key, token_payload)
traversal_token = base64(token_payload + ":" + signature)
Aturan validasi relai:
- Jika query parameter
tokentidak ditemukan, relai menganggap ini hop pertama (hop=0). - Jika token ditemukan, verifikasi tanda tangan HMAC dan pastikan
exp > current_timestamp. Tolak jika signature mismatch atau waktu kedaluwarsa lewat. - Jika
hop >= MAX_HOPS(misal: 5) atauorigin == current_target, hentikan propagasi redirect. Berikan fallback ke direktori statis atau halaman indeks ring dengan status200 OK.
2. Proteksi SSRF dan DNS Rebinding pada Validasi Peer
Validasi keaktifan peer tidak boleh menggunakan HTTP client standar secara naif. Serangan Time-of-Check to Time-of-Use (TOCTOU) via DNS Rebinding terjadi jika DNS mengembalikan IP publik saat validasi pertama, namun mengembalikan IP lokal (127.0.0.1) saat request dijalankan.
Pola Pertahanan:
- Strict Allowlist Parsing: URL peer harus di-parse menggunakan parser URI standar. Skema wajib dibatasi hanya ke
https://(atauhttp://jika lingkungan non-TLS diizinkan eksplisit). Blokir penyebutan port non-standar (misal port 22, 6379, 8080). - DNS Pinning & IP Blacklisting: Lakukan resolusi DNS sebelum koneksi TCP dibangun. Evaluasi alamat IPv4 dan IPv6 terhadap daftar hitam rentang IP terlarang: loopback, link-local (
169.254.0.0/16), dan private network (RFC 1918, RFC 4193). Hubungi IP hasil validasi secara langsung via custom transport untuk mengunci binding IP.
3. Circular Fallback Berbasis Asynchronous Health Check
Relai dilarang mengecek ketersediaan peer di dalam jalur kritis (critical path) penanganan HTTP /next. Lakukan verifikasi secara asynchronous di latar belakang.
Relai menyimpan status node dalam cache in-memory yang diperbarui oleh background worker:
type RingMember struct {
Domain string
IsActive bool
}
// Array terurut representasi circular ring
var Ring = []RingMember{
{"alice.dev", true},
{"bob.net", false}, // Node down terdeteksi worker
{"charlie.org", true},
}
Jika node bob.net ditandai IsActive == false, algoritma circular traversal langsung melompat ke node berikutnya yang aktif: next_index = (current_index + 1) % len(Ring). User terhindar dari latency overhead dan gateway timeout (504).
Implementasi Endpoint Navigasi di Go
Contoh berikut mengimplementasikan endpoint /next minimal dengan proteksi batas hop HMAC dan seleksi circular fallback berbasis Go standard library.
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
const (
SecretKey = "super-secret-hmac-key"
MaxHops = 5
)
type Node struct {
Domain string
Active bool
}
// Ring terurut statis untuk demonstrasi
var Ring = []Node{
{Domain: "alpha.site", Active: true},
{Domain: "broken.site", Active: false}, // node mati
{Domain: "gamma.site", Active: true},
}
func sign(data string) string {
h := hmac.New(sha256.New, []byte(SecretKey))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func findNextActive(current string) (string, error) {
idx := -1
for i, n := range Ring {
if n.Domain == current {
idx = i
break
}
}
if idx == -1 {
return Ring[0].Domain, nil
}
n := len(Ring)
for step := 1; step <= n; step++ {
candidate := Ring[(idx+step)%n]
if candidate.Active {
return candidate.Domain, nil
}
}
return "", fmt.Errorf("semua node tidak aktif")
}
func NextHandler(w http.ResponseWriter, r *http.Request) {
current := r.URL.Query().Get("current")
token := r.URL.Query().Get("token")
hop := 0
if token != "" {
parts := strings.Split(token, ":")
if len(parts) == 3 {
tokenHop, expStr, sig := parts[0], parts[1], parts[2]
payload := fmt.Sprintf("%s:%s", tokenHop, expStr)
if hmac.Equal([]byte(sign(payload)), []byte(sig)) {
exp, _ := strconv.ParseInt(expStr, 10, 64)
if time.Now().Unix() <= exp {
hop, _ = strconv.Atoi(tokenHop)
}
}
}
}
hop++
if hop > MaxHops {
http.Error(w, "Hop limit exceeded. Directing to directory index.", http.StatusLoopDetected)
return
}
target, err := findNextActive(current)
if err != nil {
http.Error(w, "Webring unavailable", http.StatusServiceUnavailable)
return
}
exp := time.Now().Add(1 * time.Minute).Unix()
payload := fmt.Sprintf("%d:%d", hop, exp)
nextToken := fmt.Sprintf("%s:%s", payload, sign(payload))
redirectURL := fmt.Sprintf("https://%s/?webring_token=%s", target, nextToken)
http.Redirect(w, r, redirectURL, http.StatusFound)
}
func main() {
http.HandleFunc("/next", NextHandler)
http.ListenAndServe(":8080", nil)
}
Unit Test Validasi Hop Limit
Pengujian mandiri menggunakan net/http/httptest untuk memverifikasi bahwa relai memotong loop redirect ketika batas MaxHops terlampaui.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestHopLimitExceeded(t *testing.T) {
hop := 5
exp := time.Now().Add(1 * time.Minute).Unix()
payload := fmt.Sprintf("%d:%d", hop, exp)
validToken := fmt.Sprintf("%s:%s", payload, sign(payload))
req := httptest.NewRequest(http.MethodGet, "/next?current=alpha.site&token="+validToken, nil)
rec := httptest.NewRecorder()
NextHandler(rec, req)
// Hop ke-6 harus ditolak dengan status 508 Loop Detected
if rec.Code != http.StatusLoopDetected {
t.Fatalf("Ekspektasi HTTP 508 Loop Detected, didapat: %d", rec.Code)
}
}
Catatan Implementasi: Hindari penyimpanan state traversal di cookie peramban. Pengguna sering menonaktifkan cookie pihak ketiga atau membuka tautan di tab terisolasi. Parameter URL dengan HMAC memberikan garansi kompatibilitas tertinggi pada arsitektur webring lintas-domain.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!