From 2 to 5 October 2026, attackers rewired hundreds of Tor exit relays so that SSH traffic on port 22 went to 12 interception servers. Anyone who logged in with a password over Tor after accepting a new or changed host key should treat that password as stolen, rotate it, and check the target server for the Ebury backdoor.
The researcher who found it, who publishes as kaba, counted 333 hijacked exits, roughly one in ten of the Tor network's 3,000 or so exit relays. The Tor Project's network health team flagged the relays as BadExit on 4 October and none were left in the consensus by the next evening. Tor's encryption was not broken and neither was SSH's. The attack worked only when a person accepted a host key they had never verified and then typed a password. That makes this a story about SSH host key verification and password logins far more than a story about Tor.
This post covers what the primary sources say, with relay counts given as each source states them, a timeline to the minute where one was published, the full set of indicators the operators and researchers released, how to check your own known_hosts and servers, how to move to SSH certificates so that a changed key is a hard failure rather than a prompt, and the open questions that remain.
What happened
kaba published the original account on 20 October 2026. In short:
- Starting on Friday 2 October at 09:35, 333 exit relays began sending SSH connections on port 22 to one of 12 interception servers, while passing encrypted web traffic through normally, according to kaba.
- The same relays had their exit policies changed so that encrypted mail ports were blocked while plaintext mail ports stayed open, which kaba reads as a second goal: collecting as much unencrypted mail as possible.
- A user whose SSH client warned that the remote host identification had changed, and who went ahead anyway and typed a password, handed that password to the interceptor. kaba reports that the interceptor then opened its own connection to the real server, watched the commands the victim typed, and opened a second connection to try to install Ebury 1.8.3, an OpenSSH backdoor and credential stealer for Linux.
- kaba found the relays by scanning every exit with exitmap, the Tor Project's exit relay scanner, and reported them to the network health team on the evening of 2 October.
- The network health team told relay operators on 4 October and the relays were given the BadExit flag. By 5 October, kaba writes, all infected servers had been shut down, some by their operators and some by the attackers.
The Tor Project's own notice, posted to the tor-relays mailing list by hiro on 5 October, said operators had reported hacked servers and edited torrc files, and that the attacker appears to gain access to Linux hosts running non exit relays and then edits torrc to turn them into exits. The team told kaba it had "not seen an attack like that before."
On 7 October the operator of the largest affected relay family, Quetzalcoatl, posted a detailed post mortem to the same list describing Ebury 1.8.3 on two of the family's servers, the login that started it on 28 August, and a list of indicators. The Hacker News summarised all of this, plus a directory data analysis of how many relays changed policy, on 21 October.
Ebury on the relays: what is confirmed and what is not
Be careful with the headline phrase "Ebury on 333 relays." It is not established.
- Observed directly by the researcher: kaba ran sshesame, an SSH honeypot, through an affected exit. After the honeypot accepted a login, the attacker's client, which identified itself as paramiko (a Python SSH library), checked the user ID, checked whether sudo was available without a password, and then ran a base64 encoded command that attempted to install Ebury using Perl. So attempted Ebury installs on the servers that victims logged into are the confirmed part.
- Reported by one operator about his own servers: the Quetzalcoatl operator's post mortem says an implant that self reports as version 1.8.3 was installed on his management server and on one relay server, Switzerland2. That is a first person account. We have not seen independent forensic confirmation from the Tor Project or a third party.
- Not confirmed: that Ebury ran on the other relay hosts. hiro's notice mentions other operators seeing a fake uppercase CRON daemon used for root persistence and NAT rules redirecting port 22, and the Quetzalcoatl operator says his servers showed neither. Whether all of this is one actor is an open question in the sources themselves. Several news reports state or imply that Ebury was deployed across the compromised relays; treat that as reported, not confirmed.
Timeline
Times are as published. kaba gives 09:35 on 2 October without a time zone; the Quetzalcoatl post mortem and The Hacker News use UTC and give 09:34.
| Date and time | Event | Source |
|---|---|---|
| Since at least June 2026 | Ebury 1.8.3 in circulation | Marc-Etienne M. Léveillé on tor-relays, 8 Oct |
| 28 Aug, 03:19 UTC | Login to the Quetzalcoatl management server from 185.225.226[.]62 (VikHost, AS207560) with the operator password, first try, no prior failures | Quetzalcoatl post mortem |
| 28 Aug, 03:24 UTC | Root via sudo su | Quetzalcoatl post mortem |
| 28 Aug, 03:25 to 03:43 UTC | Ebury installed on the management server, logs truncated, gcc installed | Quetzalcoatl post mortem |
| 28 Aug, 04:38 UTC | Switzerland2 accepts an operator login from the management server's IP using an RSA key not in any authorized_keys file found; one second later sudo bash -c runs a base64 installer | Quetzalcoatl post mortem |
| 31 Aug to 6 Sep | Root actions with no sudo record install nmap, iptables and duplicity, pull an Alpine image, load NFS client modules, decrypt backup metadata | Quetzalcoatl post mortem |
| 30 Sep | sshd restarted from the backdoor, LogLevel dropped to ERROR so "Accepted" lines stop; hidden processes guard, failover and snf start | Quetzalcoatl post mortem |
| 1 Oct | Relays start switching to the attacker's exit policy; 40 relays from 21 operators change within 5.5 minutes, mostly by configuration reload | The Hacker News |
| 1 Oct, 15:35 to 15:43 UTC | A policy block is prepended to /etc/tor/torrc on Switzerland2, a file the relays did not actually read | Quetzalcoatl post mortem |
| 2 Oct, 09:34 UTC | Both Switzerland2 instance torrcs rewritten; both relays publish the new policy at 09:34:04 | Quetzalcoatl post mortem |
| 2 Oct, 09:35 | Interception begins on 333 exits | kaba |
| 2 Oct, morning | kaba's own SSH connection shows a changed key warning | The Hacker News |
| 2 Oct, evening | kaba reports to the Tor network health team | kaba |
| 4 Oct, 05:46 UTC | Kernel upgrade and reboot on Switzerland2 kills the hidden tools; Ebury returns | Quetzalcoatl post mortem |
| 4 Oct | At the peak, relays with the attacker's policy hold 7.46% of exit consensus weight | The Hacker News, citing directory data analysis |
| 4 Oct, 13:56 UTC | Network health team flags the Switzerland2 policy change; relays marked bad; Tor stopped on Switzerland2 | Quetzalcoatl post mortem |
| 4 Oct, 18:00 UTC | Directory authorities start voting BadExit and MiddleOnly | The Hacker News |
| 4 Oct, 21:00 UTC | Flags in the consensus | The Hacker News |
| 5 Oct, 08:56 to 10:26 UTC | Scripted restart sweeps 55 Quetzalcoatl hosts in text sorted IP order; who ran it is unknown | Quetzalcoatl post mortem |
| 5 Oct | Attackers restart many relays; already flagged, they can only act as middle relays | kaba |
| 5 Oct, 17:00 UTC | No relay with the attacker's policy remains in the consensus | The Hacker News |
| 5 Oct, 5:50 p.m. | hiro posts "Security incident affecting some relay operators" to tor-relays | tor-relays |
| 6 Oct | Quetzalcoatl management server and Switzerland2 taken offline and deleted; all family relays and the bridge retired | Quetzalcoatl post mortem |
| 7 Oct, 12:29 p.m. | Quetzalcoatl post mortem published on tor-relays | tor-relays |
| 8 Oct, 4:40 p.m. | Léveillé replies with Ebury background and an updated detection script | tor-relays |
| 9 Oct | No affected relay back; 41 relays, nine of them Quetzalcoatl's, still publish the policy but are not listed | The Hacker News |
| 18 to 19 Oct | Exit flag count still not recovered from a drop of about 370 between 3 and 5 Oct | The Hacker News; kaba |
| 20 Oct | kaba publishes the write up | kaba |
| 21 Oct | The Hacker News, SecurityOnline, Cyber Security News and GBHackers report it | Coverage |
How many relays? Counts as each source states them
The numbers differ because the sources measure different things. None of them is wrong on its own terms.
| Source | Figure | What it measures |
|---|---|---|
| kaba | 333 exits, about 10% of roughly 3,000 | Exits that redirected port 22 to the 12 interception servers in an active exitmap scan on 2 Oct |
| kaba | about a dozen | Exits that served the fake host key during the scan |
| kaba | over 300 | Exits that redirected to the interception servers without changing the key during the scan |
| The Hacker News (headline) | more than 300 | Exit relays taken over |
| The Hacker News, citing directory data analysis | 377 relays from 59 operators | Relays that switched to the attacker's policy between 1 and 4 Oct, described as "a policy match, not proof of compromise" |
| The Hacker News | 342 | Affected relays that had blocked port 22 and then allowed it |
| The Hacker News | 24 | Non exit relays turned into exits |
| The Hacker News | 7.46% | Exit consensus weight held by relays with the attacker's policy at the 4 Oct peak |
| The Hacker News, citing Tor Metrics | about 370 | Drop in the Exit flag count between 3 and 5 Oct |
| The Hacker News | 273 | Quetzalcoatl relays affected, the largest group |
| The Hacker News | 71 of 101 vs 13 of 9,054 | Relays on FranTech Solutions (the network behind BuyVM) that switched, against relays hosted elsewhere |
| The Hacker News, citing kaba | 26 of 27 | Compromised servers outside the Quetzalcoatl family that were at FranTech/BuyVM |
| Quetzalcoatl post mortem | 168 relays on 57 IPs | Family relays listed by Onionoo on 6 Oct, all down |
| Quetzalcoatl post mortem | 55 hosts | Family hosts hit by the 5 Oct restart sweep |
| GBHackers, SecurityOnline, Cyber Security News | 333 | Repeat kaba's figure |
We tried to fetch the directory data analysis itself and could not locate it, so the 377, 342, 24 and 7.46% figures are as The Hacker News reports them. The Tor Metrics "Relays by relay flag" graph covers the period, but the published page is an image and we could not read exact values from it.
Technical details
The interception
An SSH connection starts with a key exchange in which the server proves it holds the private half of its host key. The client compares that host key with the copy in ~/.ssh/known_hosts. If the relay simply forwards the TCP stream to the real server, the key matches and the session stays end to end encrypted between client and server. The relay sees ciphertext and nothing else.
That is what kaba saw on most of the 333 exits during the scan: the connection arrived at his server B from one of the interception servers rather than from the exit, but the key was correct. The interceptor was in the path but, at that moment, only passing bytes along. On about a dozen exits it substituted its own key, which is the only way it can read the session. Whether the interceptors chose which connections to attack, and on what basis, is not stated in the sources.
When the key is substituted, OpenSSH prints the "REMOTE HOST IDENTIFICATION HAS CHANGED" warning. Under OpenSSH's default StrictHostKeyChecking ask, the ssh_config manual says a changed key causes the connection to be refused. To get past it, a user typically removes the old entry (for example with ssh-keygen -R host) and reconnects, at which point ssh asks whether to trust the new key as if it were the first visit. A user connecting for the first time from a fresh machine sees only the first visit prompt. Either way, a person had to accept a key they had not checked.
The interceptor host key
| Item | Value | Source |
|---|---|---|
| Key type | ED25519 | kaba |
| Fingerprint | SHA256:6baCZCPsl+AfQuujUS81fMEE/C7UeJ+AwjrImqmDB3E | kaba |
| Interception servers | 12 | kaba |
| Port | TCP 22 | kaba |
The Hacker News notes that other interception servers may have used different keys. So finding this fingerprint in known_hosts is strong evidence of exposure, but not finding it is not proof of safety. kaba did not publish the IP addresses of the 12 interception servers.
Why key based logins were not exposed
In SSH public key authentication, the client signs data that includes the session identifier from the key exchange (RFC 4252). The session between victim and interceptor has a different identifier from the session between interceptor and real server, so a signature captured in one cannot be replayed in the other. The Hacker News makes this point directly: an interceptor could accept a key login and show a fake shell, but could not use the key against the real server.
Passwords have no such binding. A password typed into the interceptor is just a string that works anywhere.
One caveat from general SSH practice, not from these sources: agent forwarding changes the picture. If a client connects with ForwardAgent yes to a host that turns out to be an interceptor, that host can ask the forwarded agent to sign for the real server while the session lasts. Do not forward your agent to hosts you have not verified. OpenSSH's PubkeyAuthentication host-bound option and restricted agent forwarding exist to narrow this.
The exit policy change
According to the Tor Project's notice and the Quetzalcoatl post mortem, the attacker edited torrc on compromised hosts. The changes reported:
- Port 22 opened on relays that had blocked it (342 relays, per The Hacker News).
- Non exit relays turned into exits (24, per The Hacker News; hiro's notice describes the same pattern).
- Encrypted mail ports closed while plaintext mail stayed open. The Hacker News names IMAPS and POP3S as closed and POP3 and IMAP as opened on most relays, and says Telnet and SSH were also affected. On Switzerland2, the post mortem lists the five reject lines added: ports 25, 465, 587, 993 and 995, after removing the operator's Ansible managed accept list of 60 port ranges.
- On Switzerland2, the original torrcs were saved with the suffix
.bak20261002.
A relay's exit policy is public. It is in the descriptor every relay publishes, which is why the directory analysis could count policy changes after the fact and why the network health team could spot the change on Switzerland2.
The attacker's post login behaviour
From kaba's honeypot:
- The client library was paramiko.
- It checked the current user ID.
- It checked whether sudo was available and usable without a password.
- It decoded and ran a base64 encoded command that attempted to install Ebury using Perl.
The Hacker News adds that the interceptor reused the stolen password to check for sudo. The Quetzalcoatl post mortem lists sudo running base64 -d | perl as one of the traces on its own hosts. We are not reproducing the installer; neither did kaba.
Ebury 1.8.3 indicators from the Quetzalcoatl post mortem
These come from one operator's account of two servers (Ubuntu 24.04 and Debian 13). They are the most specific public indicators for this version.
| Indicator | Value |
|---|---|
| Version | 1.8.3, self reported by the implant |
| Malicious library | /usr/lib/x86_64-linux-gnu/libkeyutils.so.1.10.2 |
| Size | 63,368 bytes |
| Attributes | setuid, timestomped, not owned by any package |
| Symlink | libkeyutils.so.1 re pointed to the file above; the packaged library itself untouched |
| Hashes | None published; each install stamps an 8 byte victim ID, so hashes differ per server |
| Abstract UNIX socket | @event-0YsOG9WSOE |
| Disguised process | fake /sbin/rsyslogd (earlier systemd-udevd or arpd) inside the ssh.service cgroup on Debian, init.scope on Ubuntu after reboot |
| Hidden processes (Switzerland2) | guard, failover, snf, no binaries left on disk |
| Implant config strings | UsePAM no, LogLevel QUIET, HISTFILE=/dev/null, AuthorizedKeysFile /proc/self/environ, PermitTunnel yes |
| Memory string | https://%s/xpost.php template, no host found |
| Initial login IP | 185.225.226[.]62 (VikHost, AS207560), 28 Aug |
| SSH key used to log in to Switzerland2 | RSA SHA256:dvACokMYcXJ2om4wuS+TvdtjrAgPK7rC7ZQHDgvBHzk |
| Key embedded in implant | RSA SHA256:KlICz/B59619CpuQdGzgmmvzWBfTMUJ77EwihTYobuo |
| Key embedded in implant | ED25519 SHA256:OfLY0uF9SwOW9xVjiMXZGDfk6Atmz6CbHy4epknLm8Y |
| Affected relay fingerprints | 29FEFE36A5F66A6C93D24775B9D2364A3831B597, 8427937D5A39E15699C850F26FED3CD59C379C48 (Switzerland2) |
| Journal traces | apt transactions without "Requested-By:"; su sessions opened by uid 0; systemctl calls from the ssh.service cgroup; unrequested Tor reloads; "remains running after unit stopped" on sshd restart; very large "Consumed ... CPU time" for ssh.service (41 minutes and 281 MB on Switzerland2) |
| Other commands | echo 3 > /proc/sys/vm/drop_caches |
| Ubuntu kernel log | apparmor="DENIED" operation="bind" profile="rsyslogd" addr="@event-..." |
The post mortem is unusually frank about this, and it is the most useful part for anyone running Linux servers:
dpkg -Vanddebsumswere clean. sshd, sudo and PAM all matched their package checksums, because Ebury hooks in through a library that no package owns.lddon sshd and on the listening process looked clean.- ESET's published
detect_ebury.shdid not flag 1.8.3. ESET's indicators stop at 1.8.2, and as of 21 October its script lists no socket name for 1.8.3, per The Hacker News. - ESET's YARA rule
libkeyutils_with_ctordid still match. - An earlier review pass dismissed the setuid library as normal and grouped logins by IP rather than by key, which hid the unknown key.
- None of the host checks in the Tor Project's advisory (NAT redirect, new users, SSH keys, cron jobs, systemd units, modified tor binary) turned anything up on these two servers. A clean result from that checklist does not rule out Ebury.
What Léveillé added
Marc-Etienne M. Léveillé, who co-wrote the 2014 Ebury paper and wrote the 2024 report while at ESET and says he no longer works there, replied on 8 October:
- Ebury 1.8.3 has circulated since at least June 2026.
- He published an updated
detect_ebury.shon a branch namedebury_updatein his fork ofeset-malware-researchon GitHub, not yet merged into ESET's version, and calls it "not a silver bullet." - The userland rootkit can reportedly be bypassed with
I=1 bash; the variable wasHin the 2024 publication. - Stolen credentials sit in memory in a spawned process until the operators fetch them with an "Xcat" command over daily SSH connections. After 14 days without a fetch, they are sent over UDP port 53.
xpost.phpcaptures only HTTP POST data from libcurl based software such as Apache and PHP, so it is inactive on servers that do not serve HTTP.- His best guesses for how the Quetzalcoatl password leaked: a login from a host already running Ebury, or a compromised hosting provider.
ESET's May 2024 report gives the background: about 400,000 servers compromised since 2009, more than 100,000 still compromised in late 2023, spread partly through compromised hosting providers and partly through adversary in the middle interception of SSH on shared network segments. Version 1.8, first seen in late 2023, added a new domain generation algorithm and a stronger userland rootkit that hides processes, files, sockets and mapped memory.
Root cause on the one host we know about
For Quetzalcoatl, the post mortem lists the conditions that let one password open everything. One password served as SSH login, sudo password and backup passphrase. It sat in cleartext in a helper script on both servers. Password authentication was enabled on the internet facing management server. That server also held an encrypted workstation backup containing the operator SSH private key, the Ansible inventory and copies of the relays' "offline" Ed25519 master keys. An internet exposed WGDashboard 4.3.0.1, which the post mortem says logs sign in passwords in cleartext and has CVE-2026-44343 (an unauthenticated file read fixed in 4.3.2), was also on that server. How the attacker first obtained the password is unknown. We describe this only because the operator published it as a lesson for others.
Who is affected
| Group | Exposure | What to do |
|---|---|---|
| Anyone who used SSH with a password over Tor between 1 and 5 Oct and accepted a new or changed host key | Password likely captured; target server may have had an attempted Ebury install | Rotate the password everywhere it was used; check the server for Ebury; review logins by key fingerprint |
| Anyone who used SSH with a key over Tor in that window | Key not reusable against the real server; if agent forwarding was on to an unverified host, review | Check known_hosts for the fake fingerprint; disable agent forwarding to unverified hosts |
| Anyone who read mail over plaintext POP3 or IMAP over Tor in that window | Mail and mail credentials may have been read | Rotate mail passwords; require TLS on mail clients |
| Tor relay operators | Hosts may have been compromised, as hiro's notice describes | Run the checks below; if compromised, rebuild, new identity, report to the Tor Project |
| Admins of any Linux server reachable over SSH | Ebury 1.8.3 is in circulation, per Léveillé | Hunt for the indicators; turn off password authentication |
How to check if you are affected
1. Look for the interceptor key in known_hosts
ssh-keygen -lf prints fingerprints for every key in a known_hosts file, including hashed ones.
# Search your own known_hosts for the published interceptor fingerprint
ssh-keygen -lf ~/.ssh/known_hosts | grep '6baCZCPsl+AfQuujUS81fMEE/C7UeJ+AwjrImqmDB3E'
# Search every user's known_hosts on a shared machine (run as root)
for f in /root/.ssh/known_hosts /home/*/.ssh/known_hosts; do
[ -f "$f" ] && ssh-keygen -lf "$f" | grep -H --label="$f" '6baCZCPsl+AfQuujUS81fMEE/C7UeJ+AwjrImqmDB3E'
done
A hit means that machine accepted the interceptor's key. Then find which host entry it belongs to:
# Show the stored key for a specific host, with fingerprint
ssh-keygen -F server.example.com -l
# Ask the real server for its keys out of band (not over Tor) and compare
ssh-keyscan -t ed25519 server.example.com | ssh-keygen -lf -
If the fingerprint you stored differs from the one the server presents on a trusted network path, remove the bad entry with ssh-keygen -R server.example.com and add the verified one. Because other interceptors may have used other keys, also compare every host you reached over Tor in early October against a fingerprint you obtain from the server's console or configuration management.
2. If you typed a password, assume it is gone
Change it on that server and anywhere else you reused it. If the account had sudo, assume the attacker tried to use it, as kaba's honeypot showed. Then check the server, step 3, from a clean machine. The Quetzalcoatl operator's advice: "Never type a password into a server you suspect, because Ebury will capture it." So rotate through your configuration management, a console session or a known good host, not by logging into the suspect server with the new password.
3. Hunt for Ebury on Linux servers
Run these as root. The post mortem and The Hacker News recommend starting a shell that sidesteps the userland rootkit before you trust any output. Léveillé says the current variable is I; the post mortem uses H. Setting both covers either:
# Start a shell intended to bypass Ebury's userland rootkit (per the post mortem and Léveillé)
H=1 I=1 LD_PRELOAD=":" LD_DEBUG=":" bash
Then:
# 1. Setuid libkeyutils files are a strong signal
find / -xdev -name 'libkeyutils*' -perm -4000 -ls 2>/dev/null
# 2. Where does the soname symlink point, and does a package own the target?
ls -l /usr/lib/x86_64-linux-gnu/libkeyutils.so.1 /lib64/libkeyutils.so.1 2>/dev/null
dpkg -S "$(readlink -f /usr/lib/x86_64-linux-gnu/libkeyutils.so.1)" # Debian, Ubuntu
rpm -qf "$(readlink -f /lib64/libkeyutils.so.1)" # RHEL, Fedora
# 3. Abstract UNIX sockets named like the one in the post mortem
grep '@event-' /proc/net/unix
# 4. Unexpected processes inside the sshd cgroup (rsyslogd should not be there)
systemd-cgls -u ssh.service 2>/dev/null || systemd-cgls -u sshd.service
# 5. Kernel log hits like the Ubuntu AppArmor denial in the post mortem
journalctl -k | grep -E 'apparmor="DENIED".*rsyslogd.*@event-'
# 6. sudo running base64 piped to perl
journalctl _COMM=sudo | grep -E 'base64 -d.*perl|base64 --decode.*perl'
# 7. Large CPU time for the ssh unit and processes left after stop
journalctl -u ssh -u sshd | grep -E 'Consumed .* CPU time|remains running after unit stopped'
A package owning the library, no setuid bit, no @event- socket and no stray process in the sshd cgroup is a good sign.
For a deeper check, use Léveillé's updated detect_ebury.sh from his GitHub fork, and the ESET YARA rule libkeyutils_with_ctor, which still matched 1.8.3 according to the post mortem.
4. Review accepted logins by key, not by IP
sshd logs the fingerprint of the key used for each public key login. Grouping by fingerprint shows keys you do not recognise even when the source IP is one of yours, which is exactly what hid the Switzerland2 login.
# Count accepted public key logins per user and key fingerprint
journalctl -u ssh -u sshd --since 2026-08-01 \
| grep 'Accepted publickey' \
| grep -oE 'for [^ ]+ from [^ ]+ .*SHA256:[A-Za-z0-9+/=]+' \
| awk '{print $2, $NF}' | sort | uniq -c | sort -rn
# Look for the specific keys from the post mortem
journalctl -u ssh -u sshd | grep -E 'dvACokMYcXJ2om4wuS|KlICz/B59619CpuQdGzgmmvzWBf|OfLY0uF9SwOW9xVjiMXZGDfk6'
# Look for the initial access IP
journalctl -u ssh -u sshd | grep '185.225.226.62'
last -i | grep '185.225.226.62'
If Accepted lines stop abruptly, check whether LogLevel was lowered. The post mortem says the attacker dropped it to ERROR on 30 September, which silenced successful logins.
5. Dump network state
Léveillé notes that Ebury operators change network settings to capture and redirect traffic, and hiro's notice mentions NAT rules redirecting port 22.
iptables-save; iptables-nft-save 2>/dev/null; nft list ruleset
iptables -t nat -S | grep -E 'dport 22|--dport ssh'
ip link show; ip addr show; ip rule show; ip route show table all; ip tunnel show
ss -tnp
Unknown WireGuard or tunnel interfaces, NAT rules on port 22, or outbound connections from processes you do not recognise all deserve a closer look.
6. For Tor relay operators
These are the checks from hiro's notice on tor-relays, plus the post mortem's additions:
# Exit related lines in torrc and anything it includes
grep -RniE '^(ExitRelay|ExitPolicy|ContactInfo|MyFamily|FamilyId|Nickname|%include)' /etc/tor/
# Leftover backups like the ones on Switzerland2
ls -la /etc/tor/ | grep -E '\.bak[0-9]{8}'
# Tor binary against the distribution package
debsums tor # Debian, Ubuntu
rpm -V tor # RHEL, Fedora
# New users, keys, timers and cron jobs
awk -F: '$3 >= 1000 || $3 == 0 {print $1, $3, $7}' /etc/passwd
find / -xdev -name authorized_keys -newermt 2026-08-01 -ls 2>/dev/null
systemctl list-unit-files --state=enabled | sort
ls -la /etc/cron* /var/spool/cron* 2>/dev/null
ps -eo pid,user,comm | grep -E '\bCRON\b'
Then check your relays from the outside. Look up each fingerprint in Relay Search on Tor Metrics and confirm the Exit flag and exit policy are ones you set. The post mortem recommends watching your descriptors through Onionoo and CollecTor for drift in policy, family or contact information, because those are visible to everyone and cannot be hidden by a rootkit on your host.
7. If you find a compromise
The Tor Project's notice and the post mortem agree:
- Take the host offline. For Debian multi instance Tor, mask the units; the post mortem says
systemctl disablewas not enough. - Preserve disks and logs. Capture memory before rebooting if you can; Léveillé suggests recovering deleted executables from
/proc/$PID/exeand usinggcore $PIDfor memory. - Reinstall. Do not clean in place. The implant survived a reboot on Switzerland2.
- For relays, generate a new identity and treat the old keys as stolen, including family keys and FamilyId.
- Rotate every credential that was on the host, and check
~/.ssh/known_hoststhere for other systems it connected to, since Ebury spreads along those paths. - Report to bad-relays@lists.torproject.org. Per The Hacker News, citing Georg Koppen, operators whose IPs were blocked can write to the same address once the host is clean.
How to remediate: make a changed host key a hard stop
The detection above is for cleanup. The fix is to remove the two conditions the attack needed: a person able to accept an unverified key, and a password that works anywhere.
Step 1. Turn off password authentication on servers
# In /etc/ssh/sshd_config, or better a drop in under /etc/ssh/sshd_config.d/
# Because sshd uses the first value it reads for each keyword,
# put this file early in sort order, for example 00-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
PubkeyAuthentication yes
Then confirm what sshd will actually use, because sshd_config says the first value obtained wins and an earlier include can override you:
sshd -T | grep -Ei '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|pubkeyauthentication|authenticationmethods|loglevel) '
systemctl reload ssh || systemctl reload sshd
Keep a second session open while you reload so you do not lock yourself out. Keep LogLevel at INFO or VERBOSE and alert if it drops lower; the post mortem recommends exactly that.
Step 2. Use hardware backed keys for people
kaba recommends hardware security keys. OpenSSH supports FIDO keys directly:
ssh-keygen -t ed25519-sk -O resident -C "alice laptop yubikey"
The private key never leaves the device and each signature needs a touch, so malware on a laptop cannot quietly use it.
Step 3. Stop trusting on first use
ssh_config offers four behaviours for StrictHostKeyChecking. Per the manual: yes never adds keys automatically and refuses changed keys; accept-new adds new keys automatically but refuses changed ones; ask, the default, adds new keys only after you confirm and refuses changed ones; no or off adds new keys and may let changed keys proceed with restrictions. For anything sensitive, and for any connection made over Tor or another network you do not control, use yes and populate known_hosts from a trusted source:
# ~/.ssh/config
Host *.prod.example.com
StrictHostKeyChecking yes
UserKnownHostsFile ~/.ssh/known_hosts_managed
ForwardAgent no
HashKnownHosts yes
Distribute known_hosts_managed through configuration management, built from host keys collected at provisioning time on a trusted path.
Step 4. Publish host keys in DNS (SSHFP)
If your zone is signed with DNSSEC, publish SSHFP records and let clients check them:
# On the server, print SSHFP records for its host keys
ssh-keygen -r server.example.com -f /etc/ssh/ssh_host_ed25519_key.pub
# On clients
# ~/.ssh/config
Host server.example.com
VerifyHostKeyDNS yes
The manual says that with yes, the client trusts keys that match a secure fingerprint from DNS, and treats insecure fingerprints as with ask. Without DNSSEC validation on the client's resolver, SSHFP gives you a prompt, not a guarantee.
Step 5. Move to SSH host and user certificates
Certificates remove the "first visit" problem entirely. Clients trust a certificate authority key rather than individual host keys, so a host key the CA did not sign is rejected outright. An interceptor cannot present a valid certificate without the CA's private key.
Sign each host key:
# On the CA machine (keep this key offline or in hardware)
ssh-keygen -s /secure/host_ca -I web01-2026 -h \
-n web01.prod.example.com,web01 -V +52w \
/tmp/ssh_host_ed25519_key.pub
# Produces ssh_host_ed25519_key-cert.pub
Install it on the host:
# /etc/ssh/sshd_config.d/10-cert.conf
HostKey /etc/ssh/ssh_host_ed25519_key
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
Trust the CA on clients, scoped to your domains, using the @cert-authority marker described in the sshd manual:
# ~/.ssh/known_hosts or /etc/ssh/ssh_known_hosts
@cert-authority *.prod.example.com ssh-ed25519 AAAAC3Nza...hostCAkey
For users, sign short lived certificates and have servers trust the user CA instead of scattering authorized_keys:
# Sign alice's public key, valid for 8 hours, for the principal "alice"
ssh-keygen -s /secure/user_ca -I alice@example.com -n alice -V +8h ~/.ssh/id_ed25519.pub
# /etc/ssh/sshd_config.d/10-cert.conf on servers
TrustedUserCAKeys /etc/ssh/user_ca.pub
RevokedKeys /etc/ssh/revoked_keys
sshd_config says certificates from a trusted user CA are accepted only if they list a principal matching the target user, and certificates with no principals are rejected. RevokedKeys can hold a plain key list or a Key Revocation List; if the file is unreadable, sshd refuses public key authentication for everyone, so deploy it carefully. On the client side, an @revoked line in known_hosts blocks a specific key.
Certificates bring their own risk: the CA key becomes the thing an attacker wants most. Keep it offline or in a hardware module, sign with short validity, and log every signing with the -I identity. Our post on certificate authority compromise risks covers the same tradeoff for TLS, and the eMudhra CA key compromise shows what happens when a CA key is weak.
Step 6. Put management access behind something that is not the internet
The post mortem's own recommendations: key only SSH everywhere, reach management hosts only through a VPN or IP allow list, keep the management server free of web exposure, key backups and admin tooling in containers, keep master keys truly offline, ship logs off host, keep NTP on, and run file integrity monitoring such as AIDE that flags unowned or setuid library files. That last one would have caught the libkeyutils file that package verification missed.
Step 7. Mail clients
Because the attackers forced plaintext POP3 and IMAP, set mail clients to require TLS on IMAPS (993) and POP3S (995) or STARTTLS, and refuse to fall back to plaintext. If a client silently dropped to cleartext during the window, rotate the mail password.
What changed since first reports
- The count. Early coverage settled on 333 from kaba's scan. The directory analysis cited by The Hacker News puts relays that switched policy at 377 across 59 operators, with its own caution that a policy match is not proof of compromise.
- The access story. kaba's post says the attackers had access since August and that initial access is unknown. The Quetzalcoatl post mortem now gives one concrete path for one family: a reused password and password authentication on an internet facing server, with the first login on 28 August at 03:19 UTC.
- Ebury detection. On 7 October ESET's public script did not detect 1.8.3. On 8 October Léveillé posted an updated script on his own fork and said 1.8.3 dates back to at least June.
- Hosting concentration. The Hacker News reports that 71 of 101 relays on FranTech Solutions switched, against 13 of 9,054 elsewhere, and that 26 of 27 non Quetzalcoatl compromised servers were there. This sharpens, but does not confirm, the hosting provider theory kaba and Léveillé raised.
- Network recovery. As of 18 and 19 October, per The Hacker News and kaba, the Exit flag count had not recovered.
Open questions
- How the attackers got into the hosts of the other 58 operators. The Hacker News calls this the main open question.
- Whether a hosting provider was compromised. kaba and Léveillé raise it; nothing confirms it, and no provider has said so.
- Whether Ebury ran on relay hosts beyond the two Quetzalcoatl servers.
- Whether the fake uppercase CRON and port 22 NAT cases are the same actor.
- How many people accepted the fake key and typed a password. The Hacker News says no victim count exists.
- Where stolen credentials went. The post mortem lists it as unknown.
- What
guard,failoverandsnfdid. Léveillé suggests "snf" may mean sniff, as a guess. - Who ran the 5 October restart sweep across 55 Quetzalcoatl hosts.
- Attribution. kaba suspects a financially motivated crime group, based on Ebury and the bulk collection of plaintext; no source attributes it.
What would have helped
Practices any team can use
For anyone using SSH:
- Treat "REMOTE HOST IDENTIFICATION HAS CHANGED" as an incident, not an inconvenience. Verify the new key out of band before removing the old one.
- Set
StrictHostKeyChecking yesfor production hosts and any connection over a network you do not control, and distributeknown_hostsfrom a trusted source. - Use SSH host certificates so the question "is this key right?" is answered by a CA, not a person at 9 a.m. on a Friday.
- Never forward your agent to a host you have not verified.
For anyone running Linux servers:
PasswordAuthentication noandKbdInteractiveAuthentication noon every internet facing sshd, confirmed withsshd -T.- Hardware backed keys for people, narrow
NOPASSWDrules and key only automation for machines, no passwords in scripts. - Review accepted logins by key fingerprint. Alert when
LogLeveldrops. - File integrity monitoring that flags setuid libraries and files no package owns, because package verification alone will pass an Ebury host.
- Off host logs, so an attacker who truncates local logs does not erase the record.
For anyone running infrastructure configuration, Tor relays included:
- Treat config files like torrc as monitored, integrity checked configuration. Manage them from Git through Ansible or similar, and alert when the deployed file differs from the committed one.
- Watch what your systems publish to the outside world. A relay descriptor, a DNS record or a public certificate log is visible to everyone and cannot be faked by a rootkit on your host.
- Keep "offline" keys actually offline. A backup of offline keys on an online server is an online key.
Where Safeguard fits
Safeguard would not have prevented this and is not the tool for hunting Ebury. It does not inspect running hosts, does not watch SSH logins, and does not check Tor relay descriptors. Use the commands above for that.
Where it can help is the code and configuration your team builds and ships:
Fixing configuration as code with an assistant. If your sshd settings, known_hosts files and Ansible roles live in a repository, the Safeguard CLI's assistant can edit code and run commands with your approval; /permissions offers Ask, Accept edits, Plan, Auto (the default) and Strict. You could ask it to set PasswordAuthentication no and KbdInteractiveAuthentication no in every sshd template, add a HostCertificate line and an @cert-authority entry, and run your lint and test playbooks, approving each edit. safeguard exec runs checks in CI, and the desktop and phone apps can send that work to a computer running the CLI and approve each command.
Inventory of what you ship. safeguard scan --dir . builds an SBOM for a repository, safeguard scan --image <name> does the same for a container image, and safeguard scan --sbom <file> ingests an existing SBOM; results land as JSON under ./output. If you ship images that include an SSH server or client libraries, the platform shows which projects contain a given component and version, matches them against vulnerability databases, and uses reachability analysis to rank what your code calls.
Policies and gates. Platform policies and gates evaluate what a build contains, so a component you have retired cannot quietly come back.
What it cannot do: detect a rootkit on a running server, tell you whether a password was typed into an interceptor, or fix a compromised host. Those stay with your operations and security teams.
Zero-Day Discovery and Gold
- Zero-Day Discovery looks for previously unknown vulnerabilities before a CVE exists, in an agentic mode and a deterministic scanner mode, across seven package ecosystems, and issues findings as SGZ advisories before they reach public CVE databases. Ebury is malware on hosts, and OpenSSH and the Linux libraries it hijacks are operating system components outside the package ecosystems Zero-Day Discovery covers.
- Gold Open Source at gold.safeguard.sh is free with no login: CVE intelligence with CVSS, EPSS, KEV status, affected and fixed versions and a recommended safe version, plus a free read API and a CI gate GitHub Action. This incident has no CVE of its own. The WGDashboard flaw named in the post mortem, CVE-2026-44343, is the kind of lookup Gold is for; we have not confirmed that Gold lists it.
- Gold Registry at registry.safeguard.sh is paid: hardened drop in components and container images held to zero exploitable critical or high CVEs, built to SLSA L3 or higher, Sigstore signed, with evidence pages. An artifact loses its badge on regression.
The link here is a habit rather than a record. A signed, provenance backed artifact and an SSH certificate solve the same problem: you decide in advance whom you trust, and anything that cannot prove it came from them is refused without asking a person to judge.
Common mistakes
- Removing the old known_hosts line to make the warning go away. That is the step that turns a refused connection into a successful interception.
- Assuming key based login means you are safe in every case. The key cannot be replayed, but agent forwarding to an interceptor is a different story, and a fake shell can still collect whatever you type into it.
- Trusting
dpkg -Vorrpm -V. They passed on infected hosts in the post mortem. Ebury adds a library no package owns. - Logging in to a suspect server to change the password. Ebury captures it. Rotate from a clean path.
- Grouping logins by IP. The Switzerland2 login came from the operator's own management server IP with an unknown key.
- Rebooting before capturing memory. The hidden processes died on reboot; the implant did not. You lose evidence and keep the infection.
- Cleaning in place. Every source says reinstall.
- Fixing
/etc/tor/torrcand missing the per instance files. On Switzerland2 the attacker edited the main torrc that the relays did not read, then the instance torrcs that they did. - Treating a clean checklist as a clean host. The Tor Project checklist found nothing on the two Quetzalcoatl servers.
- Overstating the count. 333, more than 300 and 377 are different measurements. Quote the one that matches your point and say where it came from.
FAQ
What happened to the Tor network in October 2026?
Attackers changed the exit policies of hundreds of Tor relays so that SSH traffic on port 22 went through 12 interception servers between 2 and 5 October. kaba counted 333 hijacked exits. The Tor Project flagged them BadExit on 4 October and none remained in the consensus by 17:00 UTC on 5 October, per The Hacker News.
Was Tor's encryption broken?
No. Tor and SSH encryption both worked as designed. The attack relied on users accepting a host key they had not verified and then typing a password.
I use Tor Browser for web browsing. Was I affected?
kaba reports that encrypted web traffic passed through normally. The interception targeted SSH on port 22 and plaintext mail. If you only browsed HTTPS sites, these reports do not describe a risk to you.
I use SSH keys, not passwords. Do I need to do anything?
Check known_hosts for the interceptor fingerprint. If you accepted a changed key over Tor in that window, remove it and verify the real key. A key signature cannot be replayed to the real server, but if you had agent forwarding on, treat the session as exposed and review the servers you could reach.
Did Ebury run on the hijacked Tor relays?
Not confirmed across the network. The confirmed part is kaba's honeypot observation of attempted Ebury installs on servers victims logged into. One operator's post mortem reports Ebury 1.8.3 on two of his servers; that account has not been independently verified, and other operators reported different persistence methods.
What is Ebury?
An OpenSSH backdoor and credential stealer for Linux, active since 2009. ESET estimated in 2024 that it had compromised about 400,000 servers. It hooks into sshd through a malicious libkeyutils library and hides itself with a userland rootkit.
How do I check a Linux server for Ebury 1.8.3?
Start a rootkit bypass shell, then look for setuid libkeyutils files not owned by a package, an @event- abstract socket in /proc/net/unix, and unexpected processes in the ssh.service cgroup. Use Léveillé's updated detect_ebury.sh and the libkeyutils_with_ctor YARA rule. Do not rely on package verification.
Which IP and keys should I search for?
The initial login IP in the post mortem was 185.225.226[.]62. The interceptor host key was ED25519 SHA256:6baCZCPsl+AfQuujUS81fMEE/C7UeJ+AwjrImqmDB3E. The post mortem also lists three attacker SSH key fingerprints, shown in the Technical details table above.
Who was behind it?
Unknown. kaba suspects a financially motivated crime group because of Ebury and the bulk plaintext collection. No source makes an attribution.
What is the single most useful change after this incident?
Disable SSH password authentication on internet facing servers and confirm it with sshd -T. Then move to host certificates so a changed key fails outright instead of asking a person to decide.
Fix it without waiting for upstream
This incident has no upstream patch to wait for: the fixes are your SSH configuration and your hosts. The same discipline applies to the open source components and images that sit alongside them, which is where the following helps.
In incidents like this one, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would have helped
None of these demos show this incident. Each is a real Safeguard session, and the text says how the same flow applies here. For a broader response checklist, see the incident response playbook for a compromised dependency, and for the rest of the month, October 2026 security incidents and what would have helped.
The first demo is a CLI session that fixes a lodash CVE: the assistant reads the finding and advisory, asks before each command, edits package.json, runs the install and tests with your approval, and pushes nothing. For this incident you would open the same session in the repository that holds your Ansible roles or sshd templates and ask it to set PasswordAuthentication no, add HostCertificate and @cert-authority lines, and run your playbook tests, approving each change as it comes.
Click through it at your own pace, or open it full screen.
The second demo is a desktop app session: the CLI session shows in the conversation list, you ask for work on your computer, pick the folder, then Approve or Deny each command and file edit, ending with an upgrade done and tests passing. Here you would pick the folder with your fleet scripts and ask it to add the Ebury checks from this post, the setuid libkeyutils search, the @event- socket check and the login review by key fingerprint, approving each file edit before it lands.
Click through it at your own pace, or open it full screen.
The third demo is a real Android session: work is sent to a computer running the CLI, and the command is approved on the phone with Allow once, Allow for turn or Deny. For this incident, an on call engineer could approve a read only known_hosts fingerprint search across admin workstations from the phone, and deny anything that would reload sshd until a second session is confirmed open.
Click through it at your own pace, or open it full screen.
To try it on your own projects, download Safeguard and follow the CLI documentation. For background on why a trusted signing key changes everything, see what is a certificate authority.
Quick check
Your SSH client says the remote host identification has changed while you are connected through Tor. What do you do?
Stop. Do not remove the old known_hosts entry and do not type a password. Get the server's real fingerprint over a trusted path, such as the console or your configuration management, and compare. If it does not match, you are talking to someone else.
A server passes dpkg -V, debsums and the Tor Project's host checklist. Can you rule out Ebury 1.8.3?
No. The Quetzalcoatl post mortem says all of those were clean on infected hosts. Look for a setuid libkeyutils file that no package owns, an @event- abstract socket and stray processes in the ssh.service cgroup, from a rootkit bypass shell.
Sources
- kaba: Tor exit relay SSH interception write up (20 Oct 2026)
- tor-relays: Ebury 1.8.3 on two Quetzalcoatl relay servers, timeline, lessons, open questions (7 Oct 2026)
- tor-relays: Security incident affecting some relay operators, Tor network health team (5 Oct 2026)
- tor-relays: reply by Marc-Etienne M. Léveillé on Ebury 1.8.3 (8 Oct 2026)
- Tor Project: Bad relays
- Tor Metrics: Relays by relay flag
- ESET Research: Ebury is alive but unseen (May 2024)
- OpenBSD manual: ssh_config(5)
- OpenBSD manual: sshd_config(5)
- OpenBSD manual: sshd(8)
- OpenBSD manual: ssh-keygen(1)
- The Hacker News: Attackers hijacked over 300 Tor exit relays (21 Oct 2026)
- GBHackers: Hackers compromise 333 Tor exit nodes (21 Oct 2026)
- SecurityOnline: 333 Tor exit nodes hijacked by Ebury malware (21 Oct 2026)
- Cyber Security News: Hackers hijack 333 Tor exit nodes (21 Oct 2026)
Last reviewed: 22 October 2026. This is a developing story; check the linked advisories for the latest status.