← Kota Urushigaki

Three practical attacks on the Session messenger

How a one-way key check and a missing message counter let a malicious server node, or simply the person you are chatting with, impersonate, reorder, drop and replay messages.

In short

  • Session’s deployed protocol lets the person starting a chat verify the other side’s key, but not the other way round. A malicious server node can use this to impersonate a new contact.
  • Messages are ordered by a timestamp the sender chooses, with no counter behind it. Any chat partner can backdate a message so it appears before your reply.
  • Without counters, a malicious server node can also drop messages without a trace, and replay them by exploiting how deduplication trusts server-computed hashes.

Why look at Session?

Session is a decentralized, end-to-end encrypted messenger that needs no phone number or email address to sign up. It is often recommended to people who need privacy most, such as activists and journalists, and it has been downloaded more than a million times on Google Play alone.

It began as a fork of Signal, whose protocol is among the most heavily analyzed in cryptography. In late 2020, Session replaced it with a design of its own, which we call Session Protocol V1, arguing that Signal’s assumptions fit a centralized server, not Session’s network of independently operated nodes. A 2021 audit by Quarkslab looked at the application, but the migration happened near the end of that audit, so the deployed design was only partly covered.

A custom protocol running on a permissionless network is exactly where guarantees that held in a centralized setting can quietly disappear. So instead of starting from the whitepaper, we read the code that actually ships: the iOS client, the libsession-util cryptographic library and the storage server.

How a Session message travels

Every user has two long-term key pairs: an Ed25519 key for signing and an X25519 key for key agreement. To message Bob, Alice first needs Bob’s public key, which she gets out of band by typing his Account ID or scanning his QR code. Note the asymmetry already: Alice learns Bob’s key, but Bob learns nothing about Alice’s.

For each message, Alice creates a fresh ephemeral key pair, derives a key with Bob’s long-term key, signs the message with her identity key, and encrypts it with NaCl’s crypto_box construction (XSalsa20-Poly1305):

K ← X25519(eph_A_priv, DH_Bob_pub) nonce ← BLAKE2(eph_A_pub, DH_Bob_pub) M ← m ‖ idk_A_pub ‖ Sign(idk_A_priv, m ‖ idk_A_pub ‖ DH_Bob_pub) body ← (eph_A_pub ‖ Enc(K, nonce, M), timestamp_inner, …) packet ← body, Sign(idk_A_priv, body ‖ "store" ‖ namespace ‖ timestamp_outer)

Two timestamps matter later. The inner timestamp tells Bob’s client where to place the message in the conversation. The outer timestamp lets server nodes expire old messages and reject replays.

There is no central server. Each user’s public key maps to a swarm, a small group of storage nodes. Alice sends the packet through onion routing to one node in Bob’s swarm, the entry node, which checks the signature and timestamp window, shares the message with the rest of the swarm, and stores it under a hash. Bob later polls a random node in his swarm, sending the hash of the last message he received, and gets back everything newer along with each message’s hash. His client uses those hashes to skip duplicates.

Alice Bob Bob’s swarm (≈ 6 nodes) entry polled 1. store 2. poll(last hash) 3. new msgs + hashes
Figure 1. Messages are stored in the recipient’s swarm and fetched by polling. Any node in the swarm can be the entry node for a send or the node that answers a poll.

Who is the attacker?

Session assumes neither clients nor servers are fully trusted, so we considered three adversaries.

Against these, we checked the integrity goal stated in Session’s own whitepaper, the standard notions of confidentiality and authentication, and two properties a secure messenger should have: public key authenticity in both directions, and message ordering that is cryptographically protected rather than based only on what the sender claims.

Flaw 1: key authentication only goes one way

Alice verified Bob’s key out of band. Bob, receiving a first message from someone new, has nothing to check the sender’s key against. Signal and WhatsApp also trust a key on first use, but there the server is run by a single operator. In Session, the node that relays this first message can be anyone who paid the stake.

That gives the impersonation attack. Suppose Eve runs a node in Bob’s swarm and happens to be the entry node when Alice first writes to Bob.

Alice Eve (malicious node) Bob Bob’s public key, out of band (Account ID or QR) eph_A ‖ Enc_K(m ‖ idk_A ‖ sig_A) discard Alice’s message K′ ← X25519(eph_E, DH_Bob) sign m′ with Eve’s own idk_E eph_E ‖ Enc_K′(m′ ‖ idk_E ‖ sig_E) decrypts, verifies: all checks pass Bob has no earlier key for “Alice” to compare idk_E against
Figure 2. The impersonation attack. Every signature and decryption is valid, because Eve uses real keys; they are simply not Alice’s.

How realistic is it?

In November 2025 the network had 1,596 active nodes in 274 swarms, about 5.8 nodes per swarm. Nodes cannot pick their swarm, so one attacker node lands in a given user’s swarm with probability about 1/1,596, and is then the entry node for a first message about 1 time in 5.8. That is small for one chosen victim, but an attacker does not need one chosen victim. Against a whole organization, or simply anyone, the odds add up, and every additional 2,700 USD node raises them linearly.

