Help us understand the problem. What is going on with this article?

ファジングを知って欲しい

ソフトウェアテストアドベントカレンダーの17日目です.
前回の記事はnihonbusonさんのWACATE合宿レポートでした.WACATEというワークショップがあるということを初めて知りました.一度,このネタで参加してみたいです.

fo02waca_f1.gif

さて前置きが長くなりましたが,今回ご紹介するのはファジングです.SQAよりもセキュリティの方が馴染み深いテストかもしれませんが,できるだけ分かりやすく解説していきます.

Fuzzing(ファジング)とは 

情報処理推進機構(IPA)によると,ファジングとは「検査対象のソフトウェアに対して,問題を起こしそうなデータを入力し,その応答や挙動を監視する手法」です.入力するデータのことをfuzz(ファズ)と呼んだり,テストケースと呼んだりします.

ファズの入力方法は,ソフトウェアによって様々です.例えば,bashコマンドのように標準入力からファイルや文字列を入力する場合やHTTPサーバやFTPサーバのようにTCP/IP通信でデータを入力する場合,さらに画像や音声ライブラリではメディアデータを入力します.

以上のようにファズを大量に入力することでファジングが行われます.長い時間をかけて順番に入力するツールをファザー(fuzzer)と呼びますが,これまでに多くのファザーが公開されています.

ソフトウェアテストとの関連性

次にソフトウェアテストとの関連性について説明します.これを述べないと,セキュリティのアドカレンダーが無かったからこっちに来た,ってことがバレてしまうのでここでしっかりと関連性を述べます.

VLATESによると,ソフトウェアテストとは「対象のソフトウェアが意図通り動作することを実証したり,バグや想定外の動作を検出したりするテスト」です。

ということは,ファジングは後者の「バグや想定外の動作を検出したりするテスト」に相当します.

b.png

ファジングの流れ

以下ののような感じでファジングは実施されます.

c.png

  1. 正常データをベースにファズを生成します.
  2. 対象のソフトウェアに対してファズを入力します.
  3. ソフトウェアの挙動や正常データの入力による死活監視を行います.
  4. 再び 1. に戻ります.

毎回ファズを動的に生成せずに,先にファズを大量に生成してそれを順々に入力するというタイプもあります.

ファジングの機能分解:GIDAC

以下のようにファジングを5つの機能で分けます.

  • Fuzz Generation:ファズ生成
  • Fuzz Input:ファズ入力
  • Bug Detection:バグ検出
  • Automation:自動化
  • Coverage:カバレッジ

これらの頭文字を合わせてGIDAC(ジダック)と呼ぶことにします.

ファズ生成(G)

効率的にバグを検出するためにファズの生成手法は重要です.なぜならば,その手法によって検出可能なバグが決定されるためです.
例えば,30文字以上で桁あふれを引き起こすプログラムに対して,29文字以内のファズを延々と入力していてもバグは検出できません.また,HTTPサーバのような文法が決まっている入力を必要とするプログラムでは,文法を無視したファズを入力してもプログラムの浅い部分しかテストできません.

ファズ入力(I)

ソフトウェアには「入出力(I/O)」という共通した概念がある一方,その方法は前述のように様々です.独自でファザーを開発するには,入出力(特に入力)について注意する必要があります.

バグ検出(D)

テスト対象にファズを入力後,ファザーはテスト対象の挙動を監視し異常状態を検出します.例えば,Bashコマンドでは異常終了(SIGILL/SIGSEGV)を検出することによってバグの検出が可能です.また,HTTPサーバやibusのようにプロセスまたはデーモン化する場合は,ファズ入力直後に正常データを送信して死活監視することでバグの状態異常を検出します.

自動化(A)

大規模開発では,十分なテストを行うために生成すべきファズが億兆を超えます.そのため,すべての入力をヒトの手で行うことは不可能であり,自動化という手法に頼らざるを得ません.

カバレッジ(C)

ファジングに限らずソフトウェアテスト全体で言えることですが,テストによって網羅されたプログラムの範囲を把握することは大切です.ただし,ブラックボックステスト(後述)のようにカバレッジの測定が困難な場合は,テストケース実施件数をカバレッジの代わりにします.

