WebAssembly プログラムのコントロールフローのハイジャック
WebAssembly はブラウザにとって大きな攻撃対象となることが既に証明されていますが、より多くの Web アプリケーションコードが JavaScript から WebAssembly に移行するにつれ、WebAssembly プログラム自体を調査および保護する必要性が生じてきます。WebAssembly は、C や C++ といった開発言語から継承されることがある攻撃との共通部分を排除するよう設計されていますが、それでも攻撃の可能性が完全に排除されるわけではありません。
このチュートリアルでは、WebAssembly が提供するコントロールフロー保護の保証と既知の脆弱性に加えて、コントロールフローのハイジャックに関連するリスクを軽減するために WebAssembly で Clang の Control Flow Integrity (CFI) を使う方法を紹介します。チュートリアル内では、(意図的に) 型の取り違えの脆弱性を悪用してサンプルの WebAssembly プログラムのコントロールフローをハイジャックします。一部のコー ドは Trail of Bits Blog の「Let’s talk about CFI (CFI の話をしよう)」 で紹介されているものを使用しています (この Trail of Bits Blog のシリーズは CFI にあまり馴染みがない方の入門編として最適です)。
本ブログは、WebAssembly のセキュリティを取り上げる2つのブログ記事の第1部です。第2部では、WebAssembly を埋め込む対象、つまり WebAssembly のゲストプログラムを実行する環境を提供するソフトウェアであるブラウザに関する興味深いセキュリティトピックを取り上げます。
本ブログには、WebAssembly の動作に関する詳しい説明は記載されていません。WebAssembly に馴染みのない方は、webassembly.org や公式デベロッパーズガイドのページを確認するか、Web で利用できる多くの初心者向けブログ投稿やチュートリアル、および動画などを検索してみてください。
WebAssembly を身近な Web アプリでも
WebAssembly は、安全で効率的なアセンブリ言語を Web に導入するための業界全体のオープンな取り組みです。WebAssembly テクノロジーは Mozilla、Google、Microsoft、Apple といった大手ブラウザベンダーと Fastly のような Web ブラウザを持たない Web テクノロジー企業によって共同開発されています。WebAssembly モジュールは現在、ほとんどのブラウザでダウンロードして実行できます。AutoCAD や QT などの大規模な開発では、デスクトップ、モバイル、ブラウザプラットフォーム全体に統一された C/C++ コードベースで構築したスピーディーで安全なアプリをデプロイするために、WebAssembly が活用される機会が増えています。WebAssembly のエコシステムは急速に成長し、新しいツールやアプリケーション、さまざまなアイディアが定期的に発表されています (いくつかの例をこちらで参照できます)。
WebAssembly は Google Native Client の論理的後継者です。Google Native Client は開発者がネイティブアプリケーションを Google Chrome に展開するための高性能なソフトウェア障害分離テクノロジーです。WebAssembly は Native Client での多くの教訓を取り入れた洗練された設計となっており、シングルページで収まる安定したシステム、コントロールフローの整合性、制限やローカルの非決定性、メモリ安全性保証などが特徴です。WebAssembly の設計に関する詳細は、2017 PLDI paper や webassembly.org を参照してください。
セキュリティ制御など、従来は JavaScript で実装されていたロジックの多くが今後数か月から数年のうちに WebAssembly で実装される可能性があります。また、このテクノロジーに注目が集まるにつれ、Web 開発者が WebAssembly で新しい可能性を切り開くことも期待できます。
WebAssembly の設計は、C、C++、およびその他のソース言語に固有の問題を防止するためにメモリ安全性をサポートします。次のセクションでは、この WebAssembly の特徴について取り上げます。
WebAssembly プログラムのメモリ安全性
WebAssembly のメモリ安全性に関するセキュリティ資料では、WebAssembly でメモリ安全性のバグの多くのクラス、およびスタック破壊や ROP などの不正技術が排除されている理由が説明されています。つまり、WebAssembly のコードをコンパイルして実行するプログラムが正しければ、これらの攻撃は不可能になります。これは非常に優れた特長で、非常に細かい点まで考慮された WebAssembly アーキテクチャの成果です。
しかし 、この資料には、次のような記述もあります。
「しかし、WebAssembly のセマンティクスでは、他のクラスのバグは取り除かれていません。攻撃者が直接的なコードインジェクション攻撃を実行することはできないものの、間接的な呼び出しに対するコード再利用攻撃を使ってモジュールのコントロールフローをハイジャックすることは可能です。」
以降のセクションでは、型の取り違えを悪用したシナリオを通じて、この設計が実際に何を意味しているのか確認してみます。
攻撃対象: 型の取り違えの脆弱性サンプル
ここでは、Trail of Bits Blog の「Let’s talk about CFI」で紹介されているシンプルな C++ の仮想呼び出しにおける型の取り違えのサンプルプログラムを実行例として使用します。
この例えのコンセプトは、攻撃者が何らかの形でプログ ラムをだまし、間違った型のインスタンスでメソッドを呼び出すことです。これは (C++ に限らず) さまざまな形で発生しますが、信頼できないデータソース (ネットワークなど) からインスタンス (もしくはインスタンス選択ロジック) が読み込まれる際に、そのインスタンスが想定されている型かどうか確認せずに、そのインスタンスに対し何らかのオブジェクトメソッド (もしくは関数) を呼び出すというのが一般的なシナリオです。攻撃者がプログラムに対して想定外の型のインスタンスをフィードすることが可能な場合、攻撃者はプログラムをコントロールする (もしくはその他の悪事を引き起こす) ことができます。
Trail of Bits のコードに対する変更点は以降に記載します。本チュートリアルで使用したサンプルコードは GitHub で参照できます。
チュートリアルツールのセットアップ (オプション)
このセクションでは、ツールのセットアップ方法を説明します。必要ない場合は、スキップしても構いません。
脆弱なコードをネイティブや WebAssembly ターゲットにコンパイルするために Docker Ubuntu 16.04 ゲストを使用します。ホストにインストールされたツールを使用してファイル を編集し、ホスト Web ブラウザを使用して WebAssembly を実行できるようにディレクトリを共有します。
docker run -v "$(pwd):/src" -t -i ubuntu:16.04 bash
ネイティブターゲットへのコンパイルには Clang を使用し、WebAssembly のコンパイルには [Emscripten](https://github.com/kripken/emscripten) を使用します。皆さんのやり方とは異なるかもしれないですが、Ubuntu ゲストにすべてのツールをインストールするために私が実行したコマンドを以下に示します。
```
root@2dc5f92b98cf:/src# apt-get update && apt-get install -y cmake build-essential python2.7 nodejs git wget tmux
root@2dc5f92b98cf:/src# apt-get install clang-5.0 && ln -s /usr/bin/clang-5.0 /usr/bin/clang && ln -s /usr/bin/clang++-5.0 /usr/bin/clang++
root@2dc5f92b98cf:/src# wget https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz && tar -xf emsdk-portable.tar.gz && cd emsdk-portable
root@2dc5f92b98cf:/src/emsdk-portable# ./emsdk update && ./emsdk install latest && ./emsdk activate latest
```
本チュートリアルのコンテンツを作成するため、Emscripten 環境を利用して tmux セッション (以降 [`tmux`] と表記します) を実行しました。[`tmux`] セッションは、WASM ターゲットに対して Emscripten ツールチェインを使用し、通常のゲストシェルはネイティブターゲットに対して Ubuntu clang ツールチェインを使用します。こちらが Emscripten 環境です。
```
[tmux] root@2dc5f92b98cf:/src/emsdk-portable# source ./emsdk_env.sh
&& which clang
/src/emsdk-portable/clang/e1.37.35_64bit/clang
```
そして、これが Clang/ネイティブ環境です。
```
root@2dc5f92b98cf:/src/emsdk-portable# which clang
/usr/bin/clang
```
バイナリの WebAssembly モジュールからテキスト形式への変換 (および逆の変換) には [WebAssembly Binary Toolkit (WABT)](https://github.com/WebAssembly/wabt) を使用できます。
```
root@2dc5f92b98cf:/src# git clone --recursive https://github.com/WebAssembly/wabt && cd wabt
root@2dc5f92b98cf:/src/wabt# make && make install
```
## WebAssembly の型チェックにおける脆弱性の悪用防止
WebAssembly を埋め込むもの(ブラウザ) は通常、WebAssembly プログラムによる実行を許可する前に、一般 (引数と戻り値の観点で) 関数の型 (引数と戻り値) が正しいかどうかを確認します [WebAssembly.validate](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/validate) も参照してください)。しかし、C や C++ での関数ポインタ呼び出しに類似した *間接呼び出し* に