
The Heartbleed bug `CVE-2014-0160` は、OpenSSLライブラリにおける深刻な実装上の欠陥であり、攻撃者が被害者サーバーのメモリからデータを窃取することを可能にします。窃取されたデータの内容は、サーバーのメモリに何が存在するかによって異なります。秘密鍵、TLSセッションキー、ユーザー名、パスワード、クレジットカードなどが含まれる可能性があります。この脆弱性は、SSL/TLSが接続を維持するために使用するHeartbeatプロトコルの実装に存在します。
Heartbleed バグ CVE-2014-0160 は、OpenSSL ライブラリにおける深刻な実装上の欠陥であり、攻撃者が被害者サーバーのメモリからデータを盗むことを可能にします。盗まれたデータの内容は、サーバーのメモリ内に存在するものに依存します。秘密鍵、TLS セッションキー、ユーザー名、パスワード、クレジットカードなどが含まれる可能性があります。この脆弱性は、SSL/TLS が接続を維持するために使用する Heartbeat プロトコルの実装に存在します。
影響を受ける OpenSSL のバージョン範囲は 1.0.1 から 1.0.1f です。Ubuntu VM 内のバージョンは 1.0.1 です。
Heartbleed 攻撃は Heartbeat リクエストに基づいています。このリクエストはサーバーにデータを送信するだけで、サーバーはそのデータを応答パケットにコピーするため、すべてのデータがエコーバックされます。通常の場合、リクエストに 3 バイトのデータ ”ABC” が含まれているとすると、length フィールドの値は 3 です。サーバーはデータをメモリに配置し、データの先頭から 3 バイトを応答パケットにコピーします。攻撃シナリオでは、リクエストに 3 バイトのデータが含まれていても、length フィールドが 1003 を示す場合があります。サーバーが応答パケットを構築するとき、データの先頭(つまり “ABC”)からコピーを開始しますが、3 バイトではなく 1003 バイトをコピーします。この余分な 1000 バイトは明らかにリクエストパケットから来たものではありません。サーバーのプライベートメモリから来ており、他のユーザーの情報、秘密鍵、パスワードなどを含む可能性があります。
次に、リクエストの length フィールドを変更してみましょう。まず、上の図から Heartbeat 応答パケットがどのように構築されるかを理解しましょう。Heartbeat リクエストパケットが到着すると、サーバーはパケットを解析してペイロードと Payload_length 値(上記で強調表示されています)を取得します。ここでは、ペイロードは 3 バイトの文字列 "ABC" だけで、Payload_length 値は正確に 3 です。サーバープログラムは、この length 値をリクエストパケットから盲目的に取得します。次に、"ABC" を格納しているメモリを指し、 バイトを応答ペイロードにコピーして応答パケットを構築します。このようにして、応答パケットには 3 バイトの文字列 が含まれます。
Payload_length"ABC"次に、下の図に示すように Heartbleed 攻撃を実行します。ペイロードは同じ(3 バイト)ままにし、Payload_length フィールドを 1003 に設定します。サーバーは応答パケットの構築時に、この Payload_length 値を再度盲目的に取得します。今回は、サーバープログラムが文字列 "ABC" を指し、メモリから 1003 バイトを応答パケットのペイロードとしてコピーします。文字列 ”ABC” に加えて、追加の 1000 バイトが応答パケットにコピーされます。これらはメモリ内の任意のもので、秘密の活動、ログ情報、パスワードなどが含まれる可能性があります。
攻撃コードでは Payload_length 値を変更できます。デフォルトでは、この値はかなり大きい値(0x4000)に設定されていますが、減らすことができます。
Heartbleed 脆弱性を修正する最も簡単な方法は、OpenSSL ライブラリを最新バージョンに更新することです。ただし、ここでの目的はソースコード経由で脆弱性にパッチを当てることです。
Heartbeat リクエスト/応答パケットの形式
struct {
HeartbeatMessageType type; // 1 byte: request or the response
uint16 payload_length; // 2 byte: the length of the payload
opaque payload[HeartbeatMessage.payload_length];
opaque padding[padding_length];
} HeartbeatMessage;
パケットの最初のフィールド(1 バイト)はタイプ情報で、2 番目のフィールド(2 バイト)はペイロード長です。その後に実際のペイロードとパディングが続きます。ペイロードのサイズはペイロード長フィールドの値と同じである必要がありますが、攻撃シナリオではペイロード長を異なる値に設定できます。次のコードスニペットは、サーバーがリクエストパケットから応答パケットへデータをコピーする方法を示しています。
Heartbeat リクエストパケットを処理し、応答パケットを生成する
/* Allocate memory for the response, size is 1 byte
* message type, plus 2 bytes payload length, plus
* payload, plus padding
*/
unsigned int payload;
unsigned int padding = 16; /* Use minimum padding */
// Read from type field first
hbtype = *p++; /* After this instruction, the pointer
* p will point to the payload_length field */
// Read from the payload_length field from the request packet
n2s(p, payload); /* Function n2s(p, payload) reads 16 bits
* from pointer p and store the value
* in the INT variable "payload". */
pl = p; // pl points to the beginning of the payload content
if (hbtype == TLS1_HB_REQUEST)
{
unsigned char *buffer, *bp;
int r;
/* Allocate memory for the response, size is 1 byte
* message type, plus 2 bytes payload length, plus
* payload, plus padding
*/
buffer = OPENSSL_malloc(1 + 2 + payload + padding);
bp = buffer;
// Enter response type, length and copy payload *bp++ = TLS1_HB_RESPONSE;
s2n(payload, bp);
// copy payload
memcpy(bp, pl, payload); /* pl is the pointer which
* points to the beginning
* of the payload content */
bp += payload;
// Random padding
RAND_pseudo_bytes(bp, padding);
// this function will copy the 3+payload+padding bytes
// from the buffer and put them into the heartbeat response
// packet to send back to the request client side.
OPENSSL_free(buffer);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
}
脆弱性はここにあります
// copy payload
memcpy(bp, pl, payload);
pl が有効かどうかをチェックする処理がありません。そのため、メモリ侵害が発生する可能性があります。
パッチ:
memcpy() の実行前に境界チェックを行うVM にはパッチが適用されていますが、このリポジトリには示されていません。
ご興味をお持ちいただきありがとうございます。このプロジェクトは楽しく、学びの多いものでした!