NahamStore Writeup (TryHackMe Medium Machine)

In this room you will learn the basics of bug bounty hunting and web application hacking.


Overview

NahamStore is a medium practice machine from TryHackMe. It’s a great suite of challenges, focused on web application vulnerabilities, created by the one and only NahamSec.

It teaches you about the most common web app flaws, targeting both the server and clients. During your walkthrough, you will learn some basic and advanced exploitation techniques, while making your way through this hilariously vulnerable e-shop towards the flags.


Setup

To start the challenge, we are instructed to add the “nahamstore.thm” domain to our “/etc/hosts” file (local DNS file). We can do that by switching to the “root” user and open “/etc/hosts” in editor.

┌──(kali㉿kali)-[~]
└─$ sudo su
[sudo] password for kali:
┌──(root㉿kali)-[/home/kali]
└─# nano /etc/hosts

We take the IP address of the target machine and create an entry.

Now we can visit the web server under the “nahamstore.thm” domain.


Recon (FFuF, Gobuster, Nmap)

Next, we are instructed to perform subdomain enumeration and directory fuzzing. We can use tool like FFuF to perform fast subdomain enumeration, which gives us multiple results.

┌──(kali㉿kali)-[~]
└─$ ffuf -u "http://nahamstore.thm" -w /usr/share/wordlists/SecLists-master/Discovery/DNS/subdomains-top1million-20000.txt -H "HOST: FUZZ.nahamstore.thm" -ac

/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/

v2.1.0-dev
________________________________________________

:: Method : GET
:: URL : http://nahamstore.thm
:: Wordlist : FUZZ: /usr/share/wordlists/SecLists-master/Discovery/DNS/subdomains-top1million-20000.txt
:: Header : Host: FUZZ.nahamstore.thm
:: Follow redirects : false
:: Calibration : true
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

www [Status: 301, Size: 194, Words: 7, Lines: 8, Duration: 47ms]
shop [Status: 301, Size: 194, Words: 7, Lines: 8, Duration: 48ms]
marketing [Status: 200, Size: 2025, Words: 692, Lines: 42, Duration: 69ms]
stock [Status: 200, Size: 67, Words: 1, Lines: 1, Duration: 81ms]
:: Progress: [19966/19966] :: Job [1/1] :: 724 req/sec :: Duration: [0:00:33] :: Errors: 0 ::

We add all of these subdomains to our “/etc/hosts” file so we can access them.

Now we can visit all of the individual subdomains on your browser. The “shop” and “www” subdomains just redirected me to the main website. But “marketing” and “stock” were different.

The “marketing.nahamstore.thm” contained 2 links to couple campaigns.

The “stock.nahamstore.thm” was some kind of API with 1 endpoint “/product”.


Next, it was time for some directory fuzzing. I used Gobuster to perform the enumeration.

┌──(kali㉿kali)-[~]
└─$ gobuster dir -u "http://nahamstore.thm" -w /usr/share/wordlists/dirb/big.txt -t 64 -r
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://nahamstore.thm
[+] Method: GET
[+] Threads: 64
[+] Wordlist: /usr/share/wordlists/dirb/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Follow Redirect: true
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/basket (Status: 200) [Size: 2465]
/login (Status: 200) [Size: 3099]
/logout (Status: 200) [Size: 4254]
/register (Status: 200) [Size: 3138]
/returns (Status: 200) [Size: 3628]
/robots.txt (Status: 200) [Size: 13]
/search (Status: 200) [Size: 3351]
/staff (Status: 200) [Size: 2287]
Progress: 20469 / 20470 (100.00%)


We can also perform extra Nmap scan to see what ports are open on the machine.

