GHSA-CVWP-R2G2-J824
Vulnerability from github – Published: 2026-03-16 16:26 – Updated: 2026-03-19 21:06Summary
The GHSA-gh4x fix (commit 5d3de60) addressed unauthenticated configuration secrets exposure on the /api/v4/config endpoints by introducing as_dict_secure() redaction. However, the /api/v4/args and /api/v4/args/{item} endpoints were not addressed by this fix. These endpoints return the complete command-line arguments namespace via vars(self.args), which includes the password hash (salt + pbkdf2_hmac), SNMP community strings, SNMP authentication keys, and the configuration file path. When Glances runs without --password (the default), these endpoints are accessible without any authentication.
Details
The secrets exposure fix (GHSA-gh4x, commit 5d3de60) modified three config-related endpoints to use as_dict_secure() when no password is configured:
# glances/outputs/glances_restful_api.py:1168 (FIXED)
args_json = self.config.as_dict() if self.args.password else self.config.as_dict_secure()
However, the _api_args and _api_args_item endpoints were not part of this fix and still return all arguments without any sanitization:
# glances/outputs/glances_restful_api.py:1222-1237
def _api_args(self):
try:
# Get the RAW value of the args dict
# Use vars to convert namespace to dict
args_json = vars(self.args)
except Exception as e:
raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get args ({str(e)})")
return GlancesJSONResponse(args_json)
And the item-specific endpoint:
# glances/outputs/glances_restful_api.py:1239-1258
def _api_args_item(self, item: str):
...
args_json = vars(self.args)[item]
return GlancesJSONResponse(args_json)
The self.args namespace contains sensitive fields set during initialization in glances/main.py:
-
password(line 806-819): When--passwordis used, this contains the salt + pbkdf2_hmac hash. An attacker can use this for offline brute-force attacks. -
snmp_community(line 445): Default"public", but may be set to a secret community string for SNMP monitoring. -
snmp_user(line 448): SNMP v3 username, default"private". -
snmp_auth(line 450): SNMP v3 authentication key, default"password"but typically set to a secret value. -
conf_file(line 198): Path to the configuration file, reveals filesystem structure. -
username(line 430/800): The Glances authentication username.
Both endpoints are registered on the authenticated router (line 504-505):
f'{base_path}/args': self._api_args,
f'{base_path}/args/{{item}}': self._api_args_item,
When --password is not set (the default), the router has NO authentication dependency (line 479-480), making these endpoints completely unauthenticated:
if self.args.password:
router = APIRouter(prefix=self.url_prefix, dependencies=[Depends(self.authentication)])
else:
router = APIRouter(prefix=self.url_prefix)
PoC
Scenario 1: No password configured (default deployment)
# Start Glances in web server mode (default, no password)
glances -w
# Access all command line arguments without authentication
curl -s http://localhost:61208/api/4/args | python -m json.tool
# Expected output includes sensitive fields:
# "password": "",
# "snmp_community": "public",
# "snmp_user": "private",
# "snmp_auth": "password",
# "username": "glances",
# "conf_file": "/home/user/.config/glances/glances.conf",
# Access specific sensitive argument
curl -s http://localhost:61208/api/4/args/snmp_community
curl -s http://localhost:61208/api/4/args/snmp_auth
Scenario 2: Password configured (authenticated deployment)
# Start Glances with password authentication
glances -w --password --username admin
# Authenticate and access args (password hash exposed to authenticated users)
curl -s -u admin:mypassword http://localhost:61208/api/4/args/password
# Returns the salt$pbkdf2_hmac hash which enables offline brute-force
Impact
-
Unauthenticated network reconnaissance: When Glances runs without
--password(the common default for internal/trusted networks), anyone who can reach the web server can enumerate SNMP credentials, usernames, file paths, and all runtime configuration. -
Offline password cracking: When authentication is enabled, an authenticated user can retrieve the password hash (salt + pbkdf2_hmac) and perform offline brute-force attacks. The hash uses pbkdf2_hmac with SHA-256 and 100,000 iterations (see
glances/password.py:45), which provides some protection but is still crackable with modern hardware. -
Lateral movement: Exposed SNMP community strings and v3 authentication keys can be used to access other network devices monitored by the Glances instance.
-
Supply chain for CORS attack: Combined with the default CORS misconfiguration (finding 001), these secrets can be stolen cross-origin by a malicious website.
Recommended Fix
Apply the same redaction pattern used for the /api/v4/config endpoints:
# glances/outputs/glances_restful_api.py
_SENSITIVE_ARGS = frozenset({
'password', 'snmp_community', 'snmp_user', 'snmp_auth',
'conf_file', 'password_prompt', 'username_used',
})
def _api_args(self):
try:
args_json = vars(self.args).copy()
if not self.args.password:
for key in _SENSITIVE_ARGS:
if key in args_json:
args_json[key] = "********"
# Never expose the password hash, even to authenticated users
if 'password' in args_json and args_json['password']:
args_json['password'] = "********"
except Exception as e:
raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get args ({str(e)})")
return GlancesJSONResponse(args_json)
def _api_args_item(self, item: str):
if item not in self.args:
raise HTTPException(status.HTTP_400_BAD_REQUEST, f"Unknown argument item {item}")
try:
if item in _SENSITIVE_ARGS:
if not self.args.password:
return GlancesJSONResponse("********")
if item == 'password':
return GlancesJSONResponse("********")
args_json = vars(self.args)[item]
except Exception as e:
raise HTTPException(status.HTTP_404_NOT_FOUND, f"Cannot get args item ({str(e)})")
return GlancesJSONResponse(args_json)
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "Glances"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.5.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32609"
],
"database_specific": {
"cwe_ids": [
"CWE-200"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-16T16:26:54Z",
"nvd_published_at": "2026-03-18T15:16:30Z",
"severity": "HIGH"
},
"details": "## Summary\n\nThe GHSA-gh4x fix (commit 5d3de60) addressed unauthenticated configuration secrets exposure on the `/api/v4/config` endpoints by introducing `as_dict_secure()` redaction. However, the `/api/v4/args` and `/api/v4/args/{item}` endpoints were not addressed by this fix. These endpoints return the complete command-line arguments namespace via `vars(self.args)`, which includes the password hash (salt + pbkdf2_hmac), SNMP community strings, SNMP authentication keys, and the configuration file path. When Glances runs without `--password` (the default), these endpoints are accessible without any authentication.\n\n## Details\n\nThe secrets exposure fix (GHSA-gh4x, commit 5d3de60) modified three config-related endpoints to use `as_dict_secure()` when no password is configured:\n\n```python\n# glances/outputs/glances_restful_api.py:1168 (FIXED)\nargs_json = self.config.as_dict() if self.args.password else self.config.as_dict_secure()\n```\n\nHowever, the `_api_args` and `_api_args_item` endpoints were not part of this fix and still return all arguments without any sanitization:\n\n```python\n# glances/outputs/glances_restful_api.py:1222-1237\ndef _api_args(self):\n try:\n # Get the RAW value of the args dict\n # Use vars to convert namespace to dict\n args_json = vars(self.args)\n except Exception as e:\n raise HTTPException(status.HTTP_404_NOT_FOUND, f\"Cannot get args ({str(e)})\")\n\n return GlancesJSONResponse(args_json)\n```\n\nAnd the item-specific endpoint:\n\n```python\n# glances/outputs/glances_restful_api.py:1239-1258\ndef _api_args_item(self, item: str):\n ...\n args_json = vars(self.args)[item]\n return GlancesJSONResponse(args_json)\n```\n\nThe `self.args` namespace contains sensitive fields set during initialization in `glances/main.py`:\n\n1. **`password`** (line 806-819): When `--password` is used, this contains the salt + pbkdf2_hmac hash. An attacker can use this for offline brute-force attacks.\n\n2. **`snmp_community`** (line 445): Default `\"public\"`, but may be set to a secret community string for SNMP monitoring.\n\n3. **`snmp_user`** (line 448): SNMP v3 username, default `\"private\"`.\n\n4. **`snmp_auth`** (line 450): SNMP v3 authentication key, default `\"password\"` but typically set to a secret value.\n\n5. **`conf_file`** (line 198): Path to the configuration file, reveals filesystem structure.\n\n6. **`username`** (line 430/800): The Glances authentication username.\n\nBoth endpoints are registered on the authenticated router (line 504-505):\n```python\nf\u0027{base_path}/args\u0027: self._api_args,\nf\u0027{base_path}/args/{{item}}\u0027: self._api_args_item,\n```\n\nWhen `--password` is not set (the default), the router has NO authentication dependency (line 479-480), making these endpoints completely unauthenticated:\n```python\nif self.args.password:\n router = APIRouter(prefix=self.url_prefix, dependencies=[Depends(self.authentication)])\nelse:\n router = APIRouter(prefix=self.url_prefix)\n```\n\n## PoC\n\n**Scenario 1: No password configured (default deployment)**\n\n```bash\n# Start Glances in web server mode (default, no password)\nglances -w\n\n# Access all command line arguments without authentication\ncurl -s http://localhost:61208/api/4/args | python -m json.tool\n\n# Expected output includes sensitive fields:\n# \"password\": \"\",\n# \"snmp_community\": \"public\",\n# \"snmp_user\": \"private\",\n# \"snmp_auth\": \"password\",\n# \"username\": \"glances\",\n# \"conf_file\": \"/home/user/.config/glances/glances.conf\",\n\n# Access specific sensitive argument\ncurl -s http://localhost:61208/api/4/args/snmp_community\ncurl -s http://localhost:61208/api/4/args/snmp_auth\n```\n\n**Scenario 2: Password configured (authenticated deployment)**\n\n```bash\n# Start Glances with password authentication\nglances -w --password --username admin\n\n# Authenticate and access args (password hash exposed to authenticated users)\ncurl -s -u admin:mypassword http://localhost:61208/api/4/args/password\n# Returns the salt$pbkdf2_hmac hash which enables offline brute-force\n```\n\n## Impact\n\n- **Unauthenticated network reconnaissance:** When Glances runs without `--password` (the common default for internal/trusted networks), anyone who can reach the web server can enumerate SNMP credentials, usernames, file paths, and all runtime configuration.\n\n- **Offline password cracking:** When authentication is enabled, an authenticated user can retrieve the password hash (salt + pbkdf2_hmac) and perform offline brute-force attacks. The hash uses pbkdf2_hmac with SHA-256 and 100,000 iterations (see `glances/password.py:45`), which provides some protection but is still crackable with modern hardware.\n\n- **Lateral movement:** Exposed SNMP community strings and v3 authentication keys can be used to access other network devices monitored by the Glances instance.\n\n- **Supply chain for CORS attack:** Combined with the default CORS misconfiguration (finding 001), these secrets can be stolen cross-origin by a malicious website.\n\n## Recommended Fix\n\nApply the same redaction pattern used for the `/api/v4/config` endpoints:\n\n```python\n# glances/outputs/glances_restful_api.py\n\n_SENSITIVE_ARGS = frozenset({\n \u0027password\u0027, \u0027snmp_community\u0027, \u0027snmp_user\u0027, \u0027snmp_auth\u0027,\n \u0027conf_file\u0027, \u0027password_prompt\u0027, \u0027username_used\u0027,\n})\n\ndef _api_args(self):\n try:\n args_json = vars(self.args).copy()\n if not self.args.password:\n for key in _SENSITIVE_ARGS:\n if key in args_json:\n args_json[key] = \"********\"\n # Never expose the password hash, even to authenticated users\n if \u0027password\u0027 in args_json and args_json[\u0027password\u0027]:\n args_json[\u0027password\u0027] = \"********\"\n except Exception as e:\n raise HTTPException(status.HTTP_404_NOT_FOUND, f\"Cannot get args ({str(e)})\")\n return GlancesJSONResponse(args_json)\n\ndef _api_args_item(self, item: str):\n if item not in self.args:\n raise HTTPException(status.HTTP_400_BAD_REQUEST, f\"Unknown argument item {item}\")\n try:\n if item in _SENSITIVE_ARGS:\n if not self.args.password:\n return GlancesJSONResponse(\"********\")\n if item == \u0027password\u0027:\n return GlancesJSONResponse(\"********\")\n args_json = vars(self.args)[item]\n except Exception as e:\n raise HTTPException(status.HTTP_404_NOT_FOUND, f\"Cannot get args item ({str(e)})\")\n return GlancesJSONResponse(args_json)\n```",
"id": "GHSA-cvwp-r2g2-j824",
"modified": "2026-03-19T21:06:22Z",
"published": "2026-03-16T16:26:54Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/security/advisories/GHSA-cvwp-r2g2-j824"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32609"
},
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/commit/ff14eb9780ee10ec018c754754b1c8c7bfb6c44f"
},
{
"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:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Glances has Incomplete Secrets Redaction: /api/v4/args Endpoint Leaks Password Hash and SNMP Credentials"
}
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.