Skip to main content

Die Gefahren von setattr: Mass-Assignment-Schwachstellen in Python vermeiden

Artikel von
Headshot of Jack Hair

Jack Hair

blog feature pypi spoof

15. Februar 2023

0 Min. Lesezeit

Mass Assignment, auch als Autobinding oder Object Injection bezeichnet, ist eine Kategorie von Schwachstellen, die auftreten, wenn Benutzereingaben Variablen oder Objekten innerhalb eines Programms zugewiesen werden.

Mass-Assignment-Schwachstellen entstehen häufig, wenn Angreifer einem Objekt unerwartete Felder hinzufügen, um die Programmlogik zu manipulieren. Dies war die Ursache für eine bekannte GitHub-Authentifizierungsschwachstelle: Die Mass-Assignment-Funktionalität in Ruby on Rails ermöglichte es einem Angreifer, seinen öffentlichen Schlüssel zur GitHub-Organisation rails hinzuzufügen und einen Commit in den Master-Branch zu pushen.

Dieser Blogbeitrag wurde unter anderem durch die Spring4Shell-Schwachstelle angeregt, eine Remote-Code-Execution-Schwachstelle (RCE), die durch die im Spring-Framework enthaltene Autobinding-Funktionalität verursacht wurde. Das gab Anlass zu der Frage, ob sich eine ähnliche Schwachstelle auch im Python-Ökosystem finden ließe.

In diesem Beitrag untersuchen wir die potenziellen Risiken der Python-Funktion setattr() und zeigen, wie Sie Mass-Assignment-Schwachstellen in Ihrer Anwendung verhindern können.

Einführung in setattr

Eine gängige Methode, Mass Assignment in Python zu implementieren, ist die integrierte Funktion setattr(). Diese Funktion übernimmt ein Objekt, einen Attributnamen und einen Attributwert und weist dem Attribut des Objekts den angegebenen Wert zu. Sie kann sowohl neue Attribute hinzufügen als auch bestehende ändern.

Es ist zwar praktisch, einem Benutzer das Setzen des Werts beliebiger Attribute eines Objekts zu erlauben, kann aber unbeabsichtigte Folgen haben.

Erstens können dadurch herkömmliche Schwachstellen in der Programmlogik durch Mass Assignment entstehen. Objektfelder, die Benutzer eigentlich nicht ändern können sollten, lassen sich nun ändern.

Zweitens sind die Attribute, die sich mit setattr ändern lassen, nicht auf vom Programmierer definierte Felder beschränkt — auch Funktionen und integrierte Attribute wie __class__ und __dict__ lassen sich setzen. Anders als bei der Spring4Shell-Schwachstelle kann setattr keine verschachtelten untergeordneten Attribute eines Objekts setzen, was den Umfang eines Angriffs begrenzt. Es ist jedoch leicht, eine Ausnahme auszulösen, wenn setattr aufgerufen wird, indem __class__ auf einen Zeichenfolgenwert gesetzt wird, oder später während der Programmausführung, indem eine zum Objekt gehörende Funktion überschrieben wird. Eine auf diese Weise ausgelöste Ausnahme kann zu einem abnormalen Programmverhalten oder zu einem Denial-of-Service führen.

Der folgende Code ist ein kurzes Beispiel, das mit setattr einen Benutzer erstellt. Wird er mit der Eingabe first_name=Sam,last_name=S ausgeführt, lautet die Ausgabe Sam S: Permission denied.

class User():
	def __init__(self, admin=False):
    		self.admin = admin

	def is_admin(self):
    		return "You are admin!" if self.admin else "Permission denied"

def parse_input(usr_in):
	return dict([tuple(kv.split("=", 1)) for kv in usr_in.split(",") if "=" in kv])

def create_user(info):
	user = User()
	for key, value in info.items():
    	setattr(user, key, value)
	return user

def main():
	user = create_user(parse_input(str(input())))
	if hasattr(user, "first_name") and hasattr(user, "last_name"):
    		print(f"{user.first_name} {user.last_name}: {user.is_admin()}")

