【実務・中級編】 パディングオラクル攻撃(Padding Oracle Attack)による暗号解読 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

パディングオラクル攻撃:なぜ「暗号化しているから安全」という幻想が破綻するのか

「AESで暗号化しているから、セッションIDや個人情報は安全だ」
そう信じているエンジニアが、実はまだ現場には山ほどいる。だが、暗号化は「隠す」ことには長けていても、その実装の「わずかな綻び」がシステムの全権掌握に直結することを知る者は少ない。

今日解説するのは、CBC(Cipher Block Chaining)モードにおけるパディングオラクル攻撃だ。これは、暗号化されたデータの「復号エラー」というたった一つのサイドチャネルを突き、鍵を知らずして平文をすべて暴き出す、極めてエレガントかつ凶悪な手法だ。

1. なぜ「パディングエラー」が致命的なのか

CBCモードでは、暗号化するデータの長さをブロックサイズ(AESなら16バイト)の倍数に揃えるために、末尾に「パディング」を付与する(PKCS#7が一般的だ)。

復号処理において、サーバーは末尾のパディングが正しい形式かを確認する。ここで、攻撃者が暗号文の末尾をいじってサーバーに送った際、サーバーが「パディングが不正です」というエラーを返すとしよう。これが「オラクル(神託)」となる。

  • 正常なパディング: サーバーは復号を続行し、その後のロジックで別のエラーを返す。
  • 不正なパディング: サーバーは即座にエラーを投げる。

この「エラーの種類の違い」こそが、攻撃者に「今の改ざん部分は正しいパディングとして解釈されたか?」という情報を漏洩させているのだ。攻撃者はこれを1バイトずつ試行することで、わずか数千回のアクセスで暗号文を完全に復号できてしまう。

2. 現場で起きる「泥臭い」PoCの現実

攻撃者は、ツールを回す前にまず「サーバーの反応」を観察する。例えば、以下のような挙動があれば即座にターゲットとなる。

  • 500 Internal Server Error(パディングエラー)
  • 200 OK(処理続行、または認証エラー)

このレスポンスの差異をプログラムで自動収集し、暗号ブロックを1バイトずつ総当たり(0〜255)させる。これが現代のペネトレーションテストにおける「基本のキ」だ。

3. 【セキュアな実装】AES-GCMへの完全移行

パディングオラクル攻撃を「防ごう」として、エラーメッセージを共通化するような小手先の対策はやめろ。あれは本質的な解決にならない。

唯一の正解は、認証付き暗号(AEAD)であるAES-GCMへの完全移行だ。 GCMは暗号化と同時に「認証タグ」を生成する。復号時にデータが改ざんされていれば、復号処理自体が失敗するため、パディングの正誤を推測する余地すら与えない。

Python (PyCryptodome) による推奨実装例

以下は、今すぐ既存のAES-CBCから置き換えるべき実装サンプルだ。

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import json, base64

def encrypt_data(plaintext, key):
    # AES-GCMを使用。パディングは不要(GCMが自動処理)
    cipher = AES.new(key, AES.MODE_GCM)
    ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode('utf-8'))
    
    # nonce, tag, ciphertext をセットで保存(復号に必須)
    result = {
        'nonce': base64.b64encode(cipher.nonce).decode('utf-8'),
        'tag': base64.b64encode(tag).decode('utf-8'),
        'ciphertext': base64.b64encode(ciphertext).decode('utf-8')
    }
    return json.dumps(result)

def decrypt_data(json_data, key):
    data = json.loads(json_data)
    nonce = base64.b64decode(data['nonce'])
    tag = base64.b64decode(data['tag'])
    ciphertext = base64.b64decode(data['ciphertext'])
    
    # 復号と認証を同時に行う。改ざんがあればここで例外が発生する
    cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
    try:
        return cipher.decrypt_and_verify(ciphertext, tag).decode('utf-8')
    except ValueError:
        # 認証失敗時は即座に処理を中断する(攻撃の余地を与えない)
        raise ValueError("データの改ざん、または鍵の不一致が検出されました")

4. インフラ・設定面での守り

コードの修正が完了するまでの「応急処置」や、多層防御としての設定も重要だ。

  • WAFによるトラフィック制限:

急激なエラーレスポンスの増大を検知し、IPをブロックするルールを設定せよ。AWS WAFであれば、4xx/5xxエラーのレートベースルールを設定し、特定のクライアントからの攻撃試行を即座に遮断する。

  • エラーハンドリングの統一:

アプリケーション側で、どのようなエラーであっても「ユーザーには一律の汎用エラーを表示する」ように徹底すること。ログには詳細を残すが、レスポンスには一切のヒントを与えてはいけない。

最後に:エンジニアへの提言

パディングオラクル攻撃は、古典的だが極めて強力な「サイドチャネル」の代表格だ。
「動いているから大丈夫」という思考停止は、セキュリティの世界では自殺行為に等しい。暗号化ライブラリが古いCBCモードをデフォルトにしているようなら、今すぐそのライブラリを捨てるか、GCMモードへ切り替えるためのタスクをバックログに積むことだ。

セキュリティは「魔法の杖」ではない。正しいアルゴリズムを選択し、実装のわずかな隙を埋め続ける、日々の泥臭い積み重ねこそが、君たちのプロダクトを真に堅牢にする唯一の手段だ。

コメント

タイトルとURLをコピーしました