str.format(): 36 ISP and hosting footholds from a public looking glasshyperglass is a looking-glass front end. You give it a target, it drops that target into a router command and hands the result to Netmiko. The target goes in with a bare str.format() and is never shell-escaped. On any platform where the command ends up in a Linux shell, the target stops being data.
A sweep of PeeringDB and Shodan for exposed hyperglass instances produced 36 hosts with command execution across 20 ASNs, 11 of them root or root-equivalent. Almost all are collectors and edge VMs, not core forwarding routers. None is a whole-AS compromise — but a lot of this is real infrastructure with reusable credentials behind it.
# hyperglass/execution/drivers/_construct.py:113
return command.format(target=self.target, mask=mask, **attrs)
The command templates look like this:
vtysh -c "show bgp ipv4 unicast regexp {target}"
The AS-path and community rules validate with a wildcard, so almost any string passes. Netmiko ships it to linux_ssh, and bash expands it.
Three ways it lands:
1. FRR AS-path injection. A quote closes the intended argument and $() or ; runs in the shell. Unauthenticated, this returns uid=1001(hyperglass) on Pombonet.
2. BIRD IPv6 scope-ID injection. Python's ipaddress accepts arbitrary text after the % of an IPv6 address, so fe80::1%;id passes validation as a scoped address. The original string is then inserted unquoted into ping -6 ... {target}, and bash reads the semicolon as a separator. Unauthenticated, this returns uid=1000(hyperglass) on SolNet.
3. Cisco IOS newline injection. An internal newline survives validation and the unquoted BGP community command turns into two CLI lines. The second line goes to the router inside the already-authenticated SSH session, so 1.1.1.1\nclear ip bgp * shows the route and then clears BGP. This one is proven against an IOS emulator, not a live public target.
A second, independent reproduction against a clean revision pointing at a real OpenSSH shell returned the service UID and created a file via touch /tmp/pwned_hg. It is tracked upstream in #383 / PR #382, and has been open on the public repository for four months.
| Asset | ASN / org | Access | Damage |
|---|---|---|---|
lg.biztelecom.ro / 86.105.231.35 | 49410 BizTelecom | root on a PVE cluster node, Ceph admin, PBS | Control all 42 guests, read and clone disks, pull backups |
117.20.63.181 Citra rs-core | 23951 Citra | shell, frrvty, 27 iBGP peers, 2.04M routes | Route injection and blackholing on a core route reflector |
lg.as60900.net / 170.39.49.97 | 60900 Ssmidge | root, WireGuard keys, Cloudflare tunnel, peer creds | Host control, out-of-band tunnel, route and proxy abuse |
23.132.28.204 camtr0 | 60900 Ssmidge | root, pathvector router | Alter paths toward K-root/AS25152, Virtuo and Lusory |
lg.netecom.com.br / 177.86.157.250 | 263324 Netecom | root, FRR full table, routinator, Redis | Route injection, RPKI poisoning, persistence |
lg.solnet.ch | 9044 SolNet | shell, birdc, 16 sessions, 2.96M routes | Full route-reflector control |
143.208.23.46 AMO | 264075 AMO | lg, NOPASSWD birdc | BIRD route control, credential reuse |
| Asset | ASN / org | Access | Damage |
|---|---|---|---|
lg.lcitelecom.com.br | 28213 LCI | hyperglass-lg, sudo + frrvty, key | FRR config write, 100–200G ISP |
lg.maisinternet.net.br | 61900 Mais | LXD root, frrvty | Root via container, 1.1M routes |
112.78.33.190 Panama | 55666 GMEDIA | root | BIRD control, upstream Huawei path |
37.208.104.82 uvensys | 58010 uvensys | lg, FRR/BGP passwords, key | Route control, MySQL and Redis |
lg.certto.com.br | 28130 Certto | vty_certto, frrvty, Huawei/VyOS creds | Route control, credential chain into the core |
lg.pombonet.net.br | 262988 Pombonet | hyperglass, frrvty, enable password | 3.6M routes, Huawei iBGP |
lg.bird.larus.net / 154.87.0.21 | 17561 Larus | paulw, sudo group, birdc, 22 peers | Route control |
lg.as212783.net | 212783 | lg, frrvty, BGP passwords, SNMPv3 | Four-router fabric |
lg.ziesel.network | ziesel | PVE host, 32 vCPU, 377 GiB, birdc sudo | Large compute, route control |
lg.bgp.rodeo | bgp.rodeo | 32-core bare metal, bird group | Route server, history data |
lg.atomic.co.za | 396982 path | frr/frrvty, 8 vCPU | South African edge |
lg.linqtelecom.com.br | LINQ | frrvty, 8 vCPU | Brazilian ISP edge |
lg.lsti.net (root, BGP MD5s), lg.suble.io (root, NOPASSWD), lg.sky.wf (root, 166 peers), lg.lumaserv.com (root), 45.165.244.14 (root), lg.6project.org (root), lg.jmjropc.com (birdc, unauthenticated Caddy API), lg.lge.isp.as210349.net (3.2M routes), lg.allnodes.com, 24it.pl, lg.flokinet.is, 191.5.112.142.
194.110.61.121 routing.info, with key access to seven collectors (Arelion, Cogent, DTAG, DE-CIX, AMS-IX, BCIX, Iraq), full tables and daily MRT. Plus Nebula, Nexthop, PhoenixNet and Clientco.
Critical — both.
Eleven root or root-equivalent hosts, 20 ASNs with writable routing state, and a Proxmox cluster with 42 guests and its backups all sit behind a single unauthenticated endpoint. The access is credential-based, survives reboots, and stays quiet. Nothing gets rotated unless someone notices, so the footholds remain live and reusable indefinitely.
The only thing capping this below a national outage is that the hosts are collectors and edge nodes, not forwarding cores. Everything above that — host control, backup access, route injection, blackholing, session resets, RPKI poisoning — is in scope and chained across dozens of independent networks.
Urgency: act now. One unauthenticated POST reaches all of it. Rotate what can be rotated and rebuild what cannot, before the credentials age out or quietly get shared.
The list above is a floor, not a ceiling. 388 live instances turned up from a few thousand URLs, and only the reachable ones were followed up. The same three input paths exist on every unpatched deployment, so the real number of pwnable targets is almost certainly far higher. It has to be patched as soon as possible.
The bug is boring: one unescaped format call, a wildcard validator, and a shell underneath. The result is not. hyperglass is common, the directive ships built in, and /api/query is unauthenticated by default. Swap the target for $(id) and a looking glass becomes a shell.
This research was autonomously found by a swarm of agents running DeepSeek V4.1 Flash.