危ないデシリアライズ

レッスン 2-7

信じられない入力をオブジェクトに戻すと、コードが動きます。

目安 60 分 トラック 2 Web アプリケーションの 7 本目

読み終えたらなぜ署名だけでは足りないのかを説明できる

この頁の流れ

  1. 1なぜ通るのか
  2. 2脆いコードと直したコード
  3. 3ログでの見え方と検知
  4. 4直し方と確かめ方
  5. 5関わる事例
  6. 6確認クイズ
  7. 7説明課題
  8. 8一次資料
  9. 9次へ

デシリアライズ (保存や送信のためにバイト列にした物を、プログラムの部品に戻すこと) の形式によっては、戻す途中で部品の中の処理が呼ばれます。pickle のような形式を外からの入力に使う危うさ、署名を付けても足りない理由、JSON に形の確かめを足す直し方、記録での見え方と、直ったことを試験で確かめる方法までを扱います。

出典を確かめた日 2026年10月27日

なぜ通るのか

ログイン中の利用者のカートを、サーバーに置かずに Cookie に持たせることにしたとします。Python の辞書を pickle (Python に付いている、部品をバイト列にする仕組み) でバイト列にし、Base64 で文字にして Cookie に入れます。次の要求で、Cookie を pickle で辞書に戻します。

動きは正しく、カートも残ります。ところが、Cookie は利用者のブラウザにある物で、中身は利用者が自由に書き換えられます。書き換えた Cookie を pickle で戻した時点で、サーバーの中で好きな処理を動かされるおそれがあります。

戻す途中で処理が動く

デシリアライズの形式は、大きく 2 つに分かれます。

形式戻せる物外からの入力に使えるか
JSON文字、数、真偽、並び、対応表だけ使える。戻す途中で処理は動かない
pickle、Java の直列化、PyYAML の安全でない読み込み型と、その型の作り方まで使わない。戻す途中で処理を呼べる

後者は、「この型を、この手順で組み立てる」という指示までバイト列に書けます。Python の説明書は、pickle は安全ではなく、戻す途中で任意のコードを動かすデータを作れると警告し、信じられない出どころのデータを pickle で戻してはならないと書いています。

Java のように、戻すと決まった処理 (戻したときに呼ばれる関数) が自動で動く言語では、手元の部品の組み合わせをつないで、狙った処理に届かせる手口もあります。CWE-502 は、こうした部品のつなぎ (ガジェットチェーン) を使ってシェルを起動する例を挙げています。

署名を付けても足りない理由

「Cookie に HMAC の印を付け、印が合う物だけを pickle で戻せばよい」と考えたくなります。CWE-502 も、戻すデータが書き換えられていないことを HMAC などで確かめる手当てを挙げています。印があれば、鍵を持たない人は中身を書き換えられません。

ただし、印が示すのは「鍵を持つ誰かが作った」ことだけです。中身が安全かどうかは示しません。印だけに頼ると、次の所で崩れます。

崩れる所何が起きるか
鍵が漏れる設定ファイルやリポジトリ、手本のコードから鍵が知られると、攻め手が自分で正しい印を付けられる
サーバーが何でも署名する利用者が決めた値を、サーバーがそのまま署名して返す作りがあると、攻め手の作った中身に本物の印が付く
鍵を入れ替えられない入れ替えの手順が無いと、漏れた鍵がいつまでも有効なまま残る

だから根本の直しは、外から来る物を、処理を呼べない形式 (JSON など) で受けることです。印はその上に重ねる守りで、印の代わりにはなりません。印の作り方と鍵の預け方は「ハッシュ、改ざん検知の符号、署名」のレッスンで扱います。

脆いコードと直したコード

ラボの中の小さな関数で比べます。Python に付いている pickle、json、base64、hmac だけで動き、外とは通信しません。

pickle で戻すか、JSON で戻して形を確かめるか

脆いコード Cookie を pickle で戻す

Python

def load_session(cookie):
    raw = base64.b64decode(cookie)
    return pickle.loads(raw)
直したコード JSON で戻し、形を確かめる

Python

FIELDS = {"user_id": int, "cart": list}

def load_session(cookie):
    data = json.loads(
        base64.b64decode(cookie))
    if not isinstance(data, dict):
        raise ValueError("bad session")
    if data.keys() != FIELDS.keys():
        raise ValueError("bad session")
    for k, t in FIELDS.items():
        if not isinstance(data[k], t):
            raise ValueError("bad")
    return data

直した方は、JSON で戻すので、戻す途中で処理は動きません。そのうえで、項目の名前と型が決めた形どおりかを確かめ、余計な項目 (たとえば「admin」) が混ざっていれば受け付けません。OWASP の Cheat Sheet も、Python では pickle.loads を信じられない入力に使っていないかを点検するよう挙げています。PHP の節では、利用者にデータを渡すなら JSON のような標準の形式を使うよう勧めています。

印を確かめて pickle で戻すか、印を確かめて JSON で戻すか

脆いコード 印を確かめてから pickle で戻す

Python

def load_session(cookie, key):
    body, sig = split(cookie)
    if not hmac.compare_digest(
            sign(key, body), sig):
        raise ValueError("bad sig")
    return pickle.loads(body)
直したコード 印を確かめ、戻すのは JSON

Python

def load_session(cookie, key):
    body, sig = split(cookie)
    if not hmac.compare_digest(
            sign(key, body), sig):
        raise ValueError("bad sig")
    return parse_session(body)