┌──(kali㉿kali)-[~]
└─$ sudo nmap -Pn -A nahamstore.thm -T5
Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-15 18:08 EDT
Nmap scan report for nahamstore.thm (10.10.34.120)
Host is up (0.053s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 84:6e:52:ca:db:9e:df:0a:ae:b5:70:3d:07:d6:91:78 (RSA)
| 256 1a:1d:db:ca:99:8a:64:b1:8b:10:df:a9:39:d5:5c:d3 (ECDSA)
|_ 256 f6:36:16:b7:66:8e:7b:35:09:07:cb:90:c9:84:63:38 (ED25519)
80/tcp open http nginx 1.14.0 (Ubuntu)
| http-cookie-flags:
| /:
| session:
|_ httponly flag not set
|_http-title: NahamStore - Home
|_http-server-header: nginx/1.14.0 (Ubuntu)
8000/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-robots.txt: 1 disallowed entry
|_/admin
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
|_http-open-proxy: Proxy might be redirecting requests
Device type: general purpose
Running: Linux 4.X
OS CPE: cpe:/o:linux:linux_kernel:4.15
OS details: Linux 4.15
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 256/tcp)
HOP RTT ADDRESS
1 48.22 ms 10.9.0.1
2 48.46 ms nahamstore.thm (10.10.34.120)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.19 seconds

The Nmap scan showed 3 open ports. Port 22 for SSH and ports 80 and 8000 for 2 Nginx web servers. Let’s enumerate the newly discovered web server on port 8000 now.

I used Gobuster again to perform directory fuzzing.

┌──(kali㉿kali)-[~]
└─$ gobuster dir -u "http://nahamstore.thm:8000" -w /usr/share/wordlists/dirb/big.txt -t 64 -r
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://nahamstore.thm:8000
[+] Method: GET
[+] Threads: 64
[+] Wordlist: /usr/share/wordlists/dirb/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Follow Redirect: true
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/admin (Status: 200) [Size: 1489]
/robots.txt (Status: 200) [Size: 30]
Progress: 20469 / 20470 (100.00%)

I found one interesting “/admin” page. It contained a login panel for marketing managers. This whole website on port 8000 felt like something we, as casual visitor, shouldn’t be allowed to see.

On TryHackMe, there’s a question in this section asking you for Jimmy Jones SSN. As I figured out later, we will be able to answer this question near the end of this challenge, so leave it for now.


Cross-Site Scripting (XSS)

First web app vulnerability we are going to look for is Cross-Site Scripting, a very common one, mainly utilizing Javascript. There are 3 types: reflected XSS, stored XSS and DOM-based XSS.

Cross-Site Scripting (XSS) is a web application vulnerability that allows an attacker to inject malicious scripts into web pages viewed by other users. These scripts run in the victim’s browser and can steal cookies, session tokens, or other sensitive information, or perform actions on behalf of the user without their consent. (ChatGPT)

There are multiple places where the web app is vulnerable to XSS, so let’s go through them one at the time. Also note, that there are multiple questions to each vulnerability on TryHackMe.


Reflected XSS

Back on “marketing.nahamstore.thm”, I clicked on one of the campaigns and got redirected to a new page (it’s name is in the URL). When testing web apps like this, we should try to manipulate anything we can and try to get a response from the website, like modifying the URL.

When I changed just one character of the campaign’s name in the URL, an error showed up. We can see that a parameter “error” appeared in the URL and it’s content is displayed directly on the page.

When testing for XSS, we should always target things like input fields and URL parameters. When the content of the parameter is displayed on the website, there’s a potential for reflected XSS. Firstly, we can check for HTML injection with tags like <u>, <h1>, <img> etc. As you can see, we are able to get HTML injection because the HTML tags directly modify the text.

HTML injection via “error” parameter

To take this further, we can use the <script> tag and enter some Javascript. This way, we can summon alert windows, load external scripts (if CSP isn’t set), steal session cookies etc.

reflected XSS via “error” parameter


Stored XSS

To find stored XSS, we need to register first and create a record in our address book.

Now, we can make a random order. After completing the order, we get this summary page. Notice which information are being displayed, especially the User agent header.

There are parameters that we cannot control like “Order id”. But we definitely can control the “User agent” parameter. We can try to inject Javascript code into it and see if it runs in the browser.

injecting XSS into “User-Agent” header

After submitting the order, we can find it our list of orders.

After opening the corresponding order, we get the alert, confirming that our injected Javascript was executed. It’s called stored XSS because our injected Javascript got stored in the backend database.

If you look at the source code, it looks like this.

There are several ways to prevent XSS, like properly escaping and sanitizing user-supplied data, encoding the output, implementing the Content Security Policy header (CSP) etc.


Other occurrences of XSS

There are couple other occurrences of XSS. When you click on the product from your basket, there is the “name” parameter being passed, which value gets directly passed to the title of the page.


