Skip to content

usage of tunnels and proxies in SSH

SSH protocol is one of the frequently used protocols in Linux system, usually used for remote management of hosts or servers. It defaults to port 22 and can be compared to telnet in Windows system (port 23). Here we introduce another powerful feature of ssh besides remote connection, which is the implementation of tunnel encryption and proxy function in various scenarios.

Prerequisite

For easier understanding and simplifying the network topology to the maximum extent, only two machines will be used for testing in the following scenarios, and the IP and hostname correspondences are as follows. If more complex network structures and multi-interface scenarios are needed, they can be extended accordingly. If used for some special purposes, feel free to use your imagination.

192.168.111.128  -->  Kali
192.168.111.131  -->  Centos

In the scenarios of verifying IP addresses and multi-level proxies, an additional gateway machine will be used: 192.168.111.1.

Then let’s learn more about three ssh command-line parameters, which will be used later:

-N    Do not execute remote commands after establishing the connection, nor is there any interactive shell. Usually used for port forwarding scenarios.
-f    After establishing the connection, the process will run in the background and will not occupy the foreground window.
-c    Compresses data during transmission. The compression algorithm is the same as gzip, but it is not suitable for high-speed network environments and will slow down the connection speed.
-v    Prints more detailed connection process information.

Local forwarding (-L)

Principle

Local forwarding means using the ssh -L parameter. Let’s first look at the official explanation (man ssh):

-L [bind_address:]port:host:hostport
-L [bind_address:]port:remote_socket
-L local_socket:host:hostport
-L local_socket:remote_socket
    Specifies that connections to the given TCP port or Unix socket on the local (client) host are to be forwarded to the given host and port, or Unix socket, on the remote side. This works by allocating a socket to listen to either a TCP port on the local side, optionally bound to the specified bind_address, or to a Unix socket.  Whenever a connection is made to the local port or socket, the connection is for‐warded over the secure channel, and a connection is made to either host port hostport, or the Unix socket remote_socket, from the remote machine.
    Port forwardings can also be specified in the configuration file.  Only the superuser can forward privi‐leged ports.  IPv6 addresses can be specified by enclosing the address in square brackets.
    By default, the local port is bound in accordance with the GatewayPorts setting.  However, an explicit bind_address may be used to bind the connection to a specific address.  The bind_address of "localhost” indicates that the listening port be bound for local use only, while an empty address or ‘*' indicates that the port should be available from all interfaces.

In plain language, after using this parameter to execute an ssh connection, a specified listening port 1 will be opened on the local machine, which will be bound to a specified interface (IP) and specified port 2 on the remote machine. When another program accesses the specified port 1 on the local machine, the traffic will be forwarded to the specified port 2 on the remote machine, which is equivalent to directly accessing port 2 on the remote machine. In simple terms, it means “traffic is forwarded from the local machine to the remote machine”.

The parameter value ([bind_address:]port:host:hostport) also follows a pattern, written from left to right indicating the order from local to remote: the port (port) of the local address (bind_address) is forwarded to the port (hostport) of the remote address (host). The local interface address can be omitted and defaults to 127.0.0.1. In addition to using interface and port, a connection can also be established using a Unix socket by replacing the corresponding position with the socket address.

Experiment

The forwarding process above is a bit complicated. Let’s use an experiment to understand the actual effect. Assume the scenario is Kali (192.168.111.128) connecting to Centos (192.168.111.131). First, make sure that Centos has opened port 22 and then use Python to open a simple HTTP service on port 8000.

Try to access it locally first (Centos has a second interface: 192.168.122.1):

It’s working fine. Next, test connecting to Centos’s SSH from another machine, Kali (192.168.111.128):

The connection is established. Next, use local forwarding to connect Centos directly from Kali (to facilitate this, ssh public key connection has been configured between the two machines in advance to avoid entering a password). The command to execute is:

ssh -NL 1080:192.168.122.1:8000 root@192.168.111.131

After executing it, Kali will listen to 1080 locally. Then, access this port with curl, and the returned data will be the content corresponding to port 8000 on Centos’s 192.168.122.1 interface:

Centos also recorded the corresponding connection log. The local forwarding is successful.

Remote Forwarding (-R)

Principle

The execution parameter for remote forwarding is ssh -R. The official explanation is:

