An Excruciatingly Detailed Guide To SSH (But Only The Things I Actually Find Useful)

Oh you like SSH? Name every flag.

Published: August 22, 2023

Reading Time: 15 minutes

Welcome

We’ve all seen these great diagrams of how SSH port forwarding works but if your brain is anything like mine, these diagrams leave you with a lot of unanswered questions. If you’re on a red team, understanding how to traverse a network better than the people who designed it gives you immense power to do evil things. SSH is such a powerful tool but sometimes the syntax and other concepts can get in the way of us accomplishing our goals. In an effort to do more evil things in a timely fashion I’ve put together a massive list of SSH things that I find useful. You can read it too, but if I’m being honest, this is mostly for me. I’ve learned that I really don’t grasp concepts unless I have hands on keyboard time doing them. This post is essentially just everything I learned while doing so. Also I should point out that in all of these examples I am using a websever to demonstrate port forwarding but this can be done with almost any service including RDP, SQL, etc.

Local Port Forwarding (-L)

Like the name implies, local port forwarding allows you to create a local port that is forwarded to a remote port. Let’s assume that the server internal-web.int is hosting a webpage that is only accessible on the loopback interface. This means that in order for us to access that webpage, we must be on internal-web.int. One way that we can get around this is by using an SSH local port forward. Assuming we SSH access to internal-web.int, on the host machine campfire.int, we can create a local forward that will allow us to access the remote webserver via a local port.

The command to do this is: ssh -N -f -L 1337:127.0.0.1:80 root@internal-web.int. This command is ran on campfire.int. That’s a complicated command, as always, breaking it down by flag will allow us to figure out what exactly is happening.

  • -N: This lets SSH know that we’re not going to be sending any commands after the server. Without this we’d get a shell on root@internal-web.int
  • -f: This sends SSH to the background. If we didn’t do this, our terminal would hang and we wouldn’t be able to use it.
  • -L Tells SSH to forward a local port
  • 1337:127.0.0.1:80: This tells SSH to bind the local port 1337 to the remote port of 80 (the port our webserver is on).

The best way I found to remember this is -L means local is on the left-hand side of the address. -R means the local port is on the right-hand side of the address.

  • root@internal-web.int This is telling SSH that we wish to login to the remote server as the root user to establish the SSH tunnel. Remember, port 1337 on our local machine is going to be bound to port 80 on the remote server.

Now that we have established the local port forward, we can interact with port 80 on internal-web.int by sending requests to port 1337 on our local machine( campfire.int).

Remote Port Forwarding (-R)

Remote port forwarding is the opposite of local port forwarding. Lets assume that we have access to internal-web.int and it is hosting a webpage that is only accessible on the loopback interface. Lets also assume that campfire.int cannot directly access internal-web.int. In this scenario we’d like to access internal-web.int from campfire.int. The problem is that we cannot directly communicate between campfire.int and internal-web.int due to a firewall. To get around this, we identify that vuln-server.int is reachable by both campfire.int and internal-web.int. The solution in this case is to use remote port forwarding SSH option to forward the port 80 from internal-web.int to an arbitrary port on vuln-server.int. Once we complete the remote port forward, we should be able to access the internal-web.int’s internal web page running on port 80 by issuing a curl command to vuln-server.int.

The command to do this is: ssh -N -f -R 3000:127.0.0.1:80 root@vuln-server.int.

  • -N: This lets SSH know that we’re not going to be sending any commands after the server. Without this we’d get a shell on root@internal-web.int
  • -f: This sends SSH to the background. If we didn’t do this, our terminal would hang and we wouldn’t be able to use it.
  • -R Tells SSH to forward a remote port
  • 3000:127.0.0.1:80: This tells SSH to bind the remote port 3000 to the local port of 80.

