ビッグ データ

ビッグ データと機械学習によるオンライン サービスの保護

Alisson Sol
Don Ankney
Eugene Bobukh

コード サンプルのダウンロード

オンラインのサービスを保護する手法は、現状、Security Development Lifecycle (SDL) からインシデントにすばやく対応する運用プロセスまでいくつか存在します。オンライン サービスの主要アセットのうち見過ごされがちなのは、要求ログと運用イベントの監視によって生み出されるビッグ データです。今回は、Microsoft Applications & Services Group (ASG) で Bing、Bing Ads、MSN といったサービスのオンライン アセットを保護した経験を基に、セキュリティを強化するための使用状況データの処理と機械学習 (ML) のテクニックについて取り上げます。

大半のオンライン サービスは、ログに記録するデータについていくつかの流れを生み出します。サービスに関連して格納可能な測定データの種類についての標準分類法は存在しませんが、セキュリティの問題点を追求するために調査するデータは、大きく使用状況データと運用記録データに分類できます。使用状況データとは、ターゲットとするユーザーがサービスを使用する際にログに記録される値などです。一般的な例は、Web サイトに対して行われた要求に関する以下のようなログ エントリです。

#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2014-10-27 20:46:57 127.0.0.1 GET /search q=election+results&form=PRUSEN&mkt=en-us 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+6.4;+WOW64;+Trident/7.0;+Touch;+rv:11.0)+like+Gecko - 200 0 0 5265

この種のログ エントリには、要求されたリソースだけでなく、クライアント ブラウザー、リターン コード、要求の完了までにかかった時間なども含まれています。さらに高度なサービスでは、位置情報やアプリケーション固有の情報 (ログイン ユーザー ID) など、豊富な派生情報を含む使用状況データが記録される場合もあります。テスト エージェントや監視エージェントの使用状況データを除けば、実際のユーザーが関係しない使用状況データは存在しません。

運用記録データとは、サーバーやサービスの運用状況を測定したデータのことです。これには、CPU 使用率や CPU 温度、ディスク容量、ネットワークの転送レート、アプリケーションの例外、メモリ障害などのデータや、サーバーの起動直後やサービスの開始直後に記録されるデータなどがあります。最新のデータセンターでは、コンピューター デバイスだけでなく、エアコンの測定データ、機密データの保管場所への職員や訪問者の出入り、ドアの開閉といった運用のセキュリティ標準として求められる同様の情報もログ情報として記録されるのが一般的です。

本稿に付属するコード サンプルは、使用状況データの処理に重点を置いています。ただし、運用記録データの脆弱性の特定にも、ここで説明およびデモする多くの原理を当てはめることができます。また、使用状況データと運用記録データの相関関係を調べることで、セキュリティのインシデントを特定する可能性を高めることもできます。

攻撃のエンドポイント

大規模オンライン サービスに加えられる変更のペースが上がり、SDL の通常の手法 (コード レビュー ツールやスタティック分析ツールなど) だけで保護するのは難しくなっています。毎月コミットされる変更は数千に上ります。また、ある特定の時点を見ると、少なくとも数百もの新機軸が "試されて" います。このような新機軸の実験は、広くリリースする前に、ユーザーを選んで新機能を提示し、フィードバックを集めるために行われます。そのため、正しい開発プラクティスに従い、侵入テスト チームを用意して、絶えず脆弱性を突き止めることを試みるだけでなく、脆弱性の検出をできるだけ自動化することが重要になります。

2014 年末近く、Microsoft Bing のサービスは、毎日数百 TB の使用状況データを生成し、数千億件にも及ぶ要求をログに記録しました。このような要求の一部は、おそらく、脆弱性の検出や悪用を試みる攻撃であったと考えても差し支えありません。通常、Bing に対するクエリは、以下のような URL 要求を Bing サービスに送信することで行われます。

http://www.bing.com/search?q=election+results&form=PRUSEN&mkt=en-us

