GitHub Security Advisory: GHSA-49g7-2ww7-3vf5
Package: Glances (PIP)
Severity: HIGH (CVSS 7.0)
CWE: CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
Vulnerable versions: < 4.5.2
Patched in: 4.5.2

Summary: Glances has a SQL Injection in DuckDB Export via Unparameterized DDL Statements

Details:
## Summary

The GHSA-x46r fix (commit 39161f0) addressed SQL injection in the TimescaleDB export module by converting all SQL operations to use parameterized queries and `psycopg.sql` composable objects. However, the DuckDB export module (`glances/exports/glances_duckdb/__init__.py`) was not included in this fix and contains the same class of vulnerability: table names and column names derived from monitoring statistics are directly interpolated into SQL statements via f-strings. While DuckDB INSERT values already use parameterized queries (`?` placeholders), the DDL construction and table name references do not escape or parameterize identifier names.

## Details

The DuckDB export module constructs SQL DDL statements by directly interpolating stat field names and plugin names into f-strings.

**Vulnerable CREATE TABLE construction** (`glances/exports/glances_duckdb/__init__.py:156-162`):

```python
create_query = f"""
CREATE TABLE {plugin} (
{', '.join(creation_list)}
);"""
self.client.execute(create_query)
```

The `creation_list` is built from stat dictionary keys in the `update()` method (`glances/exports/glances_duckdb/__init__.py:117-118`):

```python
for key, value in plugin_stats.items():
    creation_list.append(f"{key} {convert_types[type(self.normalize(value)).__name__]}")
```

The INSERT statement also uses the unescaped `plugin` name (`glances/exports/glances_duckdb/__init__.py:172-174`):

```python
insert_query = f"""
INSERT INTO {plugin} VALUES (
{', '.join(['?' for _ in values])}
);"""
```

While INSERT values use `?` placeholders (safe), the table name `{plugin}` is directly interpolated in both CREATE TABLE and INSERT INTO statements. Column names in creation_list are also directly interpolated without quoting.

**Comparison with the TimescaleDB fix (commit 39161f0):**

The TimescaleDB fix addressed this exact pattern by:
1. Using `psycopg.sql.Identifier()` for table and column names
2. Using `psycopg.sql.SQL()` for composing queries
3. Using `%s` placeholders for all values

The DuckDB module was not part of this fix despite having the same vulnerability class.

**Attack vector:**

The primary attack vector is through stat dictionary keys. While most keys come from hardcoded psutil field names (e.g., `cpu_percent`, `memory_usage`), any future plugin that introduces dynamic keys from external data (container labels, custom metrics, user-defined sensor names) would create an exploitable injection path. Additionally, the table name (`plugin`) comes from the internal plugins list, but any custom plugin with a crafted name could inject SQL.

## PoC

The injection is demonstrable when column or table names contain SQL metacharacters:

```python
# Simulated injection via a hypothetical plugin with dynamic keys
# If a stat dict contained a key like:
#   "cpu_percent BIGINT); DROP TABLE cpu; --"
# The creation_list would produce:
#   "cpu_percent BIGINT); DROP TABLE cpu; -- VARCHAR"
# Which in the CREATE TABLE f-string becomes:
#   CRE