OpenSSLでのハートブリードエラーの診断。 (治療はすでに本格的ですが、最終的な診断はまだ行われていません)

翻訳者の序文
この記事を翻訳し始めて、著者が問題を理解したと思いました。

ただし、一部のHabrユーザーが( VBartのおかげで)正しく示しているように、すべてがそれほど単純ではないため、malloc、mmap、sbrkについての著者の言及は、彼をさらに混乱させました。

この点で、この記事は技術的なものよりも歴史的な関心を集めています。

更新著者は、この翻訳に関するコメントの議論と同じ方法で投稿を更新しました。





GnuTLSエラーについて書いたとき、これはこれから見るTLSスタックの最後の重いエラーではないと言った。 しかし、私はすべてがとても嘆かわしいとは思っていませんでした。



Heartbleedのバグは、特に厄介なバグです。 攻撃者は最大64 KBのメモリを読み取ることができ、セキュリティ研究者は次のように述べています。



機密情報や資格情報を使用せずに、X.509証明書、ユーザー名とパスワード、インスタントメッセージ、電子メール、重要なビジネス文書と通信に使用される秘密鍵を盗むことができました。






修正ssl / d1_both.cから始まります。



int dtls1_process_heartbeat(SSL *s) { unsigned char *p = &s->s3->rrec.data[0], *pl; unsigned short hbtype; unsigned int payload; unsigned int padding = 16; /* Use minimum padding */
      
      







そのため、最初にSSLv3エントリのデータへのポインタを取得します。これは次のようになります。

 typedef struct ssl3_record_st { int type; /* type of record */ unsigned int length; /* How many bytes available */ unsigned int off; /* read/write offset into 'buf' */ unsigned char *data; /* pointer to the record data */ unsigned char *input; /* where the decode bytes are */ unsigned char *comp; /* only used with decompression - malloc()ed */ unsigned long epoch; /* epoch number, needed by DTLS1 */ unsigned char seq_num[8]; /* sequence number, needed by DTLS1 */ } SSL3_RECORD;
      
      







レコードを記述する構造には、タイプ、長さ、およびデータが含まれます。 dtls1_process_heartbeatに戻る:



 /* Read type and payload length first */ hbtype = *p++; n2s(p, payload); pl = p;
      
      





翻訳者注:コードn2s(c、s);
 #define n2s(c,s) ((s=(((unsigned int)(c[0]))<< 8)| \ (((unsigned int)(c[1])) )),c+=2)
      
      









SSLv3エントリの最初のバイトは、ハートビートタイプです。 n2sマクロはpから2バイトを取得し、それらをペイロードに入れます。 これは実際には有用なデータの長さです。 SSLv3レコードの実際の長さはチェックされないことに注意してください。

次に、 pl変数は、リクエスターから提供された「ハートビート」データを受け取ります。

関数では次のことが起こります。



 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 */ /*    ,   * 1    ,  2  -    , *   ,   */ buffer = OPENSSL_malloc(1 + 2 + payload + padding); bp = buffer;
      
      







リクエスターが要求した量のメモリが割り当てられます。正確には、最大65535 + 1 + 2 + 16です。

bp変数は、このメモリにアクセスするために使用されるポインターです。 次に:



 /* Enter response type, length and copy payload */ *bp++ = TLS1_HB_RESPONSE; s2n(payload, bp); memcpy(bp, pl, payload);
      
      





memcpyに関する翻訳者のメモ
TITLE


memcpy-メモリ領域のコピー

構文


 #include <string.h> void *memcpy(void *dest, const void *src, size_t n);
      
      





記述


memcpy()関数は、srcメモリ領域からdestメモリ領域にnバイトをコピーします。 メモリの領域は重複できません。 メモリ領域が重複する場合は、memmove(3)を使用します。

戻り値


Memcpy()は、destへのポインタを返します。

標準への準拠


SVID 3、BSD 4.3、ISO 9899




s2nマクロは、 n2sマクロの逆を行います。16ビット値を取り、2バイトに格納します。 次に、要求された同じペイロード長を設定します。

翻訳者注:コードs2n(c、s);
 #define s2n(s,c) ((c[0]=(unsigned char)(((s)>> 8)&0xff), \ c[1]=(unsigned char)(((s) )&0xff)),c+=2)
      
      









次に、ユーザーが提供したデータplの ペイロードバイトが、新しく割り当てられたbp配列にコピーされます。 その後、これらすべてがユーザーに送り返されます。

それで、間違いはどこにありますか?



ユーザーはペイロードとplを制御します





リクエスタが実際にペイロードバイトを送信しなかった場合はどうでしょうか。

plに実際に1バイトしか含まれていない場合

その後、memcpyはSSLv3レコードからそれほど遠くないものをすべてメモリから読み取ります。



どうやら近くにはたくさんの異なるものがあります。



mallocを使用して(少なくともLinuxで)メモリを動的に割り当てるには、 sbrk(2)mmap(2)を使用する2つの方法があります。 sbrk割り当てられている場合、古いヒープ成長ルールが使用されます。これにより、使用できるものが制限されますが、いくつかのクエリ(特に同時クエリ)を使用すると、いくつかの興味深いことが見つかります。 [このセクションには、ヒープがsbrkを介してどのように機能するかという性質から、PoCに対する私の懐疑論が最初に含まれていました。 ただし、多くの読者は、 mmapを代わりにmallocで使用でき、すべてが変わることを思い出しました。 ありがとう!]



著者からの更新-元の記事のこの部分は削除されました
ただし、 mmapが使用されている場合、「賭け!」 mmapの場合、未使用のメモリを割り当てることができます。 これは、Heartbleedに対するほとんどの攻撃の目標です。



そして最も重要なことは、要求されたブロックが大きいほど、 sbrkではなくmmapによって処理される可能性が高くなることです。



mmapを使用してmallocを実装しないオペレーティングシステムは、ほとんどの場合、脆弱性がわずかに低くなります。





bpの場所は、実際にはまったく関係ありません。 ただし、場所plは最も重要です。 malloc()のmmapしきい値により、sbrk()を使用してほぼ確実にメモリが割り当てられます。 ただし、興味深い資料(たとえば、ドキュメントやユーザー情報)のメモリはmmap()によって割り当てられる可能性が非常に高く、 plからアクセスできます。 同時に複数のクエリを実行すると、いくつかの興味深いデータが利用可能になります。



これはどういう意味ですか? まあ、 plのメモリ割り当てモデルは私たちに何を読むことができるかを指示します。 脆弱性の発見者の1人がこれについて言ったことは次のとおりです。



ヒープメモリ割り当てモデルにより、秘密キーの侵害が#ハートブリード#ドンパニックになる可能性は低くなります。



-ニール・メフタ(@ neelmehta)2014年4月8日





訂正



修正の最も重要な部分は次のとおりです。



 /* Read type and payload length first */ if (1 + 2 + 16 > s->s3->rrec.length) return 0; /* silently discard */ hbtype = *p++; n2s(p, payload); if (1 + 2 + payload + 16 > s->s3->rrec.length) return 0; /* silently discard per RFC 6520 sec. 4 */ pl = p;
      
      







このコードは2つのことを行います。最初のチェックは、長さがゼロの「ハートビート」を停止します。

2番目のifは、実際のレコード長が十分に長いことを確認するチェックを行います。 行くぞ



レッスン



これから何を学ぶことができますか?



私はCファンです。 これは私の最初のプログラミング言語であり、プロの目的で使用するのに慣れた最初の言語でした。 しかし、今では、その限界がかつてないほど明確になっています。



HeartbleedとGnuTLSバグの次の3つのことを行う必要があると思います。







Cで安全に記述するのがどれほど難しいかを考えると、他に選択肢はありません。



All Articles