Skip to content

Commit 9318212

Browse files
committed
fix: prevent rate limit bypass in public file shares
1 parent 205f76c commit 9318212

4 files changed

Lines changed: 153 additions & 21 deletions

File tree

core/init/proxy/proxy.go

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import (
55
"net"
66
"net/http"
77
"net/http/httputil"
8+
"slices"
9+
"strings"
810
"time"
11+
12+
"github.com/1Panel-dev/1Panel/core/utils/clientip"
13+
"github.com/1Panel-dev/1Panel/core/utils/publicshare"
914
)
1015

1116
const SockPath = "/etc/1panel/agent.sock"
@@ -28,20 +33,26 @@ func Init() {
2833
MaxIdleConnsPerHost: 50,
2934
IdleConnTimeout: 30 * time.Second,
3035
}
31-
LocalAgentProxy = &httputil.ReverseProxy{
32-
Director: func(req *http.Request) {
33-
if req.Header.Get("X-Forwarded-Proto") == "" {
34-
if req.TLS != nil {
35-
req.Header.Set("X-Forwarded-Proto", "https")
36-
} else {
37-
req.Header.Set("X-Forwarded-Proto", "http")
38-
}
36+
LocalAgentProxy = newLocalAgentProxy(transport)
37+
}
38+
39+
func newLocalAgentProxy(transport http.RoundTripper) *httputil.ReverseProxy {
40+
return &httputil.ReverseProxy{
41+
Rewrite: func(proxyReq *httputil.ProxyRequest) {
42+
if proxyReq.In.Form == nil {
43+
proxyReq.Out.URL.RawQuery = proxyReq.In.URL.RawQuery
3944
}
40-
if req.Header.Get("X-Forwarded-Host") == "" && req.Host != "" {
41-
req.Header.Set("X-Forwarded-Host", req.Host)
45+
if publicshare.IsAPI(proxyReq.In.URL.Path) {
46+
proxyReq.SetXForwarded()
47+
clientip.ReplaceForwardingHeaders(
48+
proxyReq.Out.Header,
49+
clientip.FromRemoteAddr(proxyReq.In.RemoteAddr),
50+
)
51+
} else {
52+
restoreLegacyForwardingHeaders(proxyReq)
4253
}
43-
req.URL.Scheme = "http"
44-
req.URL.Host = "unix"
54+
proxyReq.Out.URL.Scheme = "http"
55+
proxyReq.Out.URL.Host = "unix"
4556
},
4657
Transport: transport,
4758
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
@@ -50,3 +61,64 @@ func Init() {
5061
},
5162
}
5263
}
64+
65+
func restoreLegacyForwardingHeaders(proxyReq *httputil.ProxyRequest) {
66+
restoreInboundHeader(proxyReq, "Forwarded")
67+
restoreLegacyForwardedFor(proxyReq)
68+
restoreLegacyForwardedMetadata(proxyReq, "X-Forwarded-Host", proxyReq.In.Host)
69+
70+
forwardedProto := "http"
71+
if proxyReq.In.TLS != nil {
72+
forwardedProto = "https"
73+
}
74+
restoreLegacyForwardedMetadata(proxyReq, "X-Forwarded-Proto", forwardedProto)
75+
}
76+
77+
func restoreLegacyForwardedFor(proxyReq *httputil.ProxyRequest) {
78+
restoreInboundHeader(proxyReq, "X-Forwarded-For")
79+
clientIP, _, err := net.SplitHostPort(proxyReq.In.RemoteAddr)
80+
if err != nil {
81+
return
82+
}
83+
prior, ok := proxyReq.Out.Header["X-Forwarded-For"]
84+
omit := ok && prior == nil
85+
if len(prior) > 0 {
86+
clientIP = strings.Join(prior, ", ") + ", " + clientIP
87+
}
88+
if !omit {
89+
proxyReq.Out.Header.Set("X-Forwarded-For", clientIP)
90+
}
91+
}
92+
93+
func restoreLegacyForwardedMetadata(proxyReq *httputil.ProxyRequest, name, fallback string) {
94+
if !isConnectionHeader(proxyReq.In.Header, name) && proxyReq.In.Header.Get(name) != "" {
95+
restoreInboundHeader(proxyReq, name)
96+
return
97+
}
98+
proxyReq.Out.Header.Del(name)
99+
if fallback != "" {
100+
proxyReq.Out.Header.Set(name, fallback)
101+
}
102+
}
103+
104+
func restoreInboundHeader(proxyReq *httputil.ProxyRequest, name string) {
105+
proxyReq.Out.Header.Del(name)
106+
if isConnectionHeader(proxyReq.In.Header, name) {
107+
return
108+
}
109+
canonicalName := http.CanonicalHeaderKey(name)
110+
if values, ok := proxyReq.In.Header[canonicalName]; ok {
111+
proxyReq.Out.Header[canonicalName] = slices.Clone(values)
112+
}
113+
}
114+
115+
func isConnectionHeader(header http.Header, name string) bool {
116+
for _, value := range header.Values("Connection") {
117+
for _, token := range strings.Split(value, ",") {
118+
if strings.EqualFold(strings.TrimSpace(token), name) {
119+
return true
120+
}
121+
}
122+
}
123+
return false
124+
}

core/middleware/helper.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package middleware
22

3-
import "strings"
3+
import (
4+
"strings"
5+
6+
"github.com/1Panel-dev/1Panel/core/utils/publicshare"
7+
)
48

59
func ShouldProxyToAgent(reqPath string) bool {
610
if strings.HasPrefix(reqPath, "/1panel/swagger") || !strings.HasPrefix(reqPath, "/api/v2") {
@@ -13,12 +17,5 @@ func ShouldProxyToAgent(reqPath string) bool {
1317
}
1418

1519
func IsPublicFileShareAPI(reqPath string) bool {
16-
switch reqPath {
17-
case "/api/v2/files/share/info",
18-
"/api/v2/files/share/check",
19-
"/api/v2/files/share/download":
20-
return true
21-
default:
22-
return false
23-
}
20+
return publicshare.IsAPI(reqPath)
2421
}

core/utils/clientip/clientip.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package clientip
2+
3+
import (
4+
"net"
5+
"net/http"
6+
"strings"
7+
)
8+
9+
const (
10+
forwardedHeader = "Forwarded"
11+
forwardedForHeader = "X-Forwarded-For"
12+
realIPHeader = "X-Real-IP"
13+
panelClientIPHeader = "X-Panel-Client-IP"
14+
)
15+
16+
func FromRemoteAddr(remoteAddr string) string {
17+
host, _, err := net.SplitHostPort(strings.TrimSpace(remoteAddr))
18+
if err != nil {
19+
return ""
20+
}
21+
ip := net.ParseIP(strings.TrimSpace(host))
22+
if ip == nil {
23+
return ""
24+
}
25+
return ip.String()
26+
}
27+
28+
func ReplaceForwardingHeaders(header http.Header, clientIP string) {
29+
if header == nil {
30+
return
31+
}
32+
RemoveForwardingHeaders(header)
33+
34+
ip := net.ParseIP(strings.TrimSpace(clientIP))
35+
if ip == nil {
36+
return
37+
}
38+
normalized := ip.String()
39+
header.Set(forwardedForHeader, normalized)
40+
header.Set(realIPHeader, normalized)
41+
}
42+
43+
func RemoveForwardingHeaders(header http.Header) {
44+
if header == nil {
45+
return
46+
}
47+
header.Del(forwardedHeader)
48+
header.Del(forwardedForHeader)
49+
header.Del(realIPHeader)
50+
header.Del(panelClientIPHeader)
51+
}

core/utils/publicshare/routes.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package publicshare
2+
3+
func IsAPI(path string) bool {
4+
switch path {
5+
case "/api/v2/files/share/info",
6+
"/api/v2/files/share/check",
7+
"/api/v2/files/share/download":
8+
return true
9+
default:
10+
return false
11+
}
12+
}

0 commit comments

Comments
 (0)