The BGP Hijack That Rooted Virtualizor Hypervisors in 33 Hours
AS62390 hijacked a Hetzner /24 for 33 hours, used the diversion to mint a real Let's Encrypt certificate, and pushed a backdoored Virtualizor update straight to hypervisor root. Here's how the routing data proves it, step by step.
162.55.80.0/24 had never once been announced by anyone other than Hetzner. Then, at 20:57:30 UTC on August 28, 2026, a network called AS62390 started announcing it too — and every router on the planet that saw both routes chose the impostor's, because it was more specific. Nobody had to be tricked. Nobody clicked anything. BGP just did exactly what BGP has always done, and 33 hours later a hosting-provider hypervisor panel had a backdoor with root access.
I spend most of my working life on the infrastructure side of exactly this stack — routing, certificates, patch pipelines, the boring plumbing that everyone assumes is solid until it isn't. What pulled me into this particular incident wasn't the malware. It was that BGPKIT didn't just describe the hijack, they published the actual 144-byte MRT record and let you click through every field of it in a Wireshark-style dissector. When was the last time a routing security story came with receipts instead of adjectives?
The one-paragraph version
Between 2026-08-28 20:57:30 UTC and 2026-08-30 ~06:10 UTC (about 33 hours), AS62390 (NexonHost) announced 162.55.80.0/24 — a slice of Hetzner's 162.55.0.0/16 that hosts Softaculous' update and billing infrastructure — through transit provider AS6204 (ZET.NET). Because the forged route was a more specific prefix than Hetzner's legitimate /16, it won route selection almost everywhere it propagated. The attacker kept Hetzner's real ASN, AS24940, on the tail of the AS path, which meant the hijacked route validated cleanly against Hetzner's own RPKI Route Origin Authorization (ROA). With traffic for the update servers flowing to their own box, the attacker requested a Let's Encrypt certificate, sailed through domain-control validation over the hijacked path, and got a technically valid TLS certificate covering 26 Softaculous/Virtualizor/Webuzo domains. They cloned the update endpoint and served a backdoored Virtualizor package to any hypervisor panel that happened to check for updates during the diversion window.
Who's actually involved here
If you don't live in this stack day to day, four names are doing all the work in this story, and it's worth being precise about each one:
- Hetzner (AS24940) — the German hosting provider that legitimately owns
162.55.0.0/16and hosts Softaculous' infrastructure inside it. - Softaculous / Virtualizor / Webuzo — the actual target. Softaculous is best known for its one-click app installer bundled into cPanel and Plesk; Virtualizor is a hypervisor management panel that hosting providers use to spin up and administer customer VPS instances; Webuzo is a related control-panel product. Same company, same update infrastructure, same certificate.
- AS62390 (NexonHost) — the network that originated the forged, more-specific announcement.
- AS6204 (ZET.NET) — the transit provider that accepted NexonHost's forged announcement and propagated it to the rest of the internet without filtering it. Without this one missing filter, the hijack never leaves AS62390's own network.
The attack chain, step by step
1. The forged announcement
At 20:57:30 UTC on August 28, RIPE RIS collector rrc00 recorded the first sighting of the forged route. The AS path read (observer to origin):
29504 15935 6204 62390 24940
Reading that path from the origin end tells the whole story:
24940— Hetzner, the real, authorized origin. The attacker deliberately left it in place as camouflage.62390— NexonHost, inserted one hop before the real origin.6204— ZET.NET, the transit provider that accepted and re-exported the forged route without a customer-prefix filter.- everything before that — however many additional networks the route picked up propagating outward from each collector's vantage point.
2. Route selection does the rest automatically
162.55.80.0/24 is a /24; Hetzner's normal announcement is a /16. Longest-prefix match is not a security control, it is the literal, decades-old definition of how a router picks a best path, and a /24 beats a /16 covering the same space every single time, on every router that receives both. No exploit, no credential theft — just a routing table doing exactly what it was designed to do with forged input.
3. The route validates as RPKI Valid anyway
This is the part that should worry anyone who has treated “we do RPKI” as a finished checkbox. Hetzner's ROA reads:
162.55.0.0/16 | origin AS24940 | maxLength 24
Route Origin Validation asks exactly one question: does the announced origin ASN match an authorized origin for a prefix within the ROA's length bounds? The hijacked route's origin was AS24940 — correct — at a /24 — exactly at the ROA's maxLength ceiling. RPKI ROV had nothing to reject. It isn't that ROV failed here; it's that ROV was never designed to answer the question this attack actually exploited, which is “who is allowed to sit immediately upstream of the origin.” That's a different protocol's job (more on ASPA below), and as of this incident, essentially nobody had deployed it for this prefix.
4. Traffic reaches the impostor, and the real damage starts
With files.virtualizor.com resolving to the attacker's box at 162.55.80.8 for a large share of the internet, two things happened over the hijacked path:
- Certificate issuance. The attacker requested a certificate from Let's Encrypt. Domain-control validation — an HTTP challenge to the target domain — was itself routed through the hijack, so it succeeded. Let's Encrypt had no way to know the request wasn't coming from the legitimate host; it was doing exactly what domain validation is supposed to do, validate against the domain's current, apparently-authoritative routing. The result was a real, browser-trusted certificate covering
virtualizor.com,api.virtualizor.com,files.virtualizor.com,softaculous.com, and 22 more names across the company's product line. No client anywhere saw a warning. - Update-package substitution. Virtualizor's update client checked for new versions over HTTPS — which now terminated cleanly at the impostor, cert and all — but never verified a cryptographic signature on the downloaded package. A valid TLS handshake was the only bar to clear. The attacker's fake update self-reported as version
3.2.9.8; affected hosts logged “update succeeded” while actually still showing3.2.9.7— a version-mismatch that is itself one of the clearest indicators of compromise in this whole incident.
5. Root, quietly
The backdoored package planted a systemd unit disguised as a JRE updater (/etc/systemd/system/java-jre-update.service), dropped a Java-based payload, planted an ed25519 SSH key for persistent access, and phoned home to a C2 endpoint at 31.77.220.138:2025. Virtualizor runs with root on the hypervisor host by design — it has to, to manage VMs — so a compromised update client is a compromised hypervisor, full stop, with no privilege escalation step required.
6. Two waves, not one
The hijack didn't run cleanly for 33 straight hours. It flapped violently — roughly 10,600 route withdrawals across the window, with transit route dampening repeatedly suppressing it down to a single surviving collector peer for stretches. BGPKIT's forensic replay pinned the churn to a single source cycling on and off (not independent recomputation upstream): carriers on six continents synchronized to the same roughly nine-minute announce/withdraw rhythm, which is not what independent local routing decisions look like.
Figure: Minute-by-minute visibility of the hijacked route across all public RIPE RIS and Route Views collector peers. Red is diverted traffic through AS62390, green is Hetzner's clean countermeasure route, and the gap in the middle is the ~11-hour lull when neither the forged nor the corrective /24 existed anywhere in the public routing table. (Source: BGPKIT)
Hetzner countered around 08:50 UTC on August 29 by announcing the /24 itself, which killed the diversion within minutes — but only after roughly 12 hours of exposure and, per Softaculous' own writeup, only after the victim escalated repeatedly; Hetzner did not proactively detect or report it. Hetzner then withdrew its own countermeasure route around 14:30, and for about 11 hours nothing at all announced the /24 — traffic simply fell back to the legitimate /16. Then, at 19:55 UTC, the exact same hijack signature reappeared and ran for another ten hours before the final withdrawal at 05:45 on August 30.
The timeline in one table
| Time (UTC) | Event |
|---|---|
| Aug 28, 20:57:30 | First forged /24 route observed at RIPE RIS rrc00 |
| Aug 28 21:00 – Aug 29 08:50 | Wave 1: ~12 hours, diversion peaking around 72% of RIS collector peers |
| Aug 29, ~08:50 | Hetzner begins announcing the /24 directly; diversion collapses within minutes |
| Aug 29, ~14:30 | Hetzner withdraws its countermeasure route; traffic falls back to the legit /16 |
| Aug 29, 19:55 | Wave 2 begins: identical hijack signature reappears |
| Aug 29 19:55 – Aug 30 05:45 | Wave 2 runs for ~10 hours, same AS path pattern |
| Aug 30, 05:45–06:10 | Final withdrawal; global routing fully restored |
| Aug 31 | Softaculous/Virtualizor publish public disclosure |
Why the defenses that existed didn't fire
RPKI ROV: necessary, not sufficient
I want to be direct about this because I've heard “just deploy RPKI” treated as a finished sentence more than once in planning meetings. ROV answers “is this origin AS authorized to originate this prefix.” It has no concept of path integrity — it doesn't check who the origin's immediate upstream is. An attacker who keeps the real origin ASN on the path tail and inserts themselves one hop earlier sails through ROV cleanly, every time, by design. That's exactly what happened here.
There's a second wrinkle worth internalizing: Hetzner's ROA specified maxLength 24 on a /16. Had they instead followed the minimal-ROA practice recommended in RFC 9319 — signing only the exact prefix lengths actually announced, rather than leaving a wide-open maxLength — the hijacked /24 would have been RPKI Invalid and dropped by every ROV-enforcing network on the path. The over-broad maxLength on an otherwise correct ROA is what kept this specific hijack legal-looking.
ASPA would have caught it cold
Autonomous System Provider Authorization (ASPA) closes exactly the gap ROV leaves open: it lets an AS publish who its legitimate upstream providers are, so a validator can reject a path where an unauthorized AS claims to sit adjacent to the origin. BGPKIT replayed this incident's actual AS paths through the ASPA verification algorithm and found that a single ASPA object — published by Hetzner alone, listing its real providers — would have flagged the hop 24940 → 62390 as unauthorized and classified the hijacked route Invalid, with zero cooperation required from AS62390 or AS6204. Hetzner's own legitimate routes would have stayed Unknown rather than breaking, because ASPA verification only penalizes a contradicted upstream claim, not a missing one. That's about as close to a free win as network security gets: one signed object, deployed unilaterally, would have closed this entire attack class for Hetzner's address space. As of this incident, no AS on any of the observed paths had published one, and the live global ASPA corpus contained zero objects listing AS62390 as anyone's provider.
Old-fashioned prefix filtering would also have worked
None of this required ASPA to stop. AS6204 (ZET.NET) is the transit provider that accepted AS62390's announcement and propagated it onward. A basic customer-prefix filter — restricting what prefixes a customer AS is allowed to originate, a control that has existed since long before RPKI — would have rejected the /24 at the very first hop and confined the entire incident to AS62390's own network. This is the least exciting sentence in the whole story and also the one that would have mattered most.
TLS and unsigned updates: the last line of defense that wasn't there
Let's Encrypt's domain validation did exactly what it's specified to do — it validated the domain as reachable over the network as it existed at that moment, which is a real limitation of DNS/HTTP-based domain control validation that BGP hijacking has been used to exploit for years, not a new flaw in this incident. What actually should have stopped the chain, independent of routing or certificates entirely, was package signing on the update client. Virtualizor's updater trusted a valid HTTPS response as sufficient proof of authenticity. It wasn't checking a signature at all, so once TLS was subverted upstream, there was nothing left to check.
Indicators of compromise
| Indicator | Detail |
|---|---|
| Malicious systemd unit | /etc/systemd/system/java-jre-update.service, disguised as a JRE auto-updater |
| Payload | Java-based dropper, referenced in community reporting as a /tmp/widdow.jar-class file |
| Persistence | Planted ed25519 SSH key added to authorized_keys |
| C2 | 31.77.220.138:2025 |
| Version mismatch | Update log reports success and version 3.2.9.8, while the panel still shows 3.2.9.7 |
| Impostor host | 162.55.80.8, carrying reverse DNS server.softaculous.com during the incident |
My take
What actually stopped me on this one wasn't the malware — planting a fake systemd service and an SSH key is a Tuesday for any halfway competent intrusion crew. It's that the entire chain of failures is boring, individually defensible engineering decisions that happened to stack. Hetzner's ROA was correctly signed, just with a generous maxLength that's extremely common practice. AS6204 accepting a customer's route without a strict prefix filter is, unfortunately, still closer to the industry norm than the exception at plenty of transit providers I've dealt with. Let's Encrypt's domain validation behaved exactly per its own spec. Virtualizor's update client checked for HTTPS and stopped there, which used to be considered adequate diligence for a lot of vendors' update pipelines, mine included at various points over the years. None of these four things is, on its own, a scandal. Stacked in this exact order, they add up to root on someone else's hypervisor fleet.
The thing I keep coming back to is that BGP hijacking gets treated in most infrastructure teams' threat models as a routing-availability problem — something that causes outages, gets fixed, everyone moves on. This incident is a clean, well-documented example of BGP hijacking as a software supply-chain attack vector: not “traffic got rerouted for a few hours,” but “a certificate authority was fooled into minting a legitimate credential, and that credential was then used to poison a code-delivery pipeline that runs as root.” That's a fundamentally different severity class, and I don't think most of the infra teams I've worked alongside have that scenario anywhere in their incident response runbooks yet. If your update client doesn't verify package signatures, a routing incident three networks away from you — one you have zero visibility into and zero ability to prevent — can become a full compromise of your own infrastructure. That dependency is worth taking seriously on its own, independent of whether BGP or RPKI ever gets fixed.
I also want to give Softaculous credit for something that's rarer than it should be: they published a minute-by-minute measurement table, admitted Hetzner didn't proactively notify them, and didn't hide behind “a small number of servers were affected” without also saying plainly that they can't produce a definitive victim list. That kind of disclosure is what lets outside researchers like BGPKIT actually verify the claims against public MRT archives instead of taking a vendor's word for it. More vendors should be held to that bar.
What you should do right now
- If you run Virtualizor, Webuzo, or Softaculous anywhere: check for
/etc/systemd/system/java-jre-update.serviceon every host. If it's present, do not simply delete it — treat the host as compromised, preserve it for evidence, and rotate every credential that host could reach, including hypervisor-level API keys. - Audit SSH authorized_keys on every VPS host and hypervisor that could have checked for updates between 2026-08-28 20:57 UTC and 2026-08-30 06:10 UTC, looking specifically for ed25519 keys you don't recognize.
- Rotate and scope down Virtualizor/Softaculous API credentials. Reset API keys in the Virtualizor master panel and restrict API access to specific trusted IPs rather than leaving it open.
- If your organization owns announced address space, check your ROAs today. Specifically check whether your maxLength is broader than what you actually announce — a /16 ROA with
maxLength 24when you only ever announce the /16 itself is exactly the gap this attacker used. Tighten it to match your real announcements. - Publish an ASPA object if your RIR supports it. This is a unilateral action — you don't need your transit providers or upstream ASes to do anything for it to protect your own prefixes from this exact attack class.
- If you operate transit, implement customer-prefix filtering if you haven't already. This is the single control in this entire incident that would have stopped the hijack at its very first hop, and it predates RPKI by decades.
- If you build or operate any software update pipeline, verify you actually check a cryptographic signature on the package — not just that the download happened over HTTPS. TLS proves you talked to whoever currently controls the routing to a hostname. It says nothing about who built the file you downloaded.
- Set up BGP route monitoring for your own critical prefixes (BGPKIT, Cloudflare Radar, and RIPE's own tools all offer this) so a more-specific hijack of your address space gets flagged in minutes, not confirmed 2.5 days later by a community forum thread.
This article was researched and drafted with AI assistance as part of an experiment in building a faster tech-writing workflow, then reviewed and edited before publishing.
Primary source: Anatomy of the Virtualizor BGP Hijack: Reading the Attack in Raw BGP Bytes — BGPKIT, August 31, 2026
Secondary source: Security Incident: BGP Hijacking — Virtualizor/Softaculous official disclosure, August 31, 2026
Secondary source: YARA rules and IoCs for the Virtualizor compromise — Neo23x0/signature-base, GitHub
Keep Reading
SideLink: The NVLink Fabric Tying Your AI Cluster Together Has a Covert Channel
Researchers at IIT Jodhpur turned ordinary wire contention on NVIDIA's NVLink into a working covert channel and a workload fingerprinting attack that hits 93% accuracy on a shared DGX A100. NVLink was engineered for bandwidth, not isolation, and it shows.
MikroTrick: The MikroTik RouterOS SSH Bypass That Was Being Exploited Before the Patch Even Shipped
Two chained RouterOS bugs let an attacker log into a MikroTik router as an existing user without their private key, then escalate straight to root. CERT Polska confirmed exploitation a day before patches existed, and 122,500+ internet-facing routers are still exposed.
How a Flawed SSO Invitation Flow Turned Brevo Into a Phishing Launchpad for 347,000 Crypto Users
An attacker exploited how Brevo's SAML SSO handles organization invitations to reach 138 customer accounts, including Trezor and BitBox, then used Trezor's own newsletter list to push a fake hardware vulnerability alert to 347,000 subscribers.