Skip to content

Commit

Permalink
fix: ClientIP handling is unsafe
Browse files Browse the repository at this point in the history
  • Loading branch information
BaiZe1998 authored and wzekin committed Feb 13, 2023
1 parent 4d7af18 commit 4fe85da
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 12 deletions.
68 changes: 59 additions & 9 deletions pkg/app/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,22 +78,72 @@ type Handler interface {

type ClientIP func(ctx *RequestContext) string

var defaultClientIP = func(ctx *RequestContext) string {
RemoteIPHeaders := []string{"X-Real-IP", "X-Forwarded-For"}
for _, headerName := range RemoteIPHeaders {
ip := ctx.Request.Header.Get(headerName)
if ip != "" {
return ip
type ClientIPOptions struct {
RemoteIPHeaders []string
TrustedProxies map[string]bool
}

var defaultClientIPOptions = ClientIPOptions{
RemoteIPHeaders: []string{"X-Real-IP", "X-Forwarded-For"},
TrustedProxies: map[string]bool{
"0.0.0.0": true,
},
}

// ClientIPWithOption used to generate custom ClientIP function and set by engine.SetClientIPFunc
func ClientIPWithOption(opts ClientIPOptions) ClientIP {
return func(ctx *RequestContext) string {
RemoteIPHeaders := opts.RemoteIPHeaders
TrustedProxies := opts.TrustedProxies

remoteIP, _, err := net.SplitHostPort(strings.TrimSpace(ctx.RemoteAddr().String()))
if err != nil {
return ""
}
trusted := isTrustedProxy(TrustedProxies, remoteIP)

if trusted {
for _, headerName := range RemoteIPHeaders {
ip, valid := validateHeader(TrustedProxies, ctx.Request.Header.Get(headerName))
if valid {
return ip
}
}
}

return remoteIP
}
}

if ip, _, err := net.SplitHostPort(strings.TrimSpace(ctx.RemoteAddr().String())); err == nil {
return ip
// isTrustedProxy will check whether the IP address is included in the trusted list according to TrustedProxies
func isTrustedProxy(trustedProxies map[string]bool, remoteIP string) bool {
return trustedProxies[remoteIP]
}

// validateHeader will parse X-Real-IP and X-Forwarded-For header and return the Initial client IP address or an untrusted IP address
func validateHeader(trustedProxies map[string]bool, header string) (clientIP string, valid bool) {
if header == "" {
return "", false
}
items := strings.Split(header, ",")
for i := len(items) - 1; i >= 0; i-- {
ipStr := strings.TrimSpace(items[i])
ip := net.ParseIP(ipStr)
if ip == nil {
break
}

return ""
// X-Forwarded-For is appended by proxy
// Check IPs in reverse order and stop when find untrusted proxy
if (i == 0) || (!isTrustedProxy(trustedProxies, ipStr)) {
return ipStr, true
}
}
return "", false
}

var defaultClientIP = ClientIPWithOption(defaultClientIPOptions)

// SetClientIPFunc sets ClientIP function implementation to get ClientIP.
// Deprecated: Use engine.SetClientIPFunc instead of SetClientIPFunc
func SetClientIPFunc(fn ClientIP) {
Expand Down
36 changes: 33 additions & 3 deletions pkg/app/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,18 +673,48 @@ func TestContextContentType(t *testing.T) {
func TestClientIp(t *testing.T) {
c := NewContext(0)
c.conn = mock.NewConn("")
// Case 1
c.Request.Header.Set("X-Forwarded-For", "126.0.0.2")
// 0.0.0.0 simulates a trusted proxy server
c.Request.Header.Set("X-Forwarded-For", " 126.0.0.2, 0.0.0.0 ")
val := c.ClientIP()
if val != "126.0.0.2" {
t.Fatalf("unexpected %v. Expecting %v", val, "126.0.0.2")
}
// Case 2
// no proxy server
c = NewContext(0)
c.conn = mock.NewConn("")
c.Request.Header.Set("X-Real-Ip", "126.0.0.1")
val = c.ClientIP()
if val != "126.0.0.1" {
t.Fatalf("unexpected %v. Expecting %v", val, "126.0.0.1")
}
// custom RemoteIPHeaders and TrustedProxies
opts := ClientIPOptions{
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedProxies: map[string]bool{
"0.0.0.0": true,
},
}
c = NewContext(0)
c.SetClientIPFunc(ClientIPWithOption(opts))
c.conn = mock.NewConn("")
c.Request.Header.Set("X-Forwarded-For", " 126.0.0.2, 0.0.0.0 ")
val = c.ClientIP()
if val != "126.0.0.2" {
t.Fatalf("unexpected %v. Expecting %v", val, "126.0.0.2")
}
// no trusted proxy server
opts = ClientIPOptions{
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedProxies: nil,
}
c = NewContext(0)
c.SetClientIPFunc(ClientIPWithOption(opts))
c.conn = mock.NewConn("")
c.Request.Header.Set("X-Forwarded-For", " 126.0.0.2, 0.0.0.0 ")
val = c.ClientIP()
if val != "0.0.0.0" {
t.Fatalf("unexpected %v. Expecting %v", val, "0.0.0.0")
}
}

func TestSetClientIPFunc(t *testing.T) {
Expand Down

0 comments on commit 4fe85da

Please sign in to comment.