The best way I found to remember this is local forwarding with-L means local is on the left-hand side of the address. Remote forwarding with-R means the local port is on the right-hand side of the address.

  • root@vuln-server.int: This is telling SSH that we wish to login to the remote server as the root user to establish the SSH tunnel. Remember, port 3000 (on vuln-server.int) is going to be bound to port 80 on this server.

Now that we have established the remote port forward, we can access port 80 on internal-web.int by sending a curl request to vuln-server.int:3000

Dynamic Port Forwarding (-D)

Dynamic port forwarding with the -D option is an interesting option for proxying traffic over a SOCKS proxy. Lets assume that internal-web.int is hosting a web application that is only accessible on the internal network. We will assume that we have SSH access to vuln-server.int and it is on the same internal network and able to reach internal-web.int. What we would like to accomplish is accessing the webserver running on internal-web.int by proxying all of our traffic from campfire.int through vuln-server.int using both proxychains and our local web browser. First, we must ensure the /etc/proxychains.conf configuration file is set correctly.

  • Socks5: Tells proxychains to use socks5 (instead of socks4)
  • 127.0.0.1: Tells proxychains to use our localhost
  • 8080: Is the port we will use for our dynamic forward. This must match the port you specify with -D in your SSH command.

The command to do this is ssh -N -f -D 8080 root@vuln-server.int

  • -N: This lets SSH know that we’re not going to be sending any commands after the server. Without this we’d get a shell on root@vuln-server.int
  • -f: This sends SSH to the background. If we didn’t do this, our terminal would hang and we wouldn’t be able to use it.
  • -D 8080 Tells SSH to create a dynamic local port to send our traffic through.
  • root@vuln-server.int: This is telling SSH that we wish to login to the remote server as the root user to establish the SSH tunnel so that we can proxy our traffic through it.

After creating the dynamic port forward over port 8080 and setting socks5 127.0.0.1 8080 in /etc/proxychains.conf, we can now run proxychains curl 192.168.1.185 and see our webpage hosted on 192.168.1.185. Additionally, DNS over SOCKS is hit or miss for me which is why I’ve used the IP address in the below curl command.

Next, we can configure Firefox to browse the internal web server via our SOCKS proxy by configuring the proxy settings within firefox itself. To get to the settings in firefox go to: Settings -> Privacy & Security -> Network Settings. Once there select Manual proxy configuration and check the “Proxy DNS when using SOCKS V5”. Finally, tell firefox about the SOCKS proxy we just set up by setting SOCKS host to 127.0.0.1 and the port to 8080 (Or whatever you set your port to in your SSH command).

Now that we’ve set up our proxy, we can now access the webpage on internal-web.int since all of our traffic is being proxied from our local machine through vuln-server.int. Pretty cool.

Jumphosts (-J)

Compared to the previous commands, jumping through hosts with SSH is fairly straight forward. In this scenario we will proxy our traffic through two hosts to reach a destination host that is not reachable by our current host campfire.int. Our jump chain will look like this: campfire.int -> vuln-server.int -> internal-web.int -> dns.int.

The command to do this is ssh -J root@vuln-server.int,root@internal-web.int root@dns.int. Note that multiple jumps are separated by commas.

Agent Forwarding (-A)

SSH Agent forwarding is an interesting concept that I’ve written about about in my post Zero Effort Private Key Compromise: Abusing SSH-Agent for Lateral Movement which I encourage you to read if you’re considering using agent forwarding. To summarize that post, The SSH agent allows you to add private keys/identities to the agent running on your local machine using ssh-add <private_key_file>. These keys can then be listed with ssh-add -l. After adding a key to the ssh-agent utility, you can then SSH to a server using the key without having to re-enter the password. This is useful for both humans and service accounts. The -A option allows you to forward your key agent to the machine you’re connecting to, allowing you to use your private keys from the machine you’re connected to. Again, if you want more information about this, check out my previous post on the topic as there are some security concerns with this.

To demonstrate this, lets assume we want to jump through vuln-server.int into internal-web.int while also forwarding the keys in our ssh-agent so that we can utilize them once on internal-web.int.