Next one is the search parameter on the home page.

If we look into the source code, we can see the “search” variable.

We can use payload like ‘;alert(”hacked!!!”)// to get XSS. The ‘; is needed to escape the variable and the // is needed to comment out the rest of the line.


Another one is on the “/returns” page, in the “Return Information” field.

When we look into the source code, we find out that we need to escape the “textarea” tag.

We can use payload like </textarea><script>alert(”hacked!!!”)</script><textarea> to escape the tag and inject malicious Javascript. This is how it looks like in the source code:

There are couple more XSS vulnerabilities which I am not gonna show you here. I’ll just mention vulnerable parameter “discount” on the product pages and the “Page Not Found” H1 tag.


Open Redirect

Second web app vulnerability we are going to look for is Open Redirect. We are instructed to look for 2 URL parameters that allow open redirect, thus redirect user to any website.

Open Redirect is a type of web security vulnerability that occurs when a web application redirects users to an external URL based on user input, without properly validating or restricting that input. An attacker can exploit open redirect to phish users or steal credentials. (ChatGPT)


First method of looking for open redirects is simple parameter fuzzing. We can use fuzzing tool like FFuF with SecLists web content discovery wordlist to look for parameters that allow redirects.

┌──(kali㉿kali)-[~]
└─$ ffuf -u "http://nahamstore.thm/?FUZZ=https://youtube.com" -w /usr/share/wordlists/SecLists-master/Discovery/Web-Content/raft-medium-words-lowercase.txt -ac

/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/

v2.1.0-dev
________________________________________________

:: Method : GET
:: URL : http://nahamstore.thm/?FUZZ=http://10.9.1.5:9000
:: Wordlist : FUZZ: /usr/share/wordlists/SecLists-master/Discovery/Web-Content/raft-medium-words-lowercase.txt
:: Follow redirects : false
:: Calibration : true
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

r [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 142ms]
q [Status: 200, Size: 4274, Words: 985, Lines: 83, Duration: 74ms]

FFuF found until-now unknown “r” parameter. So I tested this in my browser, I set the “r” parameter and redirected myself to Youtube, confirming the Open Redirect vulnerability.


Second one was a bit trickier. You know what happens when you try to visit pages that are only accessible to authenticated clients? Websites tend to redirect you to the login page first, and once you are authenticated, only then redirect you to your desired page. I intercepted this request (as unauthenticated client) that redirects you to “/orders” once you login.

I logged in and intercepted the request. Just as before, I changed the value of “redirect_url” parameter to point to Youtube. And just as expected, I got redirected immediately.

redirecting myself to YouTube via “redirect_url” parameter

To prevent Open Redirects, you should always have some sort of whitelist of allowed pages in the backend code, use relative paths and always validate and sanitize user-supplied data.


Cross-Site Request Forgery (CSRF)

Next is Cross-Site Request Forgery (CSRF). Let’s say that victim user is authenticated on it’s banking app via cookies. When this user visits a malicious website, it silently sends a request to it’s banking app (e.g., to transfer money) using user’s browser and session. The vulnerable banking app then transfers the money, thinking that the request came from authenticated user.

Cross-Site Request Forgery (CSRF) is a type of attack that tricks a user into performing actions on a web application where they’re authenticated, without their knowledge or consent. It exploits the fact that browsers automatically send credentials (like cookies or session tokens) with each request. (ChatGPT)

This type of vulnerability can put website’s users in great danger. That’s why CSRF tokens are used for protection, which are unique and unpredictable strings, helping to verify the requests.


CSRF attack 1 (no CSRF token)

When we look into our account settings back in NahamStore, we can see 2 options to change our email and password. Such functionality is the primary target for CSRF attacks.

Firstly, if we click on “Change Password” and type in our new password, we can see from the intercepted request that there isn’t any CSRF protection implemented.

To abuse this, we can take the request and put it in online CSRF PoC Generator, which crafts an HTML form for us, which sends a POST request to NahamStore and changes the password.

generating CSRF PoC HTML form

All it takes now is one click from authenticated victim user. The form would look like this:


CSRF attack 2 (with CSRF token)

There’s also the “Change Email” option in the settings. If we enter a new email and intercept the request, we can see a long base64-encoded string under the “csrf_protect” parameter.