Screenshot from 2019-12-12 23-46-54.png

ファジングの種類

以下のように3種類に分類されます.

ブラックボックスFuzzing

  • ファズ入力後,外部からテスト対象の挙動を確認
  • プログラムの内部処理に関しては一切考慮しない
  • バグの例:httpdがファズ受信後に再起動/ポートが閉じてサービスが強制停止

グレイボックスFuzzing

  • ファズ入力後,外部からの挙動確認に加え内部処理も確認
  • ファズが通過したプログラム内のパスを追跡
  • カバレッジを計測可能のため,カバレッジガイドファジングとも呼ばれている
  • バグの例:bmp2tiffにファズを入力するとsegfaultが発生

ホワイトボックスFuzzing

  • 動的シンボリック実行,SMT, SAT 等

ファザーの動作確認

以下のファザーをUbuntu16.04(x64)上で動作させます.

  • boofuzz:ブラックボックス
  • radamsa:ブラックボックス
  • AFL:グレイボックス

boofuzz

boo(ブー)は,Pythonベースのブラックボックスファザーです.Sulley(サリー)というプロジェクトから派生したファザーで,親プロジェクトと同様,モンスターズインクにちなんで名付けられました.それでは,インストールから説明していきます。

angry-boo-puzzle-1500571160.jpg

インストール方法

# git clone https://github.com/jtpereyda/boofuzz.git
# cd ./boofuzz
# python setup.py install

ファザーの実行&ステータス

# python ftp-simple.py
# firefox http://localhost/:26000

独自ファザーの作成

自分でファザーを作成するには,boofuzz/examples/にあるサンプルを例にするか,もしくはソースコードを読んで各APIの動作を理解する必要があります.今回,説明用にファザーを作成しようと試みたのですが,時間がなかったため割愛します.

Screenshot from 2019-12-09 00-10-40.png

radamsa

radamsa(ラダムサ)は,正常データをランダムな文字列や乱数によって置換することでファズを作成する便利なツールです.scapy(スカパイ)と組合せるとさらに強力なファザーを作成できます.ただし,今回は時間の都合上組合せません.

インストール

# git clone https://gitlab.com/akihe/radamsa.git
# cd ./radamsa
# make

Fuzzing対象のTCPサーバ

80番ポートをオープンし"die"と送信されたらAbort(中止)するTCPサーバをビルドします.

# gcc -O3 -trigraphs -o tcp-server tcp-server.c

以下、TCPサーバのサンプルです。

tcp-server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main(void) {
    int srv_sock, cli_sock;
    int len, inlen;
    char buf[32];
    struct sockaddr_in server;
    struct sockaddr_in client;  
    server.sin_family = AF_INET;    
    server.sin_port = htons(80);
    server.sin_addr.s_addr = INADDR_ANY;
    srv_sock = socket(AF_INET, SOCK_STREAM, 0);
    bind(srv_sock, (struct sockaddr *)&server, sizeof(server));
    listen(srv_sock, 1);
    while (1) {
        len = sizeof(client);
        cli_sock = accept(srv_sock, (struct sockaddr *)&client, &len);
        memset(buf, 0, sizeof(buf));           
        write(cli_sock, "hello> ", 7);
        inlen = read(cli_sock, buf, sizeof(buf));
        if (strcmp(buf, "die\n") == 0) abort();     
        shutdown(cli_sock, 2);
        close(cli_sock);        
    }
    close(srv_sock);
    return 0;
}

ファザーの実行

# echo 'di' > valid-case.txt
# ./tcp-server &
# radamsa -o 127.0.0.1:80 -n inf valid-case.txt
# tcpdump -i lo

GIDACを用いた考察

radamsaが提供する機能はファズ生成(G)と一部のファズ入力(I)です.メインは,専らファズ生成で,その他の機能は完全にサポートされていません.サポート外の機能は,別のツールに頼るか自分で作成する必要があります.このときに,scapyという自由にパケットを生成できるツールを使うことで解決できます.
ただし,booもradamsaもブラックボックスファザーのため,カバレッジ(C)に関してはテストケースの実行件数や実施時間で代用するしか方法はありません.

