Compare commits

...

5 Commits

Author SHA1 Message Date
cyal1
fae4bcaa1a
Merge b7dd5120ef35e1ed60c497d770495aa3054e6ab1 into b2b489dbf4826c2c630717a77fd5e42774625410 2026-01-18 14:22:35 +08:00
WeidiDeng
b2b489dbf4
chore(context): always trust xff headers from unix socket (#3359)
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2026-01-18 12:56:22 +08:00
cyal1
b7dd5120ef
add open redirect test case
https://github.com/gin-gonic/gin/pull/3907
2024-04-01 14:52:28 +08:00
cyal1
29db90e1fc
fixed open redirect 2024-04-01 14:50:18 +08:00
cyal1
6c8ec4deda
fixed open redirect 2024-03-28 23:05:09 +08:00
4 changed files with 37 additions and 8 deletions

View File

@ -978,14 +978,27 @@ func (c *Context) ClientIP() string {
}
}
// It also checks if the remoteIP is a trusted proxy or not.
// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
// defined by Engine.SetTrustedProxies()
remoteIP := net.ParseIP(c.RemoteIP())
if remoteIP == nil {
return ""
var (
trusted bool
remoteIP net.IP
)
// If gin is listening a unix socket, always trust it.
localAddr, ok := c.Request.Context().Value(http.LocalAddrContextKey).(net.Addr)
if ok && strings.HasPrefix(localAddr.Network(), "unix") {
trusted = true
}
// Fallback
if !trusted {
// It also checks if the remoteIP is a trusted proxy or not.
// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
// defined by Engine.SetTrustedProxies()
remoteIP = net.ParseIP(c.RemoteIP())
if remoteIP == nil {
return ""
}
trusted = c.engine.isTrustedProxy(remoteIP)
}
trusted := c.engine.isTrustedProxy(remoteIP)
if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {
for _, headerName := range c.engine.RemoteIPHeaders {

View File

@ -1915,6 +1915,16 @@ func TestContextClientIP(t *testing.T) {
c.engine.trustedCIDRs, _ = c.engine.prepareTrustedCIDRs()
resetContextForClientIPTests(c)
// unix address
addr := &net.UnixAddr{Net: "unix", Name: "@"}
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), http.LocalAddrContextKey, addr))
c.Request.RemoteAddr = addr.String()
assert.Equal(t, "20.20.20.20", c.ClientIP())
// reset
c.Request = c.Request.WithContext(context.Background())
resetContextForClientIPTests(c)
// Legacy tests (validating that the defaults don't break the
// (insecure!) old behaviour)
assert.Equal(t, "20.20.20.20", c.ClientIP())

1
gin.go
View File

@ -788,6 +788,7 @@ func redirectTrailingSlash(c *Context) {
p = prefix + "/" + req.URL.Path
}
req.URL.Path = p + "/"
p = regexp.MustCompile("^/{2,}").ReplaceAllString(p, "/")
if length := len(p); length > 1 && p[length-1] == '/' {
req.URL.Path = p[:length-1]
}

View File

@ -150,8 +150,13 @@ func TestRouteRedirectTrailingSlash(t *testing.T) {
router.GET("/path2/", func(c *Context) {})
router.POST("/path3", func(c *Context) {})
router.PUT("/path4/", func(c *Context) {})
router.GET("/:param1/:param2", func(c *Context) {})
w := PerformRequest(router, http.MethodGet, "/path/")
w := PerformRequest(router, http.MethodGet, "//path/")
assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)
w = PerformRequest(router, http.MethodGet, "/path/")
assert.Equal(t, "/path", w.Header().Get("Location"))
assert.Equal(t, http.StatusMovedPermanently, w.Code)