この例では、ユーザーは「election results」を検索しています。この URL には、検索語以外に、要求を発信した Web フォームとマーケット設定 (今回はユーザー言語が英語で、米国市場を示す) を特定する 2 つのパラメーターが含まれています。この URL から、q、form、mkt の各パラメーターを指定して、Bing ドメイン内の "search" アプリケーションへの呼び出しを行っていることがわかります。このような要求はすべて、以下の標準形式で表現されます。

http://www.bing.com/search?q=[]&form=[]&mkt=[]

Bing ドメイン内には、同様の要求に応答する別のアプリケーションもあります。"election results" の結果を動画形式で求める要求は以下のようになります。

http://www.bing.com/videos?q=election+results&form=PRUSEN&mkt=en-us

オンライン サービスが拡張されるにつれて、利便性や互換性を目的として新しいアプリケーションや機能が動的に追加されます。同じ要求でも、別の形式が許可される場合がよくあります。たとえば、Bing の動画は、以下の要求も受け付けます。

http://videos.bing.com/?q=election+results&form=PRUSEN&mkt=en-us

使用状況のログからサービスに対するすべての標準要求のリストを導き出せるとしたら、既知の悪意のあるペイロードをパラメーター値に挿入することを試みる脆弱性を調査できることになります。たとえば、侵入者は以下の要求を使用すれば、クエリ パラメーターに挿入されるクロス サイト スクリプティング (XSS) に対して Bing の動画アプリケーションが脆弱であるかどうかを確認できます。

http://www.bing.com/videos?q=<script>alert("XSS")
</script>&form=PRUSEN&mkt=en-us

脆弱性をスキャンする侵入者は、考え得るあらゆる組み合わせで悪意のあるペイロードをさまざまなパラメーターに挿入し、その応答をテストします。脆弱性を見つけたら、攻撃を開始します。攻撃を行う URL は、ごく一部のユーザーが間違ってリンクをクリックすることを期待して、迷惑メールに記載されるのが一般的です。URL に JavaScript のキーワードが含まれているかどうかを気にすれば問題ないと考えるユーザーもいるかもしれませんが、要求がエンコーディングされれば、すぐに攻撃だと判断するのは難しくなります。

http://www.bing.com/videos?&form=PRUSEN&mkt=en-us&q=%3CscRipt%3Ealert(%22XSS%22)%3C%2FScriPT%3E

サービスに対する標準要求のリストを入力として受け取り、脆弱性が疑われる箇所に悪意のあるペイロードを挿入し、サービスの応答から攻撃が成功したかどうかを検出するアプリケーションを作成することができます。複数の種類の脆弱性 (XSS、SQL インジェクション、およびオープン リダイレクト) を個別に "検出する" コードは、Web 上に見つかります。今回は、ログに記録された使用状況データから、オンラインのサービスへの "攻撃対象となる面" を見つけます。

処理環境

Web ログは通常複数のコンピューターに分散され、シーケンシャルなログ ファイルの読み取り効率を上げています (ファイルを分散ファイル システムの異なるストレージ デバイスにパーティション分割するとさらに効率が上がります)。そのため、Web ログの処理は、MapReduce フレームワークの優れた適用例になります。

今回の例の場合、Web ログを Microsoft Azure Blobs の InputContainer というコンテナーに置きます。処理プラットフォームとしては、Azure HDInsight Streaming という MapReduce ジョブを使用します。HDInsight クラスターの設定と構成の方法については、オンラインで情報を検索してください。今回紹介するコードが生成するバイナリは、HDInsight クラスターにアクセスできる ClusterBinariesContainer というコンテナーに置きます。コードが実行され、入力を処理すると、出力が ClusterOutputContainer というコンテナーに作成され、同時に状態情報が ClusterStatusContainer というコンテナーに保存されます。Azure HDInsight の処理構成図を図 1 に示します。

Azure HDInsight の処理環境
図 1 Azure HDInsight の処理環境

