0x01
- シリアライゼーションとデシリアライゼーションとは何ですか?
- シリアライゼーションとデシリアライゼーションの重要な機能は?
- 逆シリアル化後のデータの特性は何ですか?
- Javaの逆シリアル化の脆弱性とphpの逆シリアル化の脆弱性の類似点は何ですか?
この章では、最初の3つの問題を理解するだけで十分です。実際、Javaの逆シリアル化の脆弱性の原則は非常に単純ですが、各POPチェーンはより複雑です。javaのシリアライゼーションを簡単に紹介します〜
0x02
私の意見では、Javaのシリアライゼーションメカニズムは、オブジェクトを永続的に保存するか、ネットワーク経由でオブジェクトを送信することです。jvmが閉じられると、javaのオブジェクトも破棄されることは誰もが知っているので、保存したい場合は、バイトシーケンスに変換してファイルなどに書き込む必要があります。
シリアライゼーション:オブジェクトをバイトシーケンスに
変換するデシリアライゼーション:バイトシーケンスをオブジェクトに変換する
0x03
クラスオブジェクトをシリアル化するには、次の2つの条件を満たす必要があります。
1.このクラスは、java.io.Serializableオブジェクトを実装する必要があります。
2.このクラスのすべての属性は、シリアライズ可能でなければなりません。シリアル化できない属性がある場合は、属性を一時的としてマークする必要があります。(これにはまだ注目しないでください)
0x04
オブジェクトをシリアル化するには、最初にOutputStreamオブジェクトを作成し、次にObjectOutputStreamオブジェクトにカプセル化します。次に、writeObject()を呼び出してオブジェクトをシリアル化し、それをOutputStreamに送信します(オブジェクトはバイトベースです。したがって、InputStreamおよびOutputStreamを使用して階層を継承します。
オブジェクトを逆シリアル化するには、ObjectInputStreamにInputStreamをカプセル化してから、readObject()を呼び出す必要があります。
テキストは直感的ではないので、コードを直接アップロードしましょう(コメントに注意してください)。
import java.io.*;
public class Test {
public static void main(String[] args){
User user = new User("axin", 18, 180);
try {
// 创建一个FIleOutputStream
FileOutputStream fos = new FileOutputStream("./user.ser");
// 将这个FIleOutputStream封装到ObjectOutputStream中
ObjectOutputStream os = new ObjectOutputStream(fos);
// 调用writeObject方法,序列化对象到文件user.ser中
os.writeObject(user);
System.out.println("读取数据:");
// 创建一个FIleInutputStream
FileInputStream fis = new FileInputStream("./user.ser");
// 将FileInputStream封装到ObjectInputStream中
ObjectInputStream oi = new ObjectInputStream(fis);
// 调用readObject从user.ser中反序列化出对象,还需要进行一下类型转换,默认是Object类型
User user1 = (User)oi.readObject();
user1.info();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable{
private String name;
private int age;
private float height;
public User(String name, int age, float height) {
this.name = name;
this.age = age;
this.height = height;
}
public void info(){
System.out.println("Name: "+name+", Age: "+age+", Height: "+height);
}
// private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException{
// System.out.println("[*]执行了自定义的readObject函数");
// }
}
プログラムが実行された後、user.serファイルが現在のディレクトリに生成されます。逆シリアル化の後、infoメソッドが実行され、ターミナルにユーザー情報が出力されます。
期待どおりに実行され、user.serファイルが正常に生成されたことがわかります。逆シリアル化後のユーザークラスオブジェクトがこのファイルに格納されています。内容を見てみましょう。内容を表示するLinuxの小さなツールxxdを次に示します。
xxdによって表示される結果では、中央の列はファイルの16進表示であり、右端の列は文字表示です。ここで注目すべき特性値は、16進表示の最初の32桁です。
AC ED:STREAM_MAGIC。これは、シリアル化プロトコルが使用されていることを宣言し、そこから、保存されたコンテンツがシリアル化されたデータであるかどうかを判断できます。(これは、ブラックボックスマイニングの逆シリアル化の脆弱性における非常に重要なポイントです)
00 05:STREAM_VERSION、シリアル化プロトコルバージョン。
0x05
シリアル化の基本については上記で説明したので、誰もがオブジェクトをシリアル化および逆シリアル化する方法を知っている必要があります。つまり、抜け穴はどこにあるのでしょうか?PHPの逆シリアル化を知っている場合は、phpオブジェクト__weakup
、__destruct
これらの関数を逆シリアル化するときに自動的にトリガーされます。これらの関数の間に危険な操作がある場合、脆弱性につながる可能性があります。これは、Javaがそれを逆シリアル化するときに自動的にトリガーされる関数ですか?そうです、それはreadObject()ですが、上のデモのreadObject()関数はObjectInputStreamのメソッドではなく、開発者はそれを制御できません。
実際、JavaはカスタムのreadObjectメソッドとwriteObjectメソッドをサポートしています。特定のクラスが特定の要件に従ってreadObjectメソッドを実装している限り、デシリアライズ中に自動的に呼び出されます。カスタムのreadObjectメソッドが使用されている場合いくつかの危険な操作は、逆シリアル化の抜け穴につながります。試してみてください。上記のクラスをまだ使用していますが、今回はUserクラスのreadObjectメソッドをカスタマイズします。つまり、コードコメントの最後のビットを削除し、もう一度実行して、結果を表示します。
ご覧のとおり、カスタムのreadObjectは実際に実行されています。
ここで、システムコマンドの実行やwiresharkの再生など、readObjectに危険な操作を記述します。
もちろん、実際のアプリケーションでこれを書く人はいないでしょうが、Li'erはそのような理由にすぎませんが、実際のアプリケーションでの危険な操作は、私が書いたほど裸ではなく、比較的隠されています
0x06
私のような誰かは、Javaのさまざまなストリーム(FileOutputStream / BufferedOutputStream / DataOutputStream / ObjectOutputStream)を理解すべきではないと思います。シリアライゼーションコードを理解するのに役立つリファレンスを以下に示します:
https:// www。 cnblogs.com/shitouer/archive/2012/12/19/2823641.html