The command to do this is: ssh -A -J root@vuln-server.int root@internal-web.int

  • -A Tells SSH to forward the keys in our SSH agent to the remote machine internal-web.int
  • -J root@vuln-server.int Tells SSH to proxy our traffic through vuln-server.int before accessing internal-web.int
  • root@internal-web.int: This is telling SSH that we wish to login to the remote server as the root user to establish the SSH tunnel.

As you can see, after executing ssh -A -J root@vuln-server.int root@internal-web.int, we can use ssh root@dns.int without having to specify a private key or enter any credentials. This is because our local machine campfire.int has the ssh key for dns.int loaded into the ssh-agent. We confirm this by running ssh-agent -l.

TTY Command Allocation (-t)

This option is super simple but very helpful for quickly running commands on a remote server that require some sort of interaction such as Vim or top. My favorite use case for this is when I need to quickly edit a file on a remote server. All you need to do is run the command ssh root@internal-web.int -t top and you will be greeted with a TTY containing the top command.

Global port (-g)

This one is a bit less common, but it allows us to define a locally forwarded port as a “global port” (my terminology, not official) that will allow us to proxy and traffic coming in on a local port to a port on an external server. This is similar to the -L option mentioned previously, but it will allow us to access the “Local” ports from an external machine. In the scenario below we have shell access to vuln-server.int and we would like to proxy any connection hitting port 2222 to port 22 on internal-web.int.

The command to do this is: ssh -N -f -g -L 2222:localhost:22 root@internal-web.int

  • -N: This lets SSH know that we’re not going to be sending any commands after the server. Without this we’d get a shell on root@internal-web.int
  • -f: This sends SSH to the background. If we didn’t do this, our terminal would hang and we wouldn’t be able to use it.
  • -g This tells SSH to allow remote hosts to connect to locally forwarded ports
  • -L Tells SSH to forward a local port

As you can see, even though our initial SSH command was to port 2222 on vuln-server.int, our shell tells us that we are actually on internal-web.int because of the ssh -N -f -g -L 2222:localhost:22 root@internal-web.int

SSH Console (~?)

The SSH console is a “hidden” feature of SSH that allows you to exert some control over SSH without having to interact with the remote system. This is useful if you’re trying to control SSH itself but your shell is broken. To access the help menu for the console press ~?. If you’re familiar with vim, this is similar to using the leader character. This will bring up the help console. There are two options that I find very useful. First is the ~. option which will kill your session (very useful if you’ve broken something).

The second is the “Console” option which can be accessed with ~C. This console has a few options for forwarding. If you find that you’ve SSH’d into a server and would like to begin using this session as a port forward session (such as a dynamic forward with the -D option mentioned previously), you can forward this session with the -D 8080 option to create a forward-on-the-fly out of this session. In this example, I’ve connected to vuln-server.int via a normal ssh command. After pressing ~C and typing -D 8080 and pressing enter twice, a normal prompt is returned to me. However, upon utilizing proxychains on the host machine campfire.int (and ensuring the /etc/proxychains.conf file is set to use port 8080), we can utilize the SSH session as if we had initiated it using ssh -D. Nifty.

SSH Config

The SSH config file is located at ~/.ssh/config and can be utilized to save you time when making connections over SSH. The config file uses a very easy to follow syntax that allows you to save your SSH configuration instead of having to type out all the options you want in the command line each time. SSH will parse this file when making an SSH connection. If the server you’re connecting to has an configuration defined in ~/.ssh/config, it will use that configuration. Note that command line parameters take precedence over the configuration file. This means that if your ~/.ssh/config file says that the user for internal-web.int should be root, but you run the SSH command ssh graham@internal-web.int, SSH will attempt to log you in as graham, not root. Below is an example of a very basic ~/.ssh/config file.