独自の環境で利用する場合は、図 1 のプレースホルダー名を固有の構成の値に置き換える必要があります。この置き換えは、構成ファイルで設定します。HDInsight ジョブを作成および実行する Windows PowerShell のスクリプトは、図 2 に示す XML 構成ファイルを読み取ります。この構成ファイルを設定したら、おそらく Microsoft Azure PowerShell プロンプト内から使用状況データを分析するスクリプトを実行することになりますが、ストレージとコンピューティング サービスにアクセスできるように Azure アカウントを正しく構成しておきます (Get-AzureAccount コマンドレットと Add-AzureAccount コマンドレットのヘルプを参照)。

図 2 Windows PowerShell スクリプトの XML 構成ファイル

<?xml version="1.0" encoding="utf-8"?>
<Configuration>
  <SubscriptionName>Your-SubscriptionName</SubscriptionName>
  <ClusterName>Your-ClusterName</ClusterName>
  <ClusterStorageAccountName>Your-ClusterStorageAccountName
    </ClusterStorageAccountName>
  <ClusterBinariesContainer>Your-ClusterBinariesContainer
    </ClusterBinariesContainer>
  <MapperBinary>UsageDataMapper.exe</MapperBinary>
  <ReducerBinary>UsageDataReducer.exe</ReducerBinary>
  <ClusterOutputContainer>Your-ClusterOutputContainer</ClusterOutputContainer>
  <ClusterStatusContainer>Your-ClusterStatusContainer</ClusterStatusContainer>
  <InputStorageAccountName>Your-InputStorageAccountName
    </InputStorageAccountName>
  <InputStorageAccountKey>Your-InputStorageAccountKey</InputStorageAccountKey>
  <InputContainer>Your-InputContainer</InputContainer>
  <DeployBinaries>true</DeployBinaries>
  <DeployFlavor>Release</DeployFlavor>
  <JobTimeOut>3600</JobTimeOut>
</Configuration>

標準要求にマップする

MapReduce 処理環境を使用してオンライン サービスの攻撃対象となる面を把握するには、マッパー アプリケーションを作成して Web ログから URL を抽出し、その URL を標準形式に変換します。変換後の値は、その後重複データを削除するレジューサのキーになります。これは、単語数をカウントする HDInsight 用サンプル アプリケーションで使用されているのと同じ原理です。以下のコードは、マッパー アプリケーションのメイン エントリ ポイントの例ですが、コメントや検証コードは取り除いています。

public static void Main(string[] args)
{
  Console.SetIn(new StreamReader(args[0]));
  string inputLogLine;
  while ((inputLogLine = Console.ReadLine()) != null)
  {
    string outputKeyAndValues =
      ExtractKeyAndValuesFromLogLine(inputLogLine);
    Console.WriteLine(outputKeyAndValues);
  }
}

このコードでは、すべての入力行を順番に処理して、一意キーと、解決する問題に関連する補足値を抽出します。たとえば、最も一般的なユーザー クエリを調べるなら、キーはクエリ パラメーターに渡される値になります。ログ行そのものは、以下のようになっています。

#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2014-10-27 20:46:57 127.0.0.1 GET /search q=election+results&form=PRUSEN&mkt=en-us 80 - 127.0.0.1 Mozilla/5.0+(Windows+NT+6.4;+WOW64;+Trident/7.0;+Touch;+rv:11.0)+like+Gecko - 200 0 0 5265

列 cs-uri-stem と列 cs-uri-query には、要求の標準形式を取得するために解析する必要がある関連情報があります (サンプル コードにはマルチ ホスティング処理は含まれていません)。ログ行からキーと値を抽出する関数を図 3 に示します。

図 3 ログ行からキーと値を抽出する関数

private static string ExtractKeyAndValuesFromLogLine(string inputLogLine)
{
  StringBuilder keyAndValues = new StringBuilder();
  string[] inputColumns = inputLogLine.Split(DataFormat.MapperInputColumnSeparator);
  string uriReference = inputColumns[DataFormat.MapperInputUriReferenceColumn];
  string uriQuery = inputColumns[DataFormat.MapperInputUriQueryColumn];
  string parameterNames = ExtractParameterNamesFromQuery(uriQuery);
  // Key = uriReference + separator + parameterNames
  keyAndValues.Append(uriReference);
  keyAndValues.Append(DataFormat.ReferenceFromQuerySeparator);
  keyAndValues.Append(parameterNames);
  keyAndValues.Append(DataFormat.MapperOutputColumnSeparator);
  keyAndValues.Append(DataFormat.OneOccurrence);
  return keyAndValues.ToString();
}