Using Burp Suite’s built-in decoder, we can decode the base64 string. We can see that the CSRF token takes our user’s ID, a timestamp and a signature, which validates the entire token.

Sometimes, even if there is a CSRF token, it can still be poorly implemented. Not validating the signature and removing the token completely are both possible misconfigurations.

If I modify the signature, website backend catches up and throws invalid token error.

However, if I remove the “csrf_protect” parameter completely, the request goes through.

removing the CSRF token, resulting in CSRF protection bypass

To protect your website’s visitors, there are couple ways to mitigate the CSRF attack. Most common and effective ways are CSRF tokens, SameSite cookies, Double Submit cookies, CAPTCHAs etc.


Insecure Direct Object Reference (IDOR)

Next up, we will look for IDORs, which is part of Broken Access Control vulnerabilities (#1 in Owasp Top 10). This vulnerability allows us to access forbidden information, usually by modifying URL or request body parameters with easily predictable values.

IDOR is a type of access control vulnerability that occurs when an application exposes a reference to an internal object (like a file, database record, or user ID) and does not properly enforce authorization checks. This allows attackers to manipulate references and gain unauthorized access to data or functions. (ChatGPT)


In NahamStore, there are 2 IDORs. One is bit more trickier than the other. The first one is in the “address_id” variable when choosing the address for your order.

Since the “address_id” stores predictable values and the web backend code doesn’t check for user’s identity, it creates IDOR. Simply changing the value exposes other visitors’ addresses.

the “address_id” parameter vulnerable to IDOR, disclosing forbidden information


Second occurrence of IDOR is in the Order PDF generator. When we go to our list of orders and click on one, we get an option to generate a PDF with all of the information about the order.

If we intercept the response, we can see the order ID variable in request body.

We can try to simply change the value, just as before, but this time, we will get an error.

Web backend code catches up this time and detects that our user doesn’t own the order. Luckily, we can try some tricks and try to get the IDOR to work. I tried modifying our “session” and “token” cookies, even removing them, but that didn’t work.

The error mentions “user_id” specifically, so it’s maybe some kind of variable that we could potentially overwrite. So I added the “user_id” parameter to my request and sent it.

Unfortunately, that didn’t change the result, the same error popped again.


Another good method always worth to try is URL encoding. So I tried to encode the “&”, which causes the “user_id” parameter to append to the “id” parameter in the request, but the backend code recognizes it as 2 separate variables after the URL decoding.

bypassing backend protection, IDOR confirmed

This worked perfectly. PDF got generated, containing information about order with ID 3.

To prevent IDORs, you should enforce server-side access control, use indirect references like tokens or UUIDs, implement role-based access control (RBAC) etc. According to “owasp.org”, 94% of applications were tested for some form of broken access control (superset of IDOR).


Local File Inclusion (LFI)

Another common vulnerability, especially in CTFs, is LFI. LFIs can cause information disclosure (e.g., config files), remote code execution or log poisoning (injecting code into server log files).

Local File Inclusion (LFI) is a type of web security vulnerability that occurs when a web application allows users to include files from the server’s local file system into the application, without proper validation or sanitization of user input. (ChatGPT)


In NahamStore, if you intercepted requests regularly, you could notice 2 GET requests that load JPG files of the products from the server. To do that, it uses the “file” parameter.

Anytime there’s a user-controlled parameter that is supposed to load files, we should intuitively remember some basic bypass techniques and try to get the LFI to work. While testing for LFIs in CTFs, we should also try to disclose files like “/etc/passwd”, which can contain valuable information.


Firstly, we should try to use the absolute path of a file. In this scenario, this doesn’t work.

Next, we can try to use the relative path of a file. That includes using bunch of “../”, until we get a different response, which in this case, wasn’t successful neither.

Another trick worth trying is to use bunch of “….//”, which worked in this scenario and I got a different response, confirming the LFI vulnerability. Sometimes, the backend code goes through the path and removes the “../” expressions (signalling path traversal). If the code iterates through the path only once, then we can use “….//” to bypass this feature, which still leaves “../” in the path.

using basic path traversal protection bypass trick, getting LFI to work

Based on the response, we were able to hit the “/etc/passwd” file. On TryHackMe, we are tasked to get a flag from “/lfi/flag.txt” file. We can replace the file and get the flag easily.

To prevent LFI, RFI and path traversal vulnerabilities, it’s important to sanitize the user-supplied input. A whitelist feature and disabling dangerous PHP functions can also be very helpful.


Server-Side Request Forgery (SSRF)

Next is one of my personal favourites, and that’s the SSRF vulnerability. We can find SSRF on this machine, that allows us to view an API that shouldn’t be available to us.

Server Side Request Forgery (SSRF) is a type of web security vulnerability where an attacker tricks a server into making unauthorized requests to internal or external resources on behalf of the attacker. In SSRF, the attacker typically manipulates a parameter in a request that the server uses to fetch a resource. (ChatGPT)


The SSRF vulnerability can be found in the “Check Stock” request, when we want to find out how many pieces of product are in stock. Particularly interesting is the “server” parameter, holding the “stock.nahamstore.thm” subdomain (API) we discovered at the beginning.

Intuitively, I tried to change the server to “nahamstore.thm” to see what the server does. I got back a response with “Server invalid” message, indicating some kind of protection from the backend.

I asked ChatGPT for some SSRF techniques. It showed a lot of them, but I was mainly interested in the URL parser and filter bypass techniques:


At first, I only appended “@nahamstore.thm” to the “server” parameter (because our task is to access secret API on the main domain), but that returned 404 Not Found. So I added additional “#” to comment out the rest of the path and got the main page of “nahamstore.thm” back.

Web app APIs usually have their own exclusive subdomains. Thus, it was time to start fuzzing for the right subdomain again. You can use tools like FFuF, Gobuster or even Burp Suite’s Intruder for fuzzing. I used FFuF with a very long SecLists wordlist (2 mil. entries):

┌──(kali㉿kali)-[~]
└─$ ffuf -u "http://nahamstore.thm/stockcheck" -w /usr/share/wordlists/SecLists-master/Discovery/DNS/dns-Jhaddix.txt -X POST -d 'product_id=1&server=stock.nahamstore.thm@FUZZ.nahamstore.thm#' -t 64 -ac

This took a lot of time. I eventually gave up and looked at the writeup to save time.


It turned out that “internal-api” is the correct subdomain. If we send appropriate request, we get the 200 OK back with some information, including the available endpoint “/orders”.

using SSRF to redirect server’s requests to different API

When I added “/orders” to my request, server responded with couple extra endpoints, each related to a specific order. We can simply visit these endpoints to get more information.

I went down the list and eventually got to Jimmy Jones credit card number, which I needed to answer the SSRF question on TryHackMe.

using SSRF to disclose sensitive information about the clients

To protect your websites and prevent SSRF attacks, validate all user-supplied URLs, implement allowlists of permitted domains, block all requests to internal IPs etc.


XML External Entity Injection (XXE)

Another vulnerability, although less popular nowadays, is XML External Entity Injection (XXE). Attackers can exploit XXE to read sensitive files, perform SSRF, execute DDoS attacks etc.

XXE (XML External Entity Injection) is a security vulnerability that occurs when an application processes XML input containing a reference to an external entity. (ChatGPT)

XML stands for eXtensible Markup Language. It is a markup language designed to store and transport data in a structured, human-readable, and machine-readable format. (ChatGPT)

On TryHackMe, we are tasked to find both the reflected and blind XXE. And read the flags.


Reflected XXE

We go back to the “stock.nahamstore.thm” subdomain, which stores how many pieces of products are available. It also has “/product/[product ID]” endpoints for communication with the web app.

When I tried to make a POST request to one of the endpoints, I got “Missing header X-Token” error back. And when I added the “X-Token” header, I got another “X-Token is invalid” error.

┌──(kali㉿kali)-[~]
└─$ curl http://stock.nahamstore.thm -X POST
{"error":"Unknown Endpoint or Method Requested"}

┌──(kali㉿kali)-[~]
└─$ curl http://stock.nahamstore.thm/product/1 -X POST
["Missing header X-Token"]

┌──(kali㉿kali)-[~]
└─$ curl http://stock.nahamstore.thm/product/1 -X POST -H 'X-Token: 1234'
["X-Token 1234 is invalid"]

Curious, I thought. But seeing the value “1234” directly reflected in the response should alert you.

If we try to fuzz for hidden parameters on this endpoint (e.g., using FFuF), we can find the “xml” parameter. Fun Fact: If we append “?xml”, the response comes back in XML instead of JSON.

ffuf -w /usr/share/seclists/Fuzzing/extensions-skipfish.fuzz.txt -u http://stock.nahamstore.thm/product/1?FUZZ -fs 41


If we try to send POST request again, we get an error. Invalid XML sounds very interesting.

So I added some basic XML to the request body and sent it. “X-Token not supplied” error came back. Tbh, that stunned me a bit. I had “X-Token” header already set in my request, what’s wrong?

Perhaps it ignores the headers. Maybe I have to specify the “X-Token” in the XML itself. So I modified my request. After sending it, my user-supplied XML got directly reflected in the response.

This is what indicates XXE vulnerability. To read files, I grabbed the right payload from Portswigger Academy. The payload defines an external entity (variable) called “xxe” with “file:///etc/passwd” as value. After sending the request, the content of mentioned file gets reflected in the response.

exploiting XXE, leading to arbitrary file read

To complete the task from TryHackMe, we can simply change the file to “/flag.txt”.


Blind XXE

Unlike reflected XXE, blind XXE doesn’t reflect any values on the website, nor in the response.

If you remember, we found the “/staff” page during our initial recon on the main “nahamstore.thm” domain. There’s a XLSX file upload functionality on the page, where the blind XXE vulnerability lives.

XLSX is a file format used for Microsoft Excel spreadsheets. It stands for Excel Spreadsheet (XML-based). XLSX is a ZIP-compressed archive that contains multiple XML files and other resources (like images etc.) which together define the spreadsheet’s content, structure, and formatting. (ChatGPT)


To create a PoC, I’ll be following this article on XXE exploitation with Excel by Marc Wickenden: https://www.4armed.com/blog/exploiting-xxe-with-excel/. When it comes to XXE, I am a total newbie, so I had to learn a lot of stuff before finally exploiting this, too :D.

Firstly, I created a blank “xxe.xlsx” file online and downloaded it. Then I made a “/xxe” directory and unzipped the blank “xxe.xlsx” into it (even blank XLSX files have some initial structure).

┌──(kali㉿kali)-[~]
└─$ mkdir xxe

┌──(kali㉿kali)-[~]
└─$ cd xxe

┌──(kali㉿kali)-[~/xxe]
└─$ unzip ../xxe.xlsx

Then, I opened the “/xl/workbook.xml” file and added an XML entity, which contained the URL of my Python server (to which the server should reach out). I got the payload from the original article.

Afterwards, I zipped the modified structure into new “poc.xlsx”.

┌──(kali㉿kali)-[~/xxe]
└─$ zip -r ../poc.xlsx *

Started my Python server, uploaded the malicious XLSX file and got a response, confirming XXE.

┌──(kali㉿kali)-[~/xxe]
└─$ python3 -m http.server 9000
Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...
10.10.42.239 - - [25/Jun/2025 17:32:05] "GET / HTTP/1.0" 200 -


Now we can get down to business and create an XXE exploit that would exfiltrate files from the target machine for us. To create one, I was following this writeup on NahamStore by Alexandre Zanni: https://blog.raw.pm/en/TryHackMe-Nahamstore-write-up/.

Firstly, we have to create an external DTD file, where we define important entities like the file we want to read and the method of exfiltration. When specifying the file, I used PHP base64 filter instead of classic “file:///flag.txt” because of apparent restrictions that I encountered while testing. We will serve the file on our Python server and instruct the target machine to grab it from us.

DTD files refer to Document Type Definition files, which define the structure and rules for XML documents. They specify what elements, attributes, and nesting structures are allowed. (ChatGPT)

malicious DTD file, sending “flag.txt” to attacker’s server

I named the file “malicious.dtd”. Then, I referenced the file in the “workbook.xml” and invoked all the necessary variables. We can understand the DTD file as some kind of external library with all the stuff we want to use for successful exploitation. By invoking the “eval” entity from the DTD file (which gets grabbed from our Python server), we trick the system into converting the content of “flag.txt” into base64 and sending it as a URL parameter back to another Python server. Following this method, we gain the ability to read arbitrary files on the target machine.

XML file loading external malicious DTD file

After that, we just zip the file back up. I named it “exploit.xlsx”. Upon uploading our malicious XLSX file, we can see that our “malicious.dtd” file got requested successfully.

┌──(kali㉿kali)-[~/xxe]
└─$ zip -r ../exploit.xlsx *


┌──(kali㉿kali)-[~]
└─$ python3 -m http.server 9000
Serving HTTP on 0.0.0.0 port 9000 (http://0.0.0.0:9000/) ...
10.10.172.27 - - [26/Jun/2025 07:55:09] "GET /malicious.dtd HTTP/1.0" 200 -

And on the other Python server, we can see that the content of the “flag.txt” file was exfiltrated successfully as well, in it’s base64-encoded form.

┌──(kali㉿kali)-[~/xxe]
└─$ python3 -m http.server 8888
Serving HTTP on 0.0.0.0 port 8888 (http://0.0.0.0:8888/) ...
10.10.172.27 - - [26/Jun/2025 07:55:09] code 404, message File not found
10.10.172.27 - - [26/Jun/2025 07:55:09] "GET /e2Q2YjIyY2IzZTM3YmVmMzJkODAwMTA1YjExMTA3ZDhmfQo= HTTP/1.0" 404 -

Although XML is not as popular as it used to be (JSON is preferred nowadays), XXE still remains as one of the most dangerous and overlooked vulnerabilities. To prevent XXE, we should disable external entity resolution in XML parsers, use modern and secure XML libraries, validate and sanitize all user-supplied input and prefer formats like JSON (JSON isn’t vulnerable to XXE).


Remote Code Execution (RCE)

Another, very dangerous vulnerability is RCE. It’s probably the most critical flaw you can find on a system, because it allows you (with certain privileges) to run arbitrary commands and make the target machine to perform unintended actions, sometimes even self-destructive ones.

Remote Code Execution is a type of security vulnerability that allows an attacker to run arbitrary code or commands on a remote system or server. It often results from flaws in software, such as improper input validation or unsafe handling of user-supplied data. (ChatGPT)

Once again, there are 2 weak spots vulnerable to RCE. Each gets us a flag.


RCE 1 (modifying page source code)

To find the first RCE, we have to go to the website on “nahamstore.thm:8000” on login page. By trying couple basic credentials, we are able to get in with super weak combo “admin:admin”.

After logging in, we get a privilege to modify the source code of 2 campaign pages.

I grabbed a PHP RCE one-liner and pasted it into the code.

By appending the “cmd” parameter to the URL, I could run arbitrary commands on the machine.

running commands via “cmd” parameter

We can simply run “cat /flag.txt” to get the much needed flag.


RCE 2 (abusing badly sanitized request data)

The second RCE is present on the PDF generator page. When we want to generate a PDF with our order information, a POST request is sent with the “id” parameter, which is vulnerable to command injection. At first, I tried basic payloads with a semicolon. That didn’t work though.

Using ChatGPT, I tried several other (more advanced) techniques.


It turned out that multiple payloads work. E.g., I used `id` to get command execution.

RCE via “id” parameter

I sent the request and the next thing I saw was the PDF containing a response from the server.

We can simply use the “cat /flag.txt” command to get the flag.

To prevent RCEs, we should always validate user input, avoid deserialization vulnerabilities, use system calls like “eval” or “exec” with caution and use up-to-date software and frameworks.


Intermezzo (discovering new subdomains & getting Jimmy’s SSN)

With RCE, we can get a reverse shell on the machine. And, believe it or not, we can actually finally find out what SSN belongs to Jimmy Jones from the “Recon” chapter.

You can get a shell by injecting a PHP one-liner. You’ll get a foothold as user “www-data”.

┌──(kali㉿kali)-[~]
└─$ nc -lnvp 1234
listening on [any] 1234 ...
connect to [10.9.1.159] from (UNKNOWN) [10.10.208.142] 49416
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Now, we can look into “/etc/hosts” file to see all of the subdomains associated with the machine. To our surprise, there are 3 subdomains we haven’t seen yet, so add those to your “/etc/hosts”, too.


I ran Gobuster to perform directory fuzzing.

┌──(kali㉿kali)-[~]
└─$ gobuster dir -u "http://nahamstore-2020-dev.nahamstore.thm" -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -t 64 -r
===============================================================
/api (Status: 200) [Size: 47]

┌──(kali㉿kali)-[~]
└─$ gobuster dir -u "http://nahamstore-2020-dev.nahamstore.thm/api" -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -t 64 -r
===============================================================
/customers (Status: 400) [Size: 27]

I got to the “/api/customers” endpoint, but got 400 Bad Request error.

I visited the endpoint in my browser. An error message appeared, saying that I need to specify a “customer_id”.

So I added that parameter to the URL and cycled through several customers. Under customer ID 2, there was Jimmy Jones with his SSN, just what we needed to answer the “Recon” question.


SQL Injection

Last web app vulnerability we’ll look for is the infamous SQL injection. When handled incorrectly, attackers can gain unauthorized access, modify database records and bypass authentication.

SQL Injection is a type of security vulnerability that occurs when an attacker is able to manipulate a Structured Query Language (SQL) query by injecting malicious input into a web application’s database query. (ChatGPT)

There are several types of SQLi, like union-based, error-based, blind time-based, blind boolean-based, out-of-band injection etc. We are tasked to find 2 occurrences of SQLi in NahamStore.


SQLi with reflecting data

The first SQLi hides in the “id” parameter on the “/product” page. The most elementary way to test for SQLi is to simply append to the URL parameter and observe website’s behaviour.

We can see that an error message showed up, confirming the SQL injection vulnerability and disclosing that MySQL is being used. SQL injection that reflects errors and data on the page is a dream for attackers, because valuable information can and will be disclosed.

reflected error in SQL syntax, indicating SQL injection vulnerability

Now we know that the “id” parameter is vulnerable. We intercept the request in Burp Suite and save it to a file, I named mine “r.txt”. We can then use automated tool like “sqlmap” to quickly extract data from the database via SQLi.

┌──(kali㉿kali)-[~]
└─$ sqlmap -r r.txt --dbms mysql --dbs

Looking at the output, there are 2 databases present.

We expose databases, then tables, and finally the data records. The following command instructs “sqlmap” to exfiltrate data from the “sqli_one” table, as we are tasked on TryHackMe:

┌──(kali㉿kali)-[~]
└─$ sqlmap -r r.txt --dbms mysql -D nahamstore -T sqli_one --dump

There’s only 1 entry. And it’s our flag.


Blind SQLi

Second SQLi is blind, thus we won’t get any information reflected on the website. Blind SQLi lives on the “/returns” page, in the order number parameter.

We intercept the request again, save it into a file and feed it to “sqlmap.” As you can see, the form is vulnerable both to boolean-based and time-based blind SQL injection.

SQLMap found multiple working payloads

We discover another table called “sqli_two”. Then we use this command to extract the data:

┌──(kali㉿kali)-[~]
└─$ sqlmap -r r.txt --dbms mysql -D nahamstore -T sqli_two --dump

Since it’s a blind SQLi, data get pulled character by character. Slowly but steadily, the flag gets exfiltrated successfully.

And that’s the whole challenge done!

To prevent SQL injections on your websites, you should use prepared (parameterized) statements in your code, employ ORMs (Object Relational Mappers), sanitize user input, apply least privilege to database accounts and regularly test for vulnerabilities with automated tools like “sqlmap”.


Summary & final thoughts

NahamStore is a medium machine from TryHackMe. This box offers you several great challenges, focused mainly on web application vulnerabilities. It serves as a practice ground for anybody who wants to get into bug bounty hunting or penetration testing. In today’s world, websites and web apps are basically everywhere, used for marketing and promotional purposes, each one of them representing a possible hole in the server. Vulnerabilities like XSS, SQL injection or SSRF are one of the most common and dangerous flaws a web app can have, that’s why knowing how to spot them and prevent them is a priceless skill to have. In my opinion, this machine serves as a great practice, the vulnerabilities are nicely done, forcing you to use some advanced bypass techniques, too. Although, it can be harder to spot them first, rather than exploit them. Recommending to anybody who is interested in the world of web app security or pursues the career of web app pentester or bug bounty hunter. Intended for intermediate hackers. But beginners shouldn’t be discouraged.

Comments

Popular posts from this blog

Hospital Writeup (HackTheBox Medium Machine)

Bucket Writeup (HackTheBox Medium Machine)

Mr Robot Writeup (Vulnhub Intermediate Machine)