mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 00:00:50 +00:00
fix: prevent rate limit bypass in public file shares (#13632)
This commit is contained in:
+84
-12
@@ -5,7 +5,12 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/utils/clientip"
|
||||
"github.com/1Panel-dev/1Panel/core/utils/publicshare"
|
||||
)
|
||||
|
||||
const SockPath = "/etc/1panel/agent.sock"
|
||||
@@ -28,20 +33,26 @@ func Init() {
|
||||
MaxIdleConnsPerHost: 50,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
}
|
||||
LocalAgentProxy = &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
if req.Header.Get("X-Forwarded-Proto") == "" {
|
||||
if req.TLS != nil {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
} else {
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
}
|
||||
LocalAgentProxy = newLocalAgentProxy(transport)
|
||||
}
|
||||
|
||||
func newLocalAgentProxy(transport http.RoundTripper) *httputil.ReverseProxy {
|
||||
return &httputil.ReverseProxy{
|
||||
Rewrite: func(proxyReq *httputil.ProxyRequest) {
|
||||
if proxyReq.In.Form == nil {
|
||||
proxyReq.Out.URL.RawQuery = proxyReq.In.URL.RawQuery
|
||||
}
|
||||
if req.Header.Get("X-Forwarded-Host") == "" && req.Host != "" {
|
||||
req.Header.Set("X-Forwarded-Host", req.Host)
|
||||
if publicshare.IsAPI(proxyReq.In.URL.Path) {
|
||||
proxyReq.SetXForwarded()
|
||||
clientip.ReplaceForwardingHeaders(
|
||||
proxyReq.Out.Header,
|
||||
clientip.FromRemoteAddr(proxyReq.In.RemoteAddr),
|
||||
)
|
||||
} else {
|
||||
restoreLegacyForwardingHeaders(proxyReq)
|
||||
}
|
||||
req.URL.Scheme = "http"
|
||||
req.URL.Host = "unix"
|
||||
proxyReq.Out.URL.Scheme = "http"
|
||||
proxyReq.Out.URL.Host = "unix"
|
||||
},
|
||||
Transport: transport,
|
||||
ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
@@ -50,3 +61,64 @@ func Init() {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func restoreLegacyForwardingHeaders(proxyReq *httputil.ProxyRequest) {
|
||||
restoreInboundHeader(proxyReq, "Forwarded")
|
||||
restoreLegacyForwardedFor(proxyReq)
|
||||
restoreLegacyForwardedMetadata(proxyReq, "X-Forwarded-Host", proxyReq.In.Host)
|
||||
|
||||
forwardedProto := "http"
|
||||
if proxyReq.In.TLS != nil {
|
||||
forwardedProto = "https"
|
||||
}
|
||||
restoreLegacyForwardedMetadata(proxyReq, "X-Forwarded-Proto", forwardedProto)
|
||||
}
|
||||
|
||||
func restoreLegacyForwardedFor(proxyReq *httputil.ProxyRequest) {
|
||||
restoreInboundHeader(proxyReq, "X-Forwarded-For")
|
||||
clientIP, _, err := net.SplitHostPort(proxyReq.In.RemoteAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
prior, ok := proxyReq.Out.Header["X-Forwarded-For"]
|
||||
omit := ok && prior == nil
|
||||
if len(prior) > 0 {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
if !omit {
|
||||
proxyReq.Out.Header.Set("X-Forwarded-For", clientIP)
|
||||
}
|
||||
}
|
||||
|
||||
func restoreLegacyForwardedMetadata(proxyReq *httputil.ProxyRequest, name, fallback string) {
|
||||
if !isConnectionHeader(proxyReq.In.Header, name) && proxyReq.In.Header.Get(name) != "" {
|
||||
restoreInboundHeader(proxyReq, name)
|
||||
return
|
||||
}
|
||||
proxyReq.Out.Header.Del(name)
|
||||
if fallback != "" {
|
||||
proxyReq.Out.Header.Set(name, fallback)
|
||||
}
|
||||
}
|
||||
|
||||
func restoreInboundHeader(proxyReq *httputil.ProxyRequest, name string) {
|
||||
proxyReq.Out.Header.Del(name)
|
||||
if isConnectionHeader(proxyReq.In.Header, name) {
|
||||
return
|
||||
}
|
||||
canonicalName := http.CanonicalHeaderKey(name)
|
||||
if values, ok := proxyReq.In.Header[canonicalName]; ok {
|
||||
proxyReq.Out.Header[canonicalName] = slices.Clone(values)
|
||||
}
|
||||
}
|
||||
|
||||
func isConnectionHeader(header http.Header, name string) bool {
|
||||
for _, value := range header.Values("Connection") {
|
||||
for _, token := range strings.Split(value, ",") {
|
||||
if strings.EqualFold(strings.TrimSpace(token), name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package middleware
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/utils/publicshare"
|
||||
)
|
||||
|
||||
func ShouldProxyToAgent(reqPath string) bool {
|
||||
if strings.HasPrefix(reqPath, "/1panel/swagger") || !strings.HasPrefix(reqPath, "/api/v2") {
|
||||
@@ -13,12 +17,5 @@ func ShouldProxyToAgent(reqPath string) bool {
|
||||
}
|
||||
|
||||
func IsPublicFileShareAPI(reqPath string) bool {
|
||||
switch reqPath {
|
||||
case "/api/v2/files/share/info",
|
||||
"/api/v2/files/share/check",
|
||||
"/api/v2/files/share/download":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
return publicshare.IsAPI(reqPath)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package clientip
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
forwardedHeader = "Forwarded"
|
||||
forwardedForHeader = "X-Forwarded-For"
|
||||
realIPHeader = "X-Real-IP"
|
||||
panelClientIPHeader = "X-Panel-Client-IP"
|
||||
)
|
||||
|
||||
func FromRemoteAddr(remoteAddr string) string {
|
||||
host, _, err := net.SplitHostPort(strings.TrimSpace(remoteAddr))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
ip := net.ParseIP(strings.TrimSpace(host))
|
||||
if ip == nil {
|
||||
return ""
|
||||
}
|
||||
return ip.String()
|
||||
}
|
||||
|
||||
func ReplaceForwardingHeaders(header http.Header, clientIP string) {
|
||||
if header == nil {
|
||||
return
|
||||
}
|
||||
RemoveForwardingHeaders(header)
|
||||
|
||||
ip := net.ParseIP(strings.TrimSpace(clientIP))
|
||||
if ip == nil {
|
||||
return
|
||||
}
|
||||
normalized := ip.String()
|
||||
header.Set(forwardedForHeader, normalized)
|
||||
header.Set(realIPHeader, normalized)
|
||||
}
|
||||
|
||||
func RemoveForwardingHeaders(header http.Header) {
|
||||
if header == nil {
|
||||
return
|
||||
}
|
||||
header.Del(forwardedHeader)
|
||||
header.Del(forwardedForHeader)
|
||||
header.Del(realIPHeader)
|
||||
header.Del(panelClientIPHeader)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package publicshare
|
||||
|
||||
func IsAPI(path string) bool {
|
||||
switch path {
|
||||
case "/api/v2/files/share/info",
|
||||
"/api/v2/files/share/check",
|
||||
"/api/v2/files/share/download":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user