{"id":3602,"date":"2017-02-19T19:36:39","date_gmt":"2017-02-19T17:36:39","guid":{"rendered":"http:\/\/www.tutego.de\/blog\/javainsel\/?p=3602"},"modified":"2017-02-19T19:36:39","modified_gmt":"2017-02-19T17:36:39","slug":"deserialisierung-absichern-mit-einem-objectinputfilter","status":"publish","type":"post","link":"https:\/\/www.tutego.de\/blog\/javainsel\/2017\/02\/deserialisierung-absichern-mit-einem-objectinputfilter\/","title":{"rendered":"Deserialisierung absichern mit einem ObjectInputFilter"},"content":{"rendered":"<p>Alles \u00fcber readObject() einzulesen was dem ObjectInputStream gegeben wird ist ein Sicherheitsrisiko. Ab Java 8 Update 121<a href=\"#_ftn1\" name=\"_ftnref1\">[1]<\/a> \u2013 und folglich auch Java 9 \u2013 l\u00e4sst sich ein Filter bei der Deserialisierung setzen, der mit Informationen \u00fcber die<\/p>\n<ul>\n<li>deserialisierte Klasse,<\/li>\n<li>Anzahl Objektreferenzen,<\/li>\n<li>Array-L\u00e4ngen,<\/li>\n<li>aktuelle Tiefe im Objektgraph und<\/li>\n<li>Gr\u00f6\u00dfe des serialisierten Objekts in Bytes<\/li>\n<\/ul>\n<p>versorgt wird, und sein OK geben muss wenn es zu keiner InvalidClassException w\u00e4hrend readObject() kommen soll.<\/p>\n<p>Ein Filter ist vom Typ ObjectInputFilter und wird auf einem ObjectInputStream \u00fcber setObjectInputFilter(ObjectInputFilter filter) gesetzt; den aktuell gesetzten Filter liefert getObjectInputFilter(). Die Deklaration der funktionalen Schnittstelle ObjectInputFilter enth\u00e4lt eine Methode checkInput(\u2026), zwei Aufz\u00e4hlungstypen und eine innere Klasse.<\/p>\n<pre>@FunctionalInterface\n\ninterface ObjectInputFilter {\n\n\n\n\n\u00a0 <strong>Status checkInput( FilterInfo filterInfo )<\/strong>;\n\n\n\n\n\u00a0 <strong>interface FilterInfo<\/strong> {\n\n\u00a0\u00a0\u00a0 Class&lt;?&gt; serialClass();\n\n\u00a0\u00a0\u00a0 long arrayLength();\n\n\u00a0\u00a0\u00a0 long depth();\n\n\u00a0\u00a0\u00a0 long references();\n\n\u00a0\u00a0\u00a0 long streamBytes();\n\n\u00a0 }\n\n\n\n\n\u00a0 <strong>enum Status<\/strong> {\n\n\u00a0\u00a0\u00a0 UNDECIDED, ALLOWED, REJECTED;\n\n\u00a0 }\n\n\n\n\n\u00a0 <strong>final class Config<\/strong> {\n\n\u00a0\u00a0\u00a0 public static ObjectInputFilter getSerialFilter() {\u2026}\n\n\u00a0\u00a0 \u00a0public static void setSerialFilter(ObjectInputFilter filter) {\u2026}\n\n\u00a0 \u00a0\u00a0public static ObjectInputFilter createFilter(String pattern) {\u2026}\n\n\u00a0 }\n\n}<\/pre>\n<p>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.<\/p>\n<pre>ByteArrayOutputStream bos = new ByteArrayOutputStream();\n\ntry ( ObjectOutputStream oos = new ObjectOutputStream( bos ) ) {\n\n\u00a0 oos.writeObject( new Point(10, 20) );\n\n\u00a0 oos.writeObject( new byte[1000] );\n\n\u00a0 oos.writeObject( new JLabel() );\n\n\u00a0 oos.writeObject( DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );\n\n}<\/pre>\n<p>Im n\u00e4chsten Schritt k\u00f6nnen wir einen ObjectInputStream auf das erzeugte Byte-Array anwenden und Deserialisieren. Zus\u00e4tzlich setzen wir einen Filter, dessen Implementierung aus drei Teilen besteht:<\/p>\n<ol>\n<li>Testen, ob ein prozessweiter Filter existiert, der eine Entscheidung schon gefallen hat,<\/li>\n<li>Loggen aller Informationen aus dem FilterInfo-Objekt,<\/li>\n<li>Alle serialisierten XML-Dokumente blockieren.<\/li>\n<\/ol>\n<p>Zum Code:<\/p>\n<pre>ObjectInputStream ois = new ObjectInputStream(\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 new ByteArrayInputStream( bos.toByteArray() ) );\n\nois.<strong>setObjectInputFilter<\/strong>( filterInfo -&gt; {\n\n\n\n\n<strong>\u00a0 ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter();<\/strong>\n\n<strong>\u00a0 if ( serialFilter != null ) {<\/strong>\n\n<strong>\u00a0\u00a0\u00a0 ObjectInputFilter.Status status = serialFilter.checkInput( filterInfo );<\/strong>\n\n<strong>\u00a0\u00a0\u00a0 if ( status != ObjectInputFilter.Status.UNDECIDED )<\/strong>\n\n<strong>\u00a0\u00a0\u00a0\u00a0\u00a0 return status;<\/strong>\n\n<strong>\u00a0 }<\/strong>\n\n\n\n\n\u00a0 System.out.printf( \"depth=%s, references=%s, serialClass=%s, arrayLength=%s, streamBytes=%s%n\",\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 <strong>filterInfo.depth(), filterInfo.references(), <\/strong>\n\n<strong>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 filterInfo.serialClass(), filterInfo.arrayLength(), filterInfo.streamBytes()<\/strong> );\n\n\n\n\n\u00a0 if ( Document.class.isAssignableFrom( filterInfo.serialClass() ) )\n\n\u00a0\u00a0\u00a0 return <strong>Status.REJECTED<\/strong>;\n\n\n\n\n\u00a0 return <strong>Status.ALLOWED<\/strong>;\n\n} );<\/pre>\n<p>Als letzte versuchen wir die vier geschriebenen Objekte einzulesen.<\/p>\n<pre>for ( int i = 0; i &lt; 4; i++ )\n\n\u00a0 System.out.printf( \"%d. gelesenes Objekt: %s%n%n\", i + 1, ois.readObject() );<\/pre>\n<p>Beim letzten Versuch kommt es zu einem Fehler, wie die Ausgabe zeigt:<\/p>\n<pre>depth=1, references=1, serialClass=class java.awt.Point, arrayLength=-1, streamBytes=43<\/pre>\n<ol>\n<li>\n<pre>gelesenes Objekt: java.awt.Point[x=10,y=20]<\/pre>\n<\/li>\n<\/ol>\n<pre>depth=1, references=2, serialClass=class [B, arrayLength=-1, streamBytes=70\n\ndepth=1, references=2, serialClass=class [B, arrayLength=1000, streamBytes=74<\/pre>\n<ol start=\"2\">\n<li>\n<pre>gelesenes Objekt: [B@45dd4eda<\/pre>\n<\/li>\n<\/ol>\n<pre>depth=1, references=3, serialClass=null, arrayLength=-1, streamBytes=1311\n\nException in thread \"main\" java.io.InvalidClassException: filter status: REJECTED\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 at java.base\/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1276)\n\n\u2026\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ... 10 more<\/pre>\n<p>Die API-Dokumentation der Typen erkl\u00e4rten 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.<\/p>\n<p><a href=\"#_ftnref1\" name=\"_ftn1\">[1]<\/a> \u00a0\u00a0\u00a0\u00a0\u00a0 http:\/\/www.oracle.com\/technetwork\/java\/javase\/8u121-relnotes-3315208.html<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Alles \u00fcber readObject() einzulesen was dem ObjectInputStream gegeben wird ist ein Sicherheitsrisiko. Ab Java 8 Update 121[1] \u2013 und folglich auch Java 9 \u2013 l\u00e4sst sich ein Filter bei der Deserialisierung setzen, der mit Informationen \u00fcber die deserialisierte Klasse, Anzahl Objektreferenzen, Array-L\u00e4ngen, aktuelle Tiefe im Objektgraph und Gr\u00f6\u00dfe des serialisierten Objekts in Bytes versorgt wird, [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":"","_links_to":"","_links_to_target":""},"categories":[1,11],"tags":[],"class_list":["post-3602","post","type-post","status-publish","format-standard","hentry","category-allgemein","category-insel"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/3602","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/comments?post=3602"}],"version-history":[{"count":6,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/3602\/revisions"}],"predecessor-version":[{"id":3608,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/posts\/3602\/revisions\/3608"}],"wp:attachment":[{"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/media?parent=3602"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/categories?post=3602"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tutego.de\/blog\/javainsel\/wp-json\/wp\/v2\/tags?post=3602"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}