FFRIエンジニアブログ

株式会社FFRIセキュリティのエンジニアが執筆する技術者向けブログです

CODE BLUE 2018 参加報告記 その2

はじめに

CODE BLUE 2018にて、JPCERT/CCが作成、公開しているLogonTracerというツールについての発表がなされました。

このツールのソースコードはGitHub上で公開されています。またインストールの手順も分かりやすく記載されているので、誰でも簡単に使用してみることが可能です。

この発表を聞いて非常に興味深いと感じたので、実際にこのツールを動かしてみながらその内容について勉強してみました。

本稿ではその内容についてご紹介させて頂こうと思います。

LogonTracerとは

JPCERT/CCのブログにも解説がありますが、ここでも簡単にご紹介致します。

標的型攻撃などのマルウェア感染のインシデントが発生して不正な侵入が発覚した後には、いったいどのホストが侵入を受けたのか、どのアカウントが利用されたのか、というような調査が必要となります。

その際にはActiveDirectoryのイベントログからログオンに関するイベントを解析する作業を行うそうですが、イベントログからテキストにしてそれを解析するというようなやり方の場合、中々大変な作業になってしまうのだそうです。

そのような大変さを解消して適切な解析が行えるようにサポートするためのツールが必要だ、ということで開発されたのがこのツールであるとのことです。

実際に使ってみる

実際に使ってみるには、GitHubのページのUse LogonTracerという箇所に記載されている手順を参考にして、Linux/Mac上でのインストールか、WindowsであればDocker for Windows上でのインストールを行うことでできます。

私はDocker for Windowsを利用して自分のPCに構築しました。

Docker for Windowsのインストーラーはこちらからダウンロードすることができます。

このページにも記載されていますが、Docker for WindowsをWindows上で動作させる場合、Hyper-V機能を有効化する必要がある関係でVirtualBoxやVMWareなどの仮想マシンが動作しなくなります。

一つのPCで両方使用する必要がある方はHyper-V機能の有効/無効切り替えを行うと良いでしょう。(詳細はこちらの記事をご参考ください。)

Docker for Windowsのインストールが完了したら、こちらのdockerイメージの起動手順に従って「PULL」「RUN」「Webアクセス」を行うだけです。[IP_Address]と書かれている部分は「127.0.0.1」など適宜置き換えて使用します。

ログオンイベントのグラフ表示

Webブラウザでhttp://[IP_Address]:8080/を入力してみると下記のような表示となります。

図1 GUI画面

左上の赤枠の部分にあるAll Usersというボタンをクリックすると、アカウントとホストを関連付けた図が表示されます。

図2 グラフ表示

丸いものがアカウント(赤:管理者権限、青:一般ユーザ)で、ひし形のものがホストです。

アカウントからホストに向けて伸びた矢印はログイン関連のイベントログが存在することを表しており、矢印上の数字はイベントログIDを表しています。

どのアカウントがどのホストに対してアクセスしたのかという情報を目で見て確認することができるのが分かります。

重要なアカウント/ホストの検出

このようにアカウントとホストの関連を図で確認することで非常に分かりやすくなりますが、実際の組織運用図は非常に複雑になるため、これだけで分析を進めていくことはやはり困難な場合が多いと発表の中で説明がありました。

そのために、悪意のあるログオン動作の兆候を検出できるように、アルゴリズムの計算によってホストまたはアカウントの重要度をスコア付けし、ランキング表示できるようにしているそうです。

図1のGUIの右側に「Rank User」「Rank Host」と記載された表が2つ表示されており、これがその重要度スコアによってランキングされたものとなっています。

そのアルゴリズムとは、PageRank、隠れマルコフモデルによる検出などの処理によって行われているそうです。

これらについてソースを見ながら詳しく見ていきたいと思います。

PageRankとは?

Wikipediaのページランクの説明によると、もともとWebページの重要度を決定するために発明されたアルゴリズムとのことです。

英語版のWikipediaの説明に詳細な説明とpythonによるコードが記載されています。

具体的な計算例を作成してみました。

簡単に例として、世の中に4つのWebサイトしかなくそれぞれAとBとCとDという名前で、各サイトはそれぞれリンクを張っているとします。

下記の図はB、Cのサイトは参照先としてAへのリンクがあり、AのサイトはBへのリンクがあったとします。DのサイトはAとCへのリンクがある、という状態を表しています。

図3 4つのサイト

PageRankアルゴリズムにおいては「すべてのサイトから参照されているA > Aが参照しているB > Dが参照しているC > どこからも参照されていないD 」となるようなスコア計算をするというのが基本的なアイデアです。

この図に記載した数値はWikipediaの説明を元に実際に計算してみたPageRankスコアです。

実際にPageRankを計算するには下記のようになります。

df : damping factor値 = 0.85
Aのスコア = 1/4 * (1 - df) + df * (B + C + (D / 2))
Bのスコア = 1/4 * (1 - df) + df * (A)
Cのスコア = 1/4 * (1 - df) + df * ((D / 2))
Dのスコア = 1/4 * (1 - df) + df * (0)