1# You can put comments with a `#` at the beginning of the line only. 
2host internal-web.int
3    User root
4    IdentityFile /home/smores/ssh_agent/internal-web-no-pw
5	Port 2222

The way SSH parses this file when running ssh internal-web.int is:

  1. It attempts to match internal-web.int in the command line invocation with a host keyword matching internal-web.int in ~/.ssh/config
  2. If internal-web.int is found in ~/.ssh/config, any of the options under host will be utilized if they’re not specified in the command line invocation
  3. If no match is found, SSH will only use options you’ve defined in your command line invocation of SSH.

SSH Config Keywords

There are many keywords you can utilize in your SSH config file but here are a few of the common ones I use that aren’t self explanatory (such as Port and User) .

  • IdentityFile /path/to/private_key: Allows you to specify the private key you wish to use for the host. Same as using -i

  • ForwardAgent: This is the same as running ssh -A (Once again, please see Zero Effort Private Key Compromise: Abusing SSH-Agent for Lateral Movement before using this.)

  • ProxyJump root@internal-web.int: Specify a server to proxy traffic through. Same as the -J option mentioned above. Note that in the below example we are asked to authenticate to root@vuln-server.int. This demonstrates that our traffic is indeed being routed through vuln-server.int before giving us a shell on internal-web.int like we asked for in the SSH command.

  • Match: This one is a bit more complex. The Match keyword allows you to define conditions in your SSH config. In the below example, SSH executes the command export | grep PROXYME=TRUE. If the program returns with a status code of 0 (in this case, meaning grep found a match), it will utilize the SSH keywords defined under the Match block (In this case, ProxyJump). Otherwise only the normal host internal-web.int block is used.

    In the below example, we first run ssh internal-web.int which successfully connects us to the server using the private key denoted with the IdentityFile keyword. Since export | grep PROXYME=TRUE returns with a status code of 1 (meaning grep did not find a match), we do not execute the ProxyJump keyword under the match statement.

    Next, we set the PROXYME environment variable to TRUE using export PROXYME=TRUE and re-run the same ssh internal-web.int. This time we are asked to authenticate to vuln-server.int before we get a shell on internal-web.int. This is because SSH evaluated the Match block and executed export | grep PROXYME=TRUE which returned status code 0 (meaning grep found the match). Since it returned true, it executed the ProxyJump keyword defined under the Match block.

I should also point out that scp (and some other SSH based utilities) can use your SSH config file! Typically it does this by default, but I’ve had instances where it does not. If you’re on a system where scp does not automatically utilize your ~/.ssh/config file, you can explicitly define it with the -F ~/.ssh/config argument.

ssh-copy-id

The ssh-copy-id utility is a small tool that allows us to quickly upload our public key to a server.

The command to do this is: ssh-copy-id -i internal-web root@internal-web.int

  • -i internal-web: Specify the name of the private key we wish to use to authenticate to the server.
  • root@internal-web.int Specify the server we wish to upload the public key to.

ssh-keygen

This utility is used to generate private/public key pairs. It is typically recommended that you specify a larger key size using the -b option. Although I’m not a crypto expert, longer is generally better and the default keysize is 3072 (At least on my machine). ssh-keygen defaults to RSA, however, other (preferably stronger) algorithms can be used by specifying the -t flag. IE ssh-keygen -t ecdsa -b 521 Additionally, you can inspect keys to see their fingerprint and byte size using ssh-keygen -lf <file-name>.

Wrapping up

So there you have it, my personal SSH cheatsheet for SSH. Go forth and SSH into machines like you know what you’re doing. If you have any questions, feel free to let me know on any of these sites or shoot me an email via blog[AT]grahamhelton.com.

References

https://github.com/cwolff411/redteamvillage-sshtunnels https://www.ssh.com/academy/ssh/tunneling-example https://goteleport.com/blog/ssh-tunneling-explained/ https://linuxize.com/post/how-to-setup-ssh-tunneling/ https://iximiuz.com/en/posts/ssh-tunnels/