-R [bind_address:]port:host:hostport
-R [bind_address:]port:local_socket
-R remote_socket:host:hostport
-R remote_socket:local_socket
-R [bind_address:]port
    Specifies that connections to the given TCP port or Unix socket on the remote (server) host are to beforwarded to the local side.

    This works by allocating a socket to listen to either a TCP port or to a Unix socket on the remote side. Whenever a connection is made to this port or Unix socket, the connection is forwarded over the secure channel, and a connection is made from the local machine to either an explicit destination specified by host port hostport, or local_socket, or, if no explicit destination was specified, ssh will act as a SOCKS 4/5 proxy and forward connections to the destinations requested by the remote SOCKS client.

    Port forwardings can also be specified in the configuration file.  Privileged ports can be forwarded only when logging in as root on the remote machine.  IPv6 addresses can be specified by enclosing the address in square brackets.
    By default, TCP listening sockets on the server will be bound to the loopback interface only.  This may be overridden by specifying a bind_address.  An empty bind_address, or the address ‘*', indicates that the remote socket should listen on all interfaces.  Specifying a remote bind_address will only succeed if the server's GatewayPorts option is enabled (see sshd_config(5)).

    If the port argument is ‘0', the listen port will be dynamically allocated on the server and reported to the client at run time.  When used together with -O forward, the allocated port will be printed to the standard output.

In layman’s terms, after executing the remote forwarding command, a specified port 1 will be opened for listening on the remote machine and bound to a specified port 2 on the local machine. All traffic accessing port 1 on the remote machine will be forwarded to port 2 on the local machine, which is equivalent to directly accessing port 2 on the local machine. It can also be simply understood as “traffic is forwarded from the remote machine to the local machine.”

It can be noted that the flow direction of the forwarding here is actually opposite to that of the local forwarding above, so the pattern of the parameter value ([bind_address:]port:host:hostport) is also reversed. Writing from left to right, it means that the port (port) on the remote machine’s interface ([bind_address]) is forwarded to the port (hostport) on the local interface (host). The remote interface address (IP) can be omitted by default to 127.0.0.1, and the socket connection is the same.

Here is a worth noting point. As can be seen from the parameter value section, compared to the previous local forwarding, an [bind_address:]port value is added, that is, the local interface address and port value are omitted at the same time. The remote interface can also be omitted as needed, which is equivalent to establishing a reverse socks5 proxy. The remote port can be used as a socks4 or socks5 proxy port, and the proxy traffic will be guided to the local machine. This will be verified by corresponding experiments later.

Experiment

This time, we will use Kali (192.168.111.128) to connect to Centos (192.168.111.131) via remote forwarding. However, we will start a simple http service using python on Kali:

Local connection is fine. Next, we enable remote forwarding on Kali with the parameters:

ssh -NR 1080:127.0.0.1:8000 root@192.168.111.131

This will open a new listening port 1080 on the remote machine (Centos, 192.168.111.131). Using curl to access it will return the content of port 8000 on Kali (192.168.111.128):

There are corresponding access records on the Kali side, and remote forwarding is successful:

Reverse socks proxy

Then we test the effect of specifying only one remote port value with the -R parameter. As mentioned earlier, this will make the local machine a socks proxy server. To test the proxy effect, we need to use the gateway machine (192.168.111.1). Start a simple php service on the gateway to get the visitor’s real IP address. The code is as follows:

<?php

echo $_SERVER['REMOTE_ADDR'] . PHP_EOL;

The access path is: http://192.168.111.1/get-ip.php. First, try accessing it with Kali and Centos separately:

The IP is correct. Then start the reverse socks proxy service on Kali (192.168.111.128):

ssh -NR 1080 root@192.168.111.131

Go to Centos (192.168.111.131) to test the proxy effect (use curl’s -x parameter to specify the proxy server):

So the reverse socks proxy is successful.

Remote interface address issue

In practical use of remote forwarding, you may encounter a small pitfall. Although the -R parameter value can specify the remote machine’s interface address arbitrarily, in fact, ssh by default only listens on the 127.0.0.1 interface address. That is to say, if this remote machine is a public network server, the newly listened port cannot be accessed on the public network and can only be accessed locally. This is caused by the default configuration of sshd, which is generally in the /etc/ssh/sshd_config file, the GatewayPorts configuration item, which is no by default. Change it to yes. If there is no such line, add it manually:

