FlawOpen

flawopen.com/Teardowns/cve-2014-0160-heartbleed

● CVE-2014-0160 · CVSS 7.5 · 高
セキュリティ研究 · FlawOpen

CVE-2014-0160:OpenSSL Heartbleedのメモリ漏えい

CVE-2014-0160 では、OpenSSL がレコード内に存在しないペイロード長を信頼すると、TLS または DTLS の相手が Heartbeat レコードの末尾を越えて最大 64 KiB を読み取れました。上流修正はペイロードを解析して返す前にレコードを検証します。[1], [2]

💡 わかりやすい解説 (ELI5)

数語しか書かれていないカードを受け取った係員が、もっと長く複写するよう求めるラベルを信じたとします。カードの端で止めず、共用トレイに続く文字まで複写してしまいます。Heartbleed では OpenSSL が受信レコードを越えて読み取りました。露出するバイト列はプロセスのメモリ内容によって変わります。[1], [3]

主要な概念と専門用語

TLS/DTLS Heartbeat(RFC 6520)
TLS または DTLS の接続維持メッセージです。要求には相手が返すペイロードと、プロトコルで必要なパディングが含まれます。[3]
payload_length
Heartbeat ペイロードの大きさを示す 16 ビットフィールドです。メッセージ型、長さフィールド、最小パディングの後に受信レコード内へ収まる必要があります。[1], [3]
memcpy()
ソースポインターから指定バイト数をコピーします。ソースバッファーの有効長は把握しないため、呼び出し側が先に検証します。[1], [2]
バッファー範囲外読み取り(CWE-126)
想定したソースバッファーの末尾より後を読むことです。Heartbleed ではメモリ内容によって最大 64 KiB のプロセスメモリが応答に含まれる可能性がありました。[5]

フレームワーク対応

根本原因の分析 (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]

ステップ・バイ・ステップの攻撃フロー

ステップ 1

相手が短い Heartbeat レコードを送る

TLS または DTLS の相手が、レコード内に実際にあるペイロードより大きな長さを宣言した Heartbeat メッセージを送ります。[1], [3]

ステップ 2

OpenSSL が宣言値を信頼する

修正前のハンドラーは、レコードの最小長や受信データとの整合性を確認せずに型と長さを読み取ります。[1], [2]

ステップ 3

応答がレコード末尾を越えてコピーする

memcpy() はレコードバッファーから宣言されたバイト数をコピーします。応答には受信した Heartbeat メッセージの後ろのデータが含まれる場合があり、上限はプロトコルの 64 KiB です。[1], [2]

ステップ 4

露出する内容はプロセス状態による

相手にプロセスメモリのバイトが返る可能性があります。特定の秘密情報が含まれる保証はなく、結果はメモリの内容とエンドポイントの状態によります。[1], [4]

ステップ 5

修正済みハンドラーは不整合を拒否する

修正では解析前にヘッダーとパディングを検査し、ペイロードが受信レコードに収まることを確認してから、コピー前に応答長を制限します。[2], [1]

ソースコード比較:脆弱 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);

インシデントのタイムライン

2012-03-14

OpenSSL 1.0.1 が公開され、TLS/DTLS Heartbeat 処理にはレコード長の検査がありませんでした。[3], [1]

2014-04-07

OpenSSL は CVE-2014-0160 のアドバイザリを公開して 1.0.1g をリリースし、Google Security の Neel Mehta に謝意を示しました。1.0.2 ベータ向けの修正は beta2 に含まれました。[1], [2]

2014-04

運用者は影響を受けるエンドポイントにパッチを適用し、露出の可能性を評価しました。Heartbleed.com によると、通常のログから応答が返したメモリのバイト列は特定できません。修正後に鍵、証明書、セッション、認証情報の交換を検討します。[6], [1]

エンジニアリング&システム堅牢化チェックリスト

参考資料