
Magento Unautorisierte Remote-Codeausführung (CVE-2016-4010)
0x00 Einleitung
Am 17. Mai veröffentlichte der Sicherheitsforscher Netanel Rubin eine nicht authentifizierte Remote-Code-Ausführungs-Schwachstelle in Magento (CVE-2016-4010). Diese Schwachstelle besteht tatsächlich aus mehreren kleineren Schwachstellen und erlaubt es einem Angreifer, auf einem anfälligen Magento-Server ohne Authentifizierung PHP-Code auszuführen. Magento ist eine sehr beliebte E-Commerce-Plattform, die 2011 von eBay übernommen wurde. Bekannte Unternehmen wie Samsung, Nikon, Lenovo sowie viele kleine E-Commerce-Seiten nutzen sie. Berichten zufolge wird Magento von 250.000 Online-Shops verwendet, mit einem jährlichen Transaktionsvolumen von 60 Milliarden US-Dollar.
0x01 Analyse
Die Nutzungsbedingungen für diese Schwachstelle:
Die Magento-Web-API erlaubt zwei verschiedene Arten von RPCs: REST RPC und SOAP API. Beide bieten die gleichen Funktionen; der einzige Unterschied besteht darin, dass erstere JSON und HTTP-Anfragen verwendet, während letztere XML nutzt.
Um nur die APIs bestimmter Module freizugeben, bietet Magento Entwicklern eine bequeme Methode: Sie deklarieren in der Datei "webapi.xml" nur die APIs der Module, die sie zugänglich machen möchten. Die webapi.xml-Datei enthält alle Klassen und Methoden der Web-APIs, die freigegeben werden sollen. Jede Methode gibt auch die spezifischen erforderlichen Berechtigungen an. Diese Berechtigungen umfassen:
Natürlich öffnet diese Möglichkeit, dass Entwickler die webapi.xml-Datei für die Kommunikation zwischen dem Frontend und Backend (Web-API) des Systems verwenden können, tatsächlich eine Hintertür direkt in den Kern des Moduls.
Selbst wenn wir die Berechtigung "anonymous" haben, brauchen wir dennoch eine Möglichkeit, Werte dynamisch zu übergeben. Dies bezieht sich auf die verschiedenen Objekte, die im System verwendet werden können. Beispielsweise erlaubt die API-Funktion "CustomerRepositoryInterface::save()" die Verwendung eines Objekts vom Typ "CustomerInterface" in der Variable "$customer". Der Code sieht folgendermaßen aus:
interface CustomerRepositoryInterface
{
/**
* Create customer.
*/
public function save(\Magento\Customer\Api\Data\CustomerInterface $customer);
}
Wie können wir nun über die RPC-Schnittstelle Objekte erstellen? Tatsächlich liegt die Antwort in der Art und Weise, wie Magento den SOAP-Server konfiguriert.
Magento verwendet den standardmäßig gebündelten PHP-"SoapServer". Für die korrekte Konfiguration benötigt "SoapServer" eine WSDL-Datei, die alle Methoden, Parameter und benutzerdefinierten Typen definiert, die in den eigentlichen RPC-Anfragen verwendet werden. Magento generiert für jedes Modul, das XMLRPC-Funktionen unterstützt, separate WSDL-Dateien und setzt die Werte direkt aus der webapi.xml-Datei des Moduls.
Wenn eine RPC-Anfrage vom Server analysiert wird, verwendet der Server die in der WSDL-Datei gefundenen Daten, um zu prüfen, ob die Anfrage gültig ist, und überprüft die Methode, die Parameter und die Typen der Anfrage. Ist die Anfrage gültig, wird das analysierte Anfrageobjekt an Magento zur weiteren Verarbeitung übergeben. Ein sehr wichtiger Punkt ist, dass "SoapServer" in keiner Weise mit Magento interagiert; alle Informationen über die Methoden und Parameter des Moduls stammen aus der WSDL-Datei. Zu diesem Zeitpunkt besteht die gesendete Anfrage immer noch aus verschachtelten Arrays, und während der Analysephase des SoapServers werden keine Objekte erstellt. Um die erforderlichen Objekte zu erstellen, verarbeitet Magento die Eingabe selbst.
Um die Parameternamen und Datentypen zu extrahieren, holt Magento den Prototyp aus der Methode der Anfrage (siehe vorherigen Code). Für einige grundlegende Datentypen wie Strings, Arrays, Boolesche Werte usw. ordnet das System die Eingabe dem entsprechenden Typ zu. Für Objekttypen ist die Lösung jedoch komplizierter.
Wenn der Datentyp eines Parameters eine Instanz einer Klasse ist, versucht Magento, mit der bereitgestellten Eingabe eine Instanz zu erstellen. Denken Sie daran, dass die Eingabe zu diesem Zeitpunkt nur ein Dictionary ist, dessen Schlüssel die Eigenschaftsnamen und die Werte die Eigenschaftswerte sind.
Zuerst erstellt Magento eine neue Instanz der erforderlichen Klasse. Dann versucht es, sie mit der folgenden Methode zu füllen:
Magento verarbeitet auf diese Weise jede Eigenschaft, die der Benutzer zu setzen versucht. Wenn alle Eigenschaften überprüft wurden, betrachtet Magento die Instanz als fertig eingerichtet und verarbeitet den nächsten Parameter. Nachdem alle Parameter auf diese Weise verarbeitet wurden, führt Magento schließlich die API-Methode aus.
Zusammenfassend lässt sich sagen: Magento erlaubt es Ihnen, ein Objekt zu erstellen, dessen öffentliche Eigenschaften zu setzen und schließlich über seine RPC jede Methode auszuführen, die mit "Set" beginnt. Und genau dieses Verhalten führte zu den Schwachstellen in Magento.
Die Forschung ergab, dass einige API-Aufrufe es erlauben, bestimmte Informationen im Warenkorb zu setzen, wie z. B. unsere Lieferadresse, Produkte oder sogar unsere Zahlungsmethode.
Wenn Magento unsere Informationen in der Warenkorbinstanz setzt, verwendet es die "save"-Methode der Instanz, um die neu hinzugefügten Daten in der Datenbank zu speichern.
Schauen wir uns an, wie die "save"-Methode funktioniert.
/**
* Save object data
*/
public function save(\Magento\Framework\Model\AbstractModel $object)
{
...
// If the object is valid and can be saved
if ($object->isSaveAllowed()) {
// Serialize whatever fields need serializing
$this->_serializeFields($object);
...
// If the object already exists in the DB, update it
if ($this->isObjectNotNew($object)) {
$this->updateObject($object);
// Otherwise, create a new record
} else {
$this->saveNewObject($object);
}
// Unserialize the fields we serialized
$this->unserializeFields($object);
}
...
return $this;
}
// AbstractDb::save()
Magento stellt sicher, dass unser Objekt gültig ist, serialisiert dann alle Teile, die serialisiert werden sollen, speichert sie in der Datenbank und deserialisiert schließlich die zuvor serialisierten Teile.
Das sieht einfach aus, oder? Nicht ganz. Lassen Sie uns weiter sehen, wie Magento bestimmt, welche Teile serialisiert werden sollen.
/**
* Serialize serializable fields of the object
*/
protected function _serializeFields(\Magento\Framework\Model\AbstractModel $object)
{
// Loops through the '_serializableFields' property
// (containing hardcoded fields that should be serialized)
foreach ($this->_serializableFields as $field => $parameters) {
// Get the field's value
$value = $object->getData($field);
// If it's an array or an object, serialize it
if (is_array($value) || is_object($value)) {
$object->setData($field, serialize($value));
}
}
}
// AbstractDb::_serializeFields()