Summary
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.
Details
Dynamic server discovery keeps both a short name and a separate ip:
# glances/servers_list_dynamic.py:56-61
def add_server(self, name, ip, port, protocol='rpc'):
new_server = {
'key': name,
'name': name.split(':')[0], # Short name
'ip': ip, # IP address seen by the client
'port': port,
...
'type': 'DYNAMIC',
}
The Zeroconf listener populates those fields directly from the service advertisement:
# glances/servers_list_dynamic.py:112-121
new_server_ip = socket.inet_ntoa(address)
new_server_port = info.port
...
self.servers.add_server(
srv_name,
new_server_ip,
new_server_port,
protocol=new_server_protocol,
)
However, the Central Browser connection logic ignores server['ip'] and instead uses the untrusted advertised server['name'] for both password lookup and the destination URI:
# glances/servers_list.py:119-130
def get_uri(self, server):
if server['password'] != "":
if server['status'] == 'PROTECTED':
clear_password = self.password.get_password(server['name'])
if clear_password is not None:
server['password'] = self.password.get_hash(clear_password)
uri = 'http://{}:{}@{}:{}'.format(
server['username'],
server['password'],
server['name'],
server['port'],
)
else:
uri = 'http://{}:{}'.format(server['name'], server['port'])
return uri