開発メモ

API Fuzzing toolのRESTlerを動かす

February 7, 2021

RESTlerとは

RESTler is the first stateful REST API fuzzing tool for automatically testing cloud services through their REST APIs and finding security and reliability bugs in these services. For a given cloud service with an OpenAPI/Swagger specification, RESTler analyzes its entire specification, and then generates and executes tests that exercise the service through its REST API.

環境

  • macOS Catelina
  • Docker Engine - Community
    • Version: 20.10.2

実行環境コンテナ準備

.NET SDK入りDockerイメージ取得

  • .NET core SDK 3.1が必要なので,下記のMicrosoft公式イメージを利用
    • .NET SDK | Docker Hub
    • 下記コマンドだとOSはDebian 10
    • 他のOSにしたい場合はDokcer Hubに記載のタグを指定
1$ docker pull mcr.microsoft.com/dotnet/sdk:3.1 
2$ docker images
3REPOSITORY                     TAG     IMAGE ID       CREATED         SIZE
4mcr.microsoft.com/dotnet/sdk   3.1     52a3845cafb1   4 days ago      710MB
5...

コンテナ起動

  • コンテナを立ち上げてbashに入る (お試しなのでDockerfile化せず直接作業)
1$ docker run -it -d --name restler-test mcr.microsoft.com/dotnet/sdk:3.1
2$ docker exec -it restler-test /bin/bash

Python 3.8.2インストール

  • Python 3.8.2が必要なのでインストール (お試しなのでrootユーザのままで作業,以下コマンド例はroot実行だが先頭シンボルは$で記載)
  • 3.7しかリポジトリにないようなので下記を参考にインストール
 1$ apt update
 2$ apt install build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev
 3$ cd ~
 4$ curl -O https://www.python.org/ftp/python/3.8.2/Python-3.8.2.tar.xz
 5$ tar -xf Python-3.8.2.tar.xz
 6$ cd Python-3.8.2
 7$ ./configure --enable-optimizations
 8$ nproc
 93
10$ make -j 3
11$ make install
12$ python3 --version
13Pyhon 3.8.2

pythonコマンドの設定

  • RESTlerの処理の中では,pythonコマンドで自身の持つスクリプトを実行する
  • ここまで設定した状態では,pythonコマンドがPython 2.7.x,python3コマンドががPython 3.8.xをさしているはず
  • そのため,下記などにてpythonコマンドでPython 3.8.xが実行されるような設定が必要
    • alias python="python3"だとRESTler実行時に反映されないようでだめだった
 1$ which python
 2/usr/bin/python
 3
 4$ ls -la /usr/bin/python
 5lrwxrwxrwx 1 root root       7 Mar  4  2019 /usr/bin/python -> python2
 6
 7$ which python3
 8/usr/local/bin/python3
 9
10$ ln -fs /usr/local/bin/python3 /usr/bin/python
11
12$ ls -la /usr/bin/python
13lrwxrwxrwx 1 root root 22 Jan 27 05:35 /usr/bin/python -> /usr/local/bin/python3
14
15$ python --version
16Python 3.8.2

RESTlerインストール

  • READMEに従い下記を実行
1$ git clone https://github.com/microsoft/restler-fuzzer.git
2$ cd restler-fuzzer/
3$ python3 ./build-restler.py --dest_dir ~/restler_bin #RESTlerファイル一式をおく任意のpath

チュートリアル

0. デモサーバ準備

  • まずFuzzing対象とするサーバを起動する必要あり
    • demo_server/README.mdを参照
  • Python環境を構築してサーバ起動
1$ cd <cloneしたrestler-fuzzerのpath>/demo_server
2$ python -m venv venv
3$ source venv/bin/activate
4$ pip install -r requirements.txt
5$ python demo_server/app.py

1. Compile

  • Swagger定義をRESTlerで解釈できる形式に変換する
1$ mkdir ~/restler-test
2$ cd $_
3$ cp <cloneしたrestler-fuzzerのpath>/demo_server/swagger.json .
4$ ~/restler_bin/restler/Restler compile --api_spec swagger.json
  • restler-fuzzer/demo_server/Compileの下に結果が出力される
    • 例えばgrammar.jsonに,RESTlerでSwagger定義を解釈した結果を保持
    • 例えばdict.jsonはfuzzingで使うパラメタ定義
 1{
 2  "restler_fuzzable_string": [
 3    "fuzzstring"
 4  ],
 5  ...
 6  "restler_fuzzable_uuid4": [
 7    "903bcc44-30cf-4ea7-968a-d9d0da7c072f"
 8  ],
 9  "restler_fuzzable_uuid4_unquoted": [],
10  "restler_fuzzable_int": [
11    "0",
12    "1"
13  ],
14  "restler_fuzzable_number": [
15    "0.1",
16    "1.2"
17  ],
18  "restler_fuzzable_bool": [
19    "true"
20  ],
21  ...
22}

