ディレクトリ
一、what、why、how 序列化
二、JDK 序列化并不简单
三、序列化攻击
四、序列化代理模式
参考
复制代码
、何を、なぜ、どのようにシリアライズ
**何をシリアライズされましたか?**だけで、それがプロトコルのコード配列に応じて、バイトストリームにオブジェクトであり、逆のプロセスがデシリアライズと呼ばれています。たとえば、私たちは、共通のJSONのシリアル化を持っています:
public class A {
private int x = 1;
private String y = "2";
}
复制代码
JSONにシリアライズした後:
{
"x" : 1,
"y" : "2"
}
复制代码
**なぜシリアライズ?**簡単に、それは伝送、圧縮収納スペースのためのオブジェクトであり、言語に何もしません。コミュニケーション両側には、ちょうど他の言語になる使用してどのようなことに注意せずに契約に従ってシリアライズ/デシリアライズを、シリアル化します。
**どのようにシリアライズ?**なXML、JSON、fastjson、いるProtobuf、できるだけ多くの既存の直列化プロトコル、 protostuff のような。また、JDKのJavaのシリアライズと定期的に連絡を(JDKのシリアル化は、言語を横断することができない)があります。各非Benpian焦点のシリアル化の長さは、それらを繰り返さない、見ることができます興味を持っているいくつかの人気の直列化プロトコルの比較。
二つは、JDKは単純で連載ではありません
JDKシリアライゼーションは、クラス宣言を増加させるのと同じくらい簡単であるimplements Serializable
シリアライゼーションインタフェースを実現することができます。単純なので、しかし、虐待を受けたが、多くの場合、どこでも見ることができます。実際にはJDKのシリアル化は複雑であり、かつ長期のコストをシリアル化するためです。
なぜ?
まず、クラスの柔軟性を減らし、クラスの進化は限られています。シーケンスが達成され、APIの一部のようなそのシリアライズされたバイトストリームすることができたら、通信当事者の一方がありませんがあるでしょう、クラスの構造を変更してリリースする場合、あなたは常に、シリアライズ/デシリアライズをサポートしている必要がありますエラーにつながった、互換性。
また、このクラスは、シリアルバージョンUID(シリアルバージョンUID)であり、デシリアライズ時に、デシリアライゼーションの一貫性のないバージョンが失敗した場合、それは最初によりInvalidClassException例外をスローし、バージョンUIDに基づいて確認されます。ディスプレイがない場合UIDを提供し、コンピュータ生成の名前のすべてのパブリックおよび保護されたメンバーは、実行時にクラス名と組み合わされます。これは、クラスでこれらの通信相手の一つは、変数やメソッドに依存しないを追加する場合、それはまた、異常ではない互換性につながる、UIDは、暗黙的に矛盾生成ようになりますので、UIDは、ショーシリアライズすることができます達成するための推奨を提供する理由です第二に、暗黙的に生成された計算は、小さなオーバーヘッドではありません。
第二に、BUGが発生したセキュリティ侵害の可能性を高めると。「暗黙のコンストラクタ」のようなアンチ直列化メカニズム、それは簡単に「本当のコンストラクタ、」制約に違反して構築され、攻撃者によって悪用される可能性があります確保するための一定の措置を講じない場合。
第三に、クラスの新しいバージョンの実現が増加し、テストの負担に関連した出版物を、シリアライズすることができますよう。
第三に、攻撃のシーケンス
オブジェクトの配列をバイトストリームに変換されるので、バイトストリームは、それが偽造中間バイトストリームであるかどうか、オブジェクトを復元する逆シリアル化?答えはイエスです。
たとえば、私たちのオブジェクトがあるPeriod
日付変数のメンバーを制限するためにstart
でなければなりませんend
前:
public class Period implements Serializable {
private static final long serialVersionUID = 4647424730390249716L;
private Date start;
private Date end;
public Period(Date start, Date end) {
if (start.after(end)) {
throw new IllegalArgumentException();
}
this.start = start;
this.end = end;
}
@Override
public String toString() {
return "PeriodA{" +
"start=" + start +
", end=" + end +
'}';
}
}
复制代码
今、私たちは、次のバイトストリームを偽造しました:
public class SerializeTest {
private static final byte[] serializedForm = new byte[] {
(byte)0xac, (byte)0xed, 0x00, 0x05, 0x73, 0x72, 0x00, 0x06,
0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x40, 0x7e, (byte)0xf8,
0x2b, 0x4f, 0x46, (byte)0xc0, (byte)0xf4, 0x02, 0x00, 0x02,
0x4c, 0x00, 0x03, 0x65, 0x6e, 0x64, 0x74, 0x00, 0x10, 0x4c,
0x6a, 0x61, 0x76, 0x61, 0x2f, 0x75, 0x74, 0x69, 0x6c, 0x2f,
0x44, 0x61, 0x74, 0x65, 0x3b, 0x4c, 0x00, 0x05, 0x73, 0x74,
0x61, 0x72, 0x74, 0x71, 0x00, 0x7e, 0x00, 0x01, 0x78, 0x70,
0x73, 0x72, 0x00, 0x0e, 0x6a, 0x61, 0x76, 0x61, 0x2e, 0x75,
0x74, 0x69, 0x6c, 0x2e, 0x44, 0x61, 0x74, 0x65, 0x68, 0x6a,
(byte)0x81, 0x01, 0x4b, 0x59, 0x74, 0x19, 0x03, 0x00, 0x00,
0x78, 0x70, 0x77, 0x08, 0x00, 0x00, 0x00, 0x66, (byte)0xdf,
0x6e, 0x1e, 0x00, 0x78, 0x73, 0x71, 0x00, 0x7e, 0x00, 0x03,
0x77, 0x08, 0x00, 0x00, 0x00, (byte)0xd5, 0x17, 0x69, 0x22,
0x00, 0x78
};
public static void main(String[] args) throws IOException, ClassNotFoundException {
Period p = (Period) deserialize(serializedForm);
System.out.println(p);
}
public 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.toString());
}
}
}
复制代码
逆シリアル化の結果によります:
PeriodA{start=Sat Jan 02 04:00:00 CST 1999, end=Mon Jan 02 04:00:00 CST 1984}
复制代码
すでに登場し、逆シリアル化は、以前、この「暗黙のコンストラクタは、」私たちの憲法の制約関係に違反するオブジェクト構築したstart
よりも後にend
、このプログラムのために非常に危険なことができます。どのようにバイトストリーム偽造として、あなたは、シリアル化形式の説明を含め、「Javaのオブジェクト直列化仕様」を見てみることができます。
したがって、effetive Javaが繰り返し書かなければならない直列化可能クラスを達成するために、強調しreadObject
た方法を、制約することを確実にするために。
private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException{
stream.defaultReadObject();
if (start.after(end)) {
throw new IllegalArgumentException();
}
}
复制代码
しかし、あなたはまだ有効期間オブジェクト、と2つの追加参照ですバイトのストリームを提供することに加えて、制約を破るために偽造バイトストリームを通じてそれを行うことができますが、これらの二つの変数への参照は、二つの部材の一例であり、その結果、任意の二つの基準操作対象によってインスタンス化の後に。次のプレゼンテーション:
public class MutablePeriod {
// 有效period对象
public final Period period;
// 两个额外的引用
public final Date start;
public final Date end;
public MutablePeriod() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(new Period(new Date(), new Date()));
// 附上额外引用
byte[] ref = { 0x71, 0, 0x7e, 0, 5 };
bos.write(ref);
ref[4] = 4;
bos.write(ref);
ObjectInputStream in = new ObjectInputStream(
new ByteArrayInputStream(bos.toByteArray()));
period = (Period) in.readObject();
start = (Date) in.readObject();
end = (Date) in.readObject();
} catch (Exception e) {
throw new AssertionError(e);
}
}
public static void main(String[] args) {
MutablePeriod mp = new MutablePeriod();
Period p = mp.period;
Date pEnd = mp.end;
pEnd.setYear(78);
System.out.println(p);
pEnd.setYear(69);
System.out.println(p);
}
}
复制代码
結果は以下のとおりです。
PeriodA{start=Fri Aug 23 12:26:53 CST 2019, end=Wed Aug 23 12:26:53 CST 1978}
PeriodA{start=Fri Aug 23 12:26:53 CST 2019, end=Sat Aug 23 12:26:53 CST 1969}
复制代码
上記の問題は、根本的な原因のreadObjectコピー保護方法は、構成オブジェクトの新しいメンバ変数、および新しいオブジェクト、2回の追加の基準攻撃者にコピープロテクトメンバ変数のうちオブジェクトをデシリアライズするために、すなわち、行われていないことを生じます変更は、オブジェクト変数をインスタンス化されていません。
private void readObject(ObjectInputStream stream) throws IOException,ClassNotFoundException {
stream.defaultReadObject();
// 保护性拷贝
start = new Date(start.getTime());
end = new Date(end.getTime());
if (start.after(end)) {
throw new IllegalArgumentException();
}
}
复制代码
なお、代わりにシャローコピークローンモードを使用するようにテストの制約に先立ち、およびコピー保護。
加えて、シリアライズプロキシモードは、以下を参照のこと、より一般的に使用される方法があります。
第四に、直列化プロキシモード
非常に簡単な薬の順序、コピーが設定されている場合、すなわち、直列化可能クラスプライベート静的シェルに入れ、シェルは、コンストラクタを持っているシリアライゼーションエージェント、プロキシクラスのコンストラクタのパラメータ、すなわちと呼ばれていますプロキシクラスのパラメータを設定します。時間のシーケンスを提供することによりwriteReplace
、実際に、プロキシクラスのシーケンス、およびreadObject
シーケンスのインターフェース直接拒否、唯一の代理直列化復元。プロキシクラスを提供することにより、readResolve
プロキシクラスをデシリアライズ。特定のコードで参照してください。
public class Period implements Serializable {
private static final long serialVersionUID = 4647424730390249716L;
private Date start;
private Date end;
public Period(Date start, Date end) {
if (start.after(end)) {
throw new IllegalArgumentException();
}
this.start = start;
this.end = end;
}
@Override
public String toString() {
return "PeriodA{" +
"start=" + start +
", end=" + end +
'}';
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
// 不允许直接反序列化,只能通过反序列化代理实例化
throw new InvalidObjectException("只允许通过代理反序列化");
}
private Object writeReplace() {
// 序列化代理
return new SerializeProxy(this);
}
// 序列化代理类
private class SerializeProxy implements Serializable {
private final Date start;
private final Date end;
// 通过构造复制代理类变量
public SerializeProxy(Period period) {
this.start = period.getStart();
this.end = period.getEnd();
}
// 反序列化为被代理类
private Object readResolve() {
return new Period(start, end);
}
}
}
复制代码
参照
[1]効果的なJavaの章XI