足りないのは、クエリ列からパラメーター名を抽出することに関するロジックのみです。この作業を実行するコードを図 4 に示します。前述のサンプル行の場合、この関数の入力は以下のような文字列になります。

q=election+results&form=PRUSEN&mkt=en-us

図 4 クエリからパラメーター名だけを取得する関数

private static string ExtractParameterNamesFromQuery(string query)
{
  StringBuilder sb = new StringBuilder();
  // Go through each parameter adding to output string
  string[] nameValuePairs = query.Split(DataFormat.ParametersSeparator);
  Array.Sort(nameValuePairs, StringComparer.InvariantCultureIgnoreCase);
  List<string> uniqueParameterNames = new List<string>();
  foreach (string nameValuePair in nameValuePairs)
  {
    int indexOfSeparatorParameterNameFromValue =
      nameValuePair.IndexOf(DataFormat.ParameterNameFromValueSeparator);
    string paramName = nameValuePair;
    paramName = nameValuePair.Substring(0,
      indexOfSeparatorParameterNameFromValue);
    uniqueParameterNames.Add(paramName);
    sb.Append(paramName);
    sb.Append(DataFormat.ParameterNameFromValueSeparator);
    sb.Append(DataFormat.OneOccurrence);
    sb.Append(DataFormat.ParametersSeparator);
  }
  return sb.ToString();
}

サンプル コードで使用する標準形式は、パラメーター値を削除し、パラメーター名を並べ替えて、以下のようにまだ有効なクエリ文字列に変換します。

form=1&mkt=1&q=1&

Web 要求はパラメーターの順序を入れ替えても問題ないため、パラメーター名の並べ替えは重複を避けるのに便利です。パラメーター値に使用するプレースホルダーは短くするため "[]" ではなく "1" にします。この方法は、要求パラメーターのすべての組み合わせの中でパラメーターが出現する回数をカウントする場合などにも使用できます (図 4 参照)。

攻撃対象面を減らす

マッピングのコードでは、Web ログの各行をシーケンシャルに読み取っってから、行ごとにキーと値を出力します。MapReduce には "結合" フェーズがあります。このフェーズでは、レジューサのコードで処理するために、同じキーを持つすべてのレコードをまとめます。入力のログに検索クエリを実行する行が複数ある場合は、このフェーズで以下のように同じ出力がまとめられます。

search?form=1&mkt=1&q=1&         1
search?form=1&mkt=1&q=1&         1
search?form=1&mkt=1&q=1&         1

図 5 にレジューサのコードの概要を示します。レジューサは、入力行を読み取り、キーと値に分離します。キーが変わるまでカウンターを保持し、結果を出力します。

図 5 レジューサのメイン ループ

public static void Main(string[] args)
{
  string currentKey, previousKey = null;
  int count = 0;
  Console.SetIn(new StreamReader(args[0]));
  string inputLine;
  while ((inputLine = Console.ReadLine()) != null)
  {
    string[] keyValuePair =
      inputLine.Split(DataFormat.ReducerInputColumnSeparator);
    currentKey = keyValuePair[0];
    if (currentKey != previousKey)
    {
      Console.WriteLine(DataFormat.ReducerOutputLineFormat, 
        previousKey, count);
      count = 1;
      previousKey = currentKey;
    }
    else count++;
  }
  Console.WriteLine(DataFormat.ReducerOutputLineFormat, 
    previousKey, count);
}

ここで示すコードやスクリプトは、別の目的に簡単に変更できます。ExtractKeyAndValuesFromLogLine 関数を変更してキーとしてパラメーターの値を持つようにすると、値の頻度分布を得ることができます。現在のフォームの場合、出力は攻撃対象となる面を示すリストで、正規化されたアプリケーション パスと要求の頻度を示します。

search?form=1&mkt=1&q=1&         3
video?form=1&mkt=1&q=1&           10

