REST API用のファジングツール “RESTler” で始めるお手軽ファジング
2021年10月08日 金曜日
CONTENTS
IIJイノベーションインスティテュートの四谷です。普段はWeb API開発の生産性向上についての調査や開発を行っています。
今日はREST APIのテスト効率を改善するツール「RESTler」を紹介します。
RESTlerについて
RESTlerはMicrosoft Researchが開発し、OSSとして公開しているREST API用のファジングツール(ファザー)です。
ファジングはネットワークプロトコルの実装等、もう少し下位レイヤーでの活用が主で、APIに対して実行できるファザーは数少ないのですが、その1つがRESTlerです。Microsoftでは実際にRESTlerを使用して、AzureやOffice365のバグを検出したそうです。
特長
RESTlerの最大の特長は、OpenAPIドキュメントとして記述されたAPI仕様さえあれば、自動的にテストケースが生成され、ファジングを実行できることです。
したがって、特別な準備をすることなく手軽に導入できます。
また、キャッチコピーとして”Stateful REST API Fuzzing“と挙げられているように、これまで人の手で設計していたようなテストシーケンスを自動的に生成して実行する点も大きな特長です。この”Stateful”を実現するために、以下の工夫が実装されています。
1. リクエスト間の依存関係の静的解析
例えば、以下のようなAPIがあるとしましょう。
POST /blog/posts
の実行結果としてリソースが生成され、レスポンスとして postId
が返ります。2、3、4番目のエンドポイントのパスに含まれる {postId}
ではその値が使用されます。この関係性は、OpenAPIドキュメントにはコメント等(の人間にはわかる形式で)記述されるのが普通です。
RESTlerは、このようなAPIリクエスト間の暗黙的な依存関係をOpenAPIドキュメントの静的解析によって推論し、テストシーケンスの生成に反映します。これにより、単なるランダムな組み合わせのテストに比べて、大幅なテストカバレッジの向上を実現しています。
2. 実行結果の動的フィードバック
RESTlerは、実行したテストの結果を以降のテストシーケンスの生成にフィードバックすることも行っています。実行結果が同じになることが予測されるテストケースをカットすることで、時間あたりのテスト効率の改善につなげています。
試してみる
では、実際にRESTlerを試してみましょう。
ファジング対象のAPIサーバとその仕様を記述したOpenAPIドキュメントが必要となりますが、RESTlerのリポジトリにはデモ用のAPIサーバが含まれていますので、今回はそれを使うことにします。
動作環境
RESTlerは現在、Windows及びLinuxでの実行をサポートしています(macOSでの動作は”experimental support”となっています)。
実行にはPython 3.8.2以降と.NET SDK 5.0が必要となります。Linuxへの.NETのインストール方法はこちらをご参照ください。
以下、Linuxで実行する場合の手順です。
ビルド
まず、任意の作業用ディレクトリにRESTlerのリポジトリをクローンします。
git clone https://github.com/microsoft/restler-fuzzer
次に、RESTlerをビルドします。
mkdir ./restler_bin python ./restler-fuzzer/build-restler.py --dest_dir ./restler_bin
ファジング対象APIの準備
以下の手順でデモ用APIサーバを実行できます(サーバプロセスはフォアグラウンドで起動するので、以下は別のターミナルをもう1つ用意して実行してください)。
cd ./restler-fuzzer/demo_server python -m venv venv source venv/bin/activate pip install -r requirements.txt python demo_server/app.py
以降の手順でOpenAPIドキュメントを使用しますので、作業用ディレクトリにコピーしておきます。
cp ./restler-fuzzer/demo_server/swagger.json .
ファジングの実行
まず、OpenAPIドキュメントの解析を実行します。
./restler_bin/restler/Restler compile --api_spec swagger.json
成功するとカレントディレクトリ内の Compile
というディレクトリに解析結果が出力されます。
では、いよいよファジングを実行します。いくつかの実行モードがありますが、ここでは短時間で終了するFuzz-Leanモードを使ってみます。
./restler_bin/restler/Restler fuzz-lean --grammar_file Compile/grammar.py --dictionary_file Compile/dict.json --settings Compile/engine_settings.json --no_ssl
おそらく以下のような出力が得られるはずです。
... Request coverage (successful / total): 6 / 6 Bugs were found! Bug buckets: InvalidDynamicObjectChecker_20x: 2 InvalidDynamicObjectChecker_500: 1 PayloadBodyChecker_500: 1 Task FuzzLean succeeded. ...
デモ用APIサーバのバグをいくつか検出したようです。実行結果はカレントディレクトリ内の Fuzz-Lean
ディレクトリに出力されます。多くのファイルが出力されますが、エラーの詳細は FuzzLean/RestlerResults/experiment69/bug_buckets
ディレクトリに出力されています。
その1つの InvalidDynamicObjectChecker_500_1.txt
というファイルを見てみましょう。エラーを検出したテストシーケンスのログが記録されています。中には以下のようなエラー発生ログがあるはずです。
-> GET /api/blog/posts/5881?? HTTP/1.1\r\nAccept: application/json\r\nHost: localhost:8888\r\n\r\n ! producer_timing_delay 0 ! max_async_wait_time 0 PREVIOUS RESPONSE: 'HTTP/1.1 500 INTERNAL SERVER ERROR\r\nContent-Type: application/json\r\nContent-Length: 176\r\nServer: Werkzeug/0.16.0 Python/3.9.7\r\nDate: Tue, 21 Sep 2021 09:34:50 GMT\r\n\r\n{\n "message": "The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application."\n}\n'
どうやら、 GET /api/blog/posts/5881??
というリクエスト(クエリ文字列が ?
という1文字)が 500 INTERNAL SERVER ERROR
を引き起こしているようです。
試しにcurlコマンドで直接APIを叩いてみると、以下のように再現できました。
# クエリストリング無しで正常応答を確認 $ curl -i "http://localhost:8888/api/blog/posts/123" HTTP/1.1 200 OK Content-Type: application/json Content-Length: 65 Server: Werkzeug/0.16.0 Python/3.9.7 Date: Tue, 21 Sep 2021 10:07:52 GMT { "id": 123, "checksum": "559dc", "body": "string" } # 問題のパターン(クエリ文字列が`?`という1文字) $ curl -i "http://localhost:8888/api/blog/posts/123??" HTTP/1.1 500 INTERNAL SERVER ERROR Content-Type: application/json Content-Length: 176 Server: Werkzeug/0.16.0 Python/3.9.7 Date: Tue, 21 Sep 2021 10:08:01 GMT { "message": "The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application." }
以上のように、OpenAPIドキュメントさえあれば、簡単に実行できることがおわかりいただけたのではないでしょうか。
今回はお試し用にFuzz-Leanモードを使いましたが、Fuzzモードを使用すれば大量のパターンのテストが実行できます。また、動作のカスタマイズによって、より効果的にファジングができるようです。詳しくはユーザーガイドをご覧ください。
試してみた感想
軽く試した感じ、特に以下のような不具合の検出に力を発揮してくれそうな印象を受けました。
- アクセスの許可、拒否等の認証関連の誤りの検出
- 入力のバリデーション処理の誤りの検出
- 実装とAPIドキュメントの乖離の検出
まだ発展途上のツールであり、例えばテスト結果のログがわかりにくい等のクセはあります。ただ、導入自体は簡単なので、CI/CDのパイプラインへの導入を検討する価値は十分にあるのではないでしょうか。