flawopen.com/Teardowns/cve-2014-0160-heartbleed
CVE-2014-0160:OpenSSL Heartbleedのメモリ漏えい
CVE-2014-0160 では、OpenSSL がレコード内に存在しないペイロード長を信頼すると、TLS または DTLS の相手が Heartbeat レコードの末尾を越えて最大 64 KiB を読み取れました。上流修正はペイロードを解析して返す前にレコードを検証します。[1], [2]
数語しか書かれていないカードを受け取った係員が、もっと長く複写するよう求めるラベルを信じたとします。カードの端で止めず、共用トレイに続く文字まで複写してしまいます。Heartbleed では OpenSSL が受信レコードを越えて読み取りました。露出するバイト列はプロセスのメモリ内容によって変わります。[1], [3]
主要な概念と専門用語
TLS/DTLS Heartbeat(RFC 6520)- TLS または DTLS の接続維持メッセージです。要求には相手が返すペイロードと、プロトコルで必要なパディングが含まれます。[3]
payload_length- Heartbeat ペイロードの大きさを示す 16 ビットフィールドです。メッセージ型、長さフィールド、最小パディングの後に受信レコード内へ収まる必要があります。[1], [3]
バッファー範囲外読み取り(CWE-126)- 想定したソースバッファーの末尾より後を読むことです。Heartbleed ではメモリ内容によって最大 64 KiB のプロセスメモリが応答に含まれる可能性がありました。[5]
フレームワーク対応
- このガイドで説明する弱点の CWE 参照: CWE-126
- CISA KEV — 追加日 2022-05-04 — CVE-2014-0160
根本原因の分析 (Root Cause)
tls1_process_heartbeat() と dtls1_process_heartbeat() は Heartbeat メッセージの 16 ビットのペイロード長を読み、応答処理で memcpy() に渡していましたが、s->s3->rrec.length 内にそのペイロード分のバイトがあることを確認していませんでした。相手は TLS または DTLS レコードに含まれる量より大きな長さを宣言できます。その結果、OpenSSL は受信レコードの後ろにあるバイトまで応答に含め、最大 64 KiB のプロセスメモリを開示する可能性がありました。内容はメモリ状態に依存するため、特定の鍵、パスワード、cookie が必ず含まれるわけではありません。上流コミット 96db902 はまず、型・長さフィールド・最小パディングを収められない短いレコードを破棄し、次に 1 + 2 + payload + 16 を受信レコード長と比較します。また SSL3_RT_MAX_PLAIN_LENGTH で応答を制限し、計算した write_length を使用します。TLS と DTLS の両ハンドラーに同様の検査が追加されました。[1], [2], [3], [4]
ステップ・バイ・ステップの攻撃フロー
ソースコード比較:脆弱 vs 堅牢化
これらは OpenSSL の上流修正から抜粋したコードで、単独で動くプログラムではありません。脆弱なコードは固定ヘッダーと最小パディングの有無を確認する前にフィールドを解析し、その後、相手が宣言したペイロード長をコピーします。修正では解析前に最小レコード長を確認し、宣言値と受信レコードを照合して、応答長も制限します。OpenSSL は tls1_process_heartbeat() と dtls1_process_heartbeat() の両方を同様に変更しました。[2], [1]
ソース抜粋: ssl/t1_lib.c
/* 修正前の tls1_process_heartbeat() の抜粋。 */
hbtype = *p++;
n2s(p, payload);
pl = p;
/* TLS1_HB_REQUEST の応答パスでは: */
memcpy(bp, pl, payload);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT,
buffer, 3 + payload + padding);
/* タイプ、長さ、最小パディングを格納するには短すぎるレコードを拒否する。 */
if (1 + 2 + 16 > s->s3->rrec.length)
return 0;
hbtype = *p++;
n2s(p, payload);
if (1 + 2 + payload + 16 > s->s3->rrec.length)
return 0;
pl = p;
/* TLS1_HB_REQUEST の応答パスでは: */
unsigned int write_length = 1 + 2 + payload + padding;
if (write_length > SSL3_RT_MAX_PLAIN_LENGTH)
return 0;
buffer = OPENSSL_malloc(write_length);
memcpy(bp, pl, payload);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, write_length);
インシデントのタイムライン
エンジニアリング&システム堅牢化チェックリスト
- ✓修正を含む、ベンダーがサポートする OpenSSL に更新してください。上流では 1.0.1g と 1.0.2-beta2 で修正されました。表示バージョンだけでなく、ディストリビューションのバックポートを確認してください。[1], [2]
- ✓露出の可能性があるエンドポイントでは、影響を受けた TLS 秘密鍵と証明書を交換し、漏えいが考えられる場合は旧証明書を失効させてください。不審な Heartbeat を処理したクライアントも対象です。[6]
- ✓脆弱なサービスにパッチを適用した後、プロセス内メモリに存在していた可能性のある認証情報をローテーションしてください。利用者のパスワード変更は、サービス担当者が修正済みと確認してから行います。[6], [8]
- ✓レコード境界で TLS と DTLS のハンドラーをテストしてください。固定ヘッダー不足、受信バイト数を超えるペイロード長、最小パディングを含む有効な要求を確認します。[2], [3]
- ✓Heartbeat が不要で互換性に問題がなければ、OpenSSL の告知にある代替策
-DOPENSSL_NO_HEARTBEATSを指定して再ビルドしてください。[1]
参考資料
- OpenSSL Security Advisory — TLS heartbeat read overrun (CVE-2014-0160)
- OpenSSL upstream fix — commit 96db902
- RFC 6520 — TLS and DTLS Heartbeat Extension
- NVD — CVE-2014-0160
- MITRE CWE-126 — Buffer Over-read
- Codenomicon / Heartbleed.com — The Heartbleed Bug
- CVE Program — CVE-2014-0160
- CISA — “Heartbleed” OpenSSL Vulnerability