これを繰り返す

これくらい単純であればExcelでも計算できますね。実際にPageRankを計算してみた結果が下記となります。

図4 Pagerank計算

徐々に各スコアの変化が収束する様子が見て取れて、A>B>C>Dの順にスコアが付けられることが分かります。

ちなみにこの例では、各ページのスコアは合計すると常に1となります。

Webページにアクセスする人の行動を「あるページを見ている人は一定の確率(df=85%)でそのページ内にリンクされているページに飛び、残りの確率でランダムに別のページに移る」というものであると仮定の下で、それぞれのページを見る確率はどれほどか?というのを計算するのとこのスコア計算が同じであるからです。

ただし、どこにもリンクを張っていないサイトがこの中に存在すると、そのサイトに流れ込んだスコアが他のサイトに反映されなくなり、計算を繰り返すたびに合計スコアが減っていくような動作になります。

これによって合計が必ず1になるわけというわけではありません。

LogonTracerはこのPagerankスコアを算出することによって、悪意をもって様々なホストに侵入を試みるなどをしたアカウントを検出できるようにする、というところを意図しているのだそうです。

LogonTracerにおけるPagerankの計算実装箇所

LogonTracerにおいて実際にPageRankの計算を行っている箇所はこちらの関数です。

アカウント⇒ホストのリンクとホスト⇒アカウントの関連を作成し、それをまとめてPageRankスコアを計算するループ処理が存在しますが、ここまでの説明とは大きく異なる箇所があります。

それは、各要素ごとにdamping factor値を変化させている処理があるという点です。

発表によると、このようにdamping factor値の調整を行うことによってより重要性の高いもののスコアが高くなるようにPageRankのスコアを調整するという先行研究事例(※1)があるのだそうです。

ここではそれを利用して、イベントログの怪しい挙動を検出した場合にdamping factor値を下げることで、PageRankのスコアを上げるということを意図しているとのことでした。

調整条件は複数ありますが、その中の一つにif (page in hmm)という記述があります。

ここでの「hmm」変数には、Hidden Markov model(隠れマルコフモデル)に基づいたアルゴリズムによって検出したアカウント名が含まれています。

したがって、ここでは隠れマルコフモデルによって検出したアカウント名に合致したアカウントのdamping factor値に対して「-0.2」の調整を掛けることで、スコア計算結果を上げようとしているのだということが見て取れます。

隠れマルコフモデルとは?

隠れマルコフモデルとは、観測された事象を元に隠れた状態を推定するというモデルです。

先ほどの「hmm」変数に怪しいアカウントを入れている箇所はdecodehmmという関数の中のこのあたりの処理です。

ここの周辺処理を日本語にすると下記のようになります。

  • あるアカウントがあるホストに対してどんなイベントログがあったのかによって0~4のIDを割り振って、それを時系列順に並べたものを用意する
  • 「model.predict」にそれぞれインプットする
  • 受け取った結果から重複を除去した結果、「2種類」となったら怪しいということでそのアカウントを「hmm」変数リストに保持する
  • 全てのアカウント⇒ホストのイベントでそれを繰り返す

この「model.predict」という関数はhmmlearnというライブラリのMultinomialHMMクラスのpredictという関数です。

このpredict関数の説明書きを読みますと、パラメータXに対応する最も尤もらしい時系列データを見つけるというようなことが書かれています。

この「尤もらしい」というのは尤度(ゆうど)という考え方で、確率の一種です。

サイコロを振って1が出る確率はいくらですか?という問いに「1/6」と答えるような問題は学校の授業で解いたことがあるという人も多いでしょう。

このようなサイコロを振った時に1が出る確率が「1/6であると分かっている」上でその目の出方を考えるのとは逆で、何回かサイコロを振ってみて目の出方を観測したものから「本当にこのサイコロで1が出る確率は1/6なのか?」ということを知りたいケースにおいて考えられるのが尤度です(※2)。

このケースで言うとサイコロを振って出てきた目のことを「観測事象」と呼び、サイコロを振って1が出る確率の方を直接観測できない事象として「真の姿=(隠れている)」などと呼ぶことがあります。

なるべく身近な例を挙げようということで、「夕焼けがきれいだと明日は晴れる」というような経験則を例にしたいと思います。

日本では雨雲は西からやってくることが多いので、西の空に雲がなければ次の日が晴れになることが多いという経験から来ていると思います。

「明日が晴れるかどうか」という事象(状態)は直接観測する方法がありませんが、晴れ / 曇り / 雨の日の前日に西の空が晴れていた(夕焼けがきれいだった)かどうかについての確率は統計データとして収集することができます。

仮に「晴れ / 曇り / 雨の日」それぞれの「前日の夕焼けがきれいな確率」の統計を取った結果が80%、60%、40%であったと仮定します。

このとき、P(前日の夕焼けがきれい | 晴れの日) = 0.8という書き方をして「条件付き確率」と呼びますが、これを「今日の夕焼けがきれい」という観測事象から推測する明日の天気のもっともらしさとして解釈しようという考え方が尤度です。

