Use rightmost IP from forwarded headers
A client can prepend a spoofed entry to X-Forwarded-For before the request reaches our load balancer. Taking the first value would return the attacker's address. Since we sit behind a single trusted LB that appends the real client IP as the last entry, switch to rightmost extraction for both X-Forwarded-For and RFC 7239 Forwarded headers. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -22,7 +22,9 @@ import (
|
|||||||
|
|
||||||
// Extract resolves the client IP address from standard proxy headers
|
// Extract resolves the client IP address from standard proxy headers
|
||||||
// in priority order: RFC 7239 Forwarded, then X-Forwarded-For, then
|
// in priority order: RFC 7239 Forwarded, then X-Forwarded-For, then
|
||||||
// the connection's remote address.
|
// the connection's remote address. It takes the rightmost (last)
|
||||||
|
// entry from multi-value headers — the one appended by the trusted
|
||||||
|
// load balancer closest to us.
|
||||||
func Extract(r *http.Request) string {
|
func Extract(r *http.Request) string {
|
||||||
if fwd := r.Header.Get("Forwarded"); fwd != "" {
|
if fwd := r.Header.Get("Forwarded"); fwd != "" {
|
||||||
if ip := parseForwardedFor(fwd); ip != "" {
|
if ip := parseForwardedFor(fwd); ip != "" {
|
||||||
@@ -31,8 +33,8 @@ func Extract(r *http.Request) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||||
if i := strings.IndexByte(xff, ','); i != -1 {
|
if i := strings.LastIndexByte(xff, ','); i != -1 {
|
||||||
xff = xff[:i]
|
xff = xff[i+1:]
|
||||||
}
|
}
|
||||||
xff = strings.TrimSpace(xff)
|
xff = strings.TrimSpace(xff)
|
||||||
|
|
||||||
@@ -50,11 +52,11 @@ func Extract(r *http.Request) string {
|
|||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseForwardedFor extracts the client IP from the first "for=" directive
|
// parseForwardedFor extracts the client IP from the last "for=" directive
|
||||||
// of an RFC 7239 Forwarded header value.
|
// of an RFC 7239 Forwarded header value.
|
||||||
func parseForwardedFor(header string) string {
|
func parseForwardedFor(header string) string {
|
||||||
if i := strings.IndexByte(header, ','); i != -1 {
|
if i := strings.LastIndexByte(header, ','); i != -1 {
|
||||||
header = header[:i]
|
header = header[i+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
for part := range strings.SplitSeq(header, ";") {
|
for part := range strings.SplitSeq(header, ";") {
|
||||||
|
|||||||
@@ -48,9 +48,15 @@ func TestExtract(t *testing.T) {
|
|||||||
want: "203.0.113.50",
|
want: "203.0.113.50",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "x-forwarded-for chain",
|
name: "x-forwarded-for chain takes rightmost",
|
||||||
remoteAddr: "10.0.0.1:1234",
|
remoteAddr: "10.0.0.1:1234",
|
||||||
headers: map[string]string{"X-Forwarded-For": "203.0.113.50, 70.41.3.18, 150.172.238.178"},
|
headers: map[string]string{"X-Forwarded-For": "203.0.113.50, 70.41.3.18, 150.172.238.178"},
|
||||||
|
want: "150.172.238.178",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "x-forwarded-for spoofed prefix",
|
||||||
|
remoteAddr: "10.0.0.1:1234",
|
||||||
|
headers: map[string]string{"X-Forwarded-For": "1.2.3.4, 203.0.113.50"},
|
||||||
want: "203.0.113.50",
|
want: "203.0.113.50",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -84,10 +90,10 @@ func TestExtract(t *testing.T) {
|
|||||||
want: "198.51.100.17",
|
want: "198.51.100.17",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "forwarded header chain",
|
name: "forwarded header chain takes rightmost",
|
||||||
remoteAddr: "10.0.0.1:1234",
|
remoteAddr: "10.0.0.1:1234",
|
||||||
headers: map[string]string{"Forwarded": "for=198.51.100.17, for=70.41.3.18"},
|
headers: map[string]string{"Forwarded": "for=198.51.100.17, for=70.41.3.18"},
|
||||||
want: "198.51.100.17",
|
want: "70.41.3.18",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "forwarded takes precedence over x-forwarded-for",
|
name: "forwarded takes precedence over x-forwarded-for",
|
||||||
|
|||||||
Reference in New Issue
Block a user