| Gói / sản phẩm | Phiên bản bị ảnh hưởng | Phiên bản đã vá |
|---|---|---|
| github.com/getarcaneapp/arcane/backend | < 1.19.0 | 1.19.0 |
The unauthenticated GET /api/app-images/logo endpoint reflects a user-supplied color query parameter into the body of an SVG document via strings.ReplaceAll with no escaping. The substitution lands inside a <style> element of the embedded logo.svg, allowing an attacker to close the style block and inject executable <script> content. Because the response is served as image/svg+xml and Arcane sets no Content-Security-Policy or X-Content-Type-Options headers, navigating a logged-in admin victim to a crafted URL executes attacker-controlled JavaScript in Arcane's origin and rides the victim's HttpOnly JWT cookie to fully compromise the admin account.
The route is registered in backend/internal/huma/handlers/appimages.go:53-61 with an explicitly empty security requirement, marking it as public:
huma.Register(api, huma.Operation{
OperationID: "get-logo",
Method: http.MethodGet,
Path: "/app-images/logo",
...
Security: []map[string][]string{}, // explicit: no auth
}, h.GetLogo)
backend/internal/huma/middleware/auth.go:209-213 honors the empty Security value by returning reqs.isRequired == false and short-circuiting with next(ctx), so no JWT/API-key check runs.
GetLogoInput.Color (appimages.go:23) is declared with no validation tags:
type GetLogoInput struct {
Full bool `query:"full" default:"false" ...`
Color string `query:"color" doc:"Optional accent color override ..."`
}
The handler passes the value straight through getImageWithColor → ApplicationImagesService.GetImageWithColor → applyAccentColorToSVG (backend/internal/services/app_images_service.go:79-105):
svgStr = strings.ReplaceAll(svgStr, "fill:#6D28D9", fmt.Sprintf("fill:%s", accentColor))
svgStr = strings.ReplaceAll(svgStr, "fill:#6d28d9", fmt.Sprintf("fill:%s", accentColor))
The bundled backend/resources/images/logo.svg contains:
<style id="style1" type="text/css">.st0{fill:#6d28d9}</style>
so a color value like red}</style><script>fetch('/api/users',...)</script><style>x{ produces a valid SVG that closes the <style> element and embeds a element. The response Content-Type is (from ), and a grep of the backend confirms no , , or framing headers are emitted on any route.
Bạn đang dùng một trong các sản phẩm này? Quét site để biết có đang lộ phiên bản có lỗ hổng không.
Quét site miễn phí<script>image/svg+xmlpkg/utils/image/image_util.goContent-Security-PolicyX-Content-Type-OptionsBrowsers execute scripts in SVG documents loaded as top-level navigations or via <iframe src=…> / window.open(…). The execution context is origin(arcane-host), so the victim's __Host-token / token HttpOnly JWT cookie (recognized by extractTokenFromCookieHeaderInternal at auth.go:274-286) is automatically attached to subsequent same-origin fetch() calls. From there the attacker can invoke any privileged API the victim possesses — most damagingly POST /api/users to create a new admin account, after which the attacker has standalone admin access to manage Docker containers, registries, GitOps secrets, and SSH/registry credentials stored by Arcane.
POST /api/users.fetch(). Absence of CSP and X-Content-Type-Options: nosniff removes available defenses-in-depth.Defense-in-depth — add to all responses (and especially to /api/app-images/*):
X-Content-Type-Options: nosniffContent-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; img-src 'self' data: on the SVG image responses (or the most permissive policy compatible with the frontend on app routes).Content-Disposition: inline and from a separate cookie-less origin to remove the same-origin session-riding primitive entirely.Also enforce the same allowlist on the settings write path (SettingsService → AccentColor) so a stored XSS variant cannot be introduced via the settings API.