NatJack: What a NAT-Table Hijack Means When You're the One Designing the NAT Boundary

The assumption that just quietly stopped being true

Every NAT device on the planet has been operating on the same unspoken deal since the 1990s: hosts sharing a NAT boundary don’t mess with each other’s connections. You trust the guy in the next cubicle, or the next container, or the next tenant, not to reach into the connection-tracking table and start moving things around. It was never really a security boundary. It was a convenience mechanism that got treated like one for three decades because nobody tested the assumption hard enough to break it.

At Black Hat USA 2026, independent researcher Malcolm Stagg did exactly that. NatJack is a class of attacks against the NAT connection-state table itself, not the traffic flowing through it, the table that tracks it. An attacker who already has a foothold on any device sharing your NAT boundary can manipulate that table to hijack an active TCP session, redirect traffic to their own endpoint, spoof DNS responses, or exhaust the table entirely for a denial of service. Two implementation-specific bugs picked up CVEs: CVE-2026-56181 in Windows NAT / Hyper-V, and CVE-2026-63913 in Linux Netfilter’s conntrack. The wider technique isn’t vendor-specific though. Routers, hypervisors, container bridges, cloud NAT gateways, all of it shares the same trust assumption, and testing found the behaviour nearly everywhere it was looked for. Thirteen vendors were notified; every one of the 32 tested products and configurations was vulnerable to at least some of the four techniques.

The part that should make you sit up isn’t the individual CVEs. Microsoft and the kernel team will patch those, and probably already have by the time you’re reading this. It’s the underlying lesson: “everyone behind this NAT is roughly trustworthy” is a threat model from an era where that was mostly true, and a lot of the infrastructure we run, homelabs, multi-tenant clouds, and any kind of shared service-provider hub, still quietly leans on it.

Where this actually bites: CVE-2026-63913 on Debian

To see this rather than just read about it, I built a dedicated Debian 13 (trixie) VM in my homelab as a proof-of-concept test bed for this post. uname -r puts it on 6.12.101+deb13-amd64, past the fixed release for that kernel line (6.12.93), so this particular box was never exposed to CVE-2026-63913 to begin with. Worth saying plainly rather than skipping past: what follows shows what conntrack pressure looks like in general, not a reproduction of the bug itself. The bug is a state-machine flaw, Netfilter accepting an out-of-direction, invalid-sequence RST without confirming it matches an opposing SYN, and this box had already shipped the fix.

The first thing worth knowing is that nf_conntrack isn’t always running. On a fresh install with no NAT or stateful firewall rule in place, lsmod | grep nf_conntrack came back empty, and /proc/sys/net/netfilter/nf_conntrack_max didn’t even exist yet. The module loads the moment something asks the kernel to track a connection, not before. Adding a real nftables rule that referenced connection state pulled it in immediately:

table inet filter {
  chain input {
    type filter hook input priority 0;
    ct state established,related accept
    ct state invalid drop
    iif lo accept
    tcp dport 22 accept
    counter drop
  }
}

That ct state invalid drop line is the actual interim mitigation for this class of bug, not a sysctl. It’s what catches a malformed, out-of-direction packet before it ever reaches the conntrack state machine, and it holds regardless of kernel patch level. With it in place, conntrack -L showed a normal working table: DNS lookups, an established SSH session, a couple of closed TLS connections, each with its own state and timeout.

The exhaustion technique is the one NatJack angle that’s genuinely reproducible without touching the actual state-machine bug, since it’s just resource pressure rather than a crafted packet. Capping nf_conntrack_max at 8 on the same disposable VM and opening 15 real TCP connections against a local listener filled the table exactly as expected. conntrack -L topped out at 8 flow entries, and dmesg logged the kernel dropping the rest outright:

nf_conntrack: nf_conntrack: table full, dropping packet

That’s the fourth NatJack technique in isolation: no spoofed sequence numbers, no off-path injection, just enough legitimate-looking connections to hit a ceiling. On a shared NAT boundary with an attacker-controlled tenant, that ceiling is reachable from inside the trust zone the whole design assumes is safe.

The angle nobody’s writing about: multi-tenant hubs