「夕焼けがきれい」という事象を観測することによって、明日の天気が晴れ / 曇り / 雨のいずれになるかの尤もらしさは80 : 60 : 40の比率であることから予測を立てよう、ということです。

この中で最も尤もらしいものは80%の「明日は晴れ」ということになります。

ちなみに、晴れ / 曇り / 雨の日のそれぞれの条件付き確率を合計すると180%となりますが、これらは晴れ / 曇り / 雨の日の確率分布ではなく、夕焼けがきれいだった日の翌日のそれぞれの発生しやすさを表すようなものとは違うということを述べておきます。(自分はこんがらがりました)

隠れマルコフモデルとは、観測できる時系列事象をインプットとして、それの元になる真の姿(時系列順にどの状態に遷移したのか)を推定するためのアルゴリズムです。

パラメータとして、各状態の遷移確率、それぞれの状態のときにそれぞれの観測事象が発生する確率などをパラメータとして推定します。

これらのパラメータは初期値として設定することもできるし、入力情報からの学習によって生成することもできます。

LogonTracerでは隠れた状態を3つとして定義し、それぞれ「ログオンしていない」「ログオンの試行中」「ログオンしている」というような位置づけとしていると発表の中で説明されていました。

あるアカウントがホストにログインしたイベントの時系列情報から推定した状態遷移が「3種類ではなく2種類で構成されている」ものが怪しい、ということで「hmm」変数にアカウント名を加えていることが見て取れます。

このように検出することによってどのようなメリットがあるのかについてという詳細についての説明までは聞くことができませんでしたが、論理的な状態遷移としてこういった特徴がみられたという知見の上で、パラメータの学習機能を利用して運用組織ごとの特徴をとらえ、検出精度を高めたいとする意図があるのではないかなと推測しました。

これらの動作過程を確認するには

GitHubのLogonTracerのソースコードと一緒に、解析用のイベントログデータのサンプルがあります。

こちらをダウンロードして頂き、LogonTracerのGUI画面左下にある「Upload Event Log」というボタンからこのファイルをアップロードしてみてください。

アップロードの度にこれらの解析処理が実行されるようになっています。

ただアップロードしただけでは中で起きていることを知ることができませんが、実行するスクリプトのソースにログ出力文を追加することで詳細な処理内容を確認することができます。

docker exec -i -t [dockerのコンテナ名] bashというコマンドを実施することで実行中のLogonTracerコンテナに入ることができます。

アップロード時に実行されるスクリプトは下記の場所に存在しますので、ログ出力処理を追加するなどの対応が可能です。

vi /usr/local/src/LogonTracer/logontracer.py

例えば「実際にアカウントやホストにつけられた最終的なスコアが知りたい」というような場合であれば、pagerank関数の最後で「nranks」変数をログに出力させるために、

    print("nranks : ")
    print(nranks)

というような文を付け加えてみてください。

そうすると、nrank : {'urayasu.chiba@': 0.0, 'machida.kanagawa@': 0.5803173334414728,…}などという変数の内容がログに出力されるので、アップデート処理におけるログ表示画面上でこれを確認することができます。

興味を持たれた方は、詳細な処理について動かしながら確認してみることにチャレンジしてみてはいかがでしょうか?

発表時に言及されていた課題について

現在のLogonTracerの検出アルゴリズムでは、「不正に取得したアカウントで一つのホストにしかログインしなかったケース」を検出することが難しい、とお話されていました。

先ほどのサンプルイベントログにおいて、隠れマルコフモデルによる検出対象となるアカウントは「yokohama.kanagawa@」と「urayasu.chiba@」の2つです。

しかし、「urayasu.chiba@」のアカウントは1つのマシンにしかログインしておらず中心性スコアが低いために最下位となっています。

発表で述べられていた課題点はこのあたりのことかもしれません。

今後JPCERT/CCではこういった点についての改善やリアルタイムに検知処理を行うようにするにはどうするかというような点について検討し取り組んでいく予定とのことでした。

おわりに

このツールにはここで紹介した以外のアルゴリズムによる工夫もなされていて、実に様々な工夫が詰まっていると思います。

プログラムを動かしてみながら勉強してみた感想ですが、絶対にいいものを作ってやるぞという情熱のようなものを感じられました。

そのあたりが紹介できたら良いなと思い本稿を作成しましたが、こういったものに興味がある方にとっての取っ掛かりになってくれれば幸いだなと思っております。

参考文献

  • ※1
    • 発表の中ではどの研究であるかについて直接言及はありませんでしたが、相互参照することによって意図的にPageRank値を上昇させようとする(collusion)サイトに対する耐性を高めようとする、という下記の資料のような内容ではないかと推測します。
    • Making Eigenvector-based Reputation Systems Robust to Collusion
    • ここではdamping factor値の逆のresetting probablity = εとして説明がなされています。collusion状態のサイトほどεの値を上げるほどPageRankスコアが下がりやすいという性質を利用して、εとのPageRankスコアの相関係数に応じて個々のε値を増加させるアイデアについての説明がなされています。
  • ※2