parse_session は、1 組目の直した方と同じく、JSON で戻して形を確かめる関数です。2 つの違いは、鍵が漏れたときに出ます。脆い方は、鍵を知った攻め手にサーバーの中で処理を動かされます。直した方は、鍵を知られても、作れるのは決めた形の値だけです。それでも user_id を他人の番号にされれば成りすましになるので、鍵の入れ替えは別に要ります。被害の大きさが「サーバーを乗っ取られる」から「値を偽られる」まで下がる、という違いです。

ログでの見え方と検知

直したコードに、pickle で作った Cookie や、余計な項目の付いた Cookie が届くと、記録には次のような行が残ります。

形の合わないセッションが届いたとき

記録

ValueError: bad session
  path=/cart cookie_len=88

ふだんの利用では、サーバーが自分で作った Cookie しか届かないので、この行はほとんど出ません。守る側は次を見張ります。

見る物なぜ効くか
コードの中の pickle.loads、yaml.load、ObjectInputStream外からの入力に届いていないかを、書いた時点で見つけられる
形や印の合わない Cookie の数ふだんは出ない。増えたら書き換えの試みを疑う
Web の働き手のプロセスから起動された別のプロセス戻す途中で処理を動かされたとき、シェルなどの起動として表に出る
署名の鍵の置き場と、入れ替えた日手本からの写しや、長く変えていない鍵は、漏れの入口になる

記録には Cookie の中身そのものは残しません。セッションの中身は利用者の情報で、記録の置き場が新しい漏れ口になるためです。

直し方と、直ったことの確かめ方

自分の PC の中だけで行います。外のサービスや、他人の機械には向けません。

  1. 作業用のフォルダに、脆い load_session と、辞書を Cookie の文字にする小さな関数を置く
  2. 脆い方に、辞書ではない型 (たとえば datetime) を pickle で包んだ Cookie を渡し、その型がそのまま組み立てられて返ることを確かめる (型の作り方まで戻せる、という性質を、害の無い型で見るだけにする)
  3. 関数を直したコードに置き換える
  4. 同じ Cookie と、余計な項目の付いた Cookie が、どちらも ValueError で止まることを確かめる
  5. 下の試験を足し、pickle で戻すコードに戻すと試験が落ちることも確かめる
  6. 終わったら、作業用のフォルダごと消す
直ったことを固める試験

Python

def enc(obj):
    return base64.b64encode(
        json.dumps(obj).encode())

def test_rejects_pickle():
    raw = base64.b64encode(pickle.dumps(
        {"user_id": 1, "cart": []}))
    with pytest.raises(ValueError):
        load_session(raw)

def test_rejects_extra_field():
    raw = enc({"user_id": 1, "cart": [],
               "admin": True})
    with pytest.raises(ValueError):
        load_session(raw)

試験は「pickle の形式は受け付けない」ことと「決めた形以外は受け付けない」ことを固定します。あとで誰かが、楽だからと pickle に戻しても、ここで止まります。

あわせて、使っている部品の中にも同じ穴が無いかを点検します。Java の直列化を使う所では、OWASP が勧めるように、戻してよい型を絞る仕組み (直列化のフィルター) を置きます。署名の鍵は、手本や公開のリポジトリから写した物を使わず、自分で作り、入れ替えの手順を決めておきます。

関わる事例

Microsoft は2025年2月6日、ASP.NET の machine key (画面の状態 ViewState に印を付け、暗号化する鍵) が、説明書やリポジトリなどの公開された場所に載っていて、それが攻撃に使われていると公表しました。2024年12月には、公開されていた固定の鍵を使って、正しい印の付いた悪意のある ViewState をサーバーに送り、サーバーの中で攻撃用の道具を読み込ませて動かした活動を確認したとしています。同社は、この攻撃に使える公開された鍵を 3,000 以上見つけたと書いています。

ViewState には印が付いていました。それでも、印を付ける鍵が誰にでも見える所にあれば、攻め手は本物の印を付けられます。Microsoft は、公開された所から鍵を写さないこと、鍵を定期的に入れ替えることを勧めています。

確認クイズ

1 問目

利用者の設定を Cookie に持たせます。サーバーで辞書に戻すとき、どの形式を使うのがよいですか。

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。

2 問目

pickle で戻す Cookie に HMAC の印を付け、印が合う物だけを戻しています。残る危うさとして正しいのはどれですか。

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。

説明課題

次のことを、自分の言葉で 3 行にまとめてください。書けたら、このレッスンは修了です。

  • なぜ署名だけでは足りないのか
模範解答を見る

署名が示すのは、鍵を持つ誰かが作ったことだけで、中身が安全かどうかではない。鍵が漏れたり、サーバーが利用者の決めた値をそのまま署名したりすれば、攻め手の作った中身にも本物の印が付く。そのとき pickle のように戻す途中で処理を呼べる形式だとコードを動かされるので、外からの物は JSON のような形式で受け、署名はその上に重ねる。

一次資料

どれも 2026年10月27日に開いて、題と中身を確かめました。

次へ

次のレッスンは「安全のためのヘッダーと CSP」です。アプリのコードで守ったつもりの物が、配り方の経路しだいで抜け落ちる所を、ブラウザに渡す決まりの側から扱います。

次のレッスン 2-8 安全のためのヘッダーと CSP

このレッスンにつながる物

トラック 2 Web アプリケーションの中で

前のレッスン 2-6 ファイルの受け取りとパス

学んだことを次につなげる

ゆめさくセキュリティは、攻撃の仕組みを知り、自分の PC の中で壊して直し、守る側の判断を身につけるための学びの場です。

ラボはすべて自分の PC の中 (127.0.0.1) で閉じます。自分が管理していない相手に試すことは扱いません。