BSidesPDX CTF 2019

Sat, Oct 27, 2019

I wrote the web challenges for BSidesPDX this year! You can find them here, along with the solution write-ups. It was so exciting seeing people try to solve the challenges, seeing where they got stuck, and where they came up with an even more clever than intended way to solve a challenge.

The first web challenge was a simple JWT "none" algorithm one. If you're not familiar, a common problem in JWT libraries (in the past, it's since been fixed in most libraries) was that they would accept a "none" algorithm in the JWT header (instead of HS256 or whatever was intended), and do no validation of the signature. This allows attackers to craft a JWT with whatever they want in it: say they're someone they're not, change what they're allowed to do, etc. This challenge ended with 18 solves which I was very happy with since it means that most of the people who likely played the challenge were able to solve it. This is the idea behind a 100 challenge: anybody should be able to solve it with some help from Google. Another thing I really liked: Aaron Perecki's talk on Hacking OAuth described this exact vulnerability. This shows not only that the challenge is relevant, but it's nice that people that saw that talk could apply what they learned on the CTF. I also talked about this in my talk, but that wasn't until 3:00 PM (just a few hours until the CTF ended, intentionally).

The second was a slightly more complicated SSRF challenge. You had to view OpenAPI documentation to find a "deprecated" header which controlled where the backend would retrieve it's list of Hackers characters, and create a server that returned an empty photo path for one of the characters. When the backend hit your server and saw the empty photo path, it would presign a URL for that path in S3. This would allow you to view the contents of the entire bucket. At this point, you find a photo in the bucket that isn't normally returned, use the same approach to create a presigned URL to it, and get the flag. I heard from multiple people that they learned a lot about SSRF and presigned URLs, which made me really happy. This challenge got 7 solves, which I was happy with. It means it wasn't so hard that only a couple people got it, but it was hard enough that not just anybody could solve it by googling some stuff (it actually took some investigating and observing of how the service behaved).

The last challenge Hell.js combined MongoDB injection, Server-Side Javascript Injection, and signing your own JWTs. To solve the challenge you would use MongoDB injection to login to a service, use Server-Side Javascript Injection to exfiltrate the JWT secret, and then use that JWT secret to sign a token to get the flag from another "secure" service. I like this challenge because it had layers to it. People got past the MongoDB injection quickly, but struggled with a way to exfiltrate the JWT secret from the server. I also liked the idea that one service is totally secure, but since the other isn't, and they share JWT secrets, you can use the vulnerable service to pivot to the "secure" one. This challenge ended with just 4 solves, which I think is an appropriate number.

People seemed to really like the challenges, and I think one of the main reasons behind that is probably because they were all new-ish vulnerabilities. For example, most web CTFs have a component of SQL injection. None of these involved a SQL database. Instead, people had to deal with JWTs, SSRF, NoSQLi, SSJSi, cloud APIs (s3 at least), and actually do some thinking rather than just try some random payloads they found online. I also thought it was really funny how some people solved the challenges with Burp Suite, when they all could have been solved with cURL, and dev tools. After all: "Why use a bullet when you can use a nuke?"

Anyways, the entire event was a blast, and I've already got some ideas in my head for some web challenges next year ;)