if __name__ == "__main__":
	main()

Um das Programm durch eine nicht abgefangene Ausnahme zum Absturz zu bringen, kann die Eingabe first_name=Sam,last_name=S,is_admin=foo verwendet werden — dadurch wird die Funktion is_admin überschrieben und durch eine Zeichenfolge ersetzt. Beim Aufruf der Funktion wird dann eine Ausnahme ausgelöst.

Die Eingabe first_name=Sam,last_name=S,admin=foo überschreibt das admin-Attribut des User-Objekts mit der Zeichenfolge foo, da es sich dabei um einen Truthy-Wert handelt. Wenn die Funktion is_admin das admin-Attribut prüft, wird der Benutzer als Administrator behandelt und Sam S: You are admin! ausgegeben.

Mass Assignment verhindern

Eine allgemeine Strategie zur Eindämmung von Mass-Assignment-Schwachstellen besteht darin, Objekte mit Benutzereingaben von den Objekten zu trennen, die für die interne Programmlogik zuständig sind. Dazu werden Data Transfer Objects (DTOs) erstellt, die nur die Felder enthalten, die ein Benutzer setzen darf. In Python lässt sich dies leicht mit dataclasses umsetzen. Der folgende Code zeigt, wie sich mit einem DTO die zulässigen Felder explizit festlegen lassen. Werden ungültige Felder angegeben, wird ein TypeError ausgelöst — dieser wird anschließend abgefangen und behandelt, wodurch die oben beschriebenen Angriffe verhindert werden.

from dataclasses import dataclass

@dataclass
class UserDTO():
	first_name: str
	last_name: str

class User():
	def __init__(self, dto, admin=False):
    		self.first_name = dto.first_name
    		self.last_name = dto.last_name
    		self.admin = admin

	def is_admin(self):
    		return "You are admin!" if self.admin else "Permission denied"

def parse_input(usr_in):
	return dict([tuple(kv.split("=", 1)) for kv in usr_in.split(",") if "=" in kv])

def create_user(info):
	try:
    		return User(UserDTO(**info))
	except TypeError:
    		return None

def main():
	user = create_user(parse_input(str(input())))
	if user:
    		print(f"{user.first_name} {user.last_name}: {user.is_admin()}")

if __name__ == "__main__":
	main()

Auch die Validierung von Benutzereingaben kann Mass-Assignment-Schwachstellen verhindern. Wie immer ist es besser, die Eingaben anhand einer Liste zulässiger Daten zu validieren, statt nach verbotenen Daten zu suchen. Der folgende Code verwendet weiterhin setattr, prüft den Attributnamen aber anhand einer Liste zulässiger Attribute.

class User():
	allowed_fields = ["first_name", "last_name"]

	def __init__(self, admin=False):
    		self.admin = admin

	def is_admin(self):
    		return "You are admin!" if self.admin else "Permission denied"

def parse_input(usr_in):
	return dict([tuple(kv.split("=", 1)) for kv in usr_in.split(",") if "=" in kv])

def create_user(info):
	user = User()
	for key, value in info.items():
    		if key in User.allowed_fields:
        			setattr(user, key, value)
    		else:
        			return None
	return user

def main():
	user = create_user(parse_input(str(input())))
	if user:
    		print(f"{user.first_name} {user.last_name}: {user.is_admin()}")

if __name__ == "__main__":
	main()

Am besten geben Sie immer explizit an, welche Benutzereingaben ein Programm erwartet. Wenn Sie bei der Verarbeitung von Benutzereingaben jedoch mehr Flexibilität benötigen, gibt es Alternativen zur Funktion setattr. Das collections-Modul der Python-Standardbibliothek stellt zwei nützliche Klassen bereit: collections.UserDict und collections.abc.MutableMapping. Von diesen Klassen können Sie erben, um Schlüssel-Wert-Zuordnungen zusätzliche Funktionalität hinzuzufügen. Wenn Sie auf Werte lieber über Attribute statt über Schlüssel zugreifen möchten, können Sie dies erreichen, indem Sie in Ihrer Klasse eine __getattr__-Methode implementieren. Im folgenden Beispiel verhält sich die User-Klasse wie ein Dictionary und kann alle vom Benutzer bereitgestellten Felder speichern. Durch das Überschreiben von __getattr__ wird ein Attribut, das sich nicht über den üblichen Mechanismus für den Attributzugriff finden lässt, stattdessen aus dem Dictionary mit den vom Benutzer bereitgestellten Feldern zurückgegeben. So kann der Benutzer auf diese Felder wie auf Attribute zugreifen, während Attribute der Klasse nicht durch Benutzereingaben überschrieben werden können.

