Tunnel Injection
TL;DR
This blog discusses the flaws of most Stateless Tunnel Protocols and how I turned them into a free VPN that can be set up with just 5 lines of Linux ip
commands.
Acknowledgments
Before getting into the main topic, I’d like to thank seadog007 for STUIX, since my network’s ASN primarily peers through STUIX, and I also used a lot of their resources for testing.
Special thanks as well to NCSE for helping me by adding some IPT traffic during my testing.
Introduction
This is a little story about how I went from researching a protocol to essentially giving the whole world access to my VPN.
Here’s how it happened: around my freshman year, I heard about this cool thing called Segment Routing v6 at a Cloud Native event. So, during my sophomore year, I started digging into it a bit and got a general understanding of its principles. As for how it actually works—since that’s not the focus of this post—I won’t go into detail here. I may publish another article on it in the future.
By junior year, I received an invitation from 2024 CGGC (a CTF held in Taiwan) to contribute a challenge, which got me brainstorming for ideas.
Then one day, while showering and replaying everything in my head, I suddenly came up with this really cool exploitation method. Fun fact: this particular challenge was only solved by seadog007.
This is a detailed diagram of the attack method that seadog007 drew for me.
What is Tunnel
The tunnel most people are familiar with is the VPN, since a VPN is essentially just a type of tunnel. The core idea of a VPN is a virtual network built out of multiple encrypted or unencrypted tunnels.
Its working principle is actually quite simple—it’s basically the concept of putting an envelope inside another envelope.
In networking terms: when a packet leaves the tunnel interface, the program handling the tunnel takes the contents of that packet, encapsulates it according to the tunnel protocol, and then sends it out via TCP, UDP, or some Layer 3 protocol payload—through either a physical NIC or another lower-layer tunnel interface.
On the receiving side, the program listening for this tunnel protocol takes incoming data from the listening port, strips off the tunnel protocol header, and delivers the inner raw data directly as a Layer 3 or Layer 2 packet to the tunnel interface.
A stateless tunnel protocol simply means: whenever it receives a packet in that protocol format, it just decapsulates and forwards it—without performing any kind of verification or handshake.
Impact
So what happens if there’s no verification? Basically, as long as I can forge a packet that matches the tunnel protocol format, I can inject malicious packets through this unprotected tunnel and deliver them into the victim’s internal network.
However, past research has generally considered this kind of attack to be one-way—at most enabling DDoS attacks.
Including a paper published just this January: research
That study covered many scanning methods for exposed stateless tunnels, as well as issues with certain router models not verifying source IPs. But its main focus was still on DDoS attack surfaces.
In reality, you only need to shift your perspective a little: we don’t actually need traffic to flow back and forth through the tunnel.
The real essence of the attack is simply being able to inject traffic into the tunnel. That’s why I’ve decided to call it Tunnel Injection.
Tunnel Injection to Internal Network
The first exploitation method allows us to achieve Interactive Internal Network Access.
We begin by crafting a forged packet that conforms to the tunnel protocol.
The outer layer:
Source IP = attacker’s public IP (or in some tunnel protocols, since source IP verification is performed, this might require IP spoofing).
Destination IP = victim tunnel’s public IP.
The inner layer:
Source IP = attacker’s public IP ← this is the core idea here.
Destination IP = the private IP of the victim’s internal machine you want to target.
So what happens once we send it out?
When the packet reaches the victim’s router, it gets decapsulated. The router then forwards the inner packet according to its routing table, while leaving behind a conntrack entry for the forwarding record.
The internal machine will then see a packet with:
src = public IP, dst = victim’s internal machine’s IP
Naturally, it replies with:
src = victim’s internal machine’s IP, dst = public IP
When this reply reaches the victim’s router, because the destination is the public IP, it simply gets forwarded out via the default gateway.
One important note: most routers won’t SNAT “odd” packet types like TCP SYN/ACK or ICMP Echo Reply (pong). So when these reply packets leave the victim’s router, they are not NATed. In such cases, the source IP remains the private IP, which may cause issues in certain scenarios.
Finally, the attacker receives these responses from the victim’s internal network.
This enables Arbitrary Interactive Internal Network Access.
Tunnel Injection to External Network
Before explaining the attack, let’s first look at how NAT is usually implemented—using Linux as an example.
On a Linux kernel–based router, NAT is handled with netfilter and conntrack. The common user-facing tools are iptables and nftables. Here we’ll take iptables as an example.
Normally, to enable NAT, you might use:
iptables -t nat -A POSTROUTING -o wan -s 192.168.0.0/16 -j MASQUERADE
This rule means: when a packet leaves the wan interface, if its source IP matches 192.168.0.0/16, then perform NAT using the wan interface’s public IP as the new source IP.
But in reality, many router vendors take a shortcut and simply write:
iptables -t nat -A POSTROUTING -o wan -j MASQUERADE
This means: whenever a packet leaves the wan interface, NAT it with the wan interface’s public IP as the source.
Notice what’s missing—the source IP check. That means if the source IP is already a public IP, it still gets NATed. After all, RFC 1918 is just a definition; nothing stops you from using a public IP as if it were private.
Now, what happens if this router is also running an exposed tunnel?
The answer: we get a free proxy jump point.
In practice, all we need to do is modify the inner destination IP from the Tunnel Injection to Internal Network example—this time setting it to the target public IP on the external network.
Again, we forge a tunnel protocol–compliant packet:
The outer layer:
Source IP = attacker’s public IP (or spoofed).
Destination IP = victim tunnel’s public IP.
The inner layer:
Source IP = attacker’s public IP
Destination IP = the target public IP you want to access.
When the packet arrives at the victim’s router, it gets decapsulated, forwarded according to the routing table, NATed according to the iptables
rule above, and logged into conntrack as a NAT entry.
The target then receives the packet and replies. Since NAT has already rewritten the source IP, the target sees:
- src = victim public IP, dst = target public ip
So its response is:
- src = target public ip, dst = victim public IP
When the victim’s router receives the response, conntrack restores the original destination IP and forwards it according to its routing table.
And now the attacker receives the response.
This enables an Arbitrary Interactive Pivoting Attack via Tunnel.
RPF or source ip verify bypass
Just when you think you’re done, reality often hits you in the face.
Remember what we mentioned earlier in Tunnel Injection to Internal Network:
Most routers don’t perform SNAT on unusual packet types like TCP SYN/ACK or ICMP type 0 (pong).
So when the response packet leaves the victim router, it won’t be NATed—meaning the source IP remains private.
This can be problematic in certain scenarios.
And that “scenario” refers to Reverse Path Forwarding (RPF) or source IP verification.
Most ISPs, in order to prevent customers from launching IP spoofing attacks, will enable RPF on client-facing interfaces.
RPF works like this: when an interface receives a packet, it checks the source IP against the routing table. If the source IP doesn’t match any route that should arrive via that interface, the packet is dropped.
So if the victim’s ISP enforces this protection, you won’t receive a response. How do we bypass this?
Bypass for Internal Network Access
Let’s take a look at how P2P networks implement NAT hole punching.
If both sides already know (or can predict) each other’s source ports—in the case of P2P applications, a STUN server usually helps clients discover their NATed source ports—then both sides can simultaneously send a packet to the other’s public IP and port. Let’s use UDP as an example.
As the packets traverse the routers, NAT is applied and the conntrack table records a NAT mapping entry.
Both packets reach each other’s routers, where conntrack already has a matching NAT entry.
Thus, the destination IP is restored based on conntrack records.
That’s the basic flow of NAT hole punching. With TCP it’s more complex since TCP is stateful—look up TCP Simultaneous Open for details. In fact, HITCON CTF 2025 even had a challenge based on this, since TCP Self-Connect is essentially a special case of TCP Simultaneous Open.
By the way, I only fully understood the details of P2P transmission because back in high school I hand-coded the entire NAT hole punching process and even built my own STUN server (though looking back, the code is a bit ugly). Some people ask why reinvent the wheel, but I believe building it yourself is both fun and the best way to learn.
So we can think of NAT hole punching as a restricted form of port forwarding triggered from the inside.
Now—since Tunnel Injection allows us to inject packets from the inside out, could we manually perform NAT hole punching and then directly access the internal network from the outside?
Yes, we can. Let’s use TCP as an example.
We use Tunnel Injection to send a TCP SYN from within the victim’s internal network to perform hole punching.
- Source IP = victim internal machine’s private IP
- Destination IP = attacker’s public IP
The packet reaches the victim router, gets forwarded, NATed, and logged in conntrack.
Hole punched!
Now we send a TCP SYN toward that hole:
- Source IP = attacker’s public IP
- Destination IP = victim internal machine’s private IP
The router restores the destination IP via conntrack and forwards it to the victim’s internal machine.
The victim machine responds with TCP SYN/ACK.
And the source IP is NATed normally to the victim’s public IP.
The attacker replies with ACK.
Now the connection is established, and the attacker can interactively communicate with the internal network.
However, this process only works reliably for UDP.
For TCP, it only works on some routers or older Linux kernels. Modern Linux kernels perform strict conntrack validation of TCP Simultaneous Open. If one side never receives the SYN/ACK, the state machine remains stuck at SYN_SENT2. In this case, even if the internal host sends a TCP PUSH, it will not be NATed to the victim’s public IP.
Since the attacker’s SYN/ACK never reaches the victim’s internal host, the connection never transitions to ESTABLISHED.
Could we solve this by manually sending the attacker’s SYN/ACK to the victim’s host?
Not really—because conntrack tears down the record whenever a TCP RST is seen, and most systems will respond to an unknown SYN/ACK with an immediate RST. (After all, the victim host never initiated the SYN, so it doesn’t recognize this session.)
While this trick may work in some networks where RST packets are dropped, we should assume the default case: it will fail.
So how to fix?
Looking at sysctl parameters, we find that conntrack’s TCP close state timeout is only 10 seconds. After that, the record is freed.
Another interesting parameter is net.netfilter.nf_conntrack_tcp_loose
By default, this is set to 1.
What does it mean?
When nf_conntrack_tcp_loose = 1
, conntrack allows NAT to be applied to stray TCP PUSH or ACK packets even if no matching session exists. In other words, when there is no active conntrack entry, an outbound TCP PUSH or ACK can create a new NAT entry in the ESTABLISHED state.
This opens up an opportunity: because the close-state timeout is short and TCP retransmits unacknowledged packets, we can:
- Use Tunnel Injection to send a TCP RST from inside, freeing the conntrack record.
- Wait for the victim host to retransmit its TCP PUSH.
- Conntrack treats this retransmit as a new session, installs an ESTABLISHED NAT entry, and the attacker can now communicate interactively.
So here is the full flow recap
Use Tunnel Injection to send a TCP SYN from inside the victim’s network:
- Source IP = victim’s private IP
- Destination IP = attacker’s public IP
The packet reaches the victim router, gets forwarded, NATed, and logged in conntrack.
Now we send a TCP SYN toward that hole:
- Source IP = attacker’s public IP
- Destination IP = victim internal machine’s private IP
The router restores the destination IP via conntrack and forwards it to the victim’s internal machine.
The victim machine responds with TCP SYN/ACK.
And the source IP is NATed normally to the victim’s public IP.
The attacker replies with ACK.
Attacker sends a PUSH (e.g., HTTP request).
Initially dropped. Victim retransmits repeatedly.
Because of the mechanism explained above, the PUSH packet from the internal host is dropped. The attacker doesn’t receive it, doesn’t ACK it, and the internal host keeps retransmitting.
We then use Tunnel Injection to send a TCP RST from inside, manually freeing the conntrack record.
After freeing it, when the internal host retransmits the TCP PUSH
It will be successfully NATed, and conntrack will log a new NAT record in the ESTABLISHED state.
Now the connection is fully established, and we can happily send data into the internal network.
With this, we have successfully achieved RPF bypass for Internal Network Access.
Bypass for External Network Access
The previous section was simpler because the entire bypass process only required a single NAT operation. However, for External Network Access, bypassing requires performing two NAT translations:
- Changing the outbound packet’s source IP to the victim’s public IP
- Changing the return packet’s source IP to the victim’s public IP
So, how do we achieve this?
If the victim’s internal network has another layer of tunnel combined with NAT, we can build a NAT Chain..
We use Tunnel Injection to send a TCP SYN from the victim’s first-layer internal network to perform hole punching.
- Source IP = target’s public IP
- Destination IP = attacker’s public IP
When the packet arrives at the victim’s first-layer router, it is forwarded according to the router’s routing table, NAT source IP translation is applied, and a conntrack entry is created.
We then send a TCP SYN so that the first-layer router’s conntrack record transitions into the SYN_SENT2 state.
This packet is dropped by RPF when forwarded to the target.
Next, we use Tunnel Injection from the victim’s second-layer internal network to send a TCP SYN to the target.
- Source IP = attacker’s public IP
- Destination IP = target’s public IP
The TCP SYN is NATed by the second-layer router, and a conntrack entry is created.
It is then NATed again by the first-layer router, and a conntrack entry is created.
The target responds with SYN/ACK, and the destination IP is restored according to conntrack.
The destination IP becomes the WAN IP of the second-layer router, which forwards it, restoring the destination IP according to its conntrack record.
The destination IP then becomes the attacker’s public IP and is sent to the first-layer router.
Here’s the interesting part: the source IP is the target’s public IP, which matches the NAT record that was previously created in the first-layer router.
So the source IP is NATed again according to conntrack.
The attacker finally receives a packet with the source IP rewritten to the victim’s first-layer router’s public IP.
From here, the process is the same as described earlier in Bypass for Internal Network Access.
ACK is returned.
PUSH is sent.
RST is sent to free the conntrack record.
The target continues retransmitting.
It is successfully NATed, and conntrack now records an ESTABLISHED entry.
Thus, we have successfully achieved RPF bypass for External Network Access.
Moreover, the use of a NAT Chain is not limited to bypassing RPF. It can also be used during Internal Network Access to bypass server-side source checks, since some servers also enforce their own firewall rules.
L2 Tunnel Abuse
Earlier, we mainly talked about exploiting L3 tunnels. L2 tunnels are a bit more troublesome because they require an additional Ethernet header. This means we need the router’s or an internal machine’s MAC address to make use of them—or alternatively, we can attempt to exploit broadcast or multicast.
So, can we use broadcast or multicast to leak useful information such as MAC addresses?
Yes, it’s possible.
When an internal network not only uses IPv4 but also enables IPv6 with unicast addresses, ICMPv6 requests are allowed to be multicast by default. This means we can use multicast IPv6 ping to leak IPv6 addresses.
First, we craft a multicast ping inside the victim’s internal network, using a multicast MAC address and the attacker’s public IPv6 address.
The switch will multicast this ping to all devices subscribed to that multicast MAC address.
The internal victim devices will then reply with ICMP pong messages back to the attacker.
As a result, we can obtain all IPv6 addresses of devices in the internal network.
Now, what can we do with these IPv6 addresses?
According to RFC 4291, one method of dynamically generating SLAAC IPv6 addresses is based on the host’s MAC address.
An IPv6 address generated in this way can be reversed back into the original MAC address. By default, Linux uses this method to generate IPv6 addresses.
In addition, another researcher from Trend Micro, 123ojp, has also been researching this type of attack vector. He discovered even more efficient exploitation methods specifically targeting VXLAN, an L2 tunnel protocol. If you are interested, check out his talk at DEFCON 33.
IPSec
Some may assume that simply enabling IPSec is enough to protect against these kinds of attacks. However, in reality, if your IPSec is misconfigured, you can still be vulnerable—even with IPSec enabled.
In theory, once IPSec is enabled, all packets of the specified protocol should be intercepted by xfrm before leaving the NIC, encrypted, and then transmitted. Likewise, any raw packets of the specified protocol entering the NIC should be dropped.
However, xfrm has a special parameter called level. This parameter allows outbound traffic to be encrypted while still permitting raw inbound packets. Although this setting is not enabled by default, you never know what your IT team might do after struggling all day to configure IPSec without success—they might flip something they shouldn’t out of frustration.
As a side note: on EdgeOS, even when the level attribute is set to its default value require, raw packets may still be allowed. This was discovered by my friend zen, the current network administrator of NCKU CCNS. However, this finding is still pending further confirmation.
Scanning and Real World
Finally, let’s talk about scanning.
Although this research has already described concrete scanning methods, I’ll share my own approach here.
Internal access able
We can test this by injecting an ICMP Echo Request into the victim’s tunnel, originating from inside the victim’s internal network:
- Source IP = private IP
- Destination IP = scanner’s IP
In the ICMP ping payload, we include some marker information along with the tunnel’s outer source IP and destination IP.
If we later receive the exact same ICMP ping on the WAN side, we can confirm that the victim’s router has an exposed tunnel for that protocol.
Then, by checking whether the source IP of the received packet is rewritten to a public IP, we can determine whether the victim router performs NAT.
Next, to check for RPF (Reverse Path Forwarding), we can inject an ICMP Echo Reply (pong) through the victim’s tunnel:
- Source IP = private IP
- Destination IP = scanner’s IP
As mentioned earlier in Tunnel Injection to Internal Network, “Routers generally do not SNAT unusual packets such as TCP SYN/ACK or ICMP type 0 (pong).”
Therefore, packets leaving the victim router will definitely have a private source IP.
By simply checking whether the scanner receives the reply, we can tell whether the victim’s ISP enforces RPF.
External access able
Since the set of externally accessible tunnels is necessarily a subset of the internally accessible tunnels with NAT, we can take that filtered IP list and scan it again.
The setup is the same, but this time we add a controlled target host.
We inject an ICMP Echo Request into the victim’s tunnel from its internal network:
- Source IP = scanner’s IP
- Destination IP = target host’s IP
If the tunnel is externally accessible, the victim router should NAT the source IP to its public IP and forward the packet to the target. The target must be configured to log all incoming packets.
The target will reply with an ICMP Echo Reply, which returns to the victim router.
The victim router restores the destination IP and sends the packet back to the scanner.
At this point, by comparing the scanner logs with the target logs, we can determine exactly which tunnels are externally accessible.
Scan Results
Here are the results of scanning tunnels that do not require IP spoofing:
In total:
- GRE: 230,035 hosts
- With NAT enabled: 191,961 hosts
- Externally accessible: 22,875 hosts
- With NAT enabled: 191,961 hosts
- IPIP: 76,209 hosts
- With NAT enabled: 23,851 hosts
- Externally accessible: 5,438 hosts
- With NAT enabled: 23,851 hosts
Since some ISPs assign dynamic IPs, there may be slight inaccuracies in these numbers.
We also analyzed the ASNs of these IPs. As expected, a large portion belonged to Chinese IP ranges.
GRE:
IPIP:
Conclusion
In the end, it was a bit unfortunate — because 123ojp happened to be working on the same research slightly earlier, I didn’t end up submitting this work to HITCON 2025.
That said, I still managed to present it as a HITCON Lightning Talk, where I shared this interesting finding and also introduced the Tunnel Injection To External Network technique that 123ojp hadn’t mentioned or considered. In the end, it turned into a fun and engaging discussion.
Going forward, I might try to submit this research to other security conferences.
As a side note: although all of the demos above were implemented with custom hand-written tools, in theory — if you don’t need to perform RPF Bypass — the same internal and external attacks can actually be executed with just five lines of Linux ip commands. I’ll leave that as homework for you all. XXD