"Pivoting is the act of tunneling traffic between two networks, through a computer under our control"
In part one of the SSH pivoting guide, I'll provide a detailed (pedantic) guide of the easiest form of pivoting which is depicted by the green arrow in the diagram above. If you would like to reproduce this pivoting guide in your local lab, click on the following links for installation guides:
All commands are run from the attacker machine "kali" unless explicitly stated
Pivoting is the act of tunneling traffic between two networks, through a computer under our control. Generally, pivoting is used to bypass a lack of routing between the networks, or to bypass a firewall which prevents routing between the two networks. There are various tools that can be used to pivot, including ssh, sshuttle, meterpreter, iptables, python, a custom tool etc.
Due to the proliferation of secure shell (SSH) it makes sense to first detail how ssh can be used to pivot.
The two commands to pivot through 10.123.1.210 to 10.2.1.22:2222 using SSH local port forwarding are:
ssh -L 127.0.0.1:9922:10.2.1.22:2222 firstname.lastname@example.org ssh 127.0.0.1 -p 9922
We will go into detail of what exactly happens 'under the hood' when we enter those two commands on your Kali Linux machine.
The best place to learn about the SSH protocol is the RFC document that describes it. There are a few RFCs that make up the description of the complete SSH protocol and the RFC that describes the SSH connection protocol is available at https://www.ietf.org/rfc/rfc4254.txt
Section "7. TCP/IP Port Forwarding" describes the component we will use to pivot, specifically:
7.2. TCP/IP Forwarding Channels When a connection comes to a locally forwarded TCP/IP port, the following packet is sent to the other side. Note that these messages MAY also be sent for ports for which no forwarding has been explicitly requested. The receiving side must decide whether to allow the forwarding. byte SSH_MSG_CHANNEL_OPEN string "direct-tcpip" uint32 sender channel uint32 initial window size uint32 maximum packet size string host to connect uint32 port to connect string originator IP address uint32 originator port The 'host to connect' and 'port to connect' specify the TCP/IP host and port where the recipient should connect the channel. The 'host to connect' may be either a domain name or a numeric IP address. The 'originator IP address' is the numeric IP address of the machine from where the connection request originates, and the 'originator port' is the port on the host from where the connection originated. Forwarded TCP/IP channels are independent of any sessions, and closing a session channel does not in any way imply that forwarded connections should be closed.
We can demonstrate with an example how SSH local port forwarding works.
The following command creates a locally forwarded TCP/IP port on "kali" with some useful options.
ssh -i ~/id_rsa -q -f -N -p 22 email@example.com -L 127.0.0.1:9922:10.2.1.22:2222
The command above can be broken down as follows:
ssh = /usr/bin/ssh (the linux ssh client application) -i ~/id_rsa = login using public/private keys -q = suppresses most warnings and errors -f = run the ssh client in a background process -N = do not open a shell on the remote computer -p 22 firstname.lastname@example.org = login to server 10.123.1.210 on port 22 with user "frog" -L 127.0.0.1:9922 = open a local listener on interface 127.0.0.1 port 9922 :10.2.1.22:2222 = instruct remote server to send traffic to 10.2.1.22:2222
When we enter the command above, the following happens:
- the ssh client starts up and sends itself to the background - it's still running, just not on the foreground
- the ssh client on "kali" connects to "pivot-host-1" via a new session we'll refer to as process-1
- the ssh server on "pivot-host-1" accepts the connection from "kali"
- the ssh client on "kali" authenticates with user "frog" and matching private key "id_rsa"
- the ssh server on "pivot-host-1" logs in user "frog" by matching the key to an entry in /home/frog/.ssh/authorized_keys
- the ssh client on "kali" creates a listener on port 9922 and waits for new connections
This is the relevant netstat output on "kali" after connecting to the remote host "pivot-host-1":
Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:9922 0.0.0.0:* LISTEN 10100/ssh tcp 0 0 10.123.1.199:50108 10.123.1.210:22 ESTABLISHED 10100/ssh
And this is the relevant netstat output on "pivot-host-1" after connecting:
Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 10.123.1.210:22 10.123.1.199:50108 ESTABLISHED 4734/sshd: jollyfro
process-1 can be visually represented as follows:
Now that we have created a local ssh port-forwarding session on "kali", we can connect to the listening port 9922 on "kali" to see what happens. The following command connects to port 9922 on "kali" which we just created via an ssh local port-forward:
ssh -p 9922 127.0.0.1
When we enter the command above, the following happens:
- the ssh client on "kali" connects to its own port tcp/9922 as new process we'll refer to as process-2
- backgrounded port-forward process process-1 accepts the connection to port 9922
- process-1 sends a "direct-tcpip" request to remote ssh server "pivot-host-1" with following contents
- ssh request: "direct-tcpip"
- host to connect: 10.2.1.22
- port to connect: 2222
- originator IP address: 127.0.0.1 (process-2 connection source IP)
- originator port: 51210 (process-2 connection source port - a random src port)
- ssh server on "pivot-host-1" reads the "direct-tcpip" request
- "pivot-host-1" looks up the routing table for destination IP 10.2.1.22 and confirms it can connect via interface 10.2.1.1
- ssh server on "pivot-host-1" creates a new connection to 10.2.1.22:2222 from interface 10.2.1.1 port 33000 (a random src port)
- ssh server on "pivot-host-1" maps ssh connection from "kali" process-2 to the new connection to 10.2.1.22:2222
- Since host 10.2.1.22 port 2222 runs an SSH server, we will be asked to login to the ssh server on "target-1"
This is the relevant netstat on "kali" after connecting to port 9922 on "kali":
Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.1:9922 0.0.0.0:* LISTEN 10100/ssh tcp 0 0 10.123.1.199:50108 10.123.1.210:22 ESTABLISHED 10100/ssh tcp 0 0 127.0.0.1:9922 127.0.0.1:51210 ESTABLISHED 10100/ssh tcp 0 0 127.0.0.1:51210 127.0.0.1:9922 ESTABLISHED 10121/ssh
And this is the relevant netstat on "pivot-host-1" after connecting to port 9922 on "kali":
Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 10.2.1.1:33000 10.2.1.22:2222 ESTABLISHED 4734/sshd: jollyfro tcp 0 0 10.123.1.210:22 10.123.1.199:50108 ESTABLISHED 4734/sshd: jollyfro
process-2 can be visually represented as follows:
When process-2 connects to port 9922 on "kali", process-1 on "kali" tunnels the request via its existing ssh (port-forward) connection with "pivot-host-1". Host "pivot-host-1" connects the new connection to 10.2.1.22:2222 via its interface "10.2.1.1".
Pivoting into an unreachable network via SSH local port-forwarding can be useful when there are known ports on known hosts in the remote network. When hosts or ports are not known, or there are a very large number of hosts or ports in the remote network, creation of a large number of single port forwards is often not practical. This is where ProxyChains takes over, and it will be covered in Part 2 of the pedantic guide to pivoting.