I finally made it through the entire CTF process from start to finish.

Why I decided to change roles

Last year, our school hosted a CTF, and I helped organize it as part of the support team. This year, since the annual competition came around again, I wanted to try something different. I had also played a live CTF in person not long before that, so compared with last year I had a bit more experience under my belt. Instead of maintaining the platform this time, I decided to try writing challenges myself.

What I ended up making

Saying I had "more experience" makes it sound more impressive than it really was. I’m not a cybersecurity major, and I’ve never seriously trained for CTFs, so challenge design was definitely not something I was naturally good at 😂. Still, I had solved a few problems in earlier CTFs, so I figured I could borrow some of those ideas and build around them.

Of course, CTFs still need original work. Copying someone else’s challenge directly is a bad move. People who find it by search will think it’s boring, and people who don’t find it will be treated unfairly. After all, this isn’t a search-skills competition. Borrowing an approach is fine, though, because for most people, the normal way of thinking is still the way they’ll try to solve it.

Challenge 1

For the first challenge, I stitched together two old ideas: one from a CTF I played two years ago called Swedish State Archive, and another from a CTF a few months ago involving a PHP MD5 comparison bug.

In this one, players first had to get around some Python-side conditions before they could obtain the PHP source code. I also didn’t commit the flag directly into git, so the difficulty was a bit higher than either of the original ideas on its own. Once the PHP source was in hand, the MD5 flaw could be used to get the real flag.

Overall, I think the difficulty was reasonable, because it could still be solved through a normal line of reasoning. I was also pretty happy with how it went in practice: 12 teams solved it.

Challenge 2

If the first challenge borrowed someone else’s thinking, then the second one might as well be something I came up with myself.

Before I dropped MaBBS, I discovered a serious security issue in the project: when creating things like posts, the program used the title directly as the filename, and at the beginning I didn’t filter anything at all. Later I wrote a function called danw to filter characters that might cause trouble, but I only added it to the Wiki part. Why only there? Because at the time I wanted posts to be stored as IDs, with the title written into the meta data instead. Unfortunately I eventually abandoned the project and never came back to fix it. MaBBS ended with a line basically saying only God could understand this code.

Maybe the plan to rebuild MaBBS will never happen now. I lost the original motivation I had when I first wrote it, and now I’m just someone who can write code, nothing more… anyway, that’s getting off topic.

The point is, I had a vulnerability like this on hand, so I could deploy it and see what the cybersecurity students were actually made of. I wanted to know whether they could really analyze code, or whether they only knew how to follow an existing solve path.

The result was pretty much what I expected. Aside from the people I had already told about the bug, nobody solved it. But since I knew some people might already know the answer, I kept the score low. Otherwise, I honestly think it might have ended up as a challenge nobody could solve.

Challenge 3

The second challenge may have been too original for people without practical experience, so for the third one I teamed up with another cybersecurity student. We decided to use APICloud’s capabilities as the upper bound for the difficulty.

APICloud has a feature that encrypts HTML source code, and at the moment we couldn’t find any public method online for breaking it. But if nobody online can break it, then we as the challenge makers can’t break it either… and if there’s no way to solve it, then the challenge has no point. Worse, if we had to write the write-up ourselves, that would be awkward.

So we changed our approach. Instead of relying on decrypting the HTML, we used a setup that was not meant to be decrypted at all, but quietly included a debugging tool inside, like vConsole. Then we encrypted the JavaScript a second time so that the solver would basically have only one path: use the debugging feature to interfere with the program.

This challenge was a gacha-style program, and the goal was to click 2 billion times before getting the flag. In practice, you could just use the debugging tool to modify the click count. To be honest, this was mostly a clumsy solution born from the fact that we ourselves didn’t know how to do anything better.

It wasn’t really that hard, though. Once someone noticed what was going on, a little thinking was enough to solve it. If I ever get another chance, I’d like to make it harder: remove vConsole entirely and instead load something like https://example.com/hello.js, then require the solver to hijack hello.js to change variables. That would be much more interesting. We just didn’t think of that in time, so this one stayed fairly simple.

Even so, those cybersecurity students still didn’t do great. For a challenge this easy, only one team solved it. But honestly, I was still happy that they made it through, because it showed that this team had a wider way of thinking and wasn’t boxed in.

What I took away from the whole thing

Writing CTF challenges turned out to be a lot of fun. And when you dig a pit yourself, you also have to think about how to fill it back in, which is actually pretty helpful for learning how vulnerabilities work.

In most real environments, a vulnerability won’t politely follow the normal route. If it can be handled the normal way, chances are it was already stepped on by someone else long ago. So you need an open mind. A really good penetration tester should have broad thinking and original ideas.

As for the people studying cybersecurity right now, to me they sometimes feel like a group with very fixed thinking—good at solving exercises, but not much else. If they can’t find a hole someday, they’ll be the ones left holding the bag 😂.

At the end of last month, I also ran into something pretty interesting. A site called 萌国ICP备案 had an XSS vulnerability, and I ended up joining a sort of XSS AWD battle with several other users on the site. We were fighting for the visual effect of our own links, and it was genuinely a lot of fun.

Unfortunately, I got sick at the beginning of this month and never got around to writing it down. If I feel like writing a blog post again in a couple of days, I’ll share that story too.