パディングオラクル攻撃|「復号成功/失敗」だけで暗号文を解く
第3話まではWebアプリの「信頼境界」の崩れを見てきました。第4話は暗号そのものに切り込みます。CBCモードの暗号文は、鍵を一度も知らなくても、「復号が成功したか失敗したか」というたっ𰗱ビットの情報が漏れるだけで、平文を1バイトずつ完全に解読できてしまいます。この事実を、本物のWeb Crypto APIを使って体験します。
📋 目次
🔓 パディングオラクル攻撃とは|「成功/失敗」だけで解読する
鍵を知らなくても、平文を完全に復元できる
1ビットの情報だけで全文が判る
パディングオラクル攻撃(Padding Oracle Attack)は、CBC(Cipher Block Chaining)モードで暗号化されたデータを、鍵を一度も知ることなく完全に復元できてしまう、暗号学の中でも特に有名な攻撃手法の1つです。2014年のPOODLE、2013年のLucky13といった名前のついた深刻な脆弱性も、この攻撃手法の一対です。
攻撃者は暗号文を少しだけ改ざんしてサーバーに送ります。サーバーはそれを復号しようとし、「パディング(暗号化時に追加される埋め淨のバイト極)」が正しい形式かどうかを検査します。この検査結果(成功か失敗か)が何らかの方法で攻撃者に伝わってしまうと、攻撃者は改ざんを何度も繰り返しながら、平文𰤢バイトずつ確定させていきます。
この話の前提知識
実践編第6話で学んだ「暗号モードの構造的な弱点」という発想をさらに深めます。ECBは「暗号文の見た目のパターン」が漏れる弱点でしたが、今回�の「復号の成功/失敗」が漏れる弱点です。
- 実践編 第6話「ECBモードの弱点を見抑く|暗号文に浮かぶパターン」
🧮 CBCモードの仕組み|なぜパディングが手がかりになるのか
前のブロックを操れば次の復号結果を操れる
CBC(Cipher Block Chaining)モードでは、あるブロックCₙの復号は「Dₖ(Cₙ) XOR Cₙ₋₁」(1個前の暗号文ブロックとXOR)で求まります。注目すべきのは、前のブロックCₙ₋₁を书き書きすれば、復号結果もそのままXORされて変わるという点です。鍵Dₖを知らなくても、前のブロックを操れば復号結果を自在に操作できてしまいます。
暗号化前のデータ�バイトの倍数でなければならないため、PKCS7という方式で末尾にパディングを埋めます。埋めるバイト数と同じ値を、その数だけ並べるのが特徴です。
| 元のデータ長 | 必要なパディング | 末尾のバイト列(16ブロック中) |
|---|---|---|
| 15バイト | 1バイト | … 01 |
| 14バイト | 2バイト | … 02 02 |
| 9バイト | 7バイト | … 07 07 07 07 07 07 07 |
| 16バイト(ちょうど1ブロック) | 16バイト(丸々と1ブロック追加) | 10 10 10 …�個) |
攻撃の核心:中間値を1バイトずつ確定する
末尾バイトを0~255まで全探索し、パディングが有効になる値を1つ見つけると、そのXOR関係から𰃍ₖ(Cₙ)の末尾バイト」(中間値)がわかります。それを本当の前のブロッ𰫼ₙ₋₁とXORすれば、本当の平文の末尾バイトが手に入ります。次は「末尾� 02になる」ように既知の最後のバイトを固定しながら、その1つ手前を同じように全探索します。これをブロックの先頭まで繰り返せば、ブロック全体の平文が判明します。
👁️ オラクルの正体|エラーメッセー𰮂の違いが命取りになる
「パディングが不正です」とは一言も表示しなくても、オラクルになる
「オラクル」と呼ばれる側は、「パディングエラー」と直接表示する必要は一切ありません。サーバーの振る舞いに、パディングが有効か無効かで区別できる何か一つでも差があれば、それだけで極成り立ちます。例えばパディングが不正なときはHTTP 500エーラー、パディングが正しく(中身の値自体は意味がなくても)判定処理に進めたHTTP 200(「ユーザーが見つかりません」等)、というただそれだけの違いでも充分にオラクルとして機能してしまいます。第2話で学んだSSRFの「小さな実装の雗」と同じ発想です。
メッセージさえ同じでも成立する:Lucky13のタイミング攻撃
2013年に発見されたLucky13は、エラーメッセージを完全に統一していても成立するさらに高度なパディングオラクルです。パディングが正しい場合はその後にHMAC検証処理も走るため、不正な場合と比べて処理時間がわずかに長くなります。この数ミリ秒単位の応答時間差だけを手がかりに、エラーメッセージを完全に統一していても同じ攻撃が成立しました。
🔎 実践チャレンジ:オラクルを見抑き、暗号文を解読せよ
本物�S-CBC(Web Crypto API)を使ったラボ
架空の「やさい人事システム」には、AES-CBCで暗号化された「プロフィールトークン」を復号する機能があります。下の3つのサンプルを送信して、サーバー(のふり)の応答の違いを確認してください。この3つはすべて、本物のWeb Crypto APIで実際に復号を試行しています。
ステー𰮁をクリアすると、ステー𰮂が解放されます。両方クリアした時点でスコアが確定します。
📊 ステージ進捗: 0/2|挑戰回数: 0回
3つのサンプルのうち、「パディングが有効(200 OK相当)」と判定された番号をPADDING-OK-〇形式で入力してください(例:PADDING-OK-1)。
3つのサンプルをすべて送信して、応答文の違いを比べてみましょう。
「500 Internal Server Error」はパディングが不正、「200 OK」はパディングが正しく判定処理に進んだということです。
下の暗号文(鍵は不要)を目標に、ブラウザ𰛬onsole(F12)に下のスクリプトを貼り付けて実行してください。パディングオラクルを自動で繰り返し、Consoleに復号結果を表示します。
IV:
暗号文:
async function paddingOracleAttack(){
function hexToBytes(hex){var b=[];for(var i=0;i<hex.length;i+=2)b.push(parseInt(hex.substr(i,2),16));return new Uint8Array(b);}
function bytesToHex(b){return Array.from(b).map(function(x){return x.toString(16).padStart(2,'0');}).join('');}
var iv = hexToBytes(window.exp04Data.ivHex);
var ct = hexToBytes(window.exp04Data.ctHex);
var blocks = [];
for (var i=0;i<ct.length;i+=16) blocks.push(ct.slice(i,i+16));
var prevBlocks = [iv].concat(blocks.slice(0,-1));
var plaintext = [];
for (var b=0; b<blocks.length; b++){
var target = blocks[b];
var intermediate = new Uint8Array(16);
for (var padLen=1; padLen<=16; padLen++){
var testIv = new Uint8Array(16);
for (var k=16-padLen+1;k<16;k++) testIv[k] = intermediate[k] ^ padLen;
var found = false;
for (var guess=0; guess<256; guess++){
testIv[16-padLen] = guess;
var ok = await window.exp04Oracle(bytesToHex(testIv), bytesToHex(target));
if (ok) { intermediate[16-padLen] = guess ^ padLen; found = true; break; }
}
if (!found){ console.log('failed', b, padLen); return; }
}
var prev = prevBlocks[b];
for (var idx=0; idx<16; idx++) plaintext.push(intermediate[idx] ^ prev[idx]);
}
var padVal = plaintext[plaintext.length-1];
var result = plaintext.slice(0, plaintext.length-padVal);
console.log('復号結果:', new TextDecoder().decode(new Uint8Array(result)));
}
paddingOracleAttack();
F12を押して開発者ツールを開き、Consoleタブを選んでください。コピーしたコードを貼り付けてEnterを押すと実行されます。
実行には数百~数千回の判定が必要なため𰀑~2秒ほどかかります。Consoleに「復号結果: CTF{…}」と表示されたら成功です。
🛡️ 防御側の視点|パディングオラクル攻撃をどう防ぐか
根本対策は「パディングの有無で振る舞いを分けない」こと
- AEADモードを使う:AES-GCMなどのAEAD(Authenticated Encryption with Associated Data)モードは、暗号文の改ざんを認証タグで即座に検知し、そもそもパディングという概念を使わないため、パディングオラクル自体が成立しません。現在の実装では最一の選択とされています。
- Encrypt-then-MAC:CBCをどうしても使う必要がある場合は、復号を試みる前にHMACで暗号文の改ざんを先に検証し、改ざんがあればその時点で拒否する(パディング検査にたどり付かない)
- 一定の応答を返す:パディングエラーとその他のエラーで応答(ステータスコード・エーラー文文面)を完全に一定にする
- 処理時間を一定にする:Lucky13対策として、検証処理の所要時間が成功/失敗で差がでないように実装する(定数時間比較)
今日新規に実装するなら、CBCは選ばない
TLS1.3や最新の暗号化APIの仕様は、CBCモードを事実上廃止し、AES-GCMやChaCha20-Poly1305など�モードに一本化しています。今から新しく暗号化を実装するなら、CBCを選ぶ理由はほとんどありません。
📝 まとめ+FAQ+次回予告
鍵不要で暗号文を解き切った
第4話では、本物�S-CBCを使って、鍵を一度も知らないまま暗号文を完全に復号しました。次回はレースコンディションという、チェックと実行の間に生まれる「時間の雕間」をつくタイミング攻撃を扱います。
・CBCモードの復号は「成功/失敗」の1ビットだけで平文を完全に割り出せる
・前のブロック(IV含む)を操作し、パディングが有効になる値を1バイトずつ探す
・オラクルは「パディングエラー」と明言しなくても、応答の違い(エラーコードや文面、時間)だけでも成立する
・最強の防御はAES-GCM等�モードに切り替えること
Q. 現在、実際のWebサービスでもパディングオラクル攻撃は通用しますか?
TLS(HTTPS)の文脈ではTLS1.3でCBCモードが完全に廘止されたため大幅に減っています。しかしクーキーの暗号化や自作の暗号通信プロトコルなど、CBCモードを自前実装している箇所では今でも発見されることがあります。
Q. オラクルの応答が1回しか取れない場合(リクエスト数制限等)、この攻撃は防げますか?
遵延しますが防ぐとは限りません。1ブロック(16バイト)を復号するには最񙉱×16×256回(最�回)の問い合わせで十分なため、単位時間あたりのオラクル呼び出し数を制限すれば攻撃時間を大幅に伸ばせますが、根本対策にはならないとされています。
Q. 自分で鍵を持っていれば、もっと早く復号できるのになぜこんな手間な方法を使うのですか?
この攻撃の価値はまさに「鍵を持たない攻撃者でも、サーバーの振る舞いの違いだけで平文を得られる」という点にあります。実際の攻撃者も鍵を持っていない前提でこの手法を使います。
Q. 暗号文やIVの値はどこかに送信されますか?
送信されません。すべてブラウザのlocalStorage(あなたの端末内)だけに保存され、外部のサーバーには一切送信されません。
レースココンディション|タイミーグトのずれを突く同時リクエスト攻撃
チェックと実行の間に生まれる時間の雕間を突き、同一価値を二重に利用する不正を体験します。
📚 参考情報
- 𰃌TF上級編」実践編第6話(ECBモード)
- OWASP Top 10(A02:2021 Cryptographic Failures)


コメント