PythonのデシリアライゼーションにおけるPickleモジュールのセキュリティリスクとその対策
Wenhao Wang
Dev Intern · Leapcell

PythonのデシリアライゼーションにおけるPickleモジュールのセキュリティリスクとその対策
はじめに
皆さん、こんにちは!Pythonプログラミングの領域には、潜在的なセキュリティリスク、すなわちデシリアライゼーション攻撃が存在します。デシリアライゼーション攻撃を掘り下げる前に、シリアライゼーションとデシリアライゼーションとは何かを理解することが重要です。
概念的には、シリアライゼーションとは、データ構造またはオブジェクトをバイトストリームに変換するプロセスです。この変換により、データはファイルに便利に保存したり、ネットワーク経由で送信したりできます。一方、デシリアライゼーションは逆のプロセスであり、バイトストリームを元のデータ構造またはオブジェクトに戻します。
Pythonでは、Pickleモジュールは、シリアライゼーションとデシリアライゼーションを実装するためによく使用されるツールの1つです。これは、複雑なPythonオブジェクトをシリアライズして保存できる便利なインターフェイスを提供し、後で必要になったときに、簡単にデシリアライズして復元できます。ただし、この利便性は潜在的なセキュリティリスクももたらします。
デシリアライゼーション攻撃の概要
デシリアライゼーションのプロセスは、常に安全で信頼できるとは限りません。信頼できないデータソースからデシリアライゼーション操作を実行する場合、デシリアライゼーション攻撃を受ける可能性があります。攻撃者は、悪意のあるコードをシリアライズされたデータに埋め込むことができます。これらのデータがデシリアライズされると、埋め込まれた悪意のあるコードが実行されます。このような攻撃は、データ漏洩、システムクラッシュなどの深刻な結果につながる可能性があり、攻撃者がシステムのリモートコントロール権限を取得することさえ可能にします。
Python Pickleモジュールの概要
Pickleの基本機能
PickleモジュールはPython標準ライブラリの一部であり、追加のインストールなしで使用できます。その主な機能は、Pythonオブジェクトのシリアライゼーションとデシリアライゼーションを実装することです。単純な基本的なデータ型であろうと、複雑なデータ構造(リスト、辞書、クラスインスタンスなど)であろうと、Pickleはそれをストレージまたは送信用のバイトストリームに変換し、必要に応じて元のオブジェクト形式に復元できます。
Pickleの動作原理
Pickleの動作原理は比較的直感的です。シリアライゼーション段階では、特定のルールに従ってPythonオブジェクトをバイトストリームに変換します。これらのバイトストリームには、オブジェクトの型情報とデータコンテンツが含まれています。デシリアライゼーション段階では、Pickleはバイトストリームを読み取り、その中の情報に従って対応するPythonオブジェクトに復元します。
Pickleのシリアライゼーションとデシリアライゼーション
- シリアライゼーション:
Pickleは、
pickle.dump
とpickle.dumps
という2つの主要なシリアライゼーション関数を提供します。pickle.dump
関数は、シリアライズされたオブジェクトを指定されたファイルに直接書き込みますが、pickle.dumps
関数は、シリアライズされたデータを含むバイトストリームを返します。
import pickle # オブジェクトを作成 data = {'name': 'Leapcell', 'age': 29, 'city': 'New York'} # オブジェクトをシリアライズしてファイルに書き込む with open('data.pickle', 'wb') as file: pickle.dump(data, file) # またはバイトストリームを返す data_bytes = pickle.dumps(data)
- デシリアライゼーション:
デシリアライゼーションにも、
pickle.load
とpickle.loads
という2つのよく使用される関数があります。pickle.load
関数は、指定されたファイルからバイトストリームを読み取り、デシリアライズし、pickle.loads
関数はバイトストリームを直接デシリアライズします。
import pickle # ファイルからオブジェクトをデシリアライズする with open('data.pickle', 'rb') as file: data = pickle.load(file) # またはバイトストリームを直接デシリアライズする data = pickle.loads(data_bytes)
デシリアライゼーション攻撃の原理
攻撃メカニズム
デシリアライゼーション攻撃の核心は、攻撃者が悪意のあるコードをシリアライズされたデータに注入できることです。ターゲットシステムがこれらの悪意のあるコードを含むシリアライズされたデータをデシリアライズすると、悪意のあるコードが実行され、攻撃者の目標が達成されます。つまり、デシリアライゼーション中にデータソースの厳密な検証とスクリーニングを行わない場合、システム内で任意のコードを実行するために攻撃者にドアを開いているのと同じです。
攻撃者ができること
攻撃者は、デシリアライゼーションの脆弱性を利用して、任意のシステムコマンドの実行、システム内の重要なデータの変更、機密情報の盗用など、さまざまな悪意のある操作を実行できます。これらの操作は、システムのセキュリティと安定性に深刻な損害を与える可能性があります。
コード例
デシリアライゼーション攻撃のプロセスをより明確に示すために、具体的な例を見てみましょう。
import pickle import os # 悪意のあるコードを構築する class Malicious: def __reduce__(self): return (os.system, ('echo Hacked!',)) # 悪意のあるオブジェクトをシリアライズする malicious_data = pickle.dumps(Malicious()) # デシリアライゼーション中に悪意のあるコードを実行する pickle.loads(malicious_data)
この例では:
- 悪意のあるコードを構築する:
Malicious
という名前のクラスを定義し、その__reduce__
メソッドで実行されるコマンドos.system('echo Hacked!')
を指定します。__reduce__
メソッドは、Pickleがオブジェクトを再構築するためにデシリアライゼーションプロセス中に呼び出す特別なメソッドです。 - 悪意のあるオブジェクトをシリアライズする:
pickle.dumps
関数を使用して、Malicious
クラスのインスタンスをシリアライズして、悪意のあるコードを含むバイトストリームmalicious_data
を取得します。 - 悪意のあるオブジェクトをデシリアライズする:
pickle.loads
関数を使用してmalicious_data
をデシリアライズすると、__reduce__
メソッドが呼び出され、指定されたコマンドが実行されて「Hacked!」が出力されます。
Pickleデシリアライゼーション攻撃を防ぐ方法
安全なデシリアライゼーションの原則
デシリアライゼーション攻撃を防ぐための主要な原則は、信頼できないソースからのデシリアライゼーション操作を避けることです。データソースが完全に信頼されている場合にのみ、デシリアライゼーション操作を実行できます。
実用的な防御方法
- 安全なデシリアライゼーションコードの例:
場合によってはPickleを使用したデシリアライゼーションが必要な場合、
find_class
メソッドをオーバーロードすることにより、デシリアライズできるオブジェクトの型を制限し、それによってデシリアライゼーションの範囲を制限できます。
import pickle import types import io # デシリアライズ可能な型を制限するカスタムUnpickler class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module, name): if module == "builtins" and name in {"str", "list", "dict", "set", "int", "float", "bool"}: return getattr(__import__(module), name) raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden") def restricted_loads(s): return RestrictedUnpickler(io.BytesIO(s)).load()
上記のコードでは、pickle.Unpickler
から継承し、find_class
メソッドをオーバーライドするRestrictedUnpickler
クラスをカスタマイズしました。このようにして、一部の安全な組み込み型のみがデシリアライズできるようになり、デシリアライゼーション操作のセキュリティが向上します。
2. 他の安全なシリアライゼーションモジュール(JSONなど)を使用する:
より安全なオプションは、シリアライゼーションおよびデシリアライゼーション操作にPickleの代わりにJSONモジュールを使用することです。JSONは基本的なデータ型(文字列、数値、ブール値、配列、オブジェクトなど)のみをサポートし、任意のコードを実行しないため、セキュリティの点で一定の利点があります。
import json # オブジェクトをシリアライズする data = {'name': 'Leapcell', 'age': 29, 'city': 'New York'} data_json = json.dumps(data) # オブジェクトをデシリアライズする data = json.loads(data_json)
結論
この記事では、Pythonでのシリアライゼーションとデシリアライゼーションの概念、およびこのプロセスでのPickleモジュールの適用について包括的に紹介しました。同時に、デシリアライゼーション攻撃の原則について詳しく説明し、攻撃者が特定のコード例を通じて使用する可能性のある方法を示しました。最後に、デシリアライゼーションタイプを制限したり、より安全なシリアライゼーションモジュールを使用したりするなど、Pickleデシリアライゼーション攻撃を防ぐための原則と具体的な方法について説明しました。この記事の紹介を通じて、誰もがデシリアライゼーション攻撃についてより深く理解し、実際のプログラミングで効果的な予防措置を講じて、システムのセキュリティを確保できることを願っています。この記事の内容についてご質問やご提案がございましたら、コメント欄でご相談ください。
Leapcell:最高のサーバーレスWebホスティング
最後に、Pythonサービスをデプロイするのに最適なプラットフォームをお勧めします:Leapcell
🚀 お気に入りの言語で構築
JavaScript、Python、Go、またはRustで簡単に開発できます。
🌍 無制限のプロジェクトを無料でデプロイ
使用した分だけ料金を支払う—リクエストも、料金もありません。
⚡ 従量課金制、隠れたコストなし
アイドル料金なし、シームレスなスケーラビリティのみ。
🔹 Twitterでフォローしてください:@LeapcellHQ