Most of the coverage this week is either academic vulnerability research or “here’s what it means for your home router.” eero published a customer note, which is fine as far as it goes, but it’s not the interesting case, and it isn’t really specific to any one vendor or business model either, whatever the framing of most coverage suggests.

The interesting case is any hub that terminates traffic from tenants who don’t trust each other: a service provider’s shared VPN concentrator, an ISP’s CGNAT pool, a cloud provider’s NAT gateway serving multiple customers, a managed SD-WAN hub. I’ve built and run networks like this myself, shared hub infrastructure carrying VPN sessions from sites and tenants that have no relationship to each other beyond sharing the same box. That’s not “a few trusted VMs behind a home router.” It’s the textbook NatJack scenario: a shared NAT/session boundary where you cannot assume the tenants on it are mutually trustworthy, because by design they aren’t. A compromised tenant site is exactly the “attacker-controlled device behind the same NAT” the research assumes, and it’s a structural feature of running shared hub infrastructure at all, not something specific to any one product.

This isn’t a single-vendor problem. It’s a category problem: every device that maintains a shared session or NAT table for multiple untrusted parties inherits the same exposure, regardless of who built it. Checked directly against FortiGuard PSIRT’s own advisory list, and there’s nothing there addressing NatJack as of this writing. Cisco, by contrast, has already gone on record, calling the wider research “design-level NAT limitations rather than security vulnerabilities” with documented mitigations for Secure Firewall and IOS XE. One vendor has spoken, most haven’t, and the silence isn’t reassuring given how broadly this technique class applies across the whole NAT/session-table category.

Rather than wait on an advisory that may never come, I pulled real session-table output from a FortiGate in my own lab (FortiOS 7.6.6), one example of the kind of device this affects, not because Fortinet is uniquely exposed. A FortiOS session is a heavier object than a bare conntrack entry. Where conntrack -L shows a 5-tuple, a state, and a timeout, diagnose sys session list on the same lab returned entries carrying a policy ID, VDOM binding, and explicit ingress/egress interface hooks tagged by direction:

hook=out dir=org act=noop 10.10.20.4:10979->10.10.20.10:541(0.0.0.0:0)
hook=in dir=reply act=noop 10.10.20.10:541->10.10.20.4:10979(0.0.0.0:0)

That dir=org / dir=reply split, tracked per hook rather than inferred from the packet in front of it, is the structural difference that matters here. CVE-2026-63913’s root cause was Netfilter accepting a state transition without confirming the packet’s direction against an opposing SYN. FortiOS’s session model carries direction as an explicit, separately tracked field rather than something derived on the fly from the table alone. That’s a real architectural difference, visible in the actual data above, and it’s a reasonable basis to expect FortiOS handles this specific ambiguity differently than a bare Netfilter table does.

It isn’t proof. Confirming it properly would mean testing the exact NatJack technique, a crafted invalid-sequence RST, against a live FortiOS session, and I’m not doing that even against my own lab hardware. Reproducing an off-path hijack technique to write a blog post is the wrong trade regardless of whose NAT table is on the other end. What the session data supports is a narrower, honest claim: this device’s session objects carry more directional context than the exact field Netfilter got wrong. That’s a real reason for cautious optimism about this one vendor, not a clean bill of health for shared-hub NAT devices as a category.

What actually helps, regardless of vendor

Stagg’s own recommendations, and they hold up:

  • Don’t assume co-tenancy is safe. Untrusted containers, VMs, or tenant sites sharing a NAT boundary with anything trusted is the whole precondition for the attack.
  • Encrypt internal traffic, not just perimeter traffic. If the session gets hijacked, TLS (or IPsec, in the VPN-hub case) is what stops the redirect from being useful.
  • Watch the NAT table itself. A near-full conntrack table, a flood of packets across an unusually wide port range, or the same source IP showing up on two physical paths are the tells. This is a monitoring problem as much as a patching one.
  • Isolate cloud and container workloads onto dedicated NAT gateways rather than sharing one across trust boundaries by default.

If you’re coming at this from the Linux side, it sits naturally alongside the iptables to nftables migration guide and the rest of the kernel CVE tracking. If you’re coming at it from the Fortinet side, Part 4 of the FortiGate packet walkthrough covers exactly the NAT and session-table mechanics that NatJack is targeting, worth a re-read with this attack class in mind.