サービスのトラフィックを理解する

脆弱性を明らかにする事前侵入テストを実行しない場合でも、今回出力される攻撃対象となる面はサービスで何が行われているかを理解するのに十分役立ちます。要求の頻度は、時間、曜日、季節などの要因によって変わりますが、長い時間をかけて調べると、通常の動作は、機能のリリース時や組織的な攻撃を受けた場合に変化します。たとえば、Bing では 1 日に数千億件の要求を受け取ります。攻撃対象となる面をリストしてみると、通常、1 日に数十万件の要求が一覧されます。一覧されるパスの中には、予想外のものもあります。図 6 は、標準的な 1 日を調べ、攻撃対象となる面のリストとして見つかったものをまとめた表です。最初の行は、上位 10 件の標準パスの合計が、通常の要求トラフィックの 89.8% を占めていることを示します。上位 10 件に続く 10 件のパスの合計は、要求総数の 6.3% を占めます (つまり、上位 20 件で 96.1% です)。これらのパスは、実のところ、サービスで上位のアプリケーションです。それ以外のパスの合計は 4% 未満です。このような要求パターンは、シンジケート コンテンツを持つサイト (MSN.com など) の場合は大きく異なります。

図 6. 2013 年の Bing を対象とする要求パスの標準的な分布

標準パス パーセント
上位 10 89.8
上位 20 96.1
1,000 件以下の要求パス 99.9
100 件以下の要求パス 99.6
10 件以下の要求パス 97.6
1 件の要求パス 67.5

要求の約 3 分の 2 が一意のパスになっているのに注目することが特に重要です。このような要求の中には、特定の機能をトリガーできるアプリケーション パラメーターを調べようとする攻撃者が行っている要求もあります。ただし、オンライン サービスは、本質的にこのようなトラフィックを多く生成します。数年前のサービスへのリンクが人間や自動プロセスによってアクティブになる可能性が残っています。攻撃者を探す間は、古い URL 用に互換モードを用意して、新しいバージョンのアプリケーションへ自動的にリダイレクトする必要性が明らかになる場合があります。これはすばらしい成果です。

攻撃を行うアプリケーションは、注意して開発する必要があります。検出機能にまったく問題がないともの想定できたら、次に正しく絞り込む必要のあるサービスに負荷をかけます。サービス拒否攻撃を生み出すことや、実際のユーザーのパフォーマンスに影響を与えることは避けなければなりません。また、膨大な誤検知が生じるのを回避することも必要です。誤検知があると、攻撃を行うサービスのインシデント レポートはすぐに無視されるようになります。

サービス データから学習する

ML により、命令や規則を直接コーディングするだけでは難しい、複数のプロセスを自動化することができるようになります。たとえば、人間がカメラの前にいることを検出するコンピューター ビジョン アプリケーションのコードを想像するのは困難です。しかし、マイクロソフトの Kinect チームは、数千もの画像に深度情報情報のラベルを付け、ML モジュールを "トレーニング" して、人間がカメラの前にいることをほぼ正確に検出できるようにしました。人間の存在だけでなく、体の位置も示すラベルを深度画像に付け、学習プロセスを可能にしています。

既知のカテゴリ (XSS、SQL インジェクションなど) に分類される要求を生成する攻撃サービスは、オンライン トラフィックの評価に ML の手法を使用するために、プロセスの重要な部分を自動化します。これにより、大規模な合成グラウンド トゥルースが生成されます。使用状況のログをチェックすると、攻撃サービスによって特定の時間に行われることがわかっている攻撃要求がすべて容易に見極められます。攻撃要求は、まだ既知の分類 (標準要求または悪意のある要求) が存在しない場合、ユーザー要求と混在しています。

サービスに影響するトラフィックと完全に一致するデータの層を作成するには、そのトラフィックの性質を理解する必要があります。1 日に 1000 億件の要求を受信するサービスの使用状況データの 0.1% を使用して実験サンプルを生成するとしても、まだ 1 億件の要求を理解しなければなりません。合成データだけがこのような初期グラウンド トゥルースを作成するのに役立ちます。

