PYSEC-2026-3664
Vulnerability from pysec - Published: 2026-08-19 11:56 - Updated: 2026-08-19 12:16Summary
Glances provides as_dict_secure() explicitly designed for unauthenticated API access, with a docstring stating it returns "a sanitised copy of the configuration dict" where "Sensitive keys in remaining sections are replaced by '**'". However, the implementation only checks KEY names against a regex pattern and never inspects VALUE content. The documented [ip] config section supports public_api (URL), public_username (login), and public_password (password). While public_password is correctly masked, both public_api (when containing embedded credentials like https://user:pass@host/) and public_username are returned in full to unauthenticated users via GET /api/4/config.
Affected Versions
Glances latest (Docker: nicolargo/glances:latest)
Root Cause
In glances/config.py, as_dict_secure():
_SECURE_SENSITIVE_KEY_RE = re.compile(r"password|token|secret|api_key|apikey|ssl_keyfile", re.IGNORECASE)
def as_dict_secure(self):
"""Return a sanitised copy of the configuration dict.
Intended for unauthenticated API access.
- Sensitive keys in remaining sections are replaced by '********'.
"""
sanitized = {}
for section, options in self.as_dict().items():
if section in _SECURE_BLOCKED_SECTIONS: continue
sanitized[section] = {
key: "********" if _SECURE_SENSITIVE_KEY_RE.search(key) else value
for key, value in options.items()
}
return sanitized
In glances/outputs/glances_restful_api.py:
# Line 1294
args_json = self.config.as_dict() if self.args.password else self.config.as_dict_secure()
The [ip] config section documents: public_api (URL), public_username (login), public_password (password).
- public_password → matches "password" → masked ✓
- public_api → no match → returned in full (contains user:pass@ in URL) ✗
- public_username → no match → returned in full ✗
Impact
- Unauthenticated credential disclosure via
GET /api/4/configorGET /api/4/config/ip as_dict_secure()exists specifically to protect credentials in no-auth mode but fails to maskpublic_usernameand credential-bearing URLs inpublic_api
Prerequisites
- Glances in web server mode without
--password(default, no auth) glances.conf[ip]section withpublic_apicontaining embedded credentials and/orpublic_usernameset
Environment
- Glances latest (Docker:
nicolargo/glances:latest) - Remote Docker lab at
http://10.140.200.102:8080
Reproduction Steps
docker run -d --name glances-test -p 8080:61208 -e GLANCES_OPT='-w' nicolargo/glances:latest
sleep 20
docker exec glances-test sed -i 's|public_api=https://ipv4.ipleak.net/json/|public_api=https://admin:secret123@ipv4.ipleak.net/json/|' /etc/glances/glances.conf
docker exec glances-test sed -i 's|#public_username=<myname>|public_username=myname|' /etc/glances/glances.conf
docker exec glances-test sed -i 's|#public_password=<mysecret>|public_password=mysecret|' /etc/glances/glances.conf
docker restart glances-test
sleep 15
curl -s "$TARGET/api/4/config/ip"
# Returns: {"public_api": "https://admin:secret123@...", "public_username": "myname", "public_password": "********"}
Evidence
See C:/Tools/glances-config-leak-evidence.txt.
Dedup Check
- GHSA-gfc2-9qmw-w7vh covers CORS but NOT value-level credential leak
- No existing GHSA covers
as_dict_secure()value-level filtering gap - 13 published GHSA, none covering this issue
Suggested Remediation
Add "username" and "login" to sensitive key pattern, and check values for embedded credentials in URLs.
Disclosure Timeline
- 2026-07-28: Vulnerability discovered and verified via Docker deployment
Reporter
GitHub username: Todor
| Name | purl | glances | pkg:pypi/glances |
|---|
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "glances",
"purl": "pkg:pypi/glances"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.5.6"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"1.3.1",
"1.3.2",
"1.3.3",
"1.3.4",
"1.3.5",
"1.3.6",
"1.3.7",
"1.4",
"1.4.1",
"1.4.1.1",
"1.4.2",
"1.4.2.1",
"1.5",
"1.5.1",
"1.5.2",
"1.6",
"1.6.1",
"1.7",
"1.7.1",
"1.7.2",
"1.7.3",
"1.7.4",
"1.7.5",
"1.7.6",
"1.7.7",
"2.0",
"2.0.1",
"2.1",
"2.1.1",
"2.1.2",
"2.10",
"2.11",
"2.11.1",
"2.2",
"2.2.1",
"2.3",
"2.4",
"2.4.1",
"2.4.2",
"2.5",
"2.5.1",
"2.6",
"2.6.1",
"2.6.2",
"2.7",
"2.7.1",
"2.8",
"2.8.1",
"2.8.2",
"2.8.3",
"2.8.4",
"2.8.5",
"2.8.6",
"2.8.7",
"2.8.8",
"2.9.0",
"2.9.1",
"3.0",
"3.0.1",
"3.0.2",
"3.1.0",
"3.1.1",
"3.1.2",
"3.1.3",
"3.1.4",
"3.1.4.1",
"3.1.5",
"3.1.6",
"3.1.6.1",
"3.1.6.2",
"3.1.7",
"3.2.0",
"3.2.1",
"3.2.2",
"3.2.3",
"3.2.3.1",
"3.2.4",
"3.2.4.1",
"3.2.4.2",
"3.2.5",
"3.2.6.1",
"3.2.6.2",
"3.2.6.3",
"3.2.6.4",
"3.2.7",
"3.3.0",
"3.3.0.1",
"3.3.0.2",
"3.3.0.3",
"3.3.0.4",
"3.3.1",
"3.3.1.1",
"3.4.0",
"3.4.0.1",
"3.4.0.2",
"3.4.0.3",
"3.4.0.4",
"3.4.0.5",
"4.0.1",
"4.0.2",
"4.0.3",
"4.0.4",
"4.0.5",
"4.0.6",
"4.0.7",
"4.0.8",
"4.1.0",
"4.1.1",
"4.1.2",
"4.2.0",
"4.2.1",
"4.3.0",
"4.3.0.1",
"4.3.0.3",
"4.3.0.4",
"4.3.0.5",
"4.3.0.6",
"4.3.0.7",
"4.3.0.8",
"4.3.1",
"4.3.2",
"4.3.3",
"4.4.0",
"4.4.1",
"4.5.0",
"4.5.0.1",
"4.5.0.2",
"4.5.0.3",
"4.5.0.4",
"4.5.0.5",
"4.5.1",
"4.5.2",
"4.5.3",
"4.5.4",
"4.5.5"
]
}
],
"aliases": [
"CVE-2026-68520",
"GHSA-4h34-v6r8-mmjc"
],
"details": "## Summary\n\nGlances provides `as_dict_secure()` explicitly designed for unauthenticated API access, with a docstring stating it returns \"a sanitised copy of the configuration dict\" where \"Sensitive keys in remaining sections are replaced by \u0027********\u0027\". However, the implementation only checks KEY names against a regex pattern and never inspects VALUE content. The documented `[ip]` config section supports `public_api` (URL), `public_username` (login), and `public_password` (password). While `public_password` is correctly masked, both `public_api` (when containing embedded credentials like `https://user:pass@host/`) and `public_username` are returned in full to unauthenticated users via `GET /api/4/config`.\n\n## Affected Versions\n\nGlances latest (Docker: `nicolargo/glances:latest`)\n\n## Root Cause\n\nIn `glances/config.py`, `as_dict_secure()`:\n```python\n_SECURE_SENSITIVE_KEY_RE = re.compile(r\"password|token|secret|api_key|apikey|ssl_keyfile\", re.IGNORECASE)\n\ndef as_dict_secure(self):\n \"\"\"Return a sanitised copy of the configuration dict.\n Intended for unauthenticated API access.\n - Sensitive keys in remaining sections are replaced by \u0027********\u0027.\n \"\"\"\n sanitized = {}\n for section, options in self.as_dict().items():\n if section in _SECURE_BLOCKED_SECTIONS: continue\n sanitized[section] = {\n key: \"********\" if _SECURE_SENSITIVE_KEY_RE.search(key) else value\n for key, value in options.items()\n }\n return sanitized\n```\n\nIn `glances/outputs/glances_restful_api.py`:\n```python\n# Line 1294\nargs_json = self.config.as_dict() if self.args.password else self.config.as_dict_secure()\n```\n\nThe `[ip]` config section documents: `public_api` (URL), `public_username` (login), `public_password` (password).\n- `public_password` \u2192 matches \"password\" \u2192 masked \u2713\n- `public_api` \u2192 no match \u2192 returned in full (contains `user:pass@` in URL) \u2717\n- `public_username` \u2192 no match \u2192 returned in full \u2717\n\n## Impact\n\n- Unauthenticated credential disclosure via `GET /api/4/config` or `GET /api/4/config/ip`\n- `as_dict_secure()` exists specifically to protect credentials in no-auth mode but fails to mask `public_username` and credential-bearing URLs in `public_api`\n\n## Prerequisites\n\n- Glances in web server mode without `--password` (default, no auth)\n- `glances.conf` `[ip]` section with `public_api` containing embedded credentials and/or `public_username` set\n\n## Environment\n\n- Glances latest (Docker: `nicolargo/glances:latest`)\n- Remote Docker lab at `http://10.140.200.102:8080`\n\n## Reproduction Steps\n\n```bash\ndocker run -d --name glances-test -p 8080:61208 -e GLANCES_OPT=\u0027-w\u0027 nicolargo/glances:latest\nsleep 20\ndocker exec glances-test sed -i \u0027s|public_api=https://ipv4.ipleak.net/json/|public_api=https://admin:secret123@ipv4.ipleak.net/json/|\u0027 /etc/glances/glances.conf\ndocker exec glances-test sed -i \u0027s|#public_username=\u003cmyname\u003e|public_username=myname|\u0027 /etc/glances/glances.conf\ndocker exec glances-test sed -i \u0027s|#public_password=\u003cmysecret\u003e|public_password=mysecret|\u0027 /etc/glances/glances.conf\ndocker restart glances-test\nsleep 15\ncurl -s \"$TARGET/api/4/config/ip\"\n# Returns: {\"public_api\": \"https://admin:secret123@...\", \"public_username\": \"myname\", \"public_password\": \"********\"}\n```\n\n## Evidence\n\nSee `C:/Tools/glances-config-leak-evidence.txt`.\n\n## Dedup Check\n\n- GHSA-gfc2-9qmw-w7vh covers CORS but NOT value-level credential leak\n- No existing GHSA covers `as_dict_secure()` value-level filtering gap\n- 13 published GHSA, none covering this issue\n\n## Suggested Remediation\n\nAdd \"username\" and \"login\" to sensitive key pattern, and check values for embedded credentials in URLs.\n\n## Disclosure Timeline\n\n- 2026-07-28: Vulnerability discovered and verified via Docker deployment\n\n## Reporter\n\nGitHub username: Todor",
"id": "PYSEC-2026-3664",
"modified": "2026-08-19T12:16:24.791053Z",
"published": "2026-08-19T11:56:26.971395Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/security/advisories/GHSA-4h34-v6r8-mmjc"
},
{
"type": "WEB",
"url": "https://github.com/nicolargo/glances/commit/8d0f8276c2abd2e9d400bd6c84bdfba0dfcab065"
},
{
"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-4h34-v6r8-mmjc"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-68520"
}
],
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "Glances: as_dict_secure() Value-Level Bypass Leaks Credentials in URL Values via /api/4/config"
}
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.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.