#はじめに
前記事の「Winlogbeat 7.2.0の新機能: JavascriptプロセッサのSysmonモジュールを使ってイベントログを採取してみる」の続編です。
今回の記事ではそのJavascriptプロセッサ機能を利用してWindowsイベントログをElasticsearchへ転送する前にWinlogbeat側(エンドポイント側)でフィルタまたは加工する独自モジュールをJavascriptで書いてみます。
- hello, worldをフィールドへ書き込むHelloWorldモジュール
- PowerShell実行イベントログのBase64エンコードされたコマンド部分(-encオプション)をデコードしてからElasticsearchへ転送するモジュール
※Javascriptプロセッサはまだ新しい機能ですので仕様変更も今後予想されます。詳細は公式リファレンスを参照してください。
※本記事で扱うサンプルモジュールはこちらへ置いておきます。
#利用するソフトウェア
- OS: Ubuntu 18.04
- DNSサーバ: Dnsmasq (2.79)
- Elasticsearch (6.6.0)
- Kibana (6.6.0)
- Winlogbeat (7.2.0)
- Sysmon (v10.2)
- VMware Workstation Player等のVMソフトウェア
- Windows 10 (クライアントPC)
インストール方法や評価環境の構築の詳細は「標的型攻撃に対するJPCERT/CCのおすすめログ設定をElasticsearchで構築してみる - エンドポイントログ編(その2- Winlogbeat/Elasticsearch)」または前記事をご参照ください。
#Hello Worldモジュール
まずはWindowsイベントログに「greeting: "hello, world!"」というフィールドを追加する単純なモジュールを書いてみましょう。以下のように非常にシンプルなコードを記述しファイル名をwinlogbeat-helloworld.jsとして保存します。
※UTF-8で保存した方がいいと思います。
function process(evt) {
evt.Put("greeting","hello, world");
return;
}
###HelloWorldモジュールを配置
この自作モジュールのJavascriptファイル(winlogbeat-helloworld.js)を他のSecurityやSysmon標準モジュールと同じフォルダ構成で以下のように配置します。
C:\Program Files\winlogbeat-7.2.0-windows-x86_64\module\helloworld\config\winlogbeat-helloworld.js
「C:\Program Files\winlogbeat-7.2.0-windows-x86_64はWinlogbeat」のインストールディレクトリ、「helloworld\config」がモジュール用に作成したサブフォルダです。
このモジュールをWinlogbeatへ組み込んでイベントログをElasticsearchへ転送すると以下のようにgreetingフィールドが追加されています。
...
{
"_index" : "winlogbeat-7.2.0-2019.07.14",
"_type" : "doc",
"_id" : "klT27WsBgpTg8iKCzG_m",
"_score" : 0.0,
"_source" : {
"@timestamp" : "2019-07-14T00:51:42.880Z",
"message" : "Process Create:\nRuleName: \nUtcTime: 2019-07-14 00:51:42.880\nProcessGuid: {22052e76-7c9e-5d2a-0000-00106f766e00}\nProcessId: 7720\nImage: C:\\Windows\\System32\\PING.EXE\nFileVersion: 10.0.17763.1 (WinBuild.160101.0800)\nDescription: TCP/IP Ping Command\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: ping.exe\nCommandLine: \"C:\\WINDOWS\\system32\\PING.EXE\" www.youtube.com\nCurrentDirectory: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\\nUser: xxxxxxxxx\\xxx\nLogonGuid: {22052e76-11ef-5d2a-0000-002070890a00}\nLogonId: 0xA8970\nTerminalSessionId: 1\nIntegrityLevel: High\nHashes: SHA256=741AD992403C78A8A7DBD97C74FDA06594A247E9E2FA05A40BB6945403A90056,IMPHASH=8C3BE1286CDAD6AC1136D0BB6C83FF41\nParentProcessGuid: {22052e76-670e-5d2a-0000-001062cf5900}\nParentProcessId: 5152\nParentImage: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\nParentCommandLine: \"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" ",
...
"greeting" : "hello, world",
...
}
},
...
###自作モジュールを設定ファイルへ追加
Winlogbeatの設定ファイル(例: C:\Program Files\winlogbeat-7.2.0-windows-x86_64\winlogbeat.yml)へモジュール(winlogbeat-helloworld.js)を追加します。今回の例ではSysmonイベントに対してフィルタ・加工処理を行います。そのためwinlogbeat.event_logsの「name: Microsoft-Windows-Sysmon/Operational」フィールド配下へscriptプロセッサとして指定します。
#======================= Winlogbeat specific options ===========================
...
winlogbeat.event_logs:
- name: Application
ignore_older: 72h
- name: System
...
- name: Microsoft-Windows-Sysmon/Operational
processors:
- script:
lang: javascript
id: sysmon
file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js
- script:
lang: javascript
id: helloworld
file: ${path.home}/module/helloworld/config/winlogbeat-helloworld.js
...
###編集した設定ファイルをテスト
作成したモジュールと編集した設定内容をテストしておきます。
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Config OK
Elasticsearchへのネットワーク接続テストも行っておきましょう。
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test output -e -d "*"
###Winlogbeatを再起動
Winlogbeatサービスを再起動します。
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> Restart-Service winlogbeat
Elasticsearchへ転送されたイベントログに上述のようにgreetingフィールドが追加されます。
###HelloWorldモジュールの詳細
function process(evt) {
evt.Put("greeting","hello, world");
return;
}
モジュール記述は上記のように簡単です。process関数を記述するだけでWinlogbeatがイベントログ毎に呼び出してくれます。
各Windowsイベントログは元のXML形式からJSON形式へ変換された後で、process関数の引数へEventオブジェクト(evt引数)として一つずつ渡されます。Eventオブジェクトにはイベントログを操作するためのメソッドが用意されており、フィルタや加工処理に量できます。例えばHelloWorldモジュールではこのEventオブジェクトのメソッドである**evt.Put("フィールド名","フィールド値")**と呼び出すことでイベントログ内へJSONフィールドを挿入追加しています。
※Eventオブジェクトの実体はこちらです。
##Eventオブジェクトのメソッド
リファレンスによれば以下のメソッドがEventオブジェクトへ定義されています。
メソッド | 説明 |
---|---|
Get("フィールド名") | イベントログ(JSON)のフィールドを取得。戻り値はスカラー、オブジェクトまたはnull(見つからなかった場合)。取得するフィールド名を指定しない場合にはイベントログ全体がオブジェクトとして取得されます。 |
Put("フィールド名", "フィールド値") | イベントログ(Json)へフィールドを追加。すでにフィールドが存在する場合には値が上書きされます。追加されるフィールド値としてスカラー以外に設定可能なものについては後ほど検証してみます |
AppendTo("フィールド名", "フィールド値") | 配列形式のフィールドへ要素を追加します。ただし追加できるのは文字列値だけです。 |
Rename("旧フィールド名", "新フィールド名") | フィールド名をリネーム。 |
Delete("フィールド名") | フィールドを削除。 |
Cancel() | イベントログ全体を削除 |
Tag("タグ値") | タグフィールドへ(設定されていなければ)追加。 |
またイベントログ(Json)の各フィールドへはメソッドを利用しなくても**<イベントオブジェクト>.fields.<フィールド名>**という形式で直接アクセスすることもできます。
function process(evt) {
// イベントオブジェクトのフィールドへ直接アクセス
evt.fields.greeting = "hello, world";
return;
}
#モジュールのデバッグ方法
###テストコードを記述
モジュールのJavascriptコード内に**test()**関数をテストプログラムとして記述することで、デバッグやテストを行うことができます。
// 実装したコード
function process(evt) {
evt.Put("greeting","hello, world");
return;
}
// テスト内容を記述する関数
function test() {
//テストデータ(イベントログ)を生成
var evt = new Event({
message: "Windows event log message...",
});
// テストデータで実装コードをテスト
process(evt);
// テスト期待値の判定
if (evt.Get("greeting") === "goodbye cruel world") {
// 期待値と異なる場合には例外を投げる
throw "expected greeting === hello, world";
}
}
このtest()関数はWinlogbeatがモジュールをローディングするときに実行されます。そのため通常の設定ファイルテスト手順と同様に、以下のようにtest configを指定してwinlogbeat.exeを実行することでデバッグやテストを行うことができます。
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Config OK
では以下のようにgreetingへ「goodbye cruel world」と代わりに出力するようにしてみましょう。
// 実装したコード
function process(evt) {
// hello, worldを別の値にしてみる
evt.Put("greeting","goodbye cruel world");
return;
}
// テスト内容を記述する関数
function test() {
//Javascriptのオブジェクト型でテストデータ(イベントログ/JSON)を生成
var evt = new Event({
message: "Windows event log message...",
});
// テストデータで実装コードをテスト
process(evt);
// テスト期待値の判定
if (evt.Get("greeting") === "goodbye cruel world") {
// 期待値と異なる場合には例外を投げる
throw "expected greeting === hello, world";
}
}
以下のように「failed in test() function: expected greeting === hello, world at test」と例外が投げられエラーとなります。
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Exiting: Failed to create new event log.
failed in processor.javascript: failed in test()
function: expected greeting === hello, world at test
(C:\Program Files\winlogbeat-7.2.0-windows-x86_64/module/helloworld/config/winlogbeat-helloworld.js:12:15(22))
ちなみにこのテストエラーのままでは以下のようにWinlogbeatサービスは起動しません。
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> Start-Service winlogbeat
Start-Service : 次のエラーのため、サービス 'winlogbeat (winlogbeat)' を開始できません: コンピューター '.' でサービス 'w
inlogbeat' を開始できません。
発生場所 行:1 文字:1
+ Start-Service winlogbeat
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (System.ServiceProcess.ServiceController:ServiceController) [Start-Service],
ServiceCommandException
+ FullyQualifiedErrorId : CouldNotStartService,Microsoft.PowerShell.Commands.StartServiceCommand
PowerShellのStart-Serviceコマンドレットはエラーを返します。
###未定義またはgojaで未サポートのJavascript API呼び出し
WinlogbeatのJavascriptプロセッサのエンジンはgojaです。そのため通常のWebブラウザでサポートされているJavascriptのAPIが利用可能とは限らない点に注意が必要です。
※gojaはECMAScript 5.1ベース。
以下は未定義の関数hoge()を呼び出した例です。以下のようにエラー(「hoge is not defined at process」)となります。
function process(evt) {
evt.Put("greeting","hello, world");
// 未定義またはgoja未サポートのAPI呼び出し
hoge();
return;
}
function test() {
var evt = new Event({
message: "Windows event log message...",
});
process(evt);
if (evt.Get("greeting") === "goodbye cruel world") {
throw "expected greeting === hello, world";
}
}
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Exiting: Failed to create new event log.
failed in processor.javascript:
failed in test() function: ReferenceError: hoge is not defined at process
(C:\Program Files\winlogbeat-7.2.0-windows-x86_64/module/helloworld/config/winlogbeat-helloworld.js:3:9(10))
ただし、**test()**関数の延長でその未定義関数呼び出しのコード部分が実行されない場合には、エラーとはならないので注意が必要です。
function process(evt) {
evt.Put("greeting","hello, world");
// 未定義関数呼び出し部分が実行されない場合
if(false){
hoge();
}
return;
}
function test() {
var evt = new Event({
message: "Windows event log message...",
});
process(evt);
if (evt.Get("greeting") === "goodbye cruel world") {
throw "expected greeting === hello, world";
}
}
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Config OK
###Winlogbeatサービスへのモジュールのローディングを確認する
Winlogbeatの設定ファイルでログレベルをdebugへ変更することでモジュールのローディングを確認することができます。
...
#================================ Logging =====================================
# Sets log level. The default log level is info.
# Available log levels are: error, warning, info, debug
# 以下をコメントアウト
logging.level: debug
...
Winlogbeat(例: C:\Program Files\winlogbeat-7.2.0-windows-x86_64\logs\winlogbeat)のログへ以下のように出力されます。
2019-07-14T12:15:25.733+0900 DEBUG [processors] processors/processor.go:93
Generated new processors:
script=[type=javascript, id=, sources=C:\Program Files\winlogbeat-7.2.0-windows-x86_64/module/sysmon/config/winlogbeat-sysmon.js],
script=[type=javascript, id=, sources=C:\Program Files\winlogbeat-7.2.0-windows-x86_64/module/helloworld/config/winlogbeat-helloworld.js]
winlogbeat-helloworld.jsがScriptプロセッサのモジュールとして認識されています。
###console.log()関数でデバッグ情報などをログへ書き出す
consoleモジュールをローディングすることで、Javascriptでおなじみ**console.log()**関数も使えます。ただし、出力先はWinlogbeatのイベントログファイルになります。
####console.xxx()の記述例:
- console.debug("DEBUG: hello, world!");
- console.log("INFO: hello, world!");
- console.info("INFO: hello, world! %j", evt.fields);
- console.warn("WARN: [%s]", evt.fields.message);
- console.error("ERROR: %j", evt.fields);
以下はconsole.log()関数のサンプルプログラムです。
// consoleモジュールをローディング
var console = require("console");
function process(evt) {
// Debugログでイベント(オブジェクト)全体を出力
console.debug("process() called %j", evt.fields);
evt.Put("greeting","goodbye cruel world");
return;
}
function test() {
var evt = new Event({
message: "Windows event log message...",
winlog : {
provider_name : "Microsoft-Windows-Sysmon"
}
});
process(evt);
if (evt.Get("greeting") === "goodbye cruel world") {
// Errorログを出力
console.error("test() NG! %s",evt.fields.greeting);
}else{
console.log("test() OK!");
}
console.info("test() Done");
}
こちらもWinlogbeat(例: C:\Program Files\winlogbeat-7.2.0-windows-x86_64\logs\winlogbeat)のログへ以下のように出力されます。
...
2019-07-14T14:18:45.023+0900 DEBUG [processor.javascript]
console/console.go:48 process() called {"message":"Windows event log message...","winlog":{"provider_name":"Microsoft-Windows-Sysmon"}}
2019-07-14T14:18:45.023+0900 ERROR [processor.javascript]
console/console.go:54 test() NG! goodbye cruel world
2019-07-14T14:18:45.023+0900 INFO [processor.javascript]
console/console.go:50 test() Done
...
#イベントログをフィルタや加工するパイプラインを作成
processorモジュールを利用すると複数の関数で定義された処理を数珠つなぎにして、イベントログのフィルタや加工処理をパイプライン化することができます。
var console = require("console");
// processorモジュールをローディング
var processor = require("processor");
function process(evt) {
// 処理1: helloメッセージをイベントログへ追加
var addHello = function(evt) {
evt.Put("hello","hello, world");
console.debug("addHello() OK");
}
// 処理2: goodbyeメッセージをイベントログへ追加
var addGoodbye = function(evt) {
evt.Put("goodbye","goodbye cruel world");
console.debug("addGoodbye () OK");
}
// processorモジュールのChain()メソッドでパイプラインを生成
var pipeline = new processor.Chain()
.Add(addHello) // 処理1をパイプラインへ追加
.Add(addGoodbye) // 処理2をパイプラインへ追加
.Build() // パイプラインを組み立て
// パイプラインを実行
pipeline.Run(evt);
return;
}
function test() {
var evt = new Event({
message: "Windows event log message...",
winlog : {
provider_name : "Microsoft-Windows-Sysmon"
}
});
process(evt);
if (evt.Get("hello") !== "hello, world") {
throw "expected goodbye !== hello, world";
}
if (evt.Get("goodbye") !== "goodbye cruel world") {
throw "expected goodbye !== goodbye cruel world";
}
console.debug("test() Done");
}
パイプラインで処理1と処理2を実行し加工されたイベントログは以下のようになります。helloとgoodbyeのフィールドが追加されています。
{
"_index" : "winlogbeat-7.2.0-2019.07.14",
"_type" : "doc",
"_id" : "YFQS72sBgpTg8iKCw3VG",
"_score" : 0.0,
"_source" : {
"@timestamp" : "2019-07-14T06:01:51.413Z",
"hello" : "hello, world",
"goodbye" : "goodbye cruel world",
...
"message" : "Process Create:\nRuleName: \nUtcTime: 2019-07-14 06:01:51.413\nProcessGuid: {22052e76-c54f-5d2a-0000-001031f3a900}\nProcessId: 4132\nImage: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\nFileVersion: ?\nDescription: ?\nProduct: ?\nCompany: ?\nOriginalFileName: ?\nCommandLine: \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\" -c \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.yml\" -path.home \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\" -path.data \"C:\\ProgramData\\winlogbeat\" -path.logs \"C:\\ProgramData\\winlogbeat\\logs\"\nCurrentDirectory: C:\\WINDOWS\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {22052e76-11b2-5d2a-0000-0020e7030000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA256=16EECCE05D1A4B25CDA0442C743DCD912A1CB3C51BAB8A5E060A423C131E9ECB,IMPHASH=6DA7C12D70F874E2ABB391A456EB1EF0\nParentProcessGuid: {22052e76-11b2-5d2a-0000-00102cae0000}\nParentProcessId: 632\nParentImage: C:\\Windows\\System32\\services.exe\nParentCommandLine: C:\\WINDOWS\\system32\\services.exe",
...
}
###他のプロセッサをパイプラインへつなぐ
Winlogbeatの標準プロセッサをこのパイプラインへつなぐことももちろんできます。
例えば以下の例ではConvertプロセッサを利用してフィールド名を大文字の文字列値へ変更しています。
var console = require("console");
// processorモジュールをローディング
var processor = require("processor");
function process(evt) {
var addHello = function(evt) {
evt.Put("hello","hello, world");
}
var addGoodbye = function(evt) {
evt.Put("goodbye","goodbye cruel world");
}
var pipeline = new processor.Chain()
.Add(addHello)
.Add(addGoodbye)
.Convert({ // Convertプロセッサをつなげる
fields: [
{from: "hello", to: "HELLO"}, // フィールド名を大文字の値へ変更
{from: "goodbye", to: "GOODBYE"}
],
mode: "rename",
ignore_missing: true,
fail_on_error: false,
})
.Build()
pipeline.Run(evt);
return;
}
function test() {
var evt = new Event({
message: "Windows event log message...",
winlog : {
provider_name : "Microsoft-Windows-Sysmon"
}
});
process(evt);
if (evt.Get("HELLO") !== "hello, world") {
throw "expected goodbye !== hello, world";
}
if (evt.Get("GOODBYE") !== "goodbye cruel world") {
throw "expected goodbye !== goodbye cruel world";
}
}
{
"_index" : "winlogbeat-7.2.0-2019.07.14",
"_type" : "doc",
"_id" : "cFQ472sBgpTg8iKC8nZ3",
"_score" : 0.0,
"_source" : {
"@timestamp" : "2019-07-14T06:42:59.844Z",
"message" : "Process Create:\nRuleName: \nUtcTime: 2019-07-14 06:42:59.844\nProcessGuid: {22052e76-cef3-5d2a-0000-001054bbb200}\nProcessId: 2156\nImage: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\nFileVersion: ?\nDescription: ?\nProduct: ?\nCompany: ?\nOriginalFileName: ?\nCommandLine: \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\" test config\nCurrentDirectory: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\\nUser: xxxxxxxxx\\xxx\nLogonGuid: {22052e76-11ef-5d2a-0000-002070890a00}\nLogonId: 0xA8970\nTerminalSessionId: 1\nIntegrityLevel: High\nHashes: SHA256=16EECCE05D1A4B25CDA0442C743DCD912A1CB3C51BAB8A5E060A423C131E9ECB,IMPHASH=6DA7C12D70F874E2ABB391A456EB1EF0\nParentProcessGuid: {22052e76-670e-5d2a-0000-001062cf5900}\nParentProcessId: 5152\nParentImage: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\nParentCommandLine: \"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" ",
...
"HELLO" : "hello, world",
"GOODBYE" : "goodbye cruel world",
...
}
},
ちなみに標準のSysmonモジュールでもこのConvertプロセッサをパイプラインにつなげてWindowsイベントログのフィールド名をElastic Common Schema (ECS)へ変更するのに利用しています。
なお利用できる標準プロセッサの最新情報やラベル名はこちらのconstructors定義を見るとわかります(次期Winlogbeat 7.3.0ではBase64デコードプロセッサがサポートされそう??? UTF-8だけでなくUTF-16leの場合などもサポートされると嬉しいですね・・・笑)。
#その他の標準モジュール
consoleモジュールとprocessorモジュールについてご紹介しましたが、他にもイベントログ解析に便利なモジュールが提供されています。
###pathモジュール
pathモジュールはWindowsファイルパスをパーシングしてくれるモジュールです。
メソッド | 説明 |
---|---|
basename | ファイルパスからベース名(例:Exeファイル名)を取り出す |
dirname | ファイルパスからフォルダ名を取り出す |
extname | ファイル拡張子を取り出す |
isAbsolute | 絶対パスかどうかを判定 |
normalize | パスを正規化。例: 「C:\Windows\system32\..\system32\system32.dll」を「C:\Windows\system32\system32.dll」へ変換 |
###winlogbeatモジュール
メソッド | 説明 |
---|---|
splitCommandLine | コマンドライン文字列をMicrosoft社から提供されているCommandLineToArgvW関数でパースおよび分割し配列化 |
#いろいろな値型でフィールドへ追加してみる
Eventオブジェクトの**Put()**メソッドでいろいろな形式で値を追加してみました。
var console = require("console");
var processor = require("processor");
function process(evt) {
var putValueByMethod = function(evt) {
var numberValue = 1;
evt.Put("numberValue", numberValue);
var stringValue = "hello, world";
evt.Put("stringValue", stringValue);
var boolValue = false
evt.Put("boolValue", boolValue);
var nullValue = null
evt.Put("nullValue", nullValue);
var undefinedValue = undefined
evt.Put("undefinedValue", undefinedValue);
var arrayValue = ["element1", "element2", "element3"];
evt.Put("arrayValue", arrayValue);
var objectValue = {
key1: "value1",
key21: "value2"
};
evt.Put("objectValue", objectValue);
var objectArrayValue = [{
KEY1: "VALUE1",
KEY2: "VALUE2"
}, {
KEY3: "VALUE3",
KEY4: "VALUE4"
}]
evt.Put("objectArrayValue", objectArrayValue);
var objectObjectValue = {
Key1: {
Key2: "Value2",
Key3: 1,
Key4: true,
Key5: [1, 2, 3, 4]
}
}
evt.Put("objectObjectValue", objectObjectValue);
}
var putValueToFields = function(evt) {
var objectObjectValueToFields = {
Key1: {
Key2: "ばりゅー1",
Key3: 1,
Key4: true,
Key5: [1, 2, 3, 4]
}
}
evt.fields.objectObjectValueToFields = objectObjectValueToFields;
}
var pipeline = new processor.Chain()
.Add(putValueByMethod)
.Add(putValueToFields)
.Build()
pipeline.Run(evt);
return;
}
function test() {
var evt = new Event({});
process(evt);
console.log("evt.fields: %j", evt.fields);
}
Elasticsearchへ格納された形式は以下です。特に問題なさそうですね!
{
"_index" : "winlogbeat-7.2.0-2019.07.14",
"_type" : "doc",
"_id" : "HVSD72sBgpTg8iKCsXnz",
"_score" : 0.0,
"_source" : {
"@timestamp" : "2019-07-14T08:05:09.682Z",
...
"stringValue" : "hello, world",
"arrayValue" : [
"element1",
"element2",
"element3"
],
"nullValue" : null,
"undefinedValue" : null,
"objectArrayValue" : [
{
"KEY1" : "VALUE1",
"KEY2" : "VALUE2"
},
{
"KEY4" : "VALUE4",
"KEY3" : "VALUE3"
}
],
"objectObjectValueToFields" : {
"Key1" : {
"Key2" : "ばりゅー1",
"Key3" : 1,
"Key4" : true,
"Key5" : [
1,
2,
3,
4
]
}
},
"objectObjectValue" : {
"Key1" : {
"Key2" : "Value2",
"Key3" : 1,
"Key4" : true,
"Key5" : [
1,
2,
3,
4
]
}
},
"numberValue" : 1,
"boolValue" : false,
"objectValue" : {
"key1" : "value1",
"key21" : "value2"
},
"message" : "Process Create:\nRuleName: \nUtcTime: 2019-07-14 08:05:09.682\nProcessGuid: {22052e76-e235-5d2a-0000-00109799d600}\nProcessId: 5372\nImage: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\nFileVersion: ?\nDescription: ?\nProduct: ?\nCompany: ?\nOriginalFileName: ?\nCommandLine: \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.exe\" -c \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\winlogbeat.yml\" -path.home \"C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\" -path.data \"C:\\ProgramData\\winlogbeat\" -path.logs \"C:\\ProgramData\\winlogbeat\\logs\"\nCurrentDirectory: C:\\WINDOWS\\system32\\\nUser: NT AUTHORITY\\SYSTEM\nLogonGuid: {22052e76-11b2-5d2a-0000-0020e7030000}\nLogonId: 0x3E7\nTerminalSessionId: 0\nIntegrityLevel: System\nHashes: SHA256=16EECCE05D1A4B25CDA0442C743DCD912A1CB3C51BAB8A5E060A423C131E9ECB,IMPHASH=6DA7C12D70F874E2ABB391A456EB1EF0\nParentProcessGuid: {22052e76-11b2-5d2a-0000-00102cae0000}\nParentProcessId: 632\nParentImage: C:\\Windows\\System32\\services.exe\nParentCommandLine: C:\\WINDOWS\\system32\\services.exe",
...
}
},
#サンプルモジュールを書いてみる
HelloWorldよりも少し実用的?なサンプルモジュールを書いてみます(笑)
###PowerShellのBase64エンコードされたコマンドのデコードモジュール
PowerShellにはEncodedCommandオプション(-EncodedCommandまたは-enc)を利用してコマンド列をBase64文字列としてパッキングしてくれる機能があります。しかしこの形式でPowerShellのコマンドラインがSysmonログに含まれると分析しづらい時があります。そこでPowerShell.exeのオプションに-encオプションが指定された場合、そのオプション値のBase64文字列をデコードしてからElasticsearchへ転送するモジュールを作成してみます。
###Base64エンコードされたコマンド例
powershell.exe -executionpolicy bypass Resolve-DnsName -Name www.youtube.com
powershell.exe -executionpolicy bypass -enc "UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA="
複数のコマンドや引数からなる「Resolve-DnsName -Name www.youtube.com」の部分がBase64エンコードされて「UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA=」と一かたまりにパッキングされています。
###Base64デコードライブラリ
Base64デコードプロセッサは(Winlogbeatの開発リポジトリをのぞくと近い将来もしかしたら標準提供されるかもしれませんが)7.2には含まれていないため、Base64デコードライブラリとしてjs-base64を利用します。ただしES6のimport文が使えないのでモジュールソースコードへそのまま組み込みます(泣)。
###サンプルソース
サンプルソースは以下の通りです。js-base64の部分は省略してありますが、HelloWorldモジュールを含めてこちらへあげておきます。
...
function process(evt) {
// prosessorモジュールをローディング
var processor = require("processor");
// winlogbeatモジュールをローディング
var winlogbeat = require("winlogbeat");
// パイプライン処理1:-encオプション値があればBase64デコード
var decodePsCode = function(evt) {
var processName = evt.Get("process.name");
var args = evt.Get("process.args");
if ( !processName || processName.length < 1 ||
!args || args.length < 3 ||
processName.toLowerCase() !== "powershell.exe" ) {
return;
}
for (var i = 0; i < args.length; i++) {
if (args[i].toLowerCase().indexOf("-enc") != -1 && i < (args.length - 1)) {
var decodedArg = Base64.decode(args[i+1]);
if( decodedArg && decodedArg.length > 0 ){
evt.Put("winlog.decodedPsCode",decodedArg.replace(/\u0000/gi,'')); //ズルしてる・・・
break;
}
}
}
}
// パイプライン処理2:Base64デコードされたコマンドをパースして配列へ格納
var splitPsCode = function(evt) {
var psArgs = evt.Get("winlog.decodedPsCode");
if (!psArgs) {
return;
}
evt.Put("winlog.decodedPsCodeArgs", winlogbeat.splitCommandLine(psArgs));
}
// パイプラインを作成
var pipeline = new processor.Chain()
.Add(decodePsCode) // パイプライン処理1を登録
.Add(splitPsCode) // パイプライン処理2を登録
.Build()
// パイプライン処理を実行
pipeline.Run(evt);
return;
}
// テスト関数
function test() {
var console = require("console");
var evt = new Event({
process : {
name : "powershell.exe",
args : [
"powershell.exe",
"-executionpolicy",
"bypass",
"-enc",
"UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA="
]
},
winlog: {
event_id: 1
}
});
console.debug("evt: %j",evt.fields);
process(evt);
console.debug("evt: %s",evt.Get("winlog.decodedPsCode"));
console.debug("evt: %j",evt.Get("winlog.decodedPsCodeArgs"));
if (evt.Get("winlog.decodedPsCode") !== "Resolve-DnsName -Name www.youtube.com") {
throw "expected winlog.decodedPsCode === Resolve-DnsName -Name www.youtube.com";
}
console.debug("test() Done.");
}
###自作モジュールを設定ファイルへ追加
Winlogbeatの設定ファイル(例: C:\Program Files\winlogbeat-7.2.0-windows-x86_64\winlogbeat.yml)へモジュール(winlogbeat-helloworld.js)を追加します。今回はSysmonイベントに対してフィルタ・加工処理を行うためwinlogbeat.event_logsの「name: Microsoft-Windows-Sysmon/Operational」フィールド配下へscriptプロセッサとして追加します。
#======================= Winlogbeat specific options ===========================
...
winlogbeat.event_logs:
- name: Application
ignore_older: 72h
- name: System
...
- name: Microsoft-Windows-Sysmon/Operational
processors:
- script:
lang: javascript
id: sysmon
file: ${path.home}/module/sysmon/config/winlogbeat-sysmon.js
- script:
lang: javascript
id: psdecode
file: ${path.home}/module/psdecode/config/winlogbeat-psdecode.js
...
作成したモジュールと編集した設定内容をテストしておきましょう。
PS C:\Program Files\winlogbeat-7.2.0-windows-x86_64> .\winlogbeat.exe test config
Config OK
###実行結果
実行した結果は以下の通りです。winlog.decodedPsCodeフィールドにBase64デコードした結果、そしてwinlog.decodedPsCodeArgsフィールドにそれをパースした結果が格納されています。
...
{
"_index" : "winlogbeat-7.2.0-2019.07.14",
"_type" : "doc",
"_id" : "eVTF72sBgpTg8iKCSXqf",
"_score" : 0.0,
"_source" : {
"@timestamp" : "2019-07-14T09:16:49.667Z",
...
"winlog" : {
...
"decodedPsCode" : "Resolve-DnsName -Name www.youtube.com",
"decodedPsCodeArgs" : [
"Resolve-DnsName",
"-Name",
"www.youtube.com"
],
...
},
...
"process" : {
"pid" : 4824,
"executable" : "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"args" : [
"C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"-executionpolicy",
"bypass",
"-enc",
"UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA="
],
...
},
...
{
"_index" : "winlogbeat-7.2.0-2019.07.14",
"_type" : "doc",
"_id" : "eVTF72sBgpTg8iKCSXqf",
"_score" : 0.0,
"_source" : {
"@timestamp" : "2019-07-14T09:16:49.667Z",
"user" : {
"domain" : "xxxxx",
"name" : "xxxxx"
},
"host" : {
"os" : {
"family" : "windows",
"name" : "Windows 10 Pro",
"kernel" : "10.0.17763.615 (WinBuild.160101.0800)",
"build" : "17763.615",
"platform" : "windows",
"version" : "10.0"
},
"id" : "22052e76-721a-4007-86f6-6346e89d0c86",
"hostname" : "xxxxx",
"architecture" : "x86_64",
"name" : "xxxxx"
},
"agent" : {
"ephemeral_id" : "d34dbafc-8002-4bcc-8f51-8e1911fcd0f0",
"hostname" : "xxxxx",
"id" : "d4bdf3a8-1d7e-4c36-a259-2a2451b56656",
"version" : "7.2.0",
"type" : "winlogbeat"
},
"winlog" : {
"process" : {
"thread" : {
"id" : 3908
},
"pid" : 2720
},
"record_id" : 1744334,
"channel" : "Microsoft-Windows-Sysmon/Operational",
"event_data" : {
"FileVersion" : "10.0.17763.1 (WinBuild.160101.0800)",
"LogonId" : "0xa8970",
"TerminalSessionId" : "1",
"Description" : "Windows PowerShell",
"IntegrityLevel" : "High",
"Product" : "Microsoft® Windows® Operating System",
"OriginalFileName" : "PowerShell.EXE",
"Company" : "Microsoft Corporation",
"LogonGuid" : "{22052e76-11ef-5d2a-0000-002070890a00}"
},
"provider_name" : "Microsoft-Windows-Sysmon",
"task" : "Process Create (rule: ProcessCreate)",
"api" : "wineventlog",
"decodedPsCode" : "Resolve-DnsName -Name www.youtube.com",
"decodedPsCodeArgs" : [
"Resolve-DnsName",
"-Name",
"www.youtube.com"
],
"provider_guid" : "{5770385f-c22a-43e0-bf4c-06f5698ffbd9}",
"opcode" : "情報",
"computer_name" : "xxxxx",
"version" : 5,
"event_id" : 1,
"user" : {
"identifier" : "S-1-5-18",
"name" : "SYSTEM",
"domain" : "NT AUTHORITY",
"type" : "User"
}
},
"process" : {
"entity_id" : "{22052e76-f301-5d2a-0000-00102a5ce600}",
"pid" : 4824,
"executable" : "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"args" : [
"C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"-executionpolicy",
"bypass",
"-enc",
"UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA="
],
"working_directory" : "C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\",
"parent" : {
"entity_id" : "{22052e76-670e-5d2a-0000-001062cf5900}",
"pid" : 5152,
"executable" : "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"args" : [
"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
],
"name" : "powershell.exe"
},
"name" : "powershell.exe"
},
"event" : {
"created" : "2019-07-14T09:16:51.554Z",
"kind" : "event",
"code" : 1,
"action" : "Process Create (rule: ProcessCreate)"
},
"hash" : {
"sha256" : "de96a6e69944335375dc1ac238336066889d9ffc7d73628ef4fe1b1b160ab32c",
"imphash" : "741776aaccfc5b71ff59832dcdcace0f"
},
"ecs" : {
"version" : "1.0.0"
},
"log" : {
"level" : "情報"
},
"message" : "Process Create:\nRuleName: \nUtcTime: 2019-07-14 09:16:49.667\nProcessGuid: {22052e76-f301-5d2a-0000-00102a5ce600}\nProcessId: 4824\nImage: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\nFileVersion: 10.0.17763.1 (WinBuild.160101.0800)\nDescription: Windows PowerShell\nProduct: Microsoft® Windows® Operating System\nCompany: Microsoft Corporation\nOriginalFileName: PowerShell.EXE\nCommandLine: \"C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -executionpolicy bypass -enc UgBlAHMAbwBsAHYAZQAtAEQAbgBzAE4AYQBtAGUAIAAtAE4AYQBtAGUAIAB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA=\nCurrentDirectory: C:\\Program Files\\winlogbeat-7.2.0-windows-x86_64\\\nUser: xxxxx\\xxxxx\nLogonGuid: {22052e76-11ef-5d2a-0000-002070890a00}\nLogonId: 0xA8970\nTerminalSessionId: 1\nIntegrityLevel: High\nHashes: SHA256=DE96A6E69944335375DC1AC238336066889D9FFC7D73628EF4FE1B1B160AB32C,IMPHASH=741776AACCFC5B71FF59832DCDCACE0F\nParentProcessGuid: {22052e76-670e-5d2a-0000-001062cf5900}\nParentProcessId: 5152\nParentImage: C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\nParentCommandLine: \"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" "
}
},
...
#まとめ
Winlogbeat 7.2.0の新機能であるJavascriptプロセッサ機能を使って独自モジュールを書いてみました。様々な処理をパイプライン化するのも容易であり、応用範囲が広がりそうですね。何よりも設定をJavascriptの構文で柔軟にサクッと書けるので非常にお手軽で便利です。
※Javascriptプロセッサはまだ新しい機能ですので仕様変更も今後予想されます。詳細は公式リファレンスを参照してください。