品質の高いグラウンド トゥルースと適切なツールがあるとすると、ユーザー要求を評価する ML ソリューションの学習プロセスの反復サイクルは図 7 に示すようになります。実験はグラウンド トゥルースの合成データから始め、ML モジュールのトレーニング プロセスを開始して要求を (標準、XSS、SQL インジェクションなどのカテゴリに) 分類するか、回帰します (要求が 1 つ以上のカテゴリに属する確度を示します)。次に、ソリューションの一環としてこの ML モジュールを配置し、評価要求の受信を開始します。出力は、ML モジュールによって要求が正しく特定されたか、疑わしい要求が見逃された (偽陰性) か、誤判定が生成された (偽陽性) かを示す評価プロセスに送ります。

機械学習に基づくソリューションを作成する学習サイクル
図 7 機械学習に基づくソリューションを作成する学習サイクル

最初の実験で、合成データに基づき、十分優れた ML モジュールが生成された場合、そのモジュールはかなり正確で、実際のユーザー要求を誤って評価することはほとんどありません。次に、誤った評価を下した要求に適切なラベルを付け、グラウンド トゥルースに追加します。実験とトレーニングを数回繰り返したら、正確性が回復された新しい ML モジュールが生成されるようになります。このプロセスを注意深く繰り返していくと、トレーニング プロセスで使用されるグラウンド トゥルースに最初の合成データよりも小さな部分になり、ML モジュールの反復でユーザー要求が正確に評価されるようになります。追加の検証を行う場合、ML モジュールをオフライン アプリケーションに使用して、使用状況のログを調査し、悪意のある要求を特定することができます。十分な開発を行った後、ML モジュールをオンラインに配置して、リアル タイムに要求を評価し、以前にバックエンドのアプリケーションを襲った攻撃を防ぎます。

まとめ

堅実な開発プロセス (SDL など) に従い続ける一方、オンライン サービスはあらゆる時点で攻撃を受ける可能性があることを想定しておくことも必要です。使用状況のログには、このような攻撃の発生方法に関する洞察力に満ちた情報が含まれています。攻撃対象となる面を把握することは、事前にサービスを攻撃して脆弱性を見つけ、悪用される前に脆弱性を塞ぐのに役立ちます。攻撃サービスをビルド後に合成グラウンド トゥルースを作成することで、ML の手法を使用して ML モジュールをトレーニングし、サービスの要求を評価することができます。攻撃サービスをビルドするのは簡単な作業ではありませんが、短期ビジネスや長期ビジネスで投資に見合う以上の成果が得られます。


Alisson Sol は、マイクロソフトの主席アーキテクトです。彼は、ソフトウェア開発に長年の経験があり、画像処理、コンピューター ビジョン、ERP とビジネス インテリジェンス、ビッグ データ、機械学習、分散システムに重点的に取り組んできました。マイクロソフトに勤務し始めた 2000 年以前には、3 つのソフトウェア会社を共同設立し、複数の技術論文を公開し、複数の特許出願を行いました。ブログは AlissonSol.com/blog (英語) です。

Don Ankney は、Microsoft Information Platform Group のシニア セキュリティ研究者で、マイクロソフトの機械学習の投資をサービスのセキュリティ領域に適用することに取り組んでいます。彼は、セキュリティに重点を置く非営利の教育団体である Black Lodge Research の創設メンバーの 1 人でした。地域の集まり、ワークショップ、およびアンカンファレンスで定期的にセキュアな開発手法を教えています。

Eugene Bobukh は、マイクロソフトのシニア セキュリティ/データ プログラム マネージャーです。科学的アプローチをセキュリティの問題に適用することに重点を置いて、2000 年から、.NET、Silverlight、および Internet Explorer を含む 200 個を超えるマイクロソフトのリリースを対象としたセキュリティ テストおよびセキュリティのレビューを行ってきました。Bobukh の取り組みは、blogs.msdn.com/b/eugene_bobukh (英語) で一部紹介されています。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Barry Markey および Viresh Ramdatmisier に心より感謝いたします。