nginx's X-Forwarded-For is $proxy_add_x_forwarded_for, which only appends to whatever the client sends — a forged leftmost hop passed straight through, letting an attacker rotate spoofed values to dodge the 5s login cooldown. X-Real-IP is set by nginx to $remote_addr on every proxied location and can't be forged by the client, so key on that instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
35 lines
1.2 KiB
Go
35 lines
1.2 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"testing"
|
|
)
|
|
|
|
// nginx sets X-Real-IP to $remote_addr on every proxied location (see
|
|
// nginx/overlord.conf), so it can't be spoofed by the client. XFF, in
|
|
// contrast, is $proxy_add_x_forwarded_for — nginx APPENDS to whatever the
|
|
// client sent, so an attacker-supplied leftmost value survives untouched.
|
|
// The rate limiter must key on X-Real-IP, not XFF.
|
|
func TestLoginClientIP_IgnoresSpoofedXFF(t *testing.T) {
|
|
r := &http.Request{
|
|
RemoteAddr: "10.0.0.1:12345", // nginx's own connection to the app
|
|
Header: http.Header{},
|
|
}
|
|
r.Header.Set("X-Real-IP", "203.0.113.5")
|
|
r.Header.Set("X-Forwarded-For", "6.6.6.6, 203.0.113.5") // attacker-spoofed leftmost hop
|
|
|
|
if got := loginClientIP(r); got != "203.0.113.5" {
|
|
t.Errorf("loginClientIP = %q, want %q (trusted X-Real-IP, not spoofable XFF)", got, "203.0.113.5")
|
|
}
|
|
}
|
|
|
|
func TestLoginClientIP_FallsBackToRemoteAddr(t *testing.T) {
|
|
r := &http.Request{
|
|
RemoteAddr: "198.51.100.7:54321",
|
|
Header: http.Header{},
|
|
}
|
|
|
|
if got := loginClientIP(r); got != "198.51.100.7" {
|
|
t.Errorf("loginClientIP = %q, want %q (no X-Real-IP, fall back to RemoteAddr)", got, "198.51.100.7")
|
|
}
|
|
}
|