Pythonのpickleにおけるデシリアライズ脆弱性の徹底解説


[![Group202.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/c2cde521-f95e-4e6e-88a1-19c824b73961.png)](https://leapcell.io/)



**[Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)**


# PythonにおけるPickleモジュールの逆シリアル化攻撃と対策

## はじめに
皆さん、こんにちは！Pythonプログラミングの世界では、潜在的なセキュリティリスクである逆シリアル化攻撃が存在します。逆シリアル化攻撃について掘り下げる前に、私たちにとってシリアル化と逆シリアル化が何であるかを理解することは不可欠です。

概念的に、シリアル化はデータ構造またはオブジェクトをバイトストリームに変換するプロセスです。この変換により、データを簡単にファイルに保存したり、ネットワークを介して送信したりすることができます。逆シリアル化は、その逆のプロセスであり、バイトストリームを元のデータ構造またはオブジェクトに戻すものです。

Pythonにおいて、Pickleモジュールはシリアル化と逆シリアル化を実装するために一般的に使用されるツールの一つです。これは便利なインターフェイスを提供し、複雑なPythonオブジェクトをシリアル化して保存することができ、必要になったときに簡単に逆シリアル化して元に戻すことができます。しかし、この利便性は潜在的なセキュリティリスクももたらします。

## 逆シリアル化攻撃の概要
逆シリアル化のプロセスは、必ずしも安全で信頼性の高いものではありません。私たちが信頼できないデータソースから逆シリアル化操作を行うとき、逆シリアル化攻撃に遭う可能性があります。攻撃者はシリアル化されたデータに悪意のあるコードを埋め込むことができます。これらのデータが逆シリアル化されると、埋め込まれた悪意のあるコードが実行されます。このような攻撃は、データ漏えい、システムクラッシュ、さらには攻撃者がシステムのリモートコントロール権限を得ることさえ可能にするなど、深刻な結果をもたらすことがあります。

## PythonのPickleモジュールの概要
### Pickleの基本機能
PickleモジュールはPythonの標準ライブラリの一部であり、追加インストールを必要とせずに使用することができます。その主な機能はPythonオブジェクトのシリアル化と逆シリアル化を実装することです。シンプルな基本データ型であっても、複雑なデータ構造（リスト、辞書、クラスインスタンスなど）であっても、Pickleはそれをバイトストリームに変換して保存または送信し、必要になったときに元のオブジェクト形式に戻すことができます。

### Pickleの動作原理
Pickleの動作原理は比較的直感的です。シリアル化の段階では、特定の規則に従ってPythonオブジェクトをバイトストリームに変換します。これらのバイトストリームにはオブジェクトの型情報とデータ内容が含まれています。逆シリアル化の段階では、Pickleはバイトストリームを読み取り、その中の情報に基づいて対応するPythonオブジェクトに戻します。

### Pickleによるシリアル化と逆シリアル化
1. **シリアル化**:
Pickleは2つの主なシリアル化関数 `pickle.dump` と `pickle.dumps` を提供しています。`pickle.dump` 関数は直接シリアル化されたオブジェクトを指定されたファイルに書き込み、`pickle.dumps` 関数はシリアル化されたデータを含むバイトストリームを返します。
```python
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)
```
2. **逆シリアル化**:
逆シリアル化にも2つの一般的な関数 `pickle.load` と `pickle.loads` があります。`pickle.load` 関数は指定されたファイルからバイトストリームを読み取り逆シリアル化し、`pickle.loads` 関数は直接バイトストリームを逆シリアル化します。
```python
import pickle

# ファイルからオブジェクトを逆シリアル化する
with open('data.pickle', 'rb') as file:
    data = pickle.load(file)

# または直接バイトストリームを逆シリアル化する
data = pickle.loads(data_bytes)
```

## 逆シリアル化攻撃の原理
### 攻撃メカニズム
逆シリアル化攻撃の核心は、攻撃者がシリアル化されたデータに悪意のあるコードを注入できる点にあります。ターゲットシステムがこれらの悪意のあるコードを含むシリアル化データを逆シリアル化すると、悪意のあるコードが実行され、攻撃者の目的が達成されます。つまり、逆シリアル化時にデータソースに対して厳格な検証とスクリーニングを行わない場合、攻撃者がシステム内で任意のコードを実行するための扉を開くことに等しくなります。

### 攻撃者が行えること
攻撃者は逆シリアル化の脆弱性を利用して、様々な悪意のある操作を行うことができます。例えば、任意のシステムコマンドを実行したり、システム内の重要なデータを改ざんしたり、機密情報を盗んだりするなどです。これらの操作はシステムのセキュリティと安定性に深刻なダメージを与える可能性があります。

### サンプルコード
逆シリアル化攻撃のプロセスをより明確に示すため、具体的な例を見てみましょう：
```python
import pickle
import os

# 悪意のあるコードを構築
class Malicious:
    def __reduce__(self):
        return (os.system, ('echo Hacked!',))

# 悪意のあるオブジェクトをシリアル化
malicious_data = pickle.dumps(Malicious())

# 逆シリアル化時に悪意のあるコードを実行
pickle.loads(malicious_data)
```
この例では：
1. **悪意のあるコードを構築**: 私たちは `Malicious` という名前のクラスを定義し、その `__reduce__` メソッドで `os.system('echo Hacked!')` というコマンドを実行するように指定します。`__reduce__` メソッドはPickleが逆シリアル化のプロセスでオブジェクトを再構築するために呼び出す特別なメソッドです。
2. **悪意のあるオブジェクトをシリアル化**: `pickle.dumps` 関数を使って `Malicious` クラスのインスタンスをシリアル化し、悪意のあるコードを含むバイトストリーム `malicious_data` を得ます。
3. **悪意のあるオブジェクトを逆シリアル化**: `pickle.loads` 関数を使って `malicious_data` を逆シリアル化するとき、`__reduce__` メソッドが呼び出され、指定されたコマンドが実行され、「Hacked!」が出力されます。

## Pickleの逆シリアル化攻撃を防ぐ方法
### 安全な逆シリアル化の原則
逆シリアル化攻撃を防ぐ第一の原則は、信頼できないソースからの逆シリアル化操作を避けることです。データソースが完全に信頼できる場合にのみ、逆シリアル化操作を行うことができます。

### 実践的な防御方法
1. **安全な逆シリアル化コードの例**:
あるケースでPickleを使った逆シリアル化が必要な場合、`find_class` メソッドをオーバーロードすることで逆シリアル化可能なオブジェクトの型を制限し、逆シリアル化の範囲を制限することができます。
```python
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()
```
上記のコードでは、`RestrictedUnpickler` クラスをカスタマイズしています。これは `pickle.Unpickler` を継承し、`find_class` メソッドをオーバーライドしています。このようにして、いくつかの安全な組み込み型のみが逆シリアル化されるようになり、逆シリアル化操作のセキュリティが向上します。
2. **他の安全なシリアル化モジュール（例えばJSON）を使用する**:
より安全な選択肢として、Pickleの代わりにJSONモジュールを使ってシリアル化と逆シリアル化操作を行うことができます。JSONは基本データ型（文字列、数値、ブール値、配列、オブジェクトなど）のみをサポートし、任意のコードを実行することはありませんので、セキュリティ面で一定の利点があります。
```python
import json

# オブジェクトをシリアル化
data = {'name': 'Leapcell', 'age': 29, 'city': 'New York'}
data_json = json.dumps(data)

# オブジェクトを逆シリアル化
data = json.loads(data_json)
```

## まとめ
この記事では、Pythonにおけるシリアル化と逆シリアル化の概念、ならびにそのプロセスでのPickleモジュールの応用を総合的に紹介しました。同時に、逆シリアル化攻撃の原理を詳細に説明し、具体的なコード例を通じて攻撃者が使う可能性のある方法を実演しました。最後に、Pickleの逆シリアル化攻撃を防ぐ原則と具体的な方法、逆シリアル化の型を制限することや、より安全なシリアル化モジュールを使用することなどについて議論しました。この記事の紹介を通じて、皆さんが逆シリアル化攻撃についてより深く理解し、実際のプログラミングにおいて効果的な対策を講じてシステムのセキュリティを確保できることを願っています。この記事の内容に関する質問や提案があれば、コメント欄でぜひ議論してください。 

# [Leapcell: The Best of Serverless Web Hosting](https://leapcell.io/)
最後に、Pythonサービスをデプロイするのに最適なプラットフォームをおすすめします：**[Leapcell](https://leapcell.io/)**

[![brandpic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/9f14b21d-b23d-431b-b96b-0cff47e54fca.png)](https://leapcell.io/)


### 🚀 好きな言語で構築しよう
JavaScript、Python、Go、またはRustで簡単に開発できます。  

### 🌍 無料で無制限のプロジェクトをデプロイ
使用した分だけ支払います—リクエストがなければ、請求もありません。  

### ⚡ 使った分だけ支払い、隠された費用はありません
アイドル時の料金はなく、シームレスにスケーラブルです。  

[![Frame3-withpadding2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/b0e91d58-cbcc-4983-8a9f-b275e432db99.png)](https://leapcell.io/)


[📖 ドキュメントを参照する](https://docs.leapcell.io/)  

🔹 Twitterでフォローしてください: [@LeapcellHQ](https://x.com/LeapcellHQ)