Skip to content

Commit

Permalink
Merge pull request #57 from squ1rrel-ctf/burgess/csaw
Browse files Browse the repository at this point in the history
CSAW writeups - Kyle
  • Loading branch information
kyleburgess2025 committed Sep 10, 2024
2 parents cfc1464 + 9073ba6 commit 73b7e00
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 0 deletions.
48 changes: 48 additions & 0 deletions _posts/2024-09-09-csaw-bucketwars.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
layout: post
current: post
cover: assets/csaw/kyleburgess2025/bucket_knights.webp
navigation: True
title: "BucketWars"
date: 2024-09-09 11:59:00
tags: [csaw, web]
class: post-template
subclass: 'post'
author: kyleburgess2025
---

The hardest challenge for a CTFer to solve is how to lose their versionity.

## The Problem

No source code? No problem. Let's take a look at this website.

![A webpage with a photo of a bucket and a caption.](/assets/csaw/kyleburgess2025/homepage.webp)
*Wow. Very philosophical.*

The BucketWars website is very, very, very simple. We can see a photo of a bucket with some feaux-poetic musings along with a version label. We can also navigate to `/versions` to see a list of previous versions, from `v1` to `v5`. Clicking on the version brings you to `/index_v#.html`, each of which is a slightly different website... nothing of note here. Of course, my pattern-recognizing ass immediately got cheeky with it and tried `/index_v6.html` to see if anything appeared.

![Just a lousy 404 page.](/assets/csaw/kyleburgess2025/404.webp)

Man, just a 404 page... but an INTERESTING 404 page! First off, we can glean that the website is hosted on an AWS S3 bucket somewhere in the cloud. We can tell by the domain name that the bucket name is `bucketwars.ctf.csaw.io`. We also see a link to what should have been the 404 page. Opening the URL gives us this:

![Three Kermits doing the see no evil hear no evil speak no evil pose.](/assets/csaw/kyleburgess2025/kermit.webp)

## Messing Around

As a long-time AWSer, I definitely knew about the cloud shell inside the AWS website and definitely have not just used the CLI this whole time, definitely. I started out by trying to list everything in the bucket... maybe there's a file I don't know about! Sadly, running `aws s3 ls bucketwars.ctf.csaw.io` just gives us a "Not Authorized" error. I tried a few other S3 commands, but couldn't find anything. This is where I lost hope. Dejected, I walked home from the engineering building at 1am to shower and go to bed.

## An Epiphany

As I stood in the shower, I kept repeating to myself, "Versioning... S3... Versioning..." until I eventually hit "woah... S3 versioning!" S3 allows you to enable versioning on your files, which keeps track of all past versions of a file for you. You can see the version history by using the `aws s3api list-object-versions` command. Running `aws s3api list-object-versions --bucket` gave me...

![Versions of files!!!](/assets/csaw/kyleburgess2025/versions.webp)

Woah!! I was suddenly able to see all of the previous versions of files. I tested each one by navigating to `https:bucketwars.ctf.csaw.io.s3.amazonaws.com/path/to/file?versionId=VERSION_ID`. A bunch were nonsense, but a few on `index_v1.html` seemed to be leading somewhere, until I reached `https://bucketwars.ctf.csaw.io.s3.amazonaws.com/index_v1.html?versionId=t6G6A20JCaF5nzz6KuJR6Pj1zePOLAdB`:

![A weirdly high-res photo of a bucket.](/assets/csaw/kyleburgess2025/suspicious-bucket.webp)
*A weirdly high-res photo of a bucket.*

At first, I thought this was just some more nonsense, but stegonography run by Patryk using Aperi'Solve revealed the flag:

`csawctf{lEaKY_Bu4K3tz_oH_m3_04_mY!}`
94 changes: 94 additions & 0 deletions _posts/2024-09-09-csaw-lost-pyramid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
layout: post
current: post
cover: assets/csaw/kyleburgess2025/pyramid_token.webp
navigation: True
title: "Lost Pyramid"
date: 2024-09-09 11:59:00
tags: [csaw, web]
class: post-template
subclass: 'post'
author: kyleburgess2025
---

The only way to avoid SSTIs is to use protection.

## The Problem

In this problem, we are able to navigate through a pyramid through a website. The goal is to access the King's Lair without being turned away by the king. The king will only allow you in if your JWT token (stored in a cookie) states that you are royalty and that you are approaching on the King's Day.

![A photo of the inside of a pyramid.](/assets/csaw/kyleburgess2025/pyramid.webp)
*This 1000 sqft ranch-style home has an open floor plan, unique architecture, and whispering coming from the walls!*

Lovely. There are a few other rooms, the most notable of which allows you to provide your name, which is then rendered on the screen. Suspicious...

![A photo of a pyramid with text across the top.](/assets/csaw/kyleburgess2025/scarab.webp)
*This seems normal. I'm sure this has nothing to do with the challenge.*

## My Approach