from collections import UserDict

class User(UserDict):
	def __init__(self, info, admin=False):
    		self.admin = admin
    		super().__init__(info)

	def is_admin(self):
    		return "You are admin!" if self.admin else "Permission denied"

	def __getattr__(self, name):
    	if name in self:
     		   	return self[name]
    		else:
        		raise AttributeError("'User' object has no attribute '{}'".format(name))

def parse_input(usr_in):
	return dict([tuple(kv.split("=", 1)) for kv in usr_in.split(",") if "=" in kv])

def create_user(info):
	return User(info)

def main():
	user = create_user(parse_input(str(input())))

	if "first_name" in user and "last_name" in user:
    		print(f"{user.first_name} {user.last_name}: {user.is_admin()}")

if __name__ == "__main__":
	main()

Schutz Ihrer Python-Bibliotheken

Während der Softwareentwicklung kann es schwierig sein, Sicherheit gegen Komfort und Flexibilität abzuwägen. Was auf den ersten Blick harmlos erscheint, kann Sie und Ihre Anwendung einer Vielzahl von Sicherheitsrisiken aussetzen — deshalb sind Aufklärung und alternative Methoden so wichtig. Wir haben erläutert, wie setattr funktioniert, welche Gefahren mit seiner Verwendung in Ihren Python-Bibliotheken verbunden sind und wie Sie Mass-Assignment-Schwachstellen verhindern können. Mit diesem Wissen sind Sie gut gerüstet, um Ihre Python-Anwendungen vor Angriffen und Datenschutzverletzungen zu schützen. Weitere Informationen finden Sie auf unserer Homepage. Erstellen Sie dort ein kostenloses Konto oder buchen Sie eine Demo, um zu erfahren, wie Snyk Sie beim Schutz Ihres Codes, Ihrer Container, Abhängigkeiten und Cloud-Infrastruktur unterstützen kann.

Starten Sie mit Capture-the-Flag

Erfahren Sie in unserem virtuellen On-Demand-Workshop für Einsteiger, wie Sie Capture-the-Flag-Challenges lösen.

Weiterlesen

illustration hero ai
Blog

PHP behebt zwei kritische unauthentifizierte RCE-Schwachstellen (CVE-2026-109618, CVE-2026-109619): Jetzt aktualisieren

Zwei kritische Sicherheitslücken im Request-Lebenszyklus von PHP können unauthentifizierte Remote Code Execution ermöglichen. Aktualisieren Sie auf eine korrigierte Version oder installieren Sie für ältere Versionen einen Backport des Anbieters.

blog feature ai lilac
Blog

Mit agentischer Behebung Ihren Schwachstellen-Backlog in Pull Requests verwandeln

Der Evo Remediation Agent ist jetzt in der öffentlichen Vorschau verfügbar und erweitert Evo Agentic AppSec um die automatisierte Auflösung von Abhängigkeiten. AppSec-Teams können jetzt kontinuierliche Behebungskampagnen durchführen

AI Background min crop
Blog

KI ruft alle Risiken auf den Plan, die Sie akzeptiert haben: Sieben Erkenntnisse aus dem AI Security Panel: Unfiltered

KI macht Angriffe günstiger und schneller und legt Risiken offen, die Unternehmen bereits tragen. Vier Sicherheitsverantwortliche erklären, warum Abwehr in Maschinengeschwindigkeit und schnellere Behebung jetzt unverzichtbar sind.