PYSEC-2026-3666

Vulnerability from pysec - Published: 2026-08-19 11:56 - Updated: 2026-08-19 12:16
VLAI
Details

Summary

CVE-2026-32608 ("Command Injection via Process Names in Action Command Templates") was fixed (commit 5680a5d) by adding _sanitize_mustache_dict, which replaces the shell operators &&, |, >>, > with spaces in the values rendered into action command templates.

The sanitizer only processes top-level string values (if isinstance(v, str)). Attacker-controlled nested values — most notably a process's cmdline, which Glances exposes as a list and which is fully attacker-controlled via argv — are passed through unsanitized. Because the Mustache renderer (chevron) does not HTML-escape the pipe character |, a | embedded in such a nested value survives into the rendered command and is then interpreted by secure_popen (which still interprets &&/|/> by default, allow_operators=True), re-introducing the exact command injection the CVE was meant to close.

Details

The fix (glances/actions.py):

_SHELL_OPERATORS = ('&&', '|', '>>', '>')                     # line 25

def _sanitize_mustache_dict(mustache_dict):                   # line 28
    ...
    for k, v in mustache_dict.items():
        if isinstance(v, str):                                # line 40  <-- ONLY top-level strings
            for op in _SHELL_OPERATORS:
                v = v.replace(op, ' ')
            safe[k] = v
        else:
            safe[k] = v                                       # nested list/dict passed VERBATIM
    return safe

Render + sink (glances/actions.py:104-111):

safe_dict = _sanitize_mustache_dict(mustache_dict)
cmd_full  = chevron.render(cmd, safe_dict)                    # chevron does NOT escape '|'
...
ret = secure_popen(cmd_full)                                 # secure_popen(cmd, allow_operators=True)

secure_popen (glances/secure.py:17, default allow_operators=True) splits the command by &&, then __secure_popen interprets | (pipe to a new process) and > (write output to a file). A surviving | therefore launches an attacker-named second process.

The attacker-controlled nested value — cmdline. The action mustache_dict is the per-item plugin stat (glances/plugins/plugin/model.py:931 mustache_dict = item, then :943 self.actions.run(..., mustache_dict=mustache_dict)). For the processlist plugin, each item contains cmdline, a list of the process arguments, set by the attacker simply by launching a process with chosen argv. The sanitizer's isinstance(v, str) test skips the list, so its elements reach chevron.render unmodified.

Why the operator survives render. chevron/Mustache HTML-escapes & < > " ' for {{var}} (so > and && are neutralized) but does not escape |. A pipe in the (unsanitized) nested value therefore reaches secure_popen intact and is interpreted.

Parent-fix attribution (verified against the real diff of commit 5680a5d / CVE-2026-32608): that fix added exactly _SHELL_OPERATORS, _sanitize_mustache_dict, and the _sanitize_mustache_dict(mustache_dict) call — and the sanitizer's docstring explicitly claims to neutralize "user-controllable data (process names, container names, mount points, etc.)". It does so only for top-level strings; the list/dict case (else: safe[k] = v) was left unsanitized. This is therefore a genuine incomplete-fix gap, not a re-report of the patched (top-level string) vector.

Proof of Concept

Lab-only, harmless (touches a marker file; non-destructive). Runs the real glances chain (_sanitize_mustache_dictchevron.rendersecure_popen) — see poc/glances_nested_mustache_poc.py.

