BTC
ETH
SOL
BNB
GOLD
XRP
DOGE
ADA
Back to home
Security

[LOW] Security Advisory: Beszel has an IDOR in hub API endpoints that read system ID from URL parameter (github.com/henrygd/beszel)

Beszel, an open-source self-hosted server management tool, ships with an IDOR vulnerability in its hub API.

Beszel, an open-source self-hosted server management tool, ships with an IDOR vulnerability in its hub API. Authenticated users can read sensitive data from any system they don’t own, as long as they supply the target’s 15-character alphanumeric system ID in the URL. This affects endpoints for container logs, info, systemd services, and SMART disk refresh.

The issue hits the hub component at commit c7261b56f1bfb9ae57ef0856a0052cabb2fd3b84. Files like internal/hub/api.go (lines 283-361) fetch systems by ID without checking if the requester is a member. Standard PocketBase collection APIs enforce membership via rules like system.users.id ?= @request.auth.id, but custom routes on apiAuth skip this.

Affected Endpoints

Four endpoints expose the flaw:

Container IDs are 12-digit hex strings, adding a layer, but service names and system IDs remain guessable in some cases. System IDs aren’t public, but enumeration via API brute-force is feasible since responses differ: PocketBase returns 404 for unauthorized systems, while these endpoints contact the agent and return data.

Code Breakdown

The core problem lies in handlers like containerRequestHandler:

// internal/hub/api.go:283-305
func (h *Hub) containerRequestHandler(e *core.RequestEvent, fetchFunc func(*systems.System, string) (string, error), responseKey string) error {
	systemID := e.Request.URL.Query().Get("system")
	containerID := e.Request.URL.Query().Get("container")
	if systemID == "" || containerID == "" {
		return e.JSON(http.StatusBadRequest, map[string]string{"error": "system and container parameters are required"})
	}
	if !containerIDPattern.MatchString(containerID) {
		return e.JSON(http.StatusBadRequest, map[string]string{"error": "invalid container parameter"})
	}
	system, err := h.sm.GetSystem(systemID)
	// ^^^ No authorization check: e.Auth.Id is never verified against system.users
	if err != nil {
		return e.JSON(http.StatusNotFound, map[string]string{"error": "system not found"})
	}
	data, err := fetchFunc(system, containerID)
	// ...
}

Similar oversights plague getSystemdInfo and refreshSmartData. The code grabs the system, pings the agent, and serves data—no user membership validation.

To prove it, spin up the hub:

$ cd ~/Evidence/henrygd/beszel
$ go run .

Create two users and systems. From user A, hit user B’s endpoint with B’s system ID. You’ll get real data, not a 404.

Implications and Fixes

This is rated low severity for good reason: 15-char IDs (36^15 ~ 10^23 possibilities) resist brute-force without massive compute. But in shared hubs—think teams or service providers—leaked IDs via logs, screenshots, or side-channels turn it real. Attackers read logs exposing paths, env vars, PII; container info reveals images, ports; systemd status shows running services; SMART refresh leaks drive serials and health metrics.

Beszel agents run on servers, feeding hub data over HTTPS. No RCE or escalation here—just unauthorized read access. Still, in multi-tenant setups, it erodes isolation. Self-hosters expect RBAC; this breaks it.

Fix is straightforward: Add membership checks post GetSystem. Query system.users for e.Auth.Id, return 403 if missing. Upstream should patch soon—repo’s active, last commit recent. Users: Audit access, rotate IDs if exposed, monitor logs for odd requests. Until fixed, proxy or firewall these paths.

Why care? Beszel targets privacy-focused ops replacing cloud consoles. IDOR undermines that. Fair play: It’s custom API sloppiness, not novel; PocketBase handles it right. Devs fixed similar in past. Scan your instance—don’t assume random IDs save you.

April 10, 2026 · 3 min · 9 views · Source: GitHub Security

Related