本サイトは、快適にご利用いただくためにクッキー(Cookie)を使用しております。
Cookieの使用に同意いただける場合は「同意する」ボタンを押してください。
なお本サイトのCookie使用については、「個人情報保護方針」をご覧ください。
今回は、筆者が開発に携わっているWebアプリケーションスキャナ(の一部)で、機械学習を取り入れる試みをした話について書きます。
はじめに
セキュリティ診断でSQL Injectionを見つけた場合、脆弱性を実証するために何らかのデータをDBから取得することが多いと思います。取得するデータは会社/個人によって様々でしょうが、弊社の場合はDBのバージョン(バナー)であることが多いです。
DBバージョンは下記のような文字列です。
DB | バージョン文字列の例 | 取得用の式 |
---|---|---|
SQLite | 3.31.1 |
sqlite_version() |
MySQL | 8.0.28-0ubuntu0.20.04.3 |
version() |
SQL Server | Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) \n\tSep 24 2019 13:48:23 \n\tCopyright (C) 2019 Microsoft Corporation\n\tExpress Edition (64-bit) on Windows 10 Pro 10.0 <X64> (Build 22000: ) (Hypervisor)\n |
@@version |
Oracle | Oracle Database 18c Express Edition Release 18.0.0.0.0 – Production |
select banner from v$version where rownum=1 |
PostgreSQL | PostgreSQL 12.9 (Ubuntu 12.9-0ubuntu0.20.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0, 64-bit |
version() |
見てのとおり、一部のDBのバージョン文字列はかなり長いです。
一度に文字列全体が取得できる状況では長い文字列でも問題はありませんが、応答の内容/時間をもとにバイナリサーチにより1bitずつ特定していく場合、長い文字列の取得にはそれなりの時間がかかります。
さらに、バイナリサーチではなく、LIKE演算子で1文字(または1単語)ずつ特定していく場合には、もっと長い時間がかかります。次の例ではPostgreS
の後の1文字を特定しようとしていますが、正解を探すには考えられるすべての文字を試すことになります。
... CASE WHEN version() LIKE 'PostgreSa%' THEN ... ELSE ... END ...
... CASE WHEN version() LIKE 'PostgreSb%' THEN ... ELSE ... END ...
... CASE WHEN version() LIKE 'PostgreSc%' THEN ... ELSE ... END ...
仮にバージョン文字列が200文字であり、バージョン文字列に使われる文字が100通りであるとすると、LIKEでの文字列全体の特定には平均で10,000回の試行が必要になります。当然、人間の診断員は「PostgreS
」の後は「QL
」と続くことは知っているので、上のように非効率なことはしません。
人間の診断員と同じようなことをコンピュータにやらせてみよう、ということで作成したのが本記事で紹介する機械学習プログラムです。プログラムは人間と同じようにバージョン文字列の特徴を「知っている」ので、特定済みのバージョンの部分文字列(例:「PostgreS
」)を入力として与えると、それに続く文字を予測します。うまく予測できれば、LIKE演算子やバイナリサーチでバージョンを抜くために必要な試行数を減らせるでしょう。
方式概要と準備
まずは事前準備として、前項で挙げた5種類のDBのバージョン文字列を約190個含むデータを作成しました。このうち3/4を訓練に使用し、残りの1/4を評価テストに使います。
機械学習のモデルとしてはLSTM(Long Short-Term Memory)を試してみました。LSTMの解説は専門家に任せるとしてここでは割愛しますが、文字列データを含む時系列データの処理に適したモデルとして知られているものです。
ネットを探せばLSTMによる文字列の予測を行っている例はいくつかあります。今回は@ariera氏の以下の記事の実装(Python+TensorFlow)を参考にしました。
日本の古文書で機械学習を試す(4) LSTM(RNN)で次に来る文字を予測
https://qiita.com/ariera/items/103631729680f82c1226
使用したパラメータ等は氏のプログラムとほぼ同じです。ただし、動作環境の都合上、筆者のプログラムはPythonではなくJavaScript(TensorFlow.js)で書きました。筆者が書いたプログラムの一部は公開しています(本記事の最後を参照)。
最後に、機械学習を専門的にやっている同僚の高江洲(R&D部)に助言を依頼したところで、事前準備は完了です(適宜アドバイスをもらいながら開発しました)。
プログラムの入力と出力
プログラムへの入力は、既に特定済みのバージョン文字列(例:「PostgreS
」)です。このうち末尾からN文字を機械学習プログラム本体への入力とします。特定済みの文字列がN文字に満たない時には、便宜的にバージョン文字列には使用されていない文字を先頭に詰めてN文字とします。
今回は機械学習プログラムに与える文字列長(N)を10にしましたが、簡単のためN=5として入力を図にすると下記になります(入力/出力の各文字はベクトルですが、通常の文字として表しています)。
入力(長さ5) 出力(正解)
* * * * * P
* * * * P o
* * * P o s
* * P o s t
* P o s t g
P o s t g r
o s t g r e
s t g r e S
t g r e S Q
g r e S Q L
r e S Q L ␣
訓練時には上記のような入力とともに、正解(次の文字)を与えて訓練します。
予測させる際には、機械学習プログラム本体は次の1文字が何になるかの確率を収めた配列を出力します。この中で最も高い確率であると予測された文字が、実際の次の文字と一致すれば正解とします。
指標
第1の指標は正解率(テストデータの全文字について文字の予測を行い、正解だったものの割合)です。
なお、正解率の測定に使うテストデータは訓練用のデータとは別のものですが、機械学習プログラムへの入力とする「直前のN文字」(N=10)の単位でみると両者には共通の部分が多くあります。例えば「PostgreSQL
」という10文字を含まないPostgreSQLのバージョン文字列はないため、これはテスト/訓練データの両方に含まれています。したがって、ここでの正解率は、既知データの暗記力と、未知データに対する予測(類推)能力の両方を測るものです。
前出の古典文書を対象とした次の文字の予測では、正解率は10%程度だったようです。日本語の古典文書と比べると、DBのバージョン文字列には非常に強い規則性があり、また使用される文字数も大幅に少ないため、それより高い正解率になるはずです。
今回、正解率の比較対象となる数字は以下の3つです。
①12%(一点張り方式)
使用するデータで一番出現数が多い文字はスペース(0x20)でその割合は約12%です。常に「次の文字はスペース」と予想すれば正解率は12%になります。
②50%(辞書方式)
現在弊社のスキャナで実際に使用している方式です。バージョン文字列によく出現する単語を50個ほど含む辞書を使って、次の文字(1文字または複数文字)を予測しています。単語で推測する以外にも、数字の後はドット、単語の次はスペースなど、多少は実際のデータの傾向を踏まえて予測するプログラムで、テストデータでの正解率は約50%です。
③88%(丸暗記方式)
もう1つ、今回の試行に際して、LSTMとの比較対象として作成したのは、訓練データを丸暗記して、それを参考に文字を推測するプログラムです。
例えば「ABCDEF
」の次の文字を予測するとき、訓練データ内に「ABCDEF0
」があったならば、このプログラムは次の文字を「0
」と予測します(次の文字の候補が複数ある時は、出現数が多い方を優先する)。訓練データ内に「ABCDEF
」がなければ、「BCDEF
」「CDEF
」というように先頭を1文字ずつ削って訓練データを検索します。
このプログラムの正解率は約88%です。丸暗記という名前のとおり、未知のものを予測する能力はほぼありませんが、既知の部分は良く正解します。機械学習プログラムがこれを超えられれば筆者としては大満足です。
ちなみに、丸暗記が可能なのは、訓練データが全体で10KB程しかない小さいサイズだからであり、データの規則性が強く単純である(同じ文字列の部分が多い)ため良い正解率になります。
第2の指標は(速度という意味での)性能です。
訓練にかかる時間は多少長くかかってもよいのですが、予測にかかる時間は短くしたいです。具体的には、筆者の環境で予測1回あたりの時間が数ミリ秒であるというのが目安です。ちなみに、②辞書方式、③丸暗記方式での予測時間は約0.01ミリ秒です。
実施結果
まずは訓練データ(全体の3/4)を与えてモデルを訓練します。訓練を繰り返す回数(エポック数)は20としました。20を超えると正解率にほとんど改善が見られなかったためです。データ量が少ないこともあり訓練全体の所要時間は1~2分程度でした。
続いてテストデータ(全体の1/4)で次の文字を予測させます。以下はその結果の抜粋です。黄色の網掛けは不正解を表します。
3.31.1
8.0.28-0ubuntu0.20.04.3
Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) \n\tSep 24 2019 13:48:23 \n\tCopyright (C) 2019 Microsoft Corporation\n\tExpress Edition (64-bit) on Windows 10 Pro 10.0 <X64> (Build 22000: ) (Hypervisor)\n
Oracle Database 18c Express Edition Release 18.0.0.0.0 - Production
PostgreSQL 12.9 (Ubuntu 12.9-0ubuntu0.20.04.1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0, 64-bit
見てのとおり、先頭1文字や数字は予測しようがない部分が多いため不正解が多く、英字は割とよく正解します。これは丸暗記方式と同じです。
正解率についてはテストデータ全体で87~88%でした。特別なチューニングをした訳でもなく、また少ない訓練データの直前10文字しか与えていないにもかかわらず、丸暗記方式の88%とほぼ同じ数字が出ています。筆者の予想よりも高く、実用上充分でしょう。
予測1回当たりの所要時間は筆者の環境で5ミリ秒程度であり、目安の「数ミリ秒」に収まっています。
結果のハイライト
LSTMと丸暗記方式の予測結果の差分を何か所か見てみたいと思います。
まずはSQL Serverのバージョン文字列です。
例1: … SQL Server 2016 (SP2) (KB4052908) - 1 …
赤字の下線は丸暗記方式では不正解で、LSTMで正解だった箇所です(以下同)。
注目してほしいのは「KB4052908
」の直後の「)␣
」です。訓練データに「KB4052908
」という文字列は含まれていませんが、LSTMは訓練データに含まれる他の「KB???????
」のパターンから学習して(多分)、「KB
」の番号そのものは間違えているにもかかわらず、直後の「)␣
」を正解しています。
次はPostgreSQLのバージョンです。
例2: … compiled by gcc (GCC) 4.4.6 20120305 (Red Hat …
訓練データには「(GCC) 4.4.6
」に類似の文字列が含まれていますが、一致するものはありません。そのため完全一致を探す丸暗記方式ではその後の「␣20
」を正解できませんでしたが、LSTMは正解しています。
上の例2つ、特に例1には、LSTM(Long Short-Term Memory)の「Long-term memory」(かなり手前の文字も覚えていられる)という特性が出ているのでしょう。わずか1~2文字の正解ですが、うまくパターンを把握して予測しているように見えます。
一方で、プログラムが法則性をつかめなかったものもあります。以下はSQL Serverのバージョン文字列の抜粋です。
… SQL Server 2012 (SP1) - 11.0. …
… SQL Server 2014 - 12.0. …
… SQL Server 2016 (SP1) (KB???????) - 13.0. …
… SQL Server 2017 (RTM) - 14.0. …
… SQL Server 2019 (RTM-GDR) (KB???????) - 15.0. …
上の網掛けのように、「SQL Server
」の後の4桁(年)と、その後のバージョン番号には関連性があり、前の4桁が分かっている状態だと後ろの番号は推測できるのですが、プログラムが正解できたのは一部でした。
Oracleのバージョン文字列にはより単純な法則がありますが、それも正解できていません。
Oracle Database 18c … Edition Release 18.0.0.0.0 …
これらに正解できない理由は簡単です。今回は直前10文字だけを機械学習プログラムへの入力としており、その10文字の中に関連のある2つの値が収まっていないからです。つまりそもそも正解しようがない条件になっているということです。
それでは入力の文字数を増やせばいいのかというと、そんなに単純な話でもありません。実際に文字数を増やしても、これらに正解できるようにはなりませんでしたし、全体の正解率も上がりませんでした(一方で訓練や予測の時間は長くなる)。現状以上の結果を得るには、より大量で質のよい訓練データを与えて、適切なチューニングを行う必要がありそうです。
最後はLSTMだけ間違えた例です。
Oracle Database 10g Release …
LSTMは「Releas
」ではなく「Releat
」と予測しました(確率は78%)。訓練データには「Release
」は多く含まれていますが「Releat
」は含まれていません。理由は分かりませんが、訓練データの中に誤ったパターンを見出すことはしばしばあるようです。
セキュリティ
これはセキュリティ会社のブログ記事ですので、このプログラムのセキュリティについても少し考えてみたいと思います。
このプログラムを本稼働させた暁には、単に次の文字を予測するだけでなく、診断対象のWebアプリに対してHTTPリクエストを送ることにより次の文字を確定させることになります。この過程で、本プログラムからのアクセスを受ける診断対象のアプリ(および本プログラムを実行する診断員)は、プログラムが予測した文字が何であったかを知ることができます。
プログラムが予測する文字は訓練データに含まれる特徴を反映しているため、予測された文字を知れば、訓練データに関する一定の情報が得られます。さらに、無制限に本プログラムに予測をさせてその結果が得られる状況であれば、攻撃者は訓練データやモデルに関する情報をより多く集められます。仮に診断対象のアプリがそのような攻撃を仕掛けるとすると、対象アプリからバージョン情報を抜くはずの本プログラムが、実は逆に対象アプリから情報を抜かれるという皮肉な状況になりかねません。
このように機械学習の出力から訓練データ/モデルの情報を窃取する攻撃等について、近年盛んに研究が行われています。新しい分野なので分類や呼称は一定していませんが、JNSAの資料ではこのような攻撃を「移転攻撃(Inversion Attack)」と呼んでいます。同資料のP.32の表には、移転攻撃への一般的な対策についてもまとめられています。弊社のブログでは、メンバーシップ推論と呼ばれるデータ窃取攻撃の概要と対策を書いた高江洲による記事があります。興味のある方は参照ください。
なお、JNSAの資料は移転攻撃以外にも、中毒攻撃、回避攻撃と呼ばれる機械学習/AIシステムに対する攻撃について概説しています。弊社の運営するAIセキュリティ情報発信ポータルやAI×セキュリティにも(用語は異なりますが)機械学習/AIシステムに対する各種の攻撃手法や対策に関する情報が掲載されているので、参考にしていただければ幸いです。
まとめ
機械学習(LSTM)による予測の正解率は、丸暗記方式とほぼ同等の高い数字でした。また予測の性能(処理速度)についても十分実用的なレベルであることを確認できました。
機械学習が真価を発揮するのは、非常に大きいデータや、規則性が複雑で捉えにくいために人間が有効なロジックを考えるのが難しいデータでしょう。今回は対象にした文字列の特性や量の問題等から、真価を100%引き出すには至らなかったかもしれませんが、その能力の一端はしっかりと確認できたと思います。
またバージョン文字列の特徴は、年月とともにDBやOSのバージョンが上がることにより変わっていきます。変化に強いのはおそらく丸暗記方式ではなく機械学習の方であり、そういう点で機械学習を使う意味はありそうです(もちろん丸暗記方式にも柔軟性を持たせる改良の余地はありますし、機械学習についても正解率をモニタリングし一定期間毎にデータを更新する必要があります)。
現状で具体的な予定があるわけではありませんが、いずれ弊社のWebアプリケーションスキャナにも機械学習/AIを導入することになるはずです。今回は、スキャナの補助的な機能への適用可能性を探るに留まりましたが、その第一歩にはなったかと思います。
テスト環境
H/W | CPU Core i7-11700 / Memory 32GB / GPU無し |
---|---|
OS | Ubuntu 20.04.3 LTS |
Node.js | v14.18.2 |
Tensorflow.js | v3.13.0(npmによるデフォルトインストール) |
プログラム
実際に検証に使用したものとは異なりますが、LSTMの訓練と予測を行うJavaScriptのサンプルプログラムをGistに公開しています。訓練したモデルそのものや、訓練/テストデータは含まれていません。
おすすめ記事