Approaching this problem, I was thinking about 1 think and 1 thing only: JWTs, or JSON Web Tokens. JWTs are a way to securely send information from one place to another. A JWT contains a header (containing metadata about the token), a payload (containing the information that needs to be sent), and a signature (the result of signing the base64-encoded header and payload with a secret key and an algorithm specificed in the header). All this is cool and all, but how can we find a vulnerability here? Akash pointed out that the version of PyJWT specified in the `requirements.txt`, `2.3.0`, has a known vulnerability, described [here](https://github.com/jpadilla/pyjwt/security/advisories/GHSA-ffqj-6fqr-9h24).


In Lost Pyramid, we have a private key and a public key for signing and verifying JWT tokens. The private key is, surprisingly, private, and is used to sign the token; the public key is used to verify that the private key was used to sign the token and therefore verify that the token was sent by someone we trust. What if, and hear me out, we trick the JWT decoder into thinking we are using a symmetric algorithm, which only requires one key? Then, we could sign the token with the public key, and it will be decoded also with the public key. This would be bad, since the public key is, well, public. That's where the vulnerability comes in.

Basically, if you set `algorithms=jwt.algorithms.get_default_algorithms()` while decoding a JWT, the JWT decoder will try multiple algorithms to decode your JWT. A bad actor can use the symmetric `HS256` algorithm to sign the token with the public key, rather than the intended shared private key. By not specifying the exact algorithm we are using to decode the JWT, the decoder is tricked into thinking the key was signed with a shared private key, rather than a public key, and the decoding is successful. So, rather than needing to know the private key in order to sign the JWT token using the `EdDSA` algorithm used elsewhere in the app, we can sign our key using the public key without any problems. Done. Easy.

Except... we're not done. First off, we don't know the public key. Second off, we don't know the King's Day, which we need to include in our payload. That's where SSTI comes in. SSTI stands for server-side template injection; basically, we can expose variables from the code by injecting our own code. I actually couldn't figure this out for a while until I called fellow teammate Nisala Kalupahana calmly and nicely pointed out these lines of code:

```python
kings_safelist = ['{','}', '𓁹', '𓆣','𓀀', '𓀁', '𓀂', '𓀃', '𓀄', '𓀅', '𓀆', '𓀇', '𓀈', '𓀉', '𓀊',
'𓀐', '𓀑', '𓀒', '𓀓', '𓀔', '𓀕', '𓀖', '𓀗', '𓀘', '𓀙', '𓀚', '𓀛', '𓀜', '𓀝', '𓀞', '𓀟',
'𓀠', '𓀡', '𓀢', '𓀣', '𓀤', '𓀥', '𓀦', '𓀧', '𓀨', '𓀩', '𓀪', '𓀫', '𓀬', '𓀭', '𓀮', '𓀯',
'𓀰', '𓀱', '𓀲', '𓀳', '𓀴', '𓀵', '𓀶', '𓀷', '𓀸', '𓀹', '𓀺', '𓀻']

name = ''.join([char for char in name if char.isalnum() or char in kings_safelist])
```

and

```python
return render_template_string('''
<!DOCTYPE html>
<html lang="en">
...
<body>
<a href="{{ url_for('hallway') }}" class="return-link">RETURN</a>
{% if name %}
<h1>𓁹𓁹𓁹 Welcome to the Scarab Room, '''+ name + ''' 𓁹𓁹𓁹</h1>
{% endif %}
</body>
</html>
''', name=name, **globals()) # ok nisala yelled at us for missing this
```

Do you see that? `**globals()`. This passes all global variables into the context of the template. Do you see that other thing? Brackets are on the allowlist! Ok, fine, Nisala yelled at us for missing this. Apparently, we hosted a workshop where we discussed this exact vulnerability. Sadly, I host a lot of different workshops on a lot of different topics and also I'm a silly goose so I'm not sure how I was expected to remember all this. Whatever. We keep grinding. Both the King's Day and the public key are stored in global variables, so let's pull those out by claiming our name is `{{PUBLICKEY}}` and `{{KINGSDAY}}`:

Payload: `{{KINGSDAY}}𓁹{{PUBLICKEY}}`:

Result:
![A photo of the inside of a pyramid with the public key and the kingsday written on it.](/assets/csaw/kyleburgess2025/scarab_key.webp)
*What a beautiful name for a baby boy.*

Ok, let's put it all together. I wrote this lovely encoding function that created the token we need:

```python
PUBLICKEY= b'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPIeM72Nlr8Hh6D1GarhZ/DCPRCR1sOXLWVTrUZP9aw2'
def encode():
payload = {
"ROLE": "royalty",
"CURRENT_DATE": f"03_07_1341_BC",
"exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=(365*3000))
}
token = jwt.encode(payload, PUBLICKEY, algorithm="HS256")

return token
```

I set the `pyramid` cookie to be equal to this token and proceeded to the King's Lair:

![Gold! Gold!!!](/assets/csaw/kyleburgess2025/pyramid_flag.webp)

Done. QED. Bam.
Binary file added assets/csaw/kyleburgess2025/404.webp
Binary file not shown.
Binary file added assets/csaw/kyleburgess2025/bucket_knights.webp
Binary file not shown.
Binary file added assets/csaw/kyleburgess2025/homepage.webp
Binary file not shown.
Binary file added assets/csaw/kyleburgess2025/kermit.webp
Binary file not shown.
Binary file added assets/csaw/kyleburgess2025/pyramid.webp
Binary file not shown.
Binary file added assets/csaw/kyleburgess2025/pyramid_flag.webp
Binary file not shown.
Binary file added assets/csaw/kyleburgess2025/pyramid_token.webp
Binary file not shown.
Binary file added assets/csaw/kyleburgess2025/scarab.webp
Binary file not shown.
Binary file added assets/csaw/kyleburgess2025/scarab_key.webp
Binary file not shown.
Binary file not shown.
Binary file added assets/csaw/kyleburgess2025/versions.webp
Binary file not shown.

0 comments on commit 73b7e00

Please sign in to comment.