Session’s defenses are a separate inbox for message requests, and a key fingerprint that users can compare manually. The fingerprint is a 32-byte hex string, a format that user studies show people are poor at comparing. Worse, accounts can use any key pair, so an attacker can mine a key whose fingerprint matches the victim’s at the start and end. Matching 24 bits at each end is a 248 search, and an attacker with a whole swarm of roughly 212 users to choose from brings that to about 236, which is easy on commodity hardware.

Flaw 2: no counter behind the message order

The second flaw underlies the other two attacks. Session binds no message to a sequence number or ratchet state. Order comes entirely from the inner timestamp, and that timestamp is set by the sender. It is signed, but signing only proves the sender chose it, not that it is true. Server nodes check the outer timestamp, not the inner one, and clients enforce no strict window.

Attack 2: timestamp forgery by a chat partner

This attack needs no server at all. Any participant in a 1-to-1 or group chat can backdate or postdate their own messages. Picture a negotiation:

Order actually sentWhat Bob’s chat shows 13:03 · EveThe service fee is 10,000 USD. 13:10 · BobI agree to these terms. 13:17 · Eve, stamped 13:09…including an additional compliancesurcharge of 5,000 USD. 13:03 · EveThe service fee is 10,000 USD. 13:09 · Eve…including an additional compliancesurcharge of 5,000 USD. 13:10 · BobI agree to these terms. Every signature verifies. The protocol sees nothing wrong.
Figure 3. The contract example from the paper. Bob now appears to have agreed to a clause he never saw.

In our testbed, the attacker even restores a consistent view on their own device by clearing local state and logging in again, so both sides show the same, false, history. The attack also undercuts deniability. Session signs messages with long-term keys and tries to compensate by discarding signatures after verification, but an insider can simply keep them. The forged conversation then comes with signatures that look like proof.

Attack 3: dropping and replaying messages

A malicious node in the recipient’s swarm gets two more capabilities from the missing counter.

Drop Alice malicious discarded before propagation Bob never learns a message existed Replay malicious msg · hash h msg · forged hash h′ msg · forged hash h″ Bob poll 3 “new” messages
Figure 4. Dropping and replaying. With about 5.8 nodes per swarm, a malicious node gets picked as an entry or polling node after only a few rounds.

Neither attack reveals message content, but both quietly desynchronize what each participant believes was said. In a group, a malicious administrator can make it targeted: by generating group keys until one maps to a swarm containing a colluding node, they can drop or replay specific members’ messages while everything looks like normal network behavior.

This replay is different from the one Jaeger and Kumar found in Session’s legacy group protocol (EUROCRYPT 2025), which relied on timestamps that were not signed. That issue was fixed in the newer GroupsV2 protocol. Ours works against GroupsV2 and 1-to-1 chats, because it comes from the missing counter, not from a missing signature.

Status of the replay attack. In its technical assessment in late April 2026, the Session team classified the replay attack as unexploitable, because an incidental database configuration on the storage server currently blocks it. That protection is a side effect, not a designed security boundary: the protocol’s intended duplicate detection is still bypassed, and the protection would no longer hold if that configuration changed.

Reproducing it

We did not attack the live network. The stagenet only covered blockchain functions, not messaging, so we built a fully local copy instead and published it as the paper’s artifact, which received the USENIX Artifact Available badge.

Disclosure

DateWhat happened
Dec 2025First disclosure, covering the impersonation attack
Jan 2026Second attempt, before our domestic presentation at SCIS 2026
Mar 2026Report covering all three attack classes; the Session team acknowledged receipt
Late Apr 2026Technical assessment from the Session team: most findings accepted; replay classified as unexploitable because of an incidental database configuration
Jul 2026Remediation in progress; full patches still pending

How to fix it

Make key verification mutual and usable. Both users should verify each other, by scanning each other’s QR codes or comparing a short numeric code in person, like Signal’s safety numbers. Splitting a numeric fingerprint into blocks also makes the partial-match keys from mining far easier to spot than a 64-character hex string.

Bind messages to an ordered state. The principled fix is a ratchet, as in Signal, WhatsApp, Threema and Matrix: each message is tied to an evolving chain of keys, so reordering, replay and omission become detectable, and forward secrecy comes along. Session dropped ratcheting partly because of complexity, and its announced Protocol V2 aims at forward secrecy and post-quantum security, which is a natural place to bring it back.

Until then, two cheap measures help. Servers and clients can reject or flag messages whose timestamps fall outside a fixed window around delivery time, trading tolerance for clock skew against room for forgery. And clients can recompute each message’s hash themselves instead of trusting the server’s, which stops replays under forged hashes, the same fix our co-author Hayato Kimura and colleagues proposed for a similar binding flaw in Nostr.

What I took away

Every signature in these attacks verifies. Nothing is broken at the level of primitives. What fails is the glue around them: which key is checked against what, and what a signature actually promises. That glue is also what changes when a protocol designed for a central server moves onto a network where anyone can run the server. Trust on first use is a reasonable trade-off when one company runs the relay; it is a different bargain when the relay costs 2,700 USD to join.

It also confirmed for me that reading the shipped code is worth the effort. None of these issues appear in the whitepaper. They show up in how the client deduplicates, which timestamp the server checks, and who the entry node happens to be.

Concurrently with our work, Yu and Haines analyzed Session together with the Oxen blockchain it runs on, in a paper at EuroS&P. If you are interested in Session’s network layer, it is worth reading alongside ours.