カバレッジが取れないと,ファズがプログラムの深い部分に届いているのかわからないため不安ですよね.それを解消してくれるのが,グレイボックスファジングなのです.

AFL

グレイボックスでは,ブラックボックスと異なりカバレッジを測定できるため,ファズの生成アルゴリズムを改良すれば,プログラムの深い部分にファズを届けることが可能です.

広く知られているツールとして,AFL, LibFuzzer, HonggFuzz等があり,3者とも遺伝的アルゴリズムをファズ生成アルゴリズムに改良することで効率的なバグ検出を実現しています.

AFL_Fuzz_Logo.gif

具体的には,発見した通過パスを分類し,良性のテストケースを変異させて次のファズを生成するという流れです.これらのツールが見つけた脆弱性(CVE)は非常に多く,まだ歴史が浅いのにも関わらず数千件以上に上ります.

インストール

# git clone https://github.com/google/AFL.git
# cd ./AFL
# make
# make install

CVE-2014-9330の再現

AFLを用いてCVE-2014-9330を検出します.

IPAの解説によると,libtiffライブラリのbmp2tiftif_packbits.cに存在する,範囲外の読み取りを誘発するディメンションに関する処理に不備が,整数オーバーフローの発生要因だったとのことです.

準備

以下のように,libtiff(3.8.2)をダウンロード&Fuzzing用にビルドします.

# wget http://download.osgeo.org/libtiff/old/tiff-3.8.2.tar.gz
# tar xvzf tiff-3.8.2.tar.gz 
# cd tiff-3.8.2
# export CC=afl-gcc
# export CXX=afl-g++
# ./configure --disable-shared
# make
# ls ./tools
# cp /opt/afl-2.52b/testcases/images/bmp/not_kitty.bmp ./input

ファジングの実行

以下のように,ファジングを実行してしばらく待ちます.

# afl-fuzz -i ./input/ -o ./output/ -- ./bmp2tiff @@ /dev/null

a.png

実行時,上図のようにステータス画面が表示されますが,細かいことにはこだわらないでください.大事なのは右上の"total paths"と"uniq crashes",そして"uniq hangs"です."total paths"は,ファズが通過したプログラムのパスの識別数(パスカバレッジ??)です."uniq crashes"と"uniq hangs"はユニークなバグの検出件数です.その他のステータスの説明については,AFLのReadmeを参照してください.ここでは省略します.

結果

8分51秒で767,000回のテストケース実行を行い,209個のパスを発見し,4件のクラッシュと13件のハングを検出しました../out/crash/にクラッシュを引き起こしたファズが保存されています.それを用いて簡単に再現確認ができます.

# cat ./out/crash/id0000* | ./bmp2tiff /dev/null
Segmentation fault

再びGIDACを用いた考察

以上のように,CVE-2014-9330を簡単に再現することができました.ただし,今回実施したのはコマンドラインのアプリでした.整理すると,AFLはファズ生成(G)に遺伝的アルゴリズムを採用し,ファズ入力(I)は標準入力です.また,バグ検出(D)はテスト対象の終了ステータスで,自動化(A)もカバレッジ(C)も実現しています.

皆さんが開発されているような複雑なソフトウェアに対してファジングする場合,ファズ入力(I)の部分を改良する必要があります.

最後に

ファジングの意味とブラックボックスとグレイボックスの違いをまとめます.

ファジングとは

  • ソフトウェアテストのバグ検出のための手法
  • 世の中には多くのツールが存在する
  • ブラックボックス/グレイボックス/ホワイトボックスと分類できる

ブラックボックスファジングとは

  • 様々なソフトウェアに対して比較的簡単にファジングできる
  • カバレッジを計測ができないため,プログラムの深い部分までテストできたという根拠が示せない

グレイボックスファジングとは

  • カバレッジを計測ができるので,プログラムの深い部分までテストすることができる
  • 様々なソフトウェアでファジングを行うためにはファズ入力を改良する必要がある
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away