penSSL に存在する重大な復号化の脆弱性

OpenSSL は、広く認知・利用されている暗号ライブラリですが、今週初めにセキュリティパッチをリリースしました。

新バージョンの OpenSSL 1.1.1l は、リーン体、モダン体、サンセリフ体の書体を使用しているユーザーにとっては迷惑な話で、それぞれの書体の大文字の「I (アイ)」、小文字の「l (エル)」、数字の「 1 (一)」が全て同じに見えて、判別が難しくなっています。

Tまた、OpneSSL 1.1.1l を発音の通りに綴ると、「OpenSSL version ONE dot ONE dot ONE LIMA」となります。

(当記事の執筆時点で、Naked Security では Flama の書体を用いています。これは、20世紀初頭にドイツの鉄道や道路標識から生まれた字体である DIN1451 が派生したバウハウス風のフォントファミリーです。小文字の「l (エル)」は、読みやすくするために下部が右にカールしており、数字の「1 (一)」は古典的なヨーロッパスタイルで、下部に横線があり、上部が少し左にはねています。しかし、すべての書体が上記のように作られているわけではありません)。

バグについて

OpenSSL はその名が示すように、以前は SSL (Secure Sockets Layer) として知られたTLS プロトコル (Transport Layer Security)を使用するネットワークソフトウェアで、転送中のデータ保護のために主に使用されています。

SSL には多くの暗号上の脆弱性が存在しており、現在では TSL は SSL に取って代わりましたが、OpenSSL 、LibreSSL 、BoringSSL など、TLS を採用している人気のオープンソースライブラリ (ソースコードが無償で公開され、利用や改良が誰に対しても許可されているライブラリ) の多くは、旧称に慣れているユーザーのために旧式の製品名 (SSL) をいまだに使っています。

OpenSSL は、その主要な機能である TLS に対応しているにもかかわらず、TLS 自体が依存している、より下位レベルの関数にもアクセスすることができます。したがって、OpenSSL の「 libcrypto (暗号化のためのライブラリ)」部分を使用して、スタンドアロン (ネットワーク等への接続なしでの単独動作) での暗号化、ファイルのハッシュ値 (元になるデータから一定の計算手順により求められた固定長の値) 計算、デジタル署名の確認、さらには数千桁の長さの数字を使った演算を行うことができます。

この新バージョンでは 2 つのバグが修正されています。

  • CVE-2021-3711: SM2 復号化時のバッファオーバーフロー。
  • CVE-2021-3712: ASN.1 文字列の処理時における読み取りバッファオーバーラン。

長い文字列と短い文字列

上記 2 つ目のバグである CVE-2021-3712 は、2 つのバグの中では危険度が低く、皮肉にも、エンコードされた暗号鍵や証明書を OpenSSL が処理する方法に関連しています。

TLS の鍵と証明書のファイルに含まれる生データは、DER (Distinguished Encoding Rules の略) と呼ばれる形式でパッケージ化されています。これは ASN.1(Abstract Syntax Notation version 1 の略) の形態の 1 つで、バイナリデータ (0 と 1 のみで表される二進数のデータ) を構造化して表現する方法です。

(TLS の鍵や証明書を見たことがあれば、次のような文字列を見たことがあると思います。

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+LXZfjSOTE0cigDmC3Vlbm0VABgl
Zkmp1zbZsiN9ILxqSQy5Krrza94c/eVZORK03gteh9txboKKQOh6LyAftg==
-----END PUBLIC KEY-----

これは単に、生の DER データを Base64 でエンコードしたもので、純粋なバイナリファイルよりも、ファイルを認識しやすくしたり、転送中に破損する可能性を低くするために使用されています)。

難しい用語が並んでいますが、重要なのは専門用語ではなく、ASN.1 の文字列が Pascal のようなプログラミング言語と同じように格納されているという事実です。つまり、length (長さ) フィールドの後に、まさにそれだけの量のデータが続く構造になっているということです。

しかし、C 言語では、文字列は length フィールドなしで保存され、ゼロ (NUL) バイトで終わる生のテキストデータだけが得られます。

このため、C 言語の文字列は非常にシンプルに使用できますが、3 つ問題点があります。

第 1 に、文字列全体を横断して NUL バイトがどこにあるのかを確認しないと、文字列の長さが絶対にわからないということです。第 2 に、文字列の途中に NUL バイトを置きたくても置けないことです。第 3 に、最後の NUL バイトが抜けてしまうと、文字列のコピーや出力が延々と続いてしまい、意図したよりもはるかに多くの情報が含まれてしまう可能性があることです (「終端文字のない文字列」の後に、ゼロ以外のデータの大きなブロックが続いていると仮定した場合)。

このように、ASN.1 は構造的で制御的、C 言語はシンプルでスピーディー、といった特徴を持ちます。

両方の長所を活かすために、OpenSSL は ASN.1 文字列に必要ではないにもかかわらず、常に NUL バイトを付加しています。

つまり、 OpenSSL の特別な ASN.1 関数を使って通常通り文字列にアクセスできるだけでなく、例えば、関連する文字列を含むメッセージを出力したい場合などに、C 言語から直接文字列を安全に読み出すことができるのです。

しかし、ここが問題点でもあるのですが、完全には正しくありません。

OpenSSL のASN.1 文字列は、C 言語の文字列として安全に扱うことができますが、OpenSSL の特別な「常に NUL バイトを追加する」関数によって文字列が生成された場合に限られます。そうでなければ「終端文字のない文字列」になってしまい、あらゆる問題が発生する可能性があるからです。

自ら ASN.1 データを構築する場合、悪意があってもなくても、面倒な NUL バイトを含める必要はなく、結果として得られる文字列は C 言語から直接使用しても安全ではなく、常に OpenSSL の特別なアクセス関数を使用する必要があります。

残念なことに、NUL バイトを最後に付加して元データが作成されているかどうか確信が持てない場合でも、OpenSSL 自身の関数のいくつかは、ショートカットを使っており、C 言語から ASN.1 文字列に直接アクセスすることに依存していることが判明しました。

そのため、巧妙な手段により、攻撃者が OpenSSL を騙し、メモリバッファの終端を超えたデータを出力させることが可能になる場合があります。

このような読み取りバッファオーバーフローが発生すると、ログファイルやシステムメッセージなどの出力に個人情報が誤って含まれてしまうケースなど、データ漏洩につながる可能性があります。

また、読み取りオーバーフローはメモリアクセスエラーにつながる可能性もあり、OpenSSL が NUL を超えて余分なデータを読み込んでしまい、アクセス違反が発生してソフトウェアがクラッシュする場合もあります。

読み取りバッファオーバーフローによるデータ漏洩は、悪名高い Heartbleed バグでまさに起こった問題です。数バイトのデータの応答をメモリから送信するはずのコードが、不注意にも毎回 64 キロバイトものデータをコピーしてしまったものでした。

これにより、その時点でメモリ内に隣接していたデータが全て漏洩し、そのメモリの近くに保存されていたパスワードや復号鍵も含まれていた場合がありました。

過剰な復号化

この 2 つのバグの CVE-2021-3711 もバッファオーバーフローの問題ですが影響はより深刻です。これは復号化時のオーバーフローであり、より危険です。

割り当てられたメモリブロックの終端を越えて書き込み、プログラムの他の部分を制御するデータを変更できれば、今後、プログラムの動作を操作することが可能となる恐れがあります。

たとえば、何かの指示が成功していないのに成功した (あるいは失敗していないのに失敗した) と思わせたり、プログラム実行フロー全体を乗っ取ることも可能かもしれません。

細工したデータを使用して実行中のプログラムを乗っ取る操作は、RCE として知られています。これは Remote Code Execution の略で、文字通り「リモートから任意のコードを実行」できることを意味します。つまり、地球の反対側にいる場合でも、任意のユーザーが、ポップアップダイアログや警告を表示することなく、あなたのコンピュータを制御することができるのです。

CVE-2021-3711 のバグは、出力データを生成するソフトウェアコードで一般的に使われるプログラミングイディオムに依存しています。

そのイディオムとは、同じデータの出力関数を 2 回連続して使用するというものです。まず関数を実行しますが、「実際にデータを生成するのではなく、実際にデータを生成するときにどれくらいの容量になるかを教えてください」と指示し、その後、適切なサイズのバッファを用意してから、再度関数を実行して実際のデータを生成します。

理論上は、このように事前に十分なメモリ容量を確保することで、バッファオーバーフローを確実に回避できます。

ただし、OpenSSL のある特定のケースで、中国政府の暗号アルゴリズムである ShangMi (SM) を使用する場合には、ソフトウェアがバッファサイズを 62 バイトまで小さくするように指示することがあります。

これは、復号化のために送られたブービートラップ付きの暗号化データが、OpenSSL 内部で、重大なバッファオーバーフローを引き起こす可能性があることを意味します。

特に、ShangMi の暗号スイートを使用して TLS 接続を確立し、TLS ハンドシェイク中に相手側から提示された Web 証明書を確認するように OpenSSL が要求された場合、このバグのあるコードが呼び出されている可能性があります。

今のところ、この脆弱性を利用した攻撃方法は確認されていません。

リスクは軽微

幸い、ShangMi の公式 TLS サポートは、2021年3月付けの RFC 8998 で紹介されたばかりであり、暗号スイートの環境ではまだ新しい存在であるということです。

そのため、OpenSSL には SM アルゴリズム (鍵合意とデジタル署名の SM2、ハッシュの SM3、ブロック暗号の SM4) の実装が含まれているものの、

TLS 接続で使用する暗号スイートとして、これらのアルゴリズムを選択するために必要なコードはまだ含まれていません。

現状では、TLS のクライアントコードに他人のサーバーへの ShangMi 接続を要求するよう指示したり、TLS のサーバーコードに他人のクライアントからの ShangMi 接続を受け入れさせることはできません。

OpenSSLの libcrypto コードの低い階層にバグが存在しても、OpenSSLを TLS のレベルで使用して安全な接続を確立したり承認したりする場合は、そのバグがあるコードが起動するようなセッションを開くことはできないと考えられます。

これにより、サイバー犯罪者が、トラップを仕掛けた Web サイトにユーザーを誘い込み、接続設定時に不正な証明書を提示するなどの方法でこの脆弱性を悪用して、ノートパソコンにマルウェアを埋め込む可能性は大幅に減少すると考えられます。

対策

  • 可能であれば、 OpenSSL 1.1.1l にアップグレードしてください。 Windows、Mac、iOS、Android はそれぞれのプラットフォームで独自の TLS を実装しているため、ほとんどのソフトウェアが OpenSSL を使用していませんが、一部のソフトウェアには独自の OpenSSL のビルドが含まれている可能性があり、個別にアップデートする必要があります。疑わしい場合は、製造元に確認してください。 Linux ディストリビューションのほとんどは、システム全体で OpenSSL を使用しているため、アップデートについてディストリビューションに確認してください。 (注: Firefox はどのプラットフォームでもOpenSSL を使用していません)。
  • アップグレードできない場合は、ShangMi に対応していない OpenSSL の再構築を検討してください。再構築の前に、オプション nosm2 nosm3 nosm4 を OpenSSLの config スクリプトに移すとうまくいくでしょう。TLS 接続の使用で ShangiMi がまだ選択できないことを考えると、この変更は無害であることがわかると思います。
  • プログラマーの方々は、常に最悪の事態を想定してデータを取り扱ってください。 解読を求められたデータが、想定されているデータ構造と一致していると想定しないでください。本来の構造ではない (そして自信のテストではカバーされない) トラップが仕込まれたデータパケットを作ることは、多くのサイバーセキュリティ研究者が生業としていることです。残念ながら、彼らのすべてが「善人」のために働いているわけではないのです。