2. Test

  • 各エンドポイント・メソッドに対しアクセス可能か,レスポンス受け取れるかテスト
1$ ~/restler_bin/restler/Restler test --grammar_file Compile/grammar.py --dictionary_file Compile/dict.json --settings Compile/engine_settings.json --no_ssl
2Log share was not specified.  Logs will not be uploaded.
3Starting task Test...
4Request coverage (successful / total): 5 / 6
5No bugs were found.
6Task Test succeeded.
7Collecting logs...
  • 成功すると,5/6のリクエストが成功したことが出力される
    • デモサーバを正常に起動できていないと全リクエストが失敗になる
  • TutorialページではGET /blog/posts?per_page=で失敗するとあるが,自分の環境ではPUT /blog/posts/{post_id}で失敗していた
    • ペイロードのidに文字列を入れてしまって404ぽい
    • 直し方は詳細調べないと不明

3. Fuzz-lean

  • 各エンドポイント・メソッドに一度だけチェックをかける
    • 説明にはa default set of checkersとあるがなんのことか不明
 1$ ~/restler_bin/restler/Restler fuzz-lean --grammar_file Compile/grammar.py --dictionary_file Compile/dict.json --settings Compile/engine_settings.json --no_ssl
 2Log share was not specified.  Logs will not be uploaded.
 3Starting task FuzzLean...
 4
 5ERROR: Results analyzer for logs in /root/restler-test/FuzzLean failed.
 6
 7Request coverage (successful / total): 5 / 6
 8Bugs were found!
 9Bug buckets:
10
11InvalidDynamicObjectChecker_20x: 1
12InvalidDynamicObjectChecker_500: 1
13PayloadBodyChecker_500: 1
14Task FuzzLean succeeded.
15Collecting logs...
  • 検出されたバグの情報はFizzLean/RestlerResults/experiment<...>/bug_bucketsに出力

    • bug_buckets.txtにサマリ
    • InvalidDynamicObjectChecker_20x_1.txtなどに個別バグのもう少し詳細
      • このログのテキストファイルを入力にしてRESTlerを実行すれば,同じバグの再現が可能 (--replay_logオプション)
    • ここでは3つのバグ発見
  • 以下,発見されたバグの解説

InvalidDynamicObjectChecker_20x
  • まずブログ記事をPOST→レスポンスとして記事ID取得
  • その後,当該IDを用いてブログ記事をGET,ただしFuzzerで余分なクエリ文字列付与
    • 具体的には,GET /api/blog/posts/<ID>とするところを,GET /api/blog/posts/<ID>?inject_query_stirng=123としてリクエスト発行
  • エラー発生を期待してFuzzerで余計な情報を付け足したにもかかわらず,200系のレスポンスが返ってきてしまったのでバグと判定
InvalidDynamicObjectChecker_500
  • まずInvalidDynamicObjectChecker_20xのケースと同じくPOST→余計なクエリ文字列つけてGET
  • その後余計なクエリ文字列のところを?inject_query_stirng=123から??に変えてGET
  • すると,500 Internal Server Errorをレスポンスとして受け取ったので,バグと判定
PayloadBodyChecker_500
  • まずブログ記事をPOST→レスポンスとして記事ID取得
  • その後,当該IDのブログ記事を更新するためのPUTするが,Fuzzerによりbodyからidchecksumが削除された状態でリクエストを発行
  • その結果,500 Internal Server Errorをレスポンスとして受け取ったので,バグと判定

4. Fuzz

  • API呼び出しの組み合わせも含めてFuzzingする
    • --time_budgetは実行する時間(hour単位)の指定
    • 下記コマンドだと1時間実行される
1$ ~/restler_bin/restler/Restler fuzz --grammar_file Compile/grammar.py --dictionary_file Compile/dict.json --settings Compile/engine_settings.json --no_ssl --time_budget 1
  • 今回の例は小規模で複雑度が低いため,FuzzLeanと同じバグしか発見していない

その他

Tagged: #RESTler #REST API #API testing #Fuzzing