VYPR
High severityNVD Advisory· Published Aug 5, 2025· Updated Apr 15, 2026

CVE-2025-53534

CVE-2025-53534

Description

RatPanel is a server operation and maintenance management panel. In versions 2.3.19 through 2.5.5, when an attacker obtains the backend login path of RatPanel (including but not limited to weak default paths, brute-force cracking, etc.), they can execute system commands or take over hosts managed by the panel without logging in. In addition to this remote code execution (RCE) vulnerability, the flawed code also leads to unauthorized access. RatPanel uses the CleanPath middleware provided by github.com/go-chi/chi package to clean URLs, but but the middleware does not process r.URL.Path, which can cause the paths to be misinterpreted. This is fixed in version 2.5.6.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/tnborg/panelGo
>= 2.3.19, < 2.5.62.5.6
github.com/tnborg/panelGo
>= 0.0.0-20241111062800-91ecd04c2700, < 0.0.0-20250707071915-4985eb2e1f380.0.0-20250707071915-4985eb2e1f38

Patches

6
4985eb2e1f38

feat: remove CleanPath middleware

https://github.com/tnborg/panel耗子Jul 7, 2025via ghsa
3 files changed · +11 19
  • internal/http/middleware/entrance.go+5 7 modified
    @@ -29,8 +29,6 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
     				entrance = "/" + entrance
     			}
     
    -			routePath := chi.RouteContext(r.Context()).RoutePath
    -
     			// 情况一:设置了绑定域名、IP、UA,且请求不符合要求,返回错误
     			host, _, err := net.SplitHostPort(r.Host)
     			if err != nil {
    @@ -80,7 +78,7 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
     			}
     
     			// 情况二:请求路径与入口路径相同或者未设置访问入口,标记通过验证并重定向到登录页面
    -			if (strings.TrimSuffix(routePath, "/") == entrance || entrance == "/") &&
    +			if (strings.TrimSuffix(r.URL.Path, "/") == entrance || entrance == "/") &&
     				r.Header.Get("Authorization") == "" {
     				sess.Put("verify_entrance", true)
     				render := chix.NewRender(w, r)
    @@ -90,12 +88,12 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
     			}
     
     			// 情况三:通过APIKey+入口路径访问,重写请求路径并跳过验证
    -			if strings.HasPrefix(routePath, entrance) && r.Header.Get("Authorization") != "" {
    +			if strings.HasPrefix(r.URL.Path, entrance) && r.Header.Get("Authorization") != "" {
     				// 只在设置了入口路径的情况下,才进行重写
     				if entrance != "/" {
     					if rctx := chi.RouteContext(r.Context()); rctx != nil {
    -						rctx.RoutePath = strings.TrimPrefix(routePath, entrance)
    -						r.URL.Path = strings.TrimPrefix(routePath, entrance)
    +						rctx.RoutePath = strings.TrimPrefix(rctx.RoutePath, entrance)
    +						r.URL.Path = strings.TrimPrefix(r.URL.Path, entrance)
     					}
     				}
     				next.ServeHTTP(w, r)
    @@ -105,7 +103,7 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
     			// 情况四:非调试模式且未通过验证的请求,返回错误
     			if !conf.Bool("app.debug") &&
     				sess.Missing("verify_entrance") &&
    -				routePath != "/robots.txt" {
    +				r.URL.Path != "/robots.txt" {
     				Abort(w, http.StatusTeapot, t.Get("invalid access entrance"))
     				return
     			}
    
  • internal/http/middleware/must_install.go+4 7 modified
    @@ -4,7 +4,6 @@ import (
     	"net/http"
     	"strings"
     
    -	"github.com/go-chi/chi/v5"
     	"github.com/leonelquinteros/gotext"
     
     	"github.com/tnb-labs/panel/internal/biz"
    @@ -14,15 +13,13 @@ import (
     func MustInstall(t *gotext.Locale, app biz.AppRepo) func(next http.Handler) http.Handler {
     	return func(next http.Handler) http.Handler {
     		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    -			routePath := chi.RouteContext(r.Context()).RoutePath
    -
     			var slugs []string
    -			if strings.HasPrefix(routePath, "/api/website") {
    +			if strings.HasPrefix(r.URL.Path, "/api/website") {
     				slugs = append(slugs, "nginx")
    -			} else if strings.HasPrefix(routePath, "/api/container") {
    +			} else if strings.HasPrefix(r.URL.Path, "/api/container") {
     				slugs = append(slugs, "podman", "docker")
    -			} else if strings.HasPrefix(routePath, "/api/apps/") {
    -				pathArr := strings.Split(routePath, "/")
    +			} else if strings.HasPrefix(r.URL.Path, "/api/apps/") {
    +				pathArr := strings.Split(r.URL.Path, "/")
     				if len(pathArr) < 4 {
     					Abort(w, http.StatusForbidden, t.Get("app not found"))
     					return
    
  • internal/http/middleware/must_login.go+2 5 modified
    @@ -9,7 +9,6 @@ import (
     	"slices"
     	"strings"
     
    -	"github.com/go-chi/chi/v5"
     	"github.com/go-rat/sessions"
     	"github.com/leonelquinteros/gotext"
     	"github.com/spf13/cast"
    @@ -36,18 +35,16 @@ func MustLogin(t *gotext.Locale, session *sessions.Manager, userToken biz.UserTo
     				return
     			}
     
    -			routePath := chi.RouteContext(r.Context()).RoutePath
    -
     			// 对白名单和非 API 请求放行
    -			if slices.Contains(whiteList, routePath) || !strings.HasPrefix(routePath, "/api") {
    +			if slices.Contains(whiteList, r.URL.Path) || !strings.HasPrefix(r.URL.Path, "/api") {
     				next.ServeHTTP(w, r)
     				return
     			}
     
     			userID := uint(0)
     			if r.Header.Get("Authorization") != "" {
     				// 禁止访问 ws 相关的接口
    -				if strings.HasPrefix(routePath, "/api/ws") {
    +				if strings.HasPrefix(r.URL.Path, "/api/ws") {
     					Abort(w, http.StatusForbidden, t.Get("ws not allowed"))
     					return
     				}
    
ed5c74c75342

fix: 优化路由路径获取方式

https://github.com/tnborg/panel耗子Jul 6, 2025via ghsa
4 files changed · +22 14
  • internal/http/middleware/entrance.go+7 5 modified
    @@ -29,6 +29,8 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
     				entrance = "/" + entrance
     			}
     
    +			routePath := chi.RouteContext(r.Context()).RoutePath
    +
     			// 情况一:设置了绑定域名、IP、UA,且请求不符合要求,返回错误
     			host, _, err := net.SplitHostPort(r.Host)
     			if err != nil {
    @@ -78,7 +80,7 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
     			}
     
     			// 情况二:请求路径与入口路径相同或者未设置访问入口,标记通过验证并重定向到登录页面
    -			if (strings.TrimSuffix(r.URL.Path, "/") == entrance || entrance == "/") &&
    +			if (strings.TrimSuffix(routePath, "/") == entrance || entrance == "/") &&
     				r.Header.Get("Authorization") == "" {
     				sess.Put("verify_entrance", true)
     				render := chix.NewRender(w, r)
    @@ -88,12 +90,12 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
     			}
     
     			// 情况三:通过APIKey+入口路径访问,重写请求路径并跳过验证
    -			if strings.HasPrefix(r.URL.Path, entrance) && r.Header.Get("Authorization") != "" {
    +			if strings.HasPrefix(routePath, entrance) && r.Header.Get("Authorization") != "" {
     				// 只在设置了入口路径的情况下,才进行重写
     				if entrance != "/" {
     					if rctx := chi.RouteContext(r.Context()); rctx != nil {
    -						rctx.RoutePath = strings.TrimPrefix(rctx.RoutePath, entrance)
    -						r.URL.Path = strings.TrimPrefix(r.URL.Path, entrance)
    +						rctx.RoutePath = strings.TrimPrefix(routePath, entrance)
    +						r.URL.Path = strings.TrimPrefix(routePath, entrance)
     					}
     				}
     				next.ServeHTTP(w, r)
    @@ -103,7 +105,7 @@ func Entrance(t *gotext.Locale, conf *koanf.Koanf, session *sessions.Manager) fu
     			// 情况四:非调试模式且未通过验证的请求,返回错误
     			if !conf.Bool("app.debug") &&
     				sess.Missing("verify_entrance") &&
    -				r.URL.Path != "/robots.txt" {
    +				routePath != "/robots.txt" {
     				Abort(w, http.StatusTeapot, t.Get("invalid access entrance"))
     				return
     			}
    
  • internal/http/middleware/middleware.go+3 3 modified
    @@ -39,16 +39,16 @@ func NewMiddlewares(conf *koanf.Koanf, log *slog.Logger, session *sessions.Manag
     // Globals is a collection of global middleware that will be applied to every request.
     func (r *Middlewares) Globals(t *gotext.Locale, mux *chi.Mux) []func(http.Handler) http.Handler {
     	return []func(http.Handler) http.Handler{
    -		sessionmiddleware.StartSession(r.session),
    +		middleware.Recoverer,
     		//middleware.SupressNotFound(mux),// bug https://github.com/go-chi/chi/pull/940
     		middleware.CleanPath,
     		middleware.StripSlashes,
    -		middleware.Compress(5),
     		httplog.RequestLogger(r.log, &httplog.Options{
     			Level:             slog.LevelInfo,
     			LogRequestHeaders: []string{"User-Agent"},
     		}),
    -		middleware.Recoverer,
    +		middleware.Compress(5),
    +		sessionmiddleware.StartSession(r.session),
     		Status(t),
     		Entrance(t, r.conf, r.session),
     		MustLogin(t, r.session, r.userToken),
    
  • internal/http/middleware/must_install.go+7 4 modified
    @@ -4,6 +4,7 @@ import (
     	"net/http"
     	"strings"
     
    +	"github.com/go-chi/chi/v5"
     	"github.com/leonelquinteros/gotext"
     
     	"github.com/tnb-labs/panel/internal/biz"
    @@ -13,13 +14,15 @@ import (
     func MustInstall(t *gotext.Locale, app biz.AppRepo) func(next http.Handler) http.Handler {
     	return func(next http.Handler) http.Handler {
     		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    +			routePath := chi.RouteContext(r.Context()).RoutePath
    +
     			var slugs []string
    -			if strings.HasPrefix(r.URL.Path, "/api/website") {
    +			if strings.HasPrefix(routePath, "/api/website") {
     				slugs = append(slugs, "nginx")
    -			} else if strings.HasPrefix(r.URL.Path, "/api/container") {
    +			} else if strings.HasPrefix(routePath, "/api/container") {
     				slugs = append(slugs, "podman", "docker")
    -			} else if strings.HasPrefix(r.URL.Path, "/api/apps/") {
    -				pathArr := strings.Split(r.URL.Path, "/")
    +			} else if strings.HasPrefix(routePath, "/api/apps/") {
    +				pathArr := strings.Split(routePath, "/")
     				if len(pathArr) < 4 {
     					Abort(w, http.StatusForbidden, t.Get("app not found"))
     					return
    
  • internal/http/middleware/must_login.go+5 2 modified
    @@ -9,6 +9,7 @@ import (
     	"slices"
     	"strings"
     
    +	"github.com/go-chi/chi/v5"
     	"github.com/go-rat/sessions"
     	"github.com/leonelquinteros/gotext"
     	"github.com/spf13/cast"
    @@ -35,16 +36,18 @@ func MustLogin(t *gotext.Locale, session *sessions.Manager, userToken biz.UserTo
     				return
     			}
     
    +			routePath := chi.RouteContext(r.Context()).RoutePath
    +
     			// 对白名单和非 API 请求放行
    -			if slices.Contains(whiteList, r.URL.Path) || !strings.HasPrefix(r.URL.Path, "/api") {
    +			if slices.Contains(whiteList, routePath) || !strings.HasPrefix(routePath, "/api") {
     				next.ServeHTTP(w, r)
     				return
     			}
     
     			userID := uint(0)
     			if r.Header.Get("Authorization") != "" {
     				// 禁止访问 ws 相关的接口
    -				if strings.HasPrefix(r.URL.Path, "/api/ws") {
    +				if strings.HasPrefix(routePath, "/api/ws") {
     					Abort(w, http.StatusForbidden, t.Get("ws not allowed"))
     					return
     				}
    
91ecd04c2700

feat: 优化登录中间件使用白名单

https://github.com/tnborg/panel耗子Nov 11, 2024via ghsa
3 files changed · +28 28
  • internal/http/middleware/middleware.go+2 1 modified
    @@ -24,8 +24,9 @@ func GlobalMiddleware() []func(http.Handler) http.Handler {
     			LogRequestHeaders: []string{"User-Agent"},
     		}),
     		middleware.Recoverer,
    -		Entrance,
     		Status,
    +		Entrance,
    +		MustLogin,
     		MustInstall,
     	}
     }
    
  • internal/http/middleware/must_login.go+16 0 modified
    @@ -3,6 +3,8 @@ package middleware
     import (
     	"context"
     	"net/http"
    +	"slices"
    +	"strings"
     
     	"github.com/go-rat/chix"
     	"github.com/spf13/cast"
    @@ -12,6 +14,14 @@ import (
     
     // MustLogin 确保已登录
     func MustLogin(next http.Handler) http.Handler {
    +	// 白名单
    +	whiteList := []string{
    +		"/api/user/login",
    +		"/api/user/logout",
    +		"/api/user/isLogin",
    +		"/api/dashboard/panel",
    +	}
    +
     	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
     		sess, err := app.Session.GetSession(r)
     		if err != nil {
    @@ -22,6 +32,12 @@ func MustLogin(next http.Handler) http.Handler {
     			})
     		}
     
    +		// 对白名单和非 API 请求放行
    +		if slices.Contains(whiteList, r.URL.Path) || !strings.HasPrefix(r.URL.Path, "/api") {
    +			next.ServeHTTP(w, r)
    +			return
    +		}
    +
     		if sess.Missing("user_id") {
     			render := chix.NewRender(w)
     			render.Status(http.StatusUnauthorized)
    
  • internal/route/http.go+10 27 modified
    @@ -21,25 +21,24 @@ func Http(r chi.Router) {
     			r.With(middleware.Throttle(5, time.Minute)).Post("/login", user.Login)
     			r.Post("/logout", user.Logout)
     			r.Get("/isLogin", user.IsLogin)
    -			r.With(middleware.MustLogin).Get("/info", user.Info)
    +			r.Get("/info", user.Info)
     		})
     
     		r.Route("/dashboard", func(r chi.Router) {
     			dashboard := service.NewDashboardService()
     			r.Get("/panel", dashboard.Panel)
    -			r.With(middleware.MustLogin).Get("/homeApps", dashboard.HomeApps)
    -			r.With(middleware.MustLogin).Post("/current", dashboard.Current)
    -			r.With(middleware.MustLogin).Get("/systemInfo", dashboard.SystemInfo)
    -			r.With(middleware.MustLogin).Get("/countInfo", dashboard.CountInfo)
    -			r.With(middleware.MustLogin).Get("/installedDbAndPhp", dashboard.InstalledDbAndPhp)
    -			r.With(middleware.MustLogin).Get("/checkUpdate", dashboard.CheckUpdate)
    -			r.With(middleware.MustLogin).Get("/updateInfo", dashboard.UpdateInfo)
    -			r.With(middleware.MustLogin).Post("/update", dashboard.Update)
    -			r.With(middleware.MustLogin).Post("/restart", dashboard.Restart)
    +			r.Get("/homeApps", dashboard.HomeApps)
    +			r.Post("/current", dashboard.Current)
    +			r.Get("/systemInfo", dashboard.SystemInfo)
    +			r.Get("/countInfo", dashboard.CountInfo)
    +			r.Get("/installedDbAndPhp", dashboard.InstalledDbAndPhp)
    +			r.Get("/checkUpdate", dashboard.CheckUpdate)
    +			r.Get("/updateInfo", dashboard.UpdateInfo)
    +			r.Post("/update", dashboard.Update)
    +			r.Post("/restart", dashboard.Restart)
     		})
     
     		r.Route("/task", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			task := service.NewTaskService()
     			r.Get("/status", task.Status)
     			r.Get("/", task.List)
    @@ -48,7 +47,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/website", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			website := service.NewWebsiteService()
     			r.Get("/defaultConfig", website.GetDefaultConfig)
     			r.Post("/defaultConfig", website.UpdateDefaultConfig)
    @@ -65,7 +63,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/database", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			database := service.NewDatabaseService()
     			r.Get("/", database.List)
     			r.Post("/", database.Create)
    @@ -74,7 +71,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/databaseServer", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			database := service.NewDatabaseService()
     			r.Get("/", database.List)
     			r.Post("/", database.Create)
    @@ -83,7 +79,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/backup", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			backup := service.NewBackupService()
     			r.Get("/{type}", backup.List)
     			r.Post("/{type}", backup.Create)
    @@ -93,7 +88,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/cert", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			cert := service.NewCertService()
     			r.Get("/caProviders", cert.CAProviders)
     			r.Get("/dnsProviders", cert.DNSProviders)
    @@ -131,7 +125,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/app", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			app := service.NewAppService()
     			r.Get("/list", app.List)
     			r.Post("/install", app.Install)
    @@ -143,7 +136,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/cron", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			cron := service.NewCronService()
     			r.Get("/", cron.List)
     			r.Post("/", cron.Create)
    @@ -154,7 +146,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/safe", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			safe := service.NewSafeService()
     			r.Get("/ssh", safe.GetSSH)
     			r.Post("/ssh", safe.UpdateSSH)
    @@ -163,7 +154,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/firewall", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			firewall := service.NewFirewallService()
     			r.Get("/status", firewall.GetStatus)
     			r.Post("/status", firewall.UpdateStatus)
    @@ -179,7 +169,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/ssh", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			ssh := service.NewSSHService()
     			r.Get("/", ssh.List)
     			r.Post("/", ssh.Create)
    @@ -189,7 +178,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/container", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			r.Route("/container", func(r chi.Router) {
     				container := service.NewContainerService()
     				r.Get("/", container.List)
    @@ -230,7 +218,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/file", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			file := service.NewFileService()
     			r.Post("/create", file.Create)
     			r.Get("/content", file.Content)
    @@ -251,7 +238,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/monitor", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			monitor := service.NewMonitorService()
     			r.Get("/setting", monitor.GetSetting)
     			r.Post("/setting", monitor.UpdateSetting)
    @@ -260,14 +246,12 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/setting", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			setting := service.NewSettingService()
     			r.Get("/", setting.Get)
     			r.Post("/", setting.Update)
     		})
     
     		r.Route("/systemctl", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			systemctl := service.NewSystemctlService()
     			r.Get("/status", systemctl.Status)
     			r.Get("/isEnabled", systemctl.IsEnabled)
    @@ -280,7 +264,6 @@ func Http(r chi.Router) {
     		})
     
     		r.Route("/apps", func(r chi.Router) {
    -			r.Use(middleware.MustLogin)
     			apps.Boot(r)
     		})
     	})
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.