Then restart the sshd service. When connecting with remote forwarding, the new port will be listened on all interfaces (i.e., 0.0.0.0). However, this can only open ports on all interfaces, regardless of how the connection side sets the remote interface, so use it with caution.

Dynamic Forwarding (-D)

Principle

The execution parameter of dynamic forwarding is ssh -D. The official explanation is:

-D [bind_address:]port
    Specifies a local "dynamic” application-level port forwarding.  This works by allocating a socket to listen to port on the local side, optionally bound to the specified bind_address.  Whenever a connection is made to this port, the connection is forwarded over the secure channel, and the application protocol is then used to determine where to connect to from the remote machine.  Currently the SOCKS4 and SOCKS5 protocols are supported, and ssh will act as a SOCKS server.  Only root can forward privileged ports.

    Dynamic port forwardings can also be specified in the configuration file. IPv6 addresses can be specified by enclosing the address in square brackets.  Only the superuser can forward privileged ports.  By default, the local port is bound in accordance with the GatewayPorts set‐ting.  However, an explicit bind_address may be used to bind the connection to a specific address.  The bind_address of "localhost” indicates that the listening port be bound for local use only, while an empty address or ‘*' indicates that the port should be available from all interfaces.

This should be one of the most common use cases in actual usage, which is to use a socks proxy and dynamically forward traffic. This is achieved by listening on a specified port locally and setting the socks proxy port to this port in the application. All connection traffic will be forwarded through this port via an SSH tunnel to the remote machine for sending on behalf of the local machine. Because it is no longer restricted to the ports and interfaces of the connected end and the receiving end, it is called dynamic.

The parameter value ([bind_address:]port) is the interface address and port to be listened on the local machine. If no interface address is specified, it defaults to 127.0.0.1. To listen on all interfaces, use 0.0.0.0.

Experiment

Still operating on Kali (192.168.111.128), listen to local port 1080 and use Centos (192.168.111.131) as a SOCKS proxy server:

ssh -ND 1080 root@192.168.111.131

Test the proxy effect using curl. The SOCKS5 proxy has taken effect with the IP address of Centos (192.168.111.131):

Kali can also access the http service (8000 port) of Centos’s other interface (192.168.122.1) without any problems:

Multi-level Proxy (-J)

principle

To implement multi-level proxy, the ssh -J parameter is needed, which means setting up a “Jump host”, which can be understood as a springboard. The official explanation is:

-J destination
    Connect to the target host by first making a ssh connection to the jump host described by destination and then establishing a TCP forwarding to the ultimate destination from there.  Multiple jump hops may be specified separated by comma characters.  This is a shortcut to specify a ProxyJump configuration di‐rective.  Note that configuration directives supplied on the command-line generally apply to the desti‐nation host and not any specified jump hosts.  Use ~/.ssh/config to specify configuration for jump hosts.

In essence, during the ssh connection, one or more jump hosts can be specified to achieve the forwarding of traffic level by level. Each level of the jump host only has access records of the previous level, achieving a certain concealing effect. The syntax of the parameter value destination is the same as that of the normal ssh connection object. If multiple jump hosts are specified, multiple node values are separated by commas.

Experiment

To demonstrate the effect of multi-level nodes, we need to use the gateway machine (192.168.111.1) again here, use it to connect to Centos (192.168.111.131) through ssh, and use Kali (192.168.111.128) as the jump host, directly execute on the gateway:

ssh root@192.168.111.131 -J root@192.168.111.128

It can be seen that the access record of the finally connected Centos machine is the IP address of the jump host Kali:

Test multiple jumps:

ssh root@192.168.111.131 -J root@192.168.111.128,root@192.168.111.131

Here, in order to simplify the network topology, Centos itself is also set as a jump host, so the two jump nodes passed are .128 and .131, and the entire traffic flow process can be simplified as:

*.111.1  -->  *.111.128  -->  *.111.131  -->  *.111.131

Conclusion

The use of tunnels and proxies in SSH protocol provides additional security and flexibility. Tunnels and proxies allow users to access resources that are not directly accessible from their machines and bypass firewalls. The use of tunnels and proxies in SSH protocol is recommended for users who require additional security measures.

Reference

  • https://www.freebuf.com/articles/system/339389.html
Feedback