Das grundlegende Problem bei der Serialisierung besteht darin, dass die Bereiche, die angegriffen werden können, zu groß sind und sich ständig ausbreiten. Das Objektdiagramm (ein Bild einer Gruppe von Objekten, die durch Referenz verbunden sind) wird durch die readObject-Methode von ObjectInputStream wiederhergestellt. Mit dieser Methode können Sie Objekte fast aller Art im Klassenpfad instanziieren, solange der Typ Serializable implementiert. Beim Deserialisieren eines Byte-Streams kann diese Methode jeden Codetyp ausführen, der sich im Klassenpfad befindet, und Serialize implementieren. Daher sind alle diese Typen für Angriffe vorgesehen.
Selbst wenn Sie die Sicherheitsanfälligkeit aus einer serialisierbaren Klasse entfernen, die angegriffen werden kann, ist die Anwendung selbst möglicherweise weiterhin anfällig. Wenn Sie eine Gadget-Kette erstellen, indem Sie Methoden kombinieren, die eine gefährliche Verarbeitung (sogenannte Gadgets) aus serialisierbaren Methoden ausführen können, kann manchmal beliebiger nativer Code ausgeführt werden. Tatsächlich wurde 2016 mit diesem Mechanismus ein Hack gegen die Stadtbahn der San Francisco Metropolitan Transit Agency durchgeführt.
Wie oben erwähnt, können Sie problemlos einen Stream erstellen, dessen Deserialisierung viel Zeit in Anspruch nimmt, ohne ein Gadget zu verwenden, und Sie können damit einen DoS-Angriff ausführen. Solche Ströme werden Deserialisierungsbomben genannt.
// Deserialization bomb - deserializing this stream takes forever
static byte[] bomb() {
Set<Object> root = new HashSet<>();
Set<Object> s1 = root;
Set<Object> s2 = new HashSet<>();
for (int i = 0; i < 100; i++) {
Set<Object> t1 = new HashSet<>();
Set<Object> t2 = new HashSet<>();
t1.add("foo"); // Make t1 unequal to t2
s1.add(t1); s1.add(t2);
s2.add(t1); s2.add(t2);
s1 = t1;
s2 = t2;
}
return serialize(root); // Method omitted for brevity
}
Im obigen Code verfügt ein HashSet über 100 Schichten von HashSet-Elementen. Wenn Sie eine HashSet-Instanz erstellen, müssen Sie den Hashcode für jedes Element berechnen. Daher ist es notwendig, Hashcode 2 zur 100. Potenz aufzurufen, und die Verarbeitung ist überhaupt nicht abgeschlossen.
Wie sollen wir mit den oben genannten Angriffen im Zusammenhang mit der Serialisierung umgehen?
** Der beste Weg ist, nicht zu deserialisieren. ** ** ** ** Es gibt keinen Grund, die Java-Serialisierung in neuen Systemen zu verwenden. ** ** ** Es gibt bessere Mechanismen zum Übersetzen von Bytesequenzen und Objekten, die als plattformübergreifende Darstellungen strukturierter Daten bezeichnet werden.
Typische plattformübergreifende Darstellungen strukturierter Daten sind JSON und Protobuf. Der wichtigste Unterschied zwischen JSON und Protobuf besteht darin, dass JSON textbasiert und für Menschen lesbar ist, während Protobuf binär effizienter ist.
Die Serialisierung in Java kann bei der Wartung und Entwicklung von Legacy-Systemen unvermeidbar sein. In solchen Fällen sollten Sie Maßnahmen ergreifen wie ** unzuverlässige Daten nicht deserialisieren **. Akzeptieren Sie insbesondere keinen RMI-Verkehr aus nicht vertrauenswürdigen Quellen.
Wenn eine Serialisierung in Java unvermeidbar ist und die Sicherheit der zu deserialisierenden Daten zweifelhaft ist, ist java.io.ObjectInputFilter (von Java9 eingeführt und auf 6,7,8 zurückportiert) ein Objektdeserialisierungsfilter. ) Sollte benutzt werden. Dies bietet die Möglichkeit, Streams zu filtern, bevor sie deserialisiert werden. Es wird entschieden, ob Klasse für Klasse abgelehnt oder akzeptiert wird. Zu diesem Zeitpunkt können Sie das Whitelist-Format oder das Blacklist-Format auswählen. Da die Blacklist jedoch nur vor bekannten Bedrohungen schützen kann, sollte die Whitelist übernommen werden. Die Filterfunktion verhindert auch eine übermäßige Speichernutzung und übermäßig tiefe Objektgraphen, verhindert jedoch nicht die oben erwähnten Deserialisierungsbomben.
Recommended Posts