初めまして。LINEの夏季インターンシップにセキュリティエンジニアとして参加した小池悠生です。1ヶ月間、アプリケーションセキュリティチームにて、LINEが提供するプロダクトのリスクアセスメントや、オープンソースプロジェクト(https://github.com/line/)のバグハントなどに取り組んでいました。
インターンが始まる前、インターン中の業務として与えられるのは主にリスクアセスメントのみだと思っていたのですが、蓋を開けてみれば自分のしたい事をほとんど自由にさせていただける非常に柔軟なインターンでした。その成果の1つとして、私がLINEクライアント内で見つけた脆弱性についてお話ししたいと思います。
以下、少し技術的な話に終始してしまいますが、この記事を通して皆さんにお伝えしたいことは、
皆さん、必ずLINEクライアントを最新バージョンにアップデートしましょう!!!
ということです
きっかけ
いくつかのプロダクトのリスクアセスメントを終えて、そろそろ別のことをやりたいと思った私は、LINEが行っているオープンソースプロジェクト(https://github.com/line/)のバグハントをさせていただくことにしました。プロダクトの開発言語はどうしてもJavaやJavaScriptを用いたものが多く、私はこれらの言語があまり好きではないので、他の言語で書かれたプログラムはないものかと探しました。
結果として私が興味を持ったのはApngDrawableというライブラリでした。APNGとはAnimated Portable Network Graphicsの略で、つまるところ、画像ファイルフォーマットであるPNGを拡張し、アニメーションを再生できるようにしたファイルフォーマットです。このライブラリはAndroid上でAPNGを扱えるようにデコードや描画機能を実装したもので、実際にAndroidのLINEクライアント内部などでも使用されています。
そして素晴らしいことに、このライブラリのコア部分はJavaではなく、C++によって記述されています! 私はC/C++で書かれたコードの脆弱性が大好きなので、これを読まない選択肢ありませんでした。
結果
ApngDrawableは、libpngと呼ばれる別の有名なオープンソースライブラリを利用し、その上に必要な処理を加える形で実装されているため、比較的コード量は小さくシンプルに抑えられています。そのため、脆弱性が色々ないかくまなく探してみたのですが、結果として脆弱性は1つしか見つかりませんでした(脆弱性があまり見つからないというのは、探している私からすると残念なことなのですが、もちろんこれは良いことです)。
さて、具体的にどのような脆弱性なのか説明しましょう。ApngDrawableはオープンソースプロジェクトプロジェクトなので、実際にコードを参照して説明しても問題ないはずですね。というわけで実際に問題があった箇所を以下に掲載してみます。
ApngDecoder.cppの192行目を見てください。
auto width = static_cast<uint32_t>(png_get_image_width(png_ptr, info_ptr));
auto height = static_cast<uint32_t>(png_get_image_height(png_ptr, info_ptr));
uint channels = png_get_channels(png_ptr, info_ptr);
size_t row_bytes = png_get_rowbytes(png_ptr, info_ptr);
(中略)
// Allocate buffers
LOGV(" | allocate buffers");
size_t size = height * row_bytes;
std::unique_ptr<uint8_t[]> p_frame(new uint8_t[size]());
std::unique_ptr<uint8_t[]> p_buffer(new uint8_t[size]());
この部分は、画像ファイルの縦・横の長さを取得し、画素データを格納するために(縦の長さ)×(横の長さ)分のメモリを確保する処理を行っています。heightおよびrow_bytesがそれらの値を表す変数ですね。 size_t size = height * row_bytes;が、2数を掛けた値をsizeという変数に代入する処理になります。
この部分の何が問題なのでしょうか? 実は、この処理は典型的な整数値オーバーフローと呼ばれる脆弱性を含んでいます。C/C++では、変数には記憶できる値の範囲に制限があります。例えば、32bitの変数では 0から232−1(=4294967295)、64bitの変数では0から264−1(=18446744073709551615)といった具合です。プログラミング言語における変数とは、大雑把に言えばコンピュータのメモリですから、32bit(4byte)や64bit(8byte)など、メモリを確保した分しか値を記憶することはできません。
「画素データ用に確保するメモリのサイズ」を表す変数sizeはsize_t型と呼ばれる変数の型で宣言されています。プログラミングやC/C++に詳しくない人でも分かるように端的に言ってしまうと、sizeはコンピュータが32bit環境であれば32bit変数として、64bit環境であれば64bit変数として機能するということです。
さて、32bit環境でこのプログラムが動いていることを想定します。例えば、heightおよびrow_bytesに216=65536という値が入っていたらどうなるでしょうか? これら2数を掛けた結果は、216×216=232となって、32bit変数が格納できる値の範囲を超えてしまっています。こういった場合、C/C++は 範囲を超えてしまった値を232で割った余りを代入します。すなわち、この例ではsizeには0が代入されてしまうのですね。
結果として、プログラムは「メモリを0バイト確保すれば(つまり何も確保しない)、画素データを全て格納することができる!」と勘違いし、メモリを一切確保しないまま処理を続けてしまいます。その後、メモリを画素データ用に確保していないにも関わらず[1]、メモリに画素データを書き込もうとすることによって、クラッシュや任意のコード実行(つまり攻撃者による乗っ取り)などが起きてしまうことがあります。
このように、C/C++では信頼できない入力によって行われる足し算や掛け算の結果が、正常な範囲を超える(オーバーフローする)ことがないか常に検証する必要があります。非常に面倒かつ危険な仕様ですね。
影響
さて、どのような脆弱性が存在したかについてはお話ししましたが、この脆弱性が及ぼす影響はどの程度のものなのでしょうか?
私は初め、以下のように考え、想定される被害は軽微であると思っていました:
- LINEクライアント内でAPNG画像を読み込む処理が必要となるのは、おそらく動くLINEスタンプや広告の動画の再生など、LINE側が提供した正常なAPNG画像を使う部分だけであって、悪意のある攻撃者の送ったAPNG画像を再生してしまうような機能は存在しないだろう。
- また前述の通り、この整数値オーバーフローは32bit環境でしか発生しない。今時のパソコンや携帯は大抵64bit環境であることを考えると、影響を受ける人はあまりいないだろう。
これらは全くの間違いでした。1点目は後の検証によって、LINEクライアントには「ユーザが送信したAPNGを再生する」という"便利な"機能が存在することが分かり、間違いであることが分かりました。2点目に関しては、少なくともApngDrawableの脆弱性については、確かに32bit環境でしか発生しえません。しかし問題は、「ApngDrawableと同様の処理を、様々なLINEのプロダクト内部で別々に実装していた」ということです。ApngDrawableはAndroid向けのライブラリであるため、Android以外の環境でAPNGを扱う機能を実装する際は、ApngDrawableをそのまま組み込むというわけにはいきません。したがっていくつかのプロダクトでは同様の処理を再実装する形でAPNGを扱えるようにしていました。また、そもそもApngDrawableが誕生する以前から同様の処理を実装しているプロダクトも存在していました。LINEクライアントもその例外ではありません。そして、ApngDrawableとは全く別に実装されているにも関わらず、LINEクライアントには同一の脆弱性が存在しているのでした…
更にまずいことに、LINEクライアント側の脆弱性は、64bit環境でも攻撃することが可能でした。したがって、「32bit環境でしか影響がないなら、私には関係ない」というわけにはいきません。これを読んでいる皆さんには、必ずアプリのアップデートをするようお願いします。
こういった経緯から、ApngDrawableおよびLINEクライアントに独立にCVE[2]番号が発行されました。それぞれCVE-2019-6007およびCVE-2019-6010です。
実は自分で脆弱性を報告し、CVE番号を発行してもらうのはこれが初めてだったので、少し嬉しく思っています。あまり脆弱性があったことを喜ぶべきではない気がしますが…
また、前述の通り報告した脆弱性は複数のプロダクトで存在していたため、それら全ての修正のPRレビューを行うという体験もでき、とても新鮮でした。
まとめ
この記事ではインターン中に私が見つけた脆弱性について紹介し、記事を読まれている皆さんにアップデートを促しました。インターン中は、私が希望してこのバグハントのような”インターンらしい”こともさせていただけましたし、もちろんリスクアセスメントなど、より通常業務に近い内容にも関わらさせていただきました。私が脆弱性を見つけられたのは、こういった柔軟なインターン環境のお陰だと思っています。また、社員の方々もとても親切にしてくださり、非常に良い環境でした。興味がある方は次回のインターンシップに是非応募してみてください。
注釈
- 厳密な話をすると、一応数バイトはメモリを確保します。いずれにせよ画素データを格納するには不十分であり、本来他のものを記憶するために使っている領域を上書きしてしまいます。
- 共通脆弱性識別子。特定の製品の特定の脆弱性を呼称・識別できるように採番されている識別子のこと。