From c90f8a05bf792e61db250f210834cdabcc0b7906 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Mon, 4 May 2026 16:36:33 +0200 Subject: fix(security): sanitize remote IP headers and escape log viewer output #4135 --- web/controller/util.go | 62 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 10 deletions(-) (limited to 'web/controller') diff --git a/web/controller/util.go b/web/controller/util.go index 3d266f29..e1d53ba6 100644 --- a/web/controller/util.go +++ b/web/controller/util.go @@ -1,8 +1,10 @@ package controller import ( + "fmt" "net" "net/http" + "net/netip" "strings" "github.com/mhsanaei/3x-ui/v2/config" @@ -14,18 +16,58 @@ import ( // getRemoteIp extracts the real IP address from the request headers or remote address. func getRemoteIp(c *gin.Context) string { - value := c.GetHeader("X-Real-IP") - if value != "" { - return value + if ip, ok := extractTrustedIP(c.GetHeader("X-Real-IP")); ok { + return ip } - value = c.GetHeader("X-Forwarded-For") - if value != "" { - ips := strings.Split(value, ",") - return ips[0] + + if xff := c.GetHeader("X-Forwarded-For"); xff != "" { + for _, part := range strings.Split(xff, ",") { + if ip, ok := extractTrustedIP(part); ok { + return ip + } + } + } + + if ip, ok := extractTrustedIP(c.Request.RemoteAddr); ok { + return ip + } + + return "unknown" +} + +func extractTrustedIP(value string) (string, bool) { + candidate := strings.TrimSpace(value) + if candidate == "" { + return "", false + } + + if ip, ok := parseIPCandidate(candidate); ok { + return ip.String(), true + } + + if host, _, err := net.SplitHostPort(candidate); err == nil { + if ip, ok := parseIPCandidate(host); ok { + return ip.String(), true + } + } + + if strings.Count(candidate, ":") == 1 { + if host, _, err := net.SplitHostPort(fmt.Sprintf("[%s]", candidate)); err == nil { + if ip, ok := parseIPCandidate(host); ok { + return ip.String(), true + } + } + } + + return "", false +} + +func parseIPCandidate(value string) (netip.Addr, bool) { + ip, err := netip.ParseAddr(strings.TrimSpace(value)) + if err != nil { + return netip.Addr{}, false } - addr := c.Request.RemoteAddr - ip, _, _ := net.SplitHostPort(addr) - return ip + return ip.Unmap(), true } // jsonMsg sends a JSON response with a message and error status. -- cgit v1.2.3