PYSEC-2026-3664

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

Summary

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/config or GET /api/4/config/ip
  • 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

Prerequisites

  • Glances in web server mode without --password (default, no auth)
  • glances.conf [ip] section with public_api containing embedded credentials and/or public_username set

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

Impacted products
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"
}



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…