Aplikasi web modern sering ditempatkan di belakang reverse proxy seperti Nginx, AWS ALB, atau Cloudflare. Masalah timbul saat aplikasi membaca identitas IP klien dari HTTP header secara membabi buta tanpa memvalidasi sumber koneksi upstream.
Akar Masalah: IP Spoofing dan Header X-Forwarded-For
Header X-Forwarded-For (XFF) adalah header HTTP yang digunakan perantara untuk mencatat IP klien asal. Header ini dapat dimanipulasi dengan mudah oleh klien publik yang mengirimkan request langsung ke server aplikasi.
Secara default, jika konfigurasi proxy pada framework tidak diatur ketat, pemanggilan c.IP() pada Go Fiber dapat mengembalikan nilai dari header XFF tanpa memvalidasi apakah koneksi TCP langsung (remote IP) berasal dari proxy yang sah. Penyerang dapat menyisipkan header palsu seperti X-Forwarded-For: 1.1.1.1 lalu merotasi nilainya pada setiap request untuk mengelabui middleware rate limiter berbasis IP.
Solusi: Mengaktifkan EnableTrustedProxyCheck di Go Fiber
Go Fiber menyediakan konfigurasi bawaan pada fiber.Config untuk memeriksa apakah remote IP dari koneksi soket terdaftar dalam daftar IP atau rentang CIDR terpercaya sebelum membaca header proksi.
EnableTrustedProxyCheck: truemengaktifkan filter validasi upstream proxy.ProxyHeader: fiber.HeaderXForwardedFormenentukan header yang diekstrak saat remote IP terpercaya.TrustedProxies: []string{...}mendefinisikan daftar IP tunggal atau subnet CIDR yang diizinkan meneruskan header.
Jika request berasal dari IP di luar TrustedProxies, Fiber mengabaikan ProxyHeader dan langsung menggunakan remote IP koneksi fisik sebagai nilai c.IP().
Implementasi Kode dan Integrasi Rate Limiter
Implementasi berikut mengkonfigurasi Fiber app dengan proteksi proxy terpercaya dan mengintegrasikannya dengan middleware limiter.
package main
import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/limiter"
)
func SetupApp() *fiber.App {
app := fiber.New(fiber.Config{
EnableTrustedProxyCheck: true,
ProxyHeader: fiber.HeaderXForwardedFor,
TrustedProxies: []string{
"127.0.0.1",
"10.0.0.0/8", // Private VPC subnet
"192.168.1.0/24", // Subnet internal reverse proxy
},
})
// Rate limiter membaca c.IP() secara default
app.Use(limiter.New(limiter.Config{
Max: 2,
Expiration: 1 * time.Minute,
KeyGenerator: func(c *fiber.Ctx) string {
return c.IP()
},
LimitReached: func(c *fiber.Ctx) error {
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"error": "Rate limit exceeded",
})
},
}))
app.Get("/data", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{
"client_ip": c.IP(),
})
})
return app
}
Pengujian Unit Menggunakan app.Test
Pengujian memverifikasi dua skenario: penolakan header palsu dari host tidak terpercaya dan keberhasilan pembacaan IP sah dari upstream terpercaya.
package main
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestTrustedProxiesRateLimiting(t *testing.T) {
app := SetupApp()
// Skenario 1: Penyerang langsung (untrusted IP) mencoba spoofing XFF
untrustedRemote := "203.0.113.100:4567"
for i := 0; i < 2; i++ {
req := httptest.NewRequest(http.MethodGet, "/data", nil)
req.RemoteAddr = untrustedRemote
// Mengubah X-Forwarded-For palsu di setiap request
req.Header.Set(fiber.HeaderXForwardedFor, strings.Repeat("8.8.8.8", 1))
resp, err := app.Test(req)
if err != nil || resp.StatusCode != http.StatusOK {
t.Fatalf("Request sah ke-%d gagal: status %d", i+1, resp.StatusCode)
}
}
// Request ke-3 dari untrusted remote yang sama harus di-throttle,
// membuktikan header spoofing diabaikan dan rate limiter melacak 203.0.113.100
blockedReq := httptest.NewRequest(http.MethodGet, "/data", nil)
blockedReq.RemoteAddr = untrustedRemote
blockedReq.Header.Set(fiber.HeaderXForwardedFor, "1.2.3.4")
blockedResp, err := app.Test(blockedReq)
if err != nil {
t.Fatalf("Gagal mengeksekusi request uji: %v", err)
}
if blockedResp.StatusCode != http.StatusTooManyRequests {
t.Errorf("Ekspektasi 429 Too Many Requests, didapat %d", blockedResp.StatusCode)
}
// Skenario 2: Permintaan valid melalui Trusted Proxy (10.0.0.5)
trustedProxyRemote := "10.0.0.5:1234"
proxyReq := httptest.NewRequest(http.MethodGet, "/data", nil)
proxyReq.RemoteAddr = trustedProxyRemote
proxyReq.Header.Set(fiber.HeaderXForwardedFor, "198.51.100.22")
proxyResp, err := app.Test(proxyReq)
if err != nil {
t.Fatalf("Request proxy gagal: %v", err)
}
if proxyResp.StatusCode != http.StatusOK {
t.Errorf("Ekspektasi status 200, didapat %d", proxyResp.StatusCode)
}
body, _ := io.ReadAll(proxyResp.Body)
if !strings.Contains(string(body), "198.51.100.22") {
t.Errorf("Ekspektasi IP klien 198.51.100.22, respon: %s", string(body))
}
}
Kesalahan Umum Operasional
- CIDR Terlalu Longgar: Memasukkan
0.0.0.0/0ke dalamTrustedProxiesmeniadakan validasi keamanan dan membuka kembali celah spoofing. - Mengabaikan Multiple Proxy Chains: Jika request melewati beberapa proxy (misalnya Cloudflare > AWS ALB > Go Fiber),
X-Forwarded-Forberisi daftar IP dipisahkan koma (klien, proxy1, proxy2). Fiber mengambil IP paling kiri secara default saat proxy terverifikasi. - Koneksi Langsung Terbuka: Tidak menutup akses port aplikasi secara langsung melalui firewall (Security Group / iptables). Hanya IP reverse proxy yang boleh memiliki akses jaringan langsung ke port aplikasi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!