翻訳者の序文この記事を翻訳し始めて、著者が問題を理解したと思いました。
ただし、一部の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;
そのため、最初にSSLv3エントリのデータへのポインタを取得します。これは次のようになります。
typedef struct ssl3_record_st { int type; unsigned int length; unsigned int off; unsigned char *data; unsigned char *input; unsigned char *comp; unsigned long epoch; unsigned char seq_num[8]; } SSL3_RECORD;
レコードを記述する構造には、タイプ、長さ、およびデータが含まれます。
dtls1_process_heartbeatに戻る:
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; buffer = OPENSSL_malloc(1 + 2 + payload + padding); bp = buffer;
リクエスターが要求した量のメモリが割り当てられます。正確には、最大65535 + 1 + 2 + 16です。
bp変数は、このメモリにアクセスするために使用されるポインターです。 次に:
*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日
訂正
修正の最も重要な部分は次のとおりです。
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;
このコードは2つのことを行います。最初のチェックは、長さがゼロの「ハートビート」を停止します。
2番目のifは、実際のレコード長が十分に長いことを確認するチェックを行います。 行くぞ
レッスン
これから何を学ぶことができますか?
私はCファンです。 これは私の最初のプログラミング言語であり、プロの目的で使用するのに慣れた最初の言語でした。 しかし、今では、その限界がかつてないほど明確になっています。
Heartbleedと
GnuTLSバグの後
、次の3つのことを行う必要があると思います。
- OpenSSLなどの重要なセキュリティインフラストラクチャの要素のセキュリティ監査にお金を払います。
- これらのライブラリの単体テストおよび統合テストを多数作成します。
- より安全な言語で代替実装の作成を開始します。
Cで安全に記述するのがどれほど難しいかを考えると、他に選択肢はありません。