Deserialisierung absichern mit einem ObjectInputFilter

Alles über readObject() einzulesen was dem ObjectInputStream gegeben wird ist ein Sicherheitsrisiko. Ab Java 8 Update 121[1] – und folglich auch Java 9 – lässt sich ein Filter bei der Deserialisierung setzen, der mit Informationen über die

  • deserialisierte Klasse,
  • Anzahl Objektreferenzen,
  • Array-Längen,
  • aktuelle Tiefe im Objektgraph und
  • Größe des serialisierten Objekts in Bytes

versorgt wird, und sein OK geben muss wenn es zu keiner InvalidClassException während readObject() kommen soll.

Ein Filter ist vom Typ ObjectInputFilter und wird auf einem ObjectInputStream über setObjectInputFilter(ObjectInputFilter filter) gesetzt; den aktuell gesetzten Filter liefert getObjectInputFilter(). Die Deklaration der funktionalen Schnittstelle ObjectInputFilter enthält eine Methode checkInput(…), zwei Aufzählungstypen und eine innere Klasse.

@FunctionalInterface

interface ObjectInputFilter {




  Status checkInput( FilterInfo filterInfo );




  interface FilterInfo {

    Class<?> serialClass();

    long arrayLength();

    long depth();

    long references();

    long streamBytes();

  }




  enum Status {

    UNDECIDED, ALLOWED, REJECTED;

  }




  final class Config {

    public static ObjectInputFilter getSerialFilter() {…}

    public static void setSerialFilter(ObjectInputFilter filter) {…}

    public static ObjectInputFilter createFilter(String pattern) {…}

  }

}

Programmieren wir ein kleines Beispiel mit dieser API. Beginnen wir damit, einen java.awt.Point, ein byte-Array, eine Swing-Komponente und ein leeres org.w3c.dom.Document in ein ByteArrayOutputStream zu serialisieren.

ByteArrayOutputStream bos = new ByteArrayOutputStream();

try ( ObjectOutputStream oos = new ObjectOutputStream( bos ) ) {

  oos.writeObject( new Point(10, 20) );

  oos.writeObject( new byte[1000] );

  oos.writeObject( new JLabel() );

  oos.writeObject( DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );

}

Im nächsten Schritt können wir einen ObjectInputStream auf das erzeugte Byte-Array anwenden und Deserialisieren. Zusätzlich setzen wir einen Filter, dessen Implementierung aus drei Teilen besteht:

  1. Testen, ob ein prozessweiter Filter existiert, der eine Entscheidung schon gefallen hat,
  2. Loggen aller Informationen aus dem FilterInfo-Objekt,
  3. Alle serialisierten XML-Dokumente blockieren.

Zum Code:

ObjectInputStream ois = new ObjectInputStream(

                          new ByteArrayInputStream( bos.toByteArray() ) );

ois.setObjectInputFilter( filterInfo -> {




  ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter();

  if ( serialFilter != null ) {

    ObjectInputFilter.Status status = serialFilter.checkInput( filterInfo );

    if ( status != ObjectInputFilter.Status.UNDECIDED )

      return status;

  }




  System.out.printf( "depth=%s, references=%s, serialClass=%s, arrayLength=%s, streamBytes=%s%n",

                     filterInfo.depth(), filterInfo.references(), 

                     filterInfo.serialClass(), filterInfo.arrayLength(), filterInfo.streamBytes() );




  if ( Document.class.isAssignableFrom( filterInfo.serialClass() ) )

    return Status.REJECTED;




  return Status.ALLOWED;

} );

Als letzte versuchen wir die vier geschriebenen Objekte einzulesen.

for ( int i = 0; i < 4; i++ )

  System.out.printf( "%d. gelesenes Objekt: %s%n%n", i + 1, ois.readObject() );

Beim letzten Versuch kommt es zu einem Fehler, wie die Ausgabe zeigt:

depth=1, references=1, serialClass=class java.awt.Point, arrayLength=-1, streamBytes=43
  1. gelesenes Objekt: java.awt.Point[x=10,y=20]
depth=1, references=2, serialClass=class [B, arrayLength=-1, streamBytes=70

depth=1, references=2, serialClass=class [B, arrayLength=1000, streamBytes=74
  1. gelesenes Objekt: [B@45dd4eda
depth=1, references=3, serialClass=null, arrayLength=-1, streamBytes=1311

Exception in thread "main" java.io.InvalidClassException: filter status: REJECTED

       at java.base/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1276)

…

       ... 10 more

Die API-Dokumentation der Typen erklärten weitere paar Details, z. B. was die Bedeutung von Status.UNDECIDED ist, und wie die System-Property jdk.serialFilter mit einer textuellen Filter-Beschreibung belegt werden kann.

[1]       http://www.oracle.com/technetwork/java/javase/8u121-relnotes-3315208.html

Ähnliche Beiträge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert