Retroactive Grant Application - Zebra Block Suppression (Low) and IPv6 Ban Evasion Fix (Medium)

Hello Zcash Community!

I have submitted a second Q4 retroactive grant application, for two pieces of Zebra security work.

The first is my responsible disclosure of GHSA-4f6v-mj46-gxg3, a Low severity vulnerability that let any peer delay the newest block on a Zebra node again and again without ever being banned. The Zcash Foundation published the advisory crediting me as the sole reporter, and the Zebra 6.4.0 release notes thank me by name for reporting it.

The second is my fix to Zebra’s IPv6 peer limit, which let one cheap server fill all of a node’s connection slots. The Zcash Foundation then built on my fix to patch the Medium severity GHSA-jhpp-8h75-7pv5, where a banned peer could come straight back from another address on the same server. The Zebra 6.4.0 release notes credit my issue for both the IPv6 limit and the ban fix.

GitHub application: Retroactive Grant Application - Zebra Block Suppression (Low) and IPv6 Ban Evasion Fix (Medium) · Issue #111 · Financial-Privacy-Foundation/ZcashCoinholderGrantsProgram · GitHub

My CVE-2026-104423 application is a separate application, with no overlap.

1. Block suppression: GHSA-4f6v-mj46-gxg3 (Low)

When a new block is mined, nodes tell each other about it and then download it from whoever announced it. Unfortunately, on versions before 6.4.0 Zebra trusted the block height written inside the block’s first (coinbase) transaction before checking it, and because that height isn’t covered by the block’s hash, an attacker could change it without the block looking any different.

What an attack looked like

  1. An attacker connects to a Zebra node as an ordinary peer and announces a real, newly mined block.
  2. When the node asks for it, the attacker sends the real block with one change: the height inside the coinbase transaction is rewritten to 1.
  3. The block hash still matches, so Zebra reads height 1, decides the block is ancient and throws it away before ever validating it.
  4. Zebra also forgets which peer sent it, so the attacker is never penalized or banned and can do the same to the next block, and the next.

The node keeps running and eventually gets the block from an honest peer, but each new block is delayed for as long as the attacker keeps winning the download, which hurts the nodes feeding miners and pools the most, since they end up mining on an old block while the node is behind.

Disclosure and fix

I submitted the advisory on August 13 with a working proof of concept, and the Zcash Foundation accepted it on August 18. The fix (PR #11502) shipped in Zebra 6.4.0 on September 23, whose release notes thank me by name, and the advisory was published on September 28 as Low (CVSS 3.7), crediting me as the sole reporter. Zebra 6.4.0 and 6.4.1 were later recalled for an unrelated bug, so the advisory lists 6.4.2 as the patched version.

2. IPv6 peer limit, and the ban evasion fix built on it: GHSA-jhpp-8h75-7pv5 (Medium)

Zebra allows only one connection per IP address, which is the main thing stopping a single attacker from taking over all of a node’s connections. The problem is that on IPv6 every cheap server comes with a block of 18 quintillion addresses (a “/64”), so one machine could connect from a different address every time and the limit never kicked in.

What an attack looked like

  1. An attacker rents an ordinary server, which comes with its own /64.
  2. They connect to a Zebra node again and again, each time from a new address in that /64.
  3. Zebra counts every address as a different machine, so the one-per-IP limit never triggers and the attacker can fill every connection slot.
  4. With every slot filled, the node hears only from the attacker, which is the first step of an eclipse attack. Zebra had other defenses that slowed this down, but the per-IP limit was the main one, and it was bypassed from the first connection.

Disclosure and fix

I reported this in issue #11255 on August 13 and wrote the fix, PR #11256, which makes Zebra treat a whole IPv6 /64 (and IPv4 /24) as one machine everywhere it counts connections. It was reviewed by Conrado and oxarbitrage and merged on September 2.

Conrado then found that Zebra’s bans had the same problem: a banned IPv6 attacker could come straight back from another address in the same /64. He published that as GHSA-jhpp-8h75-7pv5 (Medium), which lists my issue and PR as references. The Foundation’s fix (PR #11502) applies the connection_limit_key() function from my PR to the ban list, so a ban now covers the whole /64. The Zebra 6.4.0 release notes credit my issue for both the IPv6 limit and the ban fix. Zakura also fixed the same IPv6 gap from my report (issue #645, fixed in PR #2243).

How I found these

After reporting CVE-2026-104423 in July, I kept studying the Zebra node, this time how it deals with its peers: who it connects to, how many, and what happens when one of them misbehaves.

Block suppression

When Zebra 6.3.0 fixed GHSA-g95h-hw6g-pvgv, a bug where a peer could trick Zebra with the unchecked block height inside a downloaded block, I wanted to know whether the same mistake existed anywhere else, so I did a variant analysis and found that the fix only covered the sync path, while the gossip path, where nodes pass along newly mined blocks, still read the same unchecked height.

From there I built a proof of concept on a local Regtest network, rewriting the height of a block the node hadn’t seen yet to 1 and serving the forged copy, and the node threw the block away as ancient without ever penalizing the peer that sent it, after which I submitted the advisory to the Zcash Foundation on August 13 with the PoC.

IPv6 peer limit

While learning how Zebra’s peer settings work, I noticed that its main defense against a single attacker taking every connection slot is max_connections_per_ip, a limit of one connection per IP address, and that Zebra had turned on IPv6 by default (#9609) without changing it, even though every cheap server comes with a /64 of 18 quintillion addresses.

Working through zebra-network with an LLM, I traced every place the limit is enforced and found none of them grouped addresses by subnet. I confirmed the bypass with a proof of concept against a Zebra 6.3.0 testnet node in Docker, then filed issue #11255 on August 13 with the fix ready in PR #11256, which, following Conrado’s review, counts the existing limit per /64 on IPv6 and per /24 on IPv4 at all three places Zebra enforces it.

Published proof

Block suppression

IPv6 peer limit and ban evasion fix

Releases

Requested Grant Amount (USD)

Work Basis Amount
Block suppression, GHSA-4f6v-mj46-gxg3 Low, sole reporter $18,750
IPv6 peer limit fix, built on by GHSA-jhpp-8h75-7pv5 Half of a Medium ($37,500) $18,750
Total $37,500

Amounts follow the former ZCG Core Node bounty table (Low $18,750, Medium $37,500), with no bonus.

2 Likes