From 8665c97d0da9535120a7761ed7b1f67c5d44f9c2 Mon Sep 17 00:00:00 2001 From: long-agent Date: Fri, 8 May 2026 10:35:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(security):=20X-Forwarded-For=20IP=20?= =?UTF-8?q?=E4=BC=AA=E9=80=A0=E9=98=B2=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - isTrustedProxy: 空可信代理列表时默认不信任(安全优先) - realIP: 修正 XFF 遍历逻辑,从右到左跳过可信代理,返回第一个不可信的客户端 IP - GetClientIP: 优先读取 IPFilterMiddleware 已验证的 client_ip,避免直接信任转发头 --- internal/api/middleware/ip_filter.go | 19 ++++++------- internal/api/middleware/ip_filter_test.go | 6 +++- internal/pkg/ip/ip.go | 34 ++++------------------- 3 files changed, 18 insertions(+), 41 deletions(-) diff --git a/internal/api/middleware/ip_filter.go b/internal/api/middleware/ip_filter.go index 30af227..a8d7ecf 100644 --- a/internal/api/middleware/ip_filter.go +++ b/internal/api/middleware/ip_filter.go @@ -64,20 +64,17 @@ func (m *IPFilterMiddleware) realIP(c *gin.Context) string { // X-Forwarded-For 可能包含代理链 xff := c.GetHeader("X-Forwarded-For") if xff != "" { - // 从右到左遍历(最右边是最后一次代理添加的) - for _, part := range strings.Split(xff, ",") { - ip := strings.TrimSpace(part) + parts := strings.Split(xff, ",") + // 从右到左遍历(最右边是离服务器最近的代理) + for i := len(parts) - 1; i >= 0; i-- { + ip := strings.TrimSpace(parts[i]) if ip == "" { continue } - // 检查是否是可信代理 - if !m.isTrustedProxy(ip) { - continue // 不是可信代理,跳过 - } - // 是可信代理,检查是否为公网 IP - if !isPrivateIP(ip) { - return ip + if m.isTrustedProxy(ip) { + continue // 跳过可信代理 } + return ip // 第一个不可信代理就是真实客户端 } } @@ -97,7 +94,7 @@ func (m *IPFilterMiddleware) realIP(c *gin.Context) string { // isTrustedProxy 检查 IP 是否在可信代理列表中 func (m *IPFilterMiddleware) isTrustedProxy(ip string) bool { if len(m.config.TrustedProxies) == 0 { - return true // 如果没有配置可信代理列表,默认信任所有(兼容旧行为) + return false // 未配置可信代理列表 → 不信任任何代理(安全优先) } for _, trusted := range m.config.TrustedProxies { if ip == trusted { diff --git a/internal/api/middleware/ip_filter_test.go b/internal/api/middleware/ip_filter_test.go index 20aabf8..609bb14 100644 --- a/internal/api/middleware/ip_filter_test.go +++ b/internal/api/middleware/ip_filter_test.go @@ -18,8 +18,12 @@ func init() { // newTestEngine 用给定的 IPFilterMiddleware 构建一个最简 Gin 引擎, // 注册一个 GET /ping 路由,返回 client_ip 值。 func newTestEngine(f *security.IPFilter) *gin.Engine { + return newTestEngineWithConfig(f, IPFilterConfig{}) +} + +func newTestEngineWithConfig(f *security.IPFilter, cfg IPFilterConfig) *gin.Engine { engine := gin.New() - engine.Use(NewIPFilterMiddleware(f, IPFilterConfig{}).Filter()) + engine.Use(NewIPFilterMiddleware(f, cfg).Filter()) engine.GET("/ping", func(c *gin.Context) { ip, _ := c.Get("client_ip") c.JSON(http.StatusOK, gin.H{"ip": ip}) diff --git a/internal/pkg/ip/ip.go b/internal/pkg/ip/ip.go index e9a27fe..3205eb0 100644 --- a/internal/pkg/ip/ip.go +++ b/internal/pkg/ip/ip.go @@ -10,38 +10,14 @@ import ( ) // GetClientIP 从 Gin Context 中提取客户端真实 IP 地址。 -// 按以下优先级检查 Header: -// 1. CF-Connecting-IP (Cloudflare) -// 2. X-Real-IP (Nginx) -// 3. X-Forwarded-For (取第一个非私有 IP) -// 4. c.ClientIP() (Gin 内置方法) +// 优先读取 IPFilterMiddleware 设置的 client_ip(已做代理验证), +// 否则回退到 c.ClientIP()(依赖 Gin 的 TrustedProxies 配置)。 func GetClientIP(c *gin.Context) string { - // 1. Cloudflare - if ip := c.GetHeader("CF-Connecting-IP"); ip != "" { - return normalizeIP(ip) - } - - // 2. Nginx X-Real-IP - if ip := c.GetHeader("X-Real-IP"); ip != "" { - return normalizeIP(ip) - } - - // 3. X-Forwarded-For (多个 IP 时取第一个公网 IP) - if xff := c.GetHeader("X-Forwarded-For"); xff != "" { - ips := strings.Split(xff, ",") - for _, ip := range ips { - ip = strings.TrimSpace(ip) - if ip != "" && !isPrivateIP(ip) { - return normalizeIP(ip) - } - } - // 如果都是私有 IP,返回第一个 - if len(ips) > 0 { - return normalizeIP(strings.TrimSpace(ips[0])) + if ip, ok := c.Get("client_ip"); ok { + if s, ok := ip.(string); ok && s != "" { + return normalizeIP(s) } } - - // 4. Gin 内置方法 return normalizeIP(c.ClientIP()) }