MSTICPyは、 Jupyter Notebookで情報セキュリティ調査を行うためのPythonライブラリで、 Microsoftが開発しています。
MSTICPyの機能の1つとして、さまざまなログ基盤にクエリーを投げて、結果をPandasデータフレームとして取り込むことができます。 Microsoft Sentinelに最もよく対応していますが、 Microsoft 365 Defender、Microsoft Graph API、Splunk、 SumoLogic、Azure Data Explorer、Cybereason、OSQuery、 Velociraptorと種類は多岐にわたります。
私たちR利用者も、ここに便乗できます。 Reticulateパッケージを使えば、 Pythonオブジェクトをシームレスに扱えるからです。
以下ではSplunkを例にして話を進めます。
VirtualEnvかCondaかで、Pythonの専用環境を作ります。私はMambaforgeを使っています。以前は手動でインストールしていましたが、いまはChocolateyから入れています。
Mambaforgeが入っていることを前提に、以下のコマンドで「msticpy」という名前の Conda環境を作ります。そしてアクティベーションし、pipでMSTICPyライブラリをインストールします。「all」をつけると補助パッケージが全部入りになります。
mamba create -n msticpy pip notebook ipykernel
conda activate msticpy
pip install msticpy[all]
RStudioでは、プロジェクトごとにPython実行環境を指定できます。〈Tools〉-〈Project Options…〉のPythonタブにて、先に指定した実行環境のPythonを選択します。
このとき、最初はConda環境の検索に失敗するかもしれません。その場合には、フィールドに直接パスを書き込みます。パスは、たとえばMambaforgeをC:/Users/MyName/home/mambafogeにインストールしているとすると、
~/home/mambaforge/envs/msticpy/python.exe
のようになります。
RStudioでMSTICPyを使うには、2つの方法があります。 1つは、RMarkdownのコード片で言語をPythonに指定して、純粋にPythonコードとして書くことです。もう1つは、RからPythonオブジェクトを使うことです。
ここでは、後者の方法を選択します。このときのポイントは、 Pythonの「.」が「$」になる――という点です。
以下のようにします。RStudioで指定しているので use_python()は不要ですが、私は備忘のためと、 VSCodeなどRStudio以外から利用するときに備えて記載しています。
pacman::p_load(tidyverse, reticulate)
use_python("~/home/mambaforge/envs/msticpy/python.exe")
mp <- import("msticpy")
prov_splunk <- mp$QueryProvider("Splunk")
3行目~4行目は、Pythonの次のコードに相当します。
# Python
import msticpy as mp
prov_splunk = mp.QueryProvider("Splunk")
私は手元のPCにVMware Workstation Proを入れているので、今回の例のためにSplunkの試験環境を作りました。会社で利用しているSplunk Cloudでも同様に接続できます。
# このように書いてはいけない
prov_splunk$connect(host="splunk-photon", port="8089", username="admin", password="testpassword")
上は接続例ですが、パスワードが平文で入る上記の書き方をすべきではありません。せめてkeyringパッケージを使いましょう。
pacman::p_load(keyring)
keyring::key_set_with_value("splunk-photon",
username = "admin",
password = "testpassword")
として資格情報マネージャーにパスワードを登録しておけば、
prov_splunk$connect(host="splunk-photon", port="8089",
username="admin", password=keyring::key_get("splunk-photon", "admin"))
## Connected.
上のようなコードで呼び出すことができます。
もっとよいのは、パスワードではなくアクセストークンを使うことです。トークン認証が有効化されているSplunk環境では、 Settings -> TokensでJWTトークン(eyJraWで始まる)を生成することができます。それを下のようにkeyringに登録しておくと、
pacman::p_load(keyring)
keyring::key_set_with_value("splunk-photon",
username = "admin-token",
password = "eyJraW...")
下のように呼び出すことができます。
prov_splunk$connect(host="splunk-photon", port="8089",
splunkToken=keyring::key_get("splunk-photon", "admin-token"))
## Connected.
ちなみに、接続情報を設定ファイルである msticpyconfig.yamlに書いておくと、 connect()の引数は必要ありません。
ふつうはサーチ文を作るところですが、急ごしらえの環境のため、 Splunkに同梱されているサンプルデータをinputlookupで持ってくるクエリを作ります。
spl_1st <- r"(
| inputlookup security_example_data.csv
| table timestamp threat_src_ip threat_dest_ip threat_status threat_type
| head 5
)"
df_1st <- prov_splunk$exec_query(spl_1st, timeout=300)
df_1st
## timestamp threat_src_ip threat_dest_ip threat_status threat_type
## 1 1659312000 202.236.255.160 37.216.171.101 detected origin
## 2 1659340800 55.253.83.181 141.20.243.217 mitigated origin
## 3 1659369600 207.88.11.238 120.140.35.130 mitigated origin
## 4 1659398400 30.29.160.213 19.109.245.203 detected origin
## 5 1659427200 3.124.241.253 45.232.249.117 mitigated origin
データフレームを見ると、timestampの型が文字列型になっていることに気づかされます。これは、Splunk REST APIの仕様ではないかと思います。というのは、cURLで直接書いても文字列として返ってくるからです。
$ curl -s -k -u admin:testpassword https://splunk-photon:8089/services/search/jobs/export `
-d search="| inputlookup security_example_data.csv `
| table timestamp threat_src_ip threat_dest_ip threat_status threat_type `
| head 1" `
-d output_mode=json -d exec_mode=oneshot | jq
{
"preview": false,
"offset": 0,
"result": {
"timestamp": "1659312000",
"threat_src_ip": "202.236.255.160",
"threat_dest_ip": "37.216.171.101",
"threat_status": "detected",
"threat_type": "origin"
}
}
そういうわけで、型の変換は自前で行いましょう。ついでにデータフレームをtibbleにしています。
df_1st_mod <-
prov_splunk$exec_query(spl_1st, timeout=300) |>
mutate(
timestamp = timestamp |> as.integer() |> as_datetime(tz = "UTC")
) |>
as_tibble()
df_1st_mod
## # A tibble: 5 × 5
## timestamp threat_src_ip threat_dest_ip threat_status threat_type
## <dttm> <chr> <chr> <chr> <chr>
## 1 2022-08-01 00:00:00 202.236.255.160 37.216.171.101 detected origin
## 2 2022-08-01 08:00:00 55.253.83.181 141.20.243.217 mitigated origin
## 3 2022-08-01 16:00:00 207.88.11.238 120.140.35.130 mitigated origin
## 4 2022-08-02 00:00:00 30.29.160.213 19.109.245.203 detected origin
## 5 2022-08-02 08:00:00 3.124.241.253 45.232.249.117 mitigated origin
ここまでくれば、こちらのものです。あとはTidyverseの関数群を使って、自由自在に加工できます。
今回はMSTICPyでデータを取得することに焦点を当てましたが、嬉しいことは他にもあります。
私自身は、もっと可能性を追求していきたいと考えています。
最後にここまで書いたコード断片を、以下にまとめておきます。
pacman::p_load(tidyverse, reticulate)
use_python("~/home/mambaforge/envs/msticpy/python.exe")
mp <- import("msticpy")
prov_splunk <- mp$QueryProvider("Splunk")
prov_splunk$connect(host="splunk-photon", port="8089",
splunkToken=keyring::key_get("splunk-photon", "admin-token"))
spl_1st <- r"(
| inputlookup security_example_data.csv
| table timestamp threat_src_ip threat_dest_ip threat_status threat_type
| head 5
)"
df_1st <-
prov_splunk$exec_query(spl_1st, timeout=300) |>
mutate(
timestamp = timestamp |> as.integer() |> as_datetime(tz = "UTC")
) |>
as_tibble()
df_1st