Описание
Glances has a command injection bypass of action-template sanitizer via cross-field shell-operator reconstruction
Summary
The Glances action system lets an administrator configure shell commands that run
when a monitoring threshold is crossed. The command is a Mustache template whose
variables are filled with runtime stat fields such as a process name, a container
name or a filesystem mount point. Those fields are attacker-influenceable: a
local, unprivileged user who starts a process (or a container) controls its name
and command line. The rendered command is executed by secure_popen(), which
interprets &&, | and > as chaining / pipe / redirection operators.
glances/actions.py defends against this with _sanitize_mustache_dict(), which
strips those operators from each individual template value before rendering.
The sanitization is applied per field, but the operators are reconstructed
across the boundary of two adjacent template variables after Mustache
rendering. When an action template concatenates two unescaped variables
({{{a}}}{{{b}}} or {{&a}}{{&b}}) and the attacker makes the first value end
with & and the second begin with &, the rendered command contains a real
&&, and secure_popen() executes the injected command. The single-& in each
value passes the per-field filter untouched.
Affected versions
glances <= 4.5.5 (verified against the published PyPI release 4.5.5, the
latest at the time of writing; glances.__version__ == "4.5.5"). The
per-field sanitizer _sanitize_mustache_dict() is present and active in this
release. Not patched in any released version.
Privilege required
Two roles are involved:
- A local, unprivileged user (or a container the attacker can name) supplies the attacker-controlled stat values (process/container name, mount point, etc.). This is the same trust boundary the action-template command-injection class already recognises: the attacker controls the process name, not the configuration.
- An administrator has configured an action whose command template concatenates
two unescaped Mustache variables with no separating character
(
{{{name}}}{{{cmdline}}}). Unescaped Mustache ({{{ }}}/{{& }}) is a documented Chevron feature and is the natural choice when the operator wants a value that contains shell-significant characters to reach the command verbatim.
No network access to the target host is required beyond the ability to run a process (or start a named container) on it.
Vulnerable code (file:line)
glances/actions.py:25-46 — the per-field sanitizer:
glances/actions.py:100-111 — sanitize-then-render-then-execute:
Root cause
_sanitize_mustache_dict() removes &&, |, >>, > from each value in
isolation. It does not remove a lone &, because a single & is not one of the
listed operators. When two values are rendered next to each other by
chevron.render(), a trailing & from the first value and a leading & from the
second value join into a literal && in cmd_full. secure_popen() then
cmd.split('&&') and runs the second half as a separate subprocess.Popen
(shell=False) process. The same reconstruction works for > written as two
adjacent > characters split across the boundary (...> + >... and the
>>/> stripping is per field), and the sanitizer's own choice to sanitize
before, rather than after, rendering is the defect.
This is an incomplete fix of the action-template command-injection issue
(CVE-2026-32608 / GHSA-vcv2-q258-wrg7): _sanitize_mustache_dict() closes the
single-field case but not the cross-field-reconstruction case. The correct place
to enforce the operator ban is on the fully rendered command string (or by never
letting template-variable data introduce operators), not on the pre-render values
one at a time.
Chevron HTML-escapes &, <, >, " inside standard double-brace {{ }}
sections, so double-brace templates neutralise the && reconstruction. The
reconstruction is reachable specifically through unescaped variables
({{{ }}} / {{& }}), which is why the per-field sanitizer is the sole
remaining control on that path.
Reachability / How input reaches sink
- A local unprivileged user starts a process (or a container) whose
nameends with&and whosecmdlinebegins with& <command>(both are stored verbatim in the plugin stat item). - When the plugin crosses a
warning/criticalthreshold,glances/plugins/plugin/model.pycallsself.actions.runwith the full stat item passed as themustache_dictargument. GlancesActions.runsanitizes each value with_sanitize_mustache_dict()(each keeps its single&), thenchevron.render()concatenates the two adjacent unescaped variables, producing a literal&&in the command string.secure_popen(cmd_full)splits on&&and runs the attacker's segment as a separatesubprocess.Popen(shell=False)process.
The trust boundary crossed is process-name / container-name → shell operator, exactly the boundary the sanitizer was introduced to close.
Reproduction (end-to-end, against pinned version glances==4.5.5)
repro.py:
Captured output (glances 4.5.5, Python 3.13, x86_64 Linux):
The negative control confirms that the same attacker values under a standard
double-brace template are blocked (chevron escapes &). The positive case shows
the injected touch executing when the template uses two adjacent unescaped
variables: the file /tmp/glances_crossfield_pwned is created by the injected
command, not by the intended logger action.
Impact
- Arbitrary command execution as the OS user running Glances (frequently root on monitored hosts) whenever an operator uses an unescaped, adjacent-variable action template and an attacker controls two neighbouring stat fields.
- The same reconstruction reaches
secure_popen()'s file-redirection (>) and pipe (|) handling, allowing arbitrary file write and output piping in addition to command chaining. - The bypass defeats the dedicated
_sanitize_mustache_dict()control that was added specifically to stop attacker-controlled stat values from injecting shell operators.
Suggested fix
Enforce the operator ban on the rendered command string that comes from
template-variable expansion, rather than on the pre-render values in isolation.
One approach that mirrors the existing helper: render each variable, then reject /
neutralise operators in the concatenated result, or strip lone &/redirection
characters that originate from variable data.
Neutralising the single & (and the single > / |) in each value removes the
cross-field reconstruction because no operator character survives on either side
of a variable boundary. Alternatively, sanitize cmd_full after
chevron.render(), or pass the template-derived data as secure_popen(..., allow_operators=False) when the command originates from stat-field substitution.
Credit
Reported by tonghuaroot.
Пакеты
glances
<= 4.5.5
4.5.6
Связанные уязвимости
Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, _sanitize_mustache_dict() in glances/actions.py sanitizes individual Mustache values before chevron.render(), allowing adjacent unescaped Mustache variables to reconstruct shell operators that secure_popen() executes when attacker-controlled process or container fields are rendered by an administrator-configured action template. This issue is fixed in 4.5.6.
Glances is an open-source system cross-platform monitoring tool. Prior to 4.5.6, _sanitize_mustache_dict() in glances/actions.py sanitizes individual Mustache values before chevron.render(), allowing adjacent unescaped Mustache variables to reconstruct shell operators that secure_popen() executes when attacker-controlled process or container fields are rendered by an administrator-configured action template. This issue is fixed in 4.5.6.
Glances is an open-source system cross-platform monitoring tool. Prior ...