Higman Sims Graph drawing, based on the construction of Paul R. Hafner: "On the Graphs of Hoffman-Singleton and Higman-Sims", The Electronic Journal of combinatorics 11 (2004).
⇧ 「ホフマン–シングルトングラフ」を元にしとるそうな。
ホフマン–シングルトングラフとは、50個の頂点と175個の辺からなる7-正則グラフである。これは(50,7,0,1)-強正則グラフであり一意である。このグラフはアラン・ホフマンとロバート・シングルトンによって、ムーアグラフの分類の過程で構成された。またホフマン–シングルトングラフは知られているムーアグラフの中でもっとも頂点数が多いグラフである。 次数7のムーアグラフであることから、内周は5であり、(7,5)-ケージとなる。
う~ん、目的は分かりませんが、綺麗ですね~、お美しい。あ、どうもボクです。
というわけで、今回も、関係ない話で開幕ですが、ここからは、Java の話ですから~、レッツトライ~。
シングルトンを盗めるはずらしいんだけど...
「Effective Java 第3版」を読んでいて、掲載されているプログラミング通りやってるんだけど、どうやっても不可能な気がしてならんって個所がでてきたんだけどね...
どういうことかというと、
⇧ 上記サイト様にありますが、「12章 89項」にある「89.インスタンス制御に対しては、readResolve より enum 型を選ぶべし」って件なのですが。
この章で言及してるのは、「シリアライズ」についてなんですが、Javaの場合、「Serializable」インターフェイスを implements すれば「シリアライズ」を実現できるらしいんですが、あんまりオススメではなく、
- JSON(JavaScript Object Notation)
- Protocol Buffers
なんかを使えるんであれば、使ってくれということらしい。
何で、「Serializable」「JSON」「Protocol Buffers」なんかが必要かというと、Javaのようなオブジェクトなんかの情報を、外部のシステムと連携したい時に、オブジェクトの形のままだと連携できないからと。
逆に、ある形のデータを、Javaのようなオブジェクトの形のデータに整形してあげることを、「デシリアライズ」っていうらしい。
他のサーバサイド言語とかでも、オブジェクトとかの概念があれば、同様の仕組みがあるんではないかと。
OWSAP(Open Web Application Security Project)によりますと、
What is Deserialization?
Serialization is the process of turning some object into a data format that can be restored later. People often serialize objects in order to save them to storage, or to send as part of communications.
Deserialization is the reverse of that process, taking data structured from some format, and rebuilding it into an object. Today, the most popular data format for serializing data is JSON. Before that, it was XML.
However, many programming languages offer a native capability for serializing objects. These native formats usually offer more features than JSON or XML, including customizability of the serialization process.
Unfortunately, the features of these native deserialization mechanisms can be repurposed for malicious effect when operating on untrusted data. Attacks against deserializers have been found to allow denial-of-service, access control, and remote code execution (RCE) attacks.
⇧ 標準で用意されてる、デシリアライズ化のプログラミングだと、抜け穴があるらしく攻撃されることが判明してるらしい。
⇧ 上記サイト様が参考になるかと。
IPAによりますと、
⇧ 上記がセキュリティ系のまとめなのかな?
Javaに関しては、
⇧ このへんかしら。
ちなみに、Wikipediaさんによりますと、
JSONは、
JavaScript Object Notation(JSON、ジェイソン)は軽量なデータ記述言語の1つである。構文はJavaScriptにおけるオブジェクトの表記法をベースとしているが、JSONはJavaScript専用のデータ形式では決してなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しに使えるよう設計されている。
Protocol Buffersは、
Protocol Buffers(プロトコルバッファー)はインタフェース定義言語 (IDL) で構造を定義する通信や永続化での利用を目的としたシリアライズフォーマットであり、Googleにより開発されている。オリジナルのGoogle実装はC++、Java、Pythonによるものであり、フリーソフトウェアとしてオープンソースライセンスで公開されている。また、ActionScript・C言語・C#・Clojure・Common Lisp・D言語・Erlang・Go・Haskell・JavaScript・Lua・MATLAB・Mercury・Objective-C・OCaml・Perl・PHP・R言語・Ruby・Scala・.NET Frameworkなどの実装が利用可能である。
ってな説明であると。まぁ、これ以外にも、いろんな技術はあると思いますが、他にどんな技術があるのか分からんので割愛。
試してみる
はい、すみません、脱線しました。
ちょっと、Effective Java 第3版の12章89頁にある例題を検証してみるんですが、
の3つのクラスが必要になるんで、作成しちゃいましょう。
Java 10だと上手くいかないということで、Java 11 環境(AdoptOpenJDK)で試してみるということで。(Eclipse 2019-09を使っているので、デフォルトのJDKは、AdoptOpenJDK 11 になっています。)
Eclipseを起動で、適当な「Java プロジェクト」を作成し、上記のクラスファイルを作成で。(Java 9 から導入されたモジュールは作成なしで。)
んで、今回、Elvis.java のシリアライズ化した情報が欲しいので、用意するために、Apache Commons の外部APIを使うので、ダウンロードします。
今回、ダウンロードするのは、「Apache Common Lang」ってAPIです。
入手する方法としては、
- Apache Common - https://commons.apache.org/downloads/index.html
- Maven Repository - https://mvnrepository.com/artifact/org.apache.commons
のどっちかからが多いんではないかと。
まぁ、今回は、Maven Repositry のほうからダウンロードということで。
https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 で、
⇧ 「3.9」のリンクをクリック。
画面遷移したらば、「Files」の「jar」のリンクをクリックで、jarファイルをダウンロードが始まり、
「この種類のファイルはコンピュータに損害を与える可能性があります。~」って表示されるので、「保存」で。
ダウンロードが完了します。
そしたらば、Eclipseに作成したJavaプロジェクトに、「lib」ディレクトリを作成し、ダウンロードした jarファイルを配置します。
そしたらば、プロジェクトを選択した状態で右クリックし、「ビルド・パス(B)」>「ビルド・パスの構成(C)...」で。
「ライブラリー(L)」タグで、「クラスパス」を選択した状態で、「外部 JAR の追加(X)...」を選択。
ダウンロードして、「C:\Eclipse_2019-09\pleiades-2019-09-java-win-64bit-jre_20191007\pleiades\workspace\work_00\TestDeserializationAttacks\lib\commons-lang3-3.9.jar」に配置してた、jarファイルを指定します。(ファイルのパスはご自身の環境のものに合わせてください。)
そしたらば、「適用して閉じる」で。
参照ライブラリーに追加されてればOK。
まずは、Elvis.java をシリアライズ化した値が必要なので、一旦、シリアライズ化した値を出すために、「Elvis.java」、「ElvisImpersonator.java」のコーディング。
package dto; import java.io.ObjectStreamException; import java.io.Serializable; import java.util.Arrays; public class Elvis implements Serializable { public static final Elvis INSTANCE = new Elvis(); private Elvis() { } private String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } private Object readResolve() throws ObjectStreamException { return INSTANCE; } }
package main; import org.apache.commons.lang3.SerializationUtils; import dto.Elvis; public class ElvisImpersonator { public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ Elvis elvis = Elvis.INSTANCE; byte[] elvisBytes = SerializationUtils.serialize(elvis); StringBuilder sb = new StringBuilder(); for (int index = 0; index < elvisBytes.length; index++) { if (index != 0) { sb.append(", "); } sb.append(String.format("0x%02x", elvisBytes[index])); if ((index +1) % 9 == 0) { sb.append("\n"); } } System.out.println(sb.toString()); } }
んで実行。
⇧ コンソールに表示された文字列をすべてコピーしときます。
では、「ElvisImpersonator.java」、「ElvisStealer.java」を編集します。
ElvisStealer.java
package dto; import java.io.Serializable; public class ElvisStealer implements Serializable { public static Elvis impersonator; private Elvis payload; private Object readResolve() { // Save a reference to the "unresolved" Elvis instance impersonator = payload; // Return an object of correct type for favorites field return new String[] { "A Fool Such as I" }; } private static final long serialVersionUID = 0; }
シリアライズ化していた値を、byte[]の変数serializedFormの要素として使用します。一部、(byte)でキャストの必要あり。
ElvisImpersonator.java
package main; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.ObjectInputStream; import dto.Elvis; import dto.ElvisStealer; public class ElvisImpersonator { // Byte stream could not have come from real Elvis instance! private static final byte[] serializedForm = new byte[] { (byte) 0xac, (byte) 0xed, 0x00, 0x05, 0x73, 0x72, 0x00, 0x09, 0x64 , 0x74, 0x6f, 0x2e, 0x45, 0x6c, 0x76, 0x69, 0x73, (byte) 0xdd , 0x7d, (byte) 0xbf, 0x02, (byte) 0xc3, 0x23, (byte) 0xf6, (byte) 0x98, 0x02, 0x00 , 0x01, 0x5b, 0x00, 0x0d, 0x66, 0x61, 0x76, 0x6f, 0x72 , 0x69, 0x74, 0x65, 0x53, 0x6f, 0x6e, 0x67, 0x73, 0x74 , 0x00, 0x13, 0x5b, 0x4c, 0x6a, 0x61, 0x76, 0x61, 0x2f , 0x6c, 0x61, 0x6e, 0x67, 0x2f, 0x53, 0x74, 0x72, 0x69 , 0x6e, 0x67, 0x3b, 0x78, 0x70, 0x75, 0x72, 0x00, 0x13 , 0x5b, 0x4c, 0x6a, 0x61, 0x76, 0x61, 0x2e, 0x6c, 0x61 , 0x6e, 0x67, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67 , 0x3b, (byte) 0xad, (byte) 0xd2, 0x56, (byte) 0xe7, (byte) 0xe9, 0x1d, 0x7b, 0x47 , 0x02, 0x00, 0x00, 0x78, 0x70, 0x00, 0x00, 0x00, 0x02 , 0x74, 0x00, 0x09, 0x48, 0x6f, 0x75, 0x6e, 0x64, 0x20 , 0x44, 0x6f, 0x67, 0x74, 0x00, 0x10, 0x48, 0x65, 0x61 , 0x72, 0x74, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x20, 0x48 , 0x6f, 0x74, 0x65, 0x6c }; public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ // Elvis elvis = Elvis.INSTANCE; // byte[] elvisBytes = SerializationUtils.serialize(elvis); // StringBuilder sb = new StringBuilder(); // for (int index = 0; index < elvisBytes.length; index++) { // if (index != 0) { // sb.append(", "); // } // sb.append(String.format("0x%02x", elvisBytes[index])); // if ((index +1) % 9 == 0) { // sb.append("\n"); // } // } // System.out.println(sb.toString()); // Initializes ElvisStealer.impersonator and returns // the real Elvis (which is Elvis.INSTANCE) Elvis elvis = (Elvis) deserialize(serializedForm); Elvis impersonator = ElvisStealer.impersonator; elvis.printFavorites(); impersonator.printFavorites(); } // Returns the object with the specified serialized form private static Object deserialize(byte[] sf) { try { InputStream is = new ByteArrayInputStream(sf); ObjectInputStream ois = new ObjectInputStream(is); return ois.readObject(); } catch (Exception e) { throw new IllegalArgumentException(e); } } }
んで、実行すると。
NullPointerExecption が発生する。
デバッグ実行で、様子を見てみると、
⇧ ElvisStealer.impersonator が、null になってると。
自分が思うに、ElvisStealer.javaのメンバ変数 impersonator は初期化が行われていない気がするから、どうやっても null にしかならない気がするんだけど、何かJavaの仕組みがよく分からん...
Elvisのインスタンスは普通に作成されてるけど、インスタンスを盗めていない...「Deserialization attack」攻撃が上手くいってないってことだと思うけど、「Effective Java 第3版」の意図とは合致しませんな...
シリアライズ化が良くないのかな?
⇧ 上記サイト様の、Util.serialize() メソッド使えば上手くいくのかな?
まぁ、「Deserialization attack」攻撃が上手くいかないから、「Elvis.java」をEnum型にする意味が薄れてしまうんですが、Effective Java は推奨してるようなので、
package dto; import java.util.Arrays; public enum Elvis { INSTANCE; private String[] favoriteSongs = {"Hound Dog", "Heartbreak Hotel"}; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
ってな感じにしたほうが良いようです。
で実行すると、「ElvisStealer.java」のデシリアライズ時にエラーになるから期待した通りの動きになっているのではないかと。
Exception in thread "main" java.lang.IllegalArgumentException: java.io.InvalidClassException: cannot bind non-enum descriptor to an enum class at main.ElvisImpersonator.deserialize(ElvisImpersonator.java:63) at main.ElvisImpersonator.main(ElvisImpersonator.java:49) Caused by: java.io.InvalidClassException: cannot bind non-enum descriptor to an enum class at java.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:679) at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1903) at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1772) at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2060) at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1594) at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:430) at main.ElvisImpersonator.deserialize(ElvisImpersonator.java:61) ... 1 more
というわけで、モヤモヤ感が半端ないですが...
Javaに詳しい人が、解決してブログとかにアップしてくれることを祈るばかりですかね、他力本願寺~。
今回はこのへんで。