Free VPNs Everywhere -- Tunnel Injection

Chumy | Aug 29, 2025 min read

Tunnel Injection

Chinese

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.

image

image

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.

image

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.

image

image

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.

image

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.

image

image

Including a paper published just this January: research

image

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.

image

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.

image

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

image

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.

image

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.

image

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.

image

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

image

When the victim’s router receives the response, conntrack restores the original destination IP and forwards it according to its routing table.

image

And now the attacker receives the response.

image

This enables an Arbitrary Interactive Pivoting Attack via Tunnel.

image

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.

image

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.

image

As the packets traverse the routers, NAT is applied and the conntrack table records a NAT mapping entry.

image

Both packets reach each other’s routers, where conntrack already has a matching NAT entry.

image

Thus, the destination IP is restored based on conntrack records.

image

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

image

The packet reaches the victim router, gets forwarded, NATed, and logged in conntrack.

Hole punched!

image

Now we send a TCP SYN toward that hole:

  • Source IP = attacker’s public IP
  • Destination IP = victim internal machine’s private IP

image

The router restores the destination IP via conntrack and forwards it to the victim’s internal machine.

image

The victim machine responds with TCP SYN/ACK.

image

And the source IP is NATed normally to the victim’s public IP.

image

The attacker replies with ACK.

image

Now the connection is established, and the attacker can interactively communicate with the internal network.

image

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.

image

Another interesting parameter is net.netfilter.nf_conntrack_tcp_loose

image

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:

  1. Use Tunnel Injection to send a TCP RST from inside, freeing the conntrack record.
  2. Wait for the victim host to retransmit its TCP PUSH.
  3. 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

image

The packet reaches the victim router, gets forwarded, NATed, and logged in conntrack.

image

Now we send a TCP SYN toward that hole:

  • Source IP = attacker’s public IP
  • Destination IP = victim internal machine’s private IP

image

The router restores the destination IP via conntrack and forwards it to the victim’s internal machine.

image

The victim machine responds with TCP SYN/ACK.

image

And the source IP is NATed normally to the victim’s public IP.

image

The attacker replies with ACK.

image

Attacker sends a PUSH (e.g., HTTP request).

image

Initially dropped. Victim retransmits repeatedly.

image

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.

image

We then use Tunnel Injection to send a TCP RST from inside, manually freeing the conntrack record.

image

After freeing it, when the internal host retransmits the TCP PUSH

image

It will be successfully NATed, and conntrack will log a new NAT record in the ESTABLISHED state.

image

Now the connection is fully established, and we can happily send data into the internal network.

image

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:

  1. Changing the outbound packet’s source IP to the victim’s public IP
  2. 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..

image

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

image

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.

image

We then send a TCP SYN so that the first-layer router’s conntrack record transitions into the SYN_SENT2 state.

image

This packet is dropped by RPF when forwarded to the target.

image

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

image

image

The TCP SYN is NATed by the second-layer router, and a conntrack entry is created.

image

It is then NATed again by the first-layer router, and a conntrack entry is created.

image

The target responds with SYN/ACK, and the destination IP is restored according to conntrack.

image

The destination IP becomes the WAN IP of the second-layer router, which forwards it, restoring the destination IP according to its conntrack record.

image

The destination IP then becomes the attacker’s public IP and is sent to the first-layer router.

image

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.

image

The attacker finally receives a packet with the source IP rewritten to the victim’s first-layer router’s public IP.

image

From here, the process is the same as described earlier in Bypass for Internal Network Access.

ACK is returned.

image

PUSH is sent.

image

RST is sent to free the conntrack record.

image

The target continues retransmitting.

image

It is successfully NATed, and conntrack now records an ESTABLISHED entry.

image

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.

image

First, we craft a multicast ping inside the victim’s internal network, using a multicast MAC address and the attacker’s public IPv6 address.

image

The switch will multicast this ping to all devices subscribed to that multicast MAC address.

image

The internal victim devices will then reply with ICMP pong messages back to the attacker.

image

As a result, we can obtain all IPv6 addresses of devices in the internal network.

image

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.

image

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.

image

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.

image

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.

image

image

image

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.

image

image

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

image

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.

image

The target will reply with an ICMP Echo Reply, which returns to the victim router.

image

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.

image

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
  • IPIP: 76,209 hosts
    • With NAT enabled: 23,851 hosts
      • Externally accessible: 5,438 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:

image

IPIP:

image

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