La guía definitiva de Python pickle
Thomas Daniels
20 de abril de 2022
0 minutos de lecturaDurante el desarrollo de aplicaciones, a menudo necesitamos conservar datos complejos (como objetos) para usarlos en distintos entornos de ejecución. Sin embargo, mantener la persistencia en estructuras de datos y objetos complejos no es nada sencillo. En Python, puedes usar la biblioteca pickle integrada para encargarte de este proceso. Pickle puede serializar un objeto de Python en un flujo de bytes plano (serialización con pickle) y también transformar un flujo de bytes de nuevo en un objeto de Python (deserialización con pickle).
Al conservar datos complejos, debes representarlos como un flujo plano de bytes para almacenarlos en disco o enviarlos por la red. Este proceso de transformar una estructura de objetos en un flujo de bytes se llama marshaling o serialización. Cuando nuestra aplicación lee o recibe el flujo de bytes, realiza el proceso inverso y lo transforma en nuestra estructura de objetos. Esto se llama unmarshaling o deserialización.
Nota: Como pickle es específico de Python, es fácil de usar en cualquier aplicación de Python. Sin embargo, esto también significa que no se puede usar pickle para intercambiar datos entre aplicaciones escritas en distintos lenguajes.
Este artículo te enseñará a usar pickle de forma segura en tus aplicaciones. Aunque pickle es increíblemente práctico en muchas situaciones, es importante tener en cuenta que solo debes deserializar con pickle los datos en los que confías. Deserializar datos que no son confiables puede provocar la ejecución de código arbitrario y es una causa común de vulnerabilidades de seguridad críticas.

Cómo usar Python pickle
Pickle es muy fácil de usar, así que vayamos directo al tema. Todas las funciones relevantes para serializar y deserializar con pickle están en el módulo pickle. Empecemos por importar ese módulo.
Al serializar un objeto de Python con pickle, podemos hacerlo directamente en un archivo o en un objeto de bytes que podremos usar después en nuestro código. En ambos casos, basta con llamar a un método.
Para serializar un objeto con pickle en un archivo, llama a pickle.dump(object, file). Para obtener solo los bytes serializados con pickle, llama a pickle.dumps(object).
Como mencionamos, pickle se usa fácilmente en Python. Puedes serializar con pickle muchos tipos de datos, como:
Tipos primitivos de Python
Estructuras de colecciones anidadas, como tuplas, listas, conjuntos y diccionarios, siempre que los diccionarios contengan solo objetos que se puedan serializar con pickle
La mayoría de las instancias de clases
La documentación de Python incluye una lista exhaustiva de los elementos que se pueden serializar con pickle.
Si intentas serializar con pickle un objeto que no se puede serializar, Python genera un PicklingError.
Ejemplos de Python pickle
Practiquemos la serialización con pickle. A continuación, hay una estructura de datos que representa el estado de un juego hipotético. En este juego, un jugador se encuentra en una ubicación determinada del mundo, representada como una tupla de coordenadas. El mundo también tiene obstáculos en ubicaciones específicas, representadas como tuplas. El jugador tiene objetos con un nombre y un costo. Las clases son así:
Ahora, construyamos un estado específico y serialicémoslo con pickle en un archivo.
Pickle es un formato binario ilegible para las personas, así que, si examinamos el contenido del archivo state.bin que acabamos de crear, no tendrá mucho sentido. Solo reconoceremos algunos nombres de identificadores, como GameState y obstacles.
Ahora que guardamos el estado del juego en un archivo, intentemos cargarlo para agregar funciones que permitan guardar la partida y reanudarla. Tanto pickle.dump como pickle.dumps tienen sus equivalentes para deserializar con pickle: pickle.load(_file_) carga un objeto de Python serializado con pickle desde un archivo, y pickle.loads(_bytes_) hace lo mismo con los bytes indicados.
Como puedes ver, los fundamentos de la serialización con pickle son muy sencillos. Basta con llamar a un método para almacenar y cargar un objeto de Python.
Cómo explotar Python pickle
Como mencionamos en la introducción, solo debes deserializar con pickle los datos en los que confías, porque el módulo pickle no es seguro. Si deserializas el flujo de bytes de un atacante, podrías ejecutar código arbitrario. Esto es un efecto secundario de la gran flexibilidad del formato pickle.
Un objeto de Python puede especificar cómo debe serializarse con pickle mediante el método especial _reduce_. Este método debe devolver una cadena o una tupla. Una cadena representa el nombre de una variable global. Una tupla representa código invocable (como una función o clase), los argumentos de ese código y cierta información opcional que no es relevante para este ejemplo. Durante la deserialización con pickle, se llama al código especificado con sus argumentos.
Con este conocimiento, puedes construir un objeto serializado con pickle que llame a cualquier función que quieras ejecutar durante la deserialización. Por ejemplo, podrías ejecutar un comando del sistema llamando a la función os.system durante la deserialización, o usar eval para ejecutar cualquier código de Python que quieras.
Este es un ejemplo (inofensivo) de este ataque, que usa la función eval (que simplemente llamará a la función print al cargarlo):
Si ejecutamos este código, se imprimirá el número 3 en la consola, porque deserializar estos datos ejecutará eval("print(1+2)").
Cómo usar Python pickle de forma segura
Si debes aceptar datos de un cliente que no es de confianza, no puedes usar pickle debido a los riesgos mencionados. En su lugar, debes usar otro formato de serialización de datos, como la notación de objetos de JavaScript (JSON).
Puedes hacerlo con el módulo json de Python. La desventaja es que el módulo json es mucho menos potente que pickle, porque no admite tipos de datos complejos, como los objetos personalizados, de forma predeterminada. Además, los tipos de datos nativos de JSON son limitados. Por ejemplo, los conjuntos de Python no tienen un equivalente en JSON, así que tendrías que usar un codificador personalizado para los conjuntos. Sin embargo, JSON es un formato seguro para trabajar con datos que no son de confianza.
Supongamos que una aplicación confiable genera datos serializados con pickle, pero no puedes garantizar su integridad entre el momento en que se serializan y el momento en que se deserializan. Quizás no puedas confiar en los datos porque los envías por una red insegura o los guardas en un almacenamiento persistente al que un atacante podría tener acceso.
En cualquiera de los dos casos, una solución es generar una firma criptográfica para los datos serializados con pickle mediante un HMAC. Luego, los datos se envían o almacenan junto con la firma. Antes de deserializarlos con pickle, quien los recibe puede validar la firma para comprobar su integridad.
Puedes generar una firma así (con SHA256):
NOTA: En la práctica, tu clave secreta se almacenaría de forma segura en una aplicación del lado del servidor que nunca se expone a ningún entorno que no sea de confianza. No incluyas una clave secreta directamente en tu código. =)
Quien recibe los datos puede calcular el resumen esperado para los datos serializados con pickle y comprobar si coincide con el resumen proporcionado.
En este escenario, aunque los atacantes alteren los datos serializados con pickle, no podrán alterar la firma correctamente (ni engañar a quien los recibe para que ejecute código que no es de confianza) si no tienen la clave secreta.
Reflexiones finales
Ahora sabes cómo usar el módulo pickle de Python para serializar y deserializar objetos complejos de Python de forma segura, y eso es un gran avance.
Recuerda que pickle puede provocar vulnerabilidades de seguridad críticas en el código, así que nunca debes deserializar con pickle datos en los que no confías. Si debes aceptar datos de un cliente que no es de confianza, usa el formato JSON, que es más seguro. Y si transfieres datos serializados con pickle entre aplicaciones confiables, pero necesitas medidas adicionales para evitar que se alteren, genera una firma HMAC que puedas verificar antes de deserializarlos con pickle.
Empieza con los retos de Capture the Flag
Aprende a resolver retos de Capture the Flag viendo nuestro taller virtual de nivel básico a pedido.