Attacker process argv (the only attacker input): cmdline = ['x', '|touch /tmp/glances_poc_marker', '#']. Admin action template (renders the offending process's cmdline): echo ALERT {{#cmdline}}{{.}} {{/cmdline}}.

Observed (confirmed on develop HEAD 92156d0/4.5.6 and verified code-identical on v4.5.5):

cmdline after sanitizer : ['x', '|touch /tmp/glances_poc_marker', '#']   <- pipe survives
cmd_full -> secure_popen : 'echo ALERT x |touch /tmp/glances_poc_marker # '
[VULNERABLE] marker created -> /tmp/glances_poc_marker  (command injection executed)

Replacing touch /tmp/... with any command yields arbitrary execution in the Glances process context.

Preconditions (stated honestly)

  • A configured alert action whose command template renders a nested stat field (e.g. the process cmdline via a {{#cmdline}}…{{/cmdline}} section). Templates that render only flat string fields ({{name}}, {{value}}, {{username}}, {{mnt_point}}) are not affected — those values are sanitized.
  • Glances running with privilege to enumerate the attacker's process (typically root in server/agent monitoring deployments) → privilege boundary crossed (S:C).

Impact

A local unprivileged user gains OS command execution in the Glances security context (commonly root) — the same impact and threat model as the parent CVE-2026-32608, re-enabled for any action template that renders a nested stat field. The injection is reliable once the (admin-set) template references such a field.

Suggested fix

  • Sanitize recursively — apply the operator stripping to strings inside lists and dicts, not only top-level str values.
  • And/or build the templated action as an argument list and run it via secure_popen(..., allow_operators=False) / shell=False without operator interpretation.
  • And/or also strip the pipe | (and treat all _SHELL_OPERATORS) on every rendered string regardless of nesting; do not rely on Mustache HTML-escaping (it does not escape |).

Credit

Ta Duc Thien

Impacted products
Name purl
glances pkg:pypi/glances

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "glances",
        "purl": "pkg:pypi/glances"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.5.2"
            },
            {
              "fixed": "4.5.6"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "4.5.2",
        "4.5.3",
        "4.5.4",
        "4.5.5"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-62982",
    "GHSA-73wf-9vmv-5pv9"
  ],
  "details": "## Summary\nCVE-2026-32608 (\"Command Injection via Process Names in Action Command Templates\") was fixed (commit `5680a5d`) by adding `_sanitize_mustache_dict`, which replaces the shell operators `\u0026\u0026`, `|`, `\u003e\u003e`, `\u003e` with spaces in the values rendered into action command templates.\n\nThe sanitizer only processes **top-level string** values (`if isinstance(v, str)`). Attacker-controlled **nested** values \u2014 most notably a process\u0027s **`cmdline`, which Glances exposes as a `list`** and which is fully attacker-controlled via argv \u2014 are passed through **unsanitized**. Because the Mustache renderer (`chevron`) does **not** HTML-escape the pipe character `|`, a `|` embedded in such a nested value survives into the rendered command and is then interpreted by `secure_popen` (which still interprets `\u0026\u0026`/`|`/`\u003e` by default, `allow_operators=True`), re-introducing the exact command injection the CVE was meant to close.\n\n## Details\nThe fix (`glances/actions.py`):\n```python\n_SHELL_OPERATORS = (\u0027\u0026\u0026\u0027, \u0027|\u0027, \u0027\u003e\u003e\u0027, \u0027\u003e\u0027)                     # line 25\n\ndef _sanitize_mustache_dict(mustache_dict):                   # line 28\n    ...\n    for k, v in mustache_dict.items():\n        if isinstance(v, str):                                # line 40  \u003c-- ONLY top-level strings\n            for op in _SHELL_OPERATORS:\n                v = v.replace(op, \u0027 \u0027)\n            safe[k] = v\n        else:\n            safe[k] = v                                       # nested list/dict passed VERBATIM\n    return safe\n```\nRender + sink (`glances/actions.py:104-111`):\n```python\nsafe_dict = _sanitize_mustache_dict(mustache_dict)\ncmd_full  = chevron.render(cmd, safe_dict)                    # chevron does NOT escape \u0027|\u0027\n...\nret = secure_popen(cmd_full)                                 # secure_popen(cmd, allow_operators=True)\n```\n`secure_popen` (`glances/secure.py:17`, default `allow_operators=True`) splits the command by `\u0026\u0026`, then `__secure_popen` interprets `|` (pipe to a new process) and `\u003e` (write output to a file). A surviving `|` therefore launches an attacker-named second process.\n\n**The attacker-controlled nested value \u2014 `cmdline`.** The action `mustache_dict` is the per-item plugin stat (`glances/plugins/plugin/model.py:931` `mustache_dict = item`, then `:943` `self.actions.run(..., mustache_dict=mustache_dict)`). For the processlist plugin, each `item` contains `cmdline`, a **list** of the process arguments, set by the attacker simply by launching a process with chosen argv. The sanitizer\u0027s `isinstance(v, str)` test skips the list, so its elements reach `chevron.render` unmodified.\n\n**Why the operator survives render.** `chevron`/Mustache HTML-escapes `\u0026 \u003c \u003e \" \u0027` for `{{var}}` (so `\u003e` and `\u0026\u0026` are neutralized) but **does not escape `|`**. A pipe in the (unsanitized) nested value therefore reaches `secure_popen` intact and is interpreted.\n\n**Parent-fix attribution (verified against the real diff of commit `5680a5d` / CVE-2026-32608):** that fix added exactly `_SHELL_OPERATORS`, `_sanitize_mustache_dict`, and the `_sanitize_mustache_dict(mustache_dict)` call \u2014 and the sanitizer\u0027s docstring explicitly claims to neutralize \"user-controllable data (process names, container names, mount points, etc.)\". It does so only for top-level strings; the list/dict case (`else: safe[k] = v`) was left unsanitized. This is therefore a genuine incomplete-fix gap, not a re-report of the patched (top-level string) vector.\n\n## Proof of Concept\nLab-only, harmless (touches a marker file; non-destructive). Runs the real `glances` chain (`_sanitize_mustache_dict` \u2192 `chevron.render` \u2192 `secure_popen`) \u2014 see `poc/glances_nested_mustache_poc.py`.\n\nAttacker process argv (the only attacker input): `cmdline = [\u0027x\u0027, \u0027|touch /tmp/glances_poc_marker\u0027, \u0027#\u0027]`.\nAdmin action template (renders the offending process\u0027s cmdline): `echo ALERT {{#cmdline}}{{.}} {{/cmdline}}`.\n\nObserved (confirmed on develop HEAD `92156d0`/4.5.6 and verified code-identical on v4.5.5):\n```\ncmdline after sanitizer : [\u0027x\u0027, \u0027|touch /tmp/glances_poc_marker\u0027, \u0027#\u0027]   \u003c- pipe survives\ncmd_full -\u003e secure_popen : \u0027echo ALERT x |touch /tmp/glances_poc_marker # \u0027\n[VULNERABLE] marker created -\u003e /tmp/glances_poc_marker  (command injection executed)\n```\nReplacing `touch /tmp/...` with any command yields arbitrary execution in the Glances process context.\n\n## Preconditions (stated honestly)\n- A configured alert **action** whose command template **renders a nested stat field** (e.g. the process `cmdline` via a `{{#cmdline}}\u2026{{/cmdline}}` section). Templates that render only **flat string** fields (`{{name}}`, `{{value}}`, `{{username}}`, `{{mnt_point}}`) are **not** affected \u2014 those values *are* sanitized.\n- Glances running with privilege to enumerate the attacker\u0027s process (typically **root** in server/agent monitoring deployments) \u2192 privilege boundary crossed (`S:C`).\n\n## Impact\nA local unprivileged user gains OS command execution in the Glances security context (commonly root) \u2014 the same impact and threat model as the parent CVE-2026-32608, re-enabled for any action template that renders a nested stat field. The injection is reliable once the (admin-set) template references such a field.\n\n## Suggested fix\n- Sanitize **recursively** \u2014 apply the operator stripping to strings inside lists and dicts, not only top-level `str` values.\n- And/or build the templated action as an argument list and run it via `secure_popen(..., allow_operators=False)` / `shell=False` without operator interpretation.\n- And/or also strip the pipe `|` (and treat all `_SHELL_OPERATORS`) on every rendered string regardless of nesting; do not rely on Mustache HTML-escaping (it does not escape `|`).\n\n## Credit\nTa Duc Thien",
  "id": "PYSEC-2026-3666",
  "modified": "2026-08-19T12:16:24.926132Z",
  "published": "2026-08-19T11:56:26.862162Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/nicolargo/glances/security/advisories/GHSA-73wf-9vmv-5pv9"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nicolargo/glances/commit/ea4cf2f54f0d961e24aa0b24fff9584bab39db93"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/nicolargo/glances"
    },
    {
      "type": "WEB",
      "url": "https://github.com/nicolargo/glances/releases/tag/v4.5.6"
    },
    {
      "type": "PACKAGE",
      "url": "https://pypi.org/project/glances"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-73wf-9vmv-5pv9"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-62982"
    }
  ],
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Glances: Incomplete fix of CVE-2026-32608: action-template sanitizer is bypassed by nested stat values (process \u0027cmdline\u0027) \u2192 OS command injection"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

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.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…