CVE-2012-2122 MySQL における認証迂回の脆弱性について

この脆弱性は2012年5月7日にリリースされた MySQL バージョン 5.1.63 と 5.5.24 において修正されました。認証時に指定するパスワードは何でもよく、認証要求を繰り返すと一定確率でログインが可能というかなり奇妙な脆弱性です。すべての環境において発生するわけではありませんが、攻撃成立時には深刻な影響を受けます。

該当するバグチケットは以下です。リリースバージョンも同様の修正でした。
MySQL Bugs: #64884: logins with incorrect password are allowed

Rapid7 により PoC や影響が確認された環境等が纏められています。
CVE-2012-2122: A Tragically Comedic Security Flaw in MySQL

アプリケーションに対するコード修正は1行のみ、発生する環境が限られている、非常に簡単な PoC と、興味深い点が多いので、何故この様な現象が起きているのかを解説したいと思います。

今回の脆弱性は以下の複合要因で発生しています。

  1. MySQL において memcmp の戻り値の int 型を my_bool 型(char)にキャストしている
  2. glibc の memcmp が -256 以下、または 256 以上の値を返す可能性がある

まずは1のキャストについてです。

認証に使用される関数内部で一致判定に memcmp 関数を使用しています。該当するコードは以下の箇所です。

== sql/password.c
my_bool
check_scramble(const char *scramble_arg, const char *message,
       const uint8 *hash_stage2)
{
  ...
  return memcmp(hash_stage2, hash_stage2_reassured, SHA1_HASH_SIZE);
}Code language: C++ (cpp)

入力の一致時のみ memcmp 関数により0が戻されるため一見問題無さそうですが、memcmp 関数の戻り値は int 型、check_scramble 関数の戻り値は my_bool 型(char)とそれぞれ異なり暗黙的にキャストが発生します。

以下はこのキャストに注目し値の変化を比較するテストコードです。

#include <stdio.h>
int main(int argc, char ** argv){
    int i;
    char c;
    for(i = -300; i < 300; i++){
        c = i;
        printf("% 4d: %d\n", i, c);
    }
    return 0;
}Code language: PHP (php)

このコードをコンパイルし生成したバイナリのディスアセンブル結果を抜粋します。int から char への代入で上位3バイトは無視され下位の1バイトのみ代入されています。

 // c = i;
 804839e:       8b 45 f4                mov    0xfffffff4(%ebp),%eax
 80483a1:       88 45 fb                mov    %al,0xfffffffb(%ebp)Code language: Intel x86 Assembly (x86asm)

更にこれを実行した結果です。出力が多いので重要な箇所のみ抜粋しています。256の周期でキャスト後の結果が0になってしまいます。

#int  char
-257: -1
-256: 0
-255: 1
...
  -1: -1
   0: 0
   1: 1
...
 255: -1
 256: 0
 257: 1Code language: plaintext (plaintext)

入力された値は一致していないにも関わらず、一致していると見なされてしまうのはこの動作に起因しています。


次は2の memcmp の戻り値についてです。

上記キャストの動作より、脆弱性の存在するバージョンであったとしても memcmp の戻り値が -255 から 255 の範囲に収まっている限り問題は発生しません。ですが、この memcmp の動作が gcc や glibc のバージョン、実行している CPU により異なる事が問題を複雑にしています。

背景として memcmpmemcpy などの頻繁に呼ばれ、大量のデータを扱う事が前提の関数は CPU の拡張機能を用いて高速化されています。この機能は比較的新しい glibc に導入されており、これらはコンパイル時ではなく実行時に CPU の拡張機能有無を判定して使用するルーチンを決定しています。

Fedora16 付属の glibc にて確認したところ、memcmp はアーキテクチャと CPU の拡張機能毎に複数の最適化ルーチンが存在していました。これを検証結果と合わせて記載したのが以下の表です。複数の拡張機能をサポートしている場合は、各アーキテクチャで下に記載してある物ほど優先されます。

memcmp 最適化ルーチンアーキテクチャ戻り値の範囲(-255〜255)
IA32x86収まる
SSSE3x86収まる
SSE4.2x86収まる
SSE2x86_64収まる
SSSE3x86_64収まる
SSE4.1x86_64外れる

これに加えて gcc のビルドイン実装が存在しますが、こちらについては影響を受けませんので詳細は割愛します。RHEL6 においては最適化オプション無し、または -O0 指定時のみ glibc 側の関数が使用されますが、Fedora16 においては最適化オプションの指定に関わらず glibc 側の関数が使われていました。この変化は glibc 側により高速な処理が実装されたため、gcc が最適化処理を見直したのではないかと推測されます。

まとめると今回の脆弱性は以下の条件が揃う場合に発生すると考えられます。

  • SSE4.1 の拡張機能を持った CPU を使用
  • 脆弱性の存在する MySQL バージョンを使用
  • 64bit(x86_64) バイナリを使用
  • memcmp の最適化が施された新しい glibc を使用
  • gcc のビルドイン実装が使用されていない

今回の脆弱性は PoC だけ見るとジョークかと思えるような物ですが、実際はコードの問題や実行環境などの複合要因により発生しています。日頃、多くの脆弱性情報に注目していると、あまりに単純な内容で明らかに偽情報であるような物も多数見つかります。しかし、今回の件のように単純でありながら影響の大きい脆弱性もあることから、一つ一つ検証していくことが必要となっています。

(2012年6月13日追記) MySQL のリリース日を間違って記載していたため訂正しました。

シェアする