コンテンツにスキップ

カスタムパーサー構文🔗

重要

Taegis XDRカスタムパーサーでサポートされている正規表現の構文は、Golangバリアントです。

ステートメント🔗

!SAMPLE=...🔗

サンプルメッセージです。=の右側にあるすべての内容が、改行までリテラルとして解釈されます。このフィールドは任意ですが、強く推奨されます。

!SCHEMA=...🔗

このメッセージタイプのスキーマです。例:scwx.nidsscwx.netflowscwx.auth。指定しない場合は、親または最も近い先祖のスキーマが使用されます。

!CONFIRMWITH🔗

PATTERN または EXPRESSION のいずれかです。これは !CONFIRMSTRING と連携して、このメッセージがこのパーサーに一致するかどうかを判定します。PATTERN に設定した場合、CONFIRMSTRINGは正規表現パターンです。EXPRESSION に設定した場合、CONFIRMSTRINGはTrue/Falseを評価する式です。

!CONFIRMSTRING=🔗

!CONFIRMWITH を参照してください。

!DISABLED=🔗

このパーサーを無効化します。パーサーはランタイムカタログから完全に削除されます。メッセージの処理方法がまだ分からないが、その存在を最小限のドキュメントとして記録しておきたい場合に便利です。

!IMPORT=🔗

他のパーサーを現在の行でこのパーサーにインポートします。変数はインポート元とインポート先のパーサー間で共有されます。これにより、パーサーコードの繰り返し行を1か所にまとめることができます。

!IMPORTONLY🔗

このパーサーが !IMPORT 経由でのみインポート用であることを示します。極めてまれな例外を除き、すべてのインポートされるパーサーは !IMPORTONLY であるべきです。このフラグにより、多くのバリデーションルール(例:親パーサーが不要、CONFIRMWITH/CONFIRMSTRINGが不要など)が免除されます。

!TRIMALLOFF🔗

すべてのパーサーで TRIM_ALL() を実行するデフォルト動作を無効にします。場合によっては、TRIM_ALL() が先頭や末尾の中括弧({}[])を削除してしまい、Jsonフィールドのデータが不正確になることがあります。

!SANITIZEALLOFF🔗

すべてのパーサーで SANITIZE_ALL() を実行するデフォルト動作を無効にします。

正規表現キャプチャグループ🔗

キャプチャグループは、非構造化ログメッセージまたはその一部から値を抽出するために使用できます。

キャプチャグループの構文は {captureVariable} = {sourceString}|({regex pattern}) です。マッチした結果はリストに格納され、captureVariableと配列値で参照できます(例:captureVariable[1])。

キャプチャグループは {captureVariable} = {sourceString}|(?P<group_name>{regex pattern}) のように名前付きにすることもできます。マッチした結果は、captureVariableとグループ名で参照できます(例:captureVariable["group_name"])。

🔗

# The pattern is read unescaped to the end of the line.

jsonMatch = originalData$|(\{.*})$

# To find patterns such as an IP address

# originalData = Dec 10 16:49:10 10.10.70.10 Dec 10 10:49:10 dddd-aaabbb-01 dddd-aaabbb-01

queryCapture = originalData$|\s\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}\s

# queryCapture = 10.10.70.10

# To capture a value after a field name

# message = Source Network Address: 10.17.2.186   Source Port:  54692

srcIp = message|Source Network Address:\s+(\S+)\s+

# srcIp = 10.17.2.186

# An example where the '%' character isn't scaped as in Golang regex

# message = %AAA-6-RADIUS_IN_GLOBAL_LIST: radius_db.c:481 RADIUS ACCT

topLevel = message|\s+%(\w+)-(\w+-)?(\d+)-(\w+):\s+(.+)

part = topLevel[1]

# part = AAA

# Named capture group example

# message = Aug 21 12:12:20 10.194.72.254 1 1566000000.000000000 IDOFDEVICE flows src=192.168.0.5 dst=192.168.0.255 mac=DE:ED:BE:EF:AB:AB protocol=udp sport=49154 dport=1128 pattern: deny (src 192.168.0.0/24)

partA = message|^(?P<prefix>0|1)\s+(?P<timestamp>\d+\.\d+)\s+(?P<idofdevice>\S+)\s+(?P<logtype>ip_flow|events|airmarshal_events|flows|security_event|ids-alerts|urls|.*firewall)\s+(?P<remainder>.*)

timestamp = partA["timestamp"]
device = partA["idofdevice"]
logtype = partA["logtype"]

# timestamp = 1566000000.000000000

# device = IDOFDEVICE

# logtype = flows

関数🔗

SPLIT(data, delimiter, makeGreedy)🔗

datadelimiter で区切ってトークンに分割します。例えば、(オプションの)makeGreedy"true" の場合、データ 0,,2 をデリミタ , で分割すると [0,2] となり、[0,'',2] にはなりません。

🔗

data = "aaa,bbb,ccc,eee"
values1 = SPLIT(data, ",", FALSE)
OUTPUT1$ = values1[3]

#OUTPUT1$: eee (String)
data = "aaa,bbb,ccc,,eee"
values1 = SPLIT(data, ",", FALSE)
values2 = SPLIT(data, ",", TRUE)
OUTPUT1$ = values1[3]
OUTPUT2$ = values2[3]

#OUTPUT1$: NULL (null)
#OUTPUT2$: eee (String)

SPLIT_NAME_VALUES(data, delimiter, separator, quoteChar)🔗

data を、delimiter でペアを区切り、separator で名前と値を分ける名前/値ペアのコレクションに分割します。quotechar は値をクォートする文字を示します。

🔗

data = "User: Unknown, InitiatorPackets: 2, ResponderPackets: 1, InitiatorBytes: 120, ResponderBytes: 66"
dict = SPLIT_NAME_VALUES(data, ",", ":", "\\")
OUTPUT$ = dict["InitiatorBytes"]

# OUTPUT$: 120 (String)

JSON(data)🔗

data をjsonオブジェクトに変換し、jsonパスを含む角括弧でアクセスできます。詳細は https://goessner.net/articles/JsonPath/ および https://github.com/ohler55/ojg を参照してください。

🔗

data= "{ \"store\": { \"book\": [ { \"category\": \"reference\", \"author\": \"Nigel Rees\", \"title\": \"Sayings of the Century\", \"price\": 8.95 }, { \"category\": \"fiction\", \"author\": \"Evelyn Waugh\", \"title\": \"Sword of Honour\", \"price\": 12.99 }, { \"category\": \"fiction\", \"author\": \"Herman Melville\", \"title\": \"Moby Dick\", \"isbn\": \"0-553-21311-3\", \"price\": 8.99 }, { \"category\": \"fiction\", \"author\": \"J.R. R. Tolkien\", \"title\": \"The Lord of the Rings\", \"isbn\": \"0-395-19395-8\", \"price\": 22.99 } ], \"bicycle\": { \"color\": \"red\", \"price\": 19.95 } } }"
json= JSON(data)
OUTPUT$ = json["$.store.book[*].author"]

# OUTPUT$: "Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien" (String)

ドットを含むJSONキーの使用例:

data= "{ \"store\": { \"book\": [ { \"id.category\": \"reference\" } ] } }"
json= JSON(data)
OUTPUT$ = json["$.store.book[0][\"id.category\"]"]

# OUTPUT$: reference(String)

CEF(data)🔗

data をCEF形式のメッセージとして解析します。ヘッダーフィールドは整数で、名前付きフィールドは名前でアクセスできます。

🔗

!SAMPLE=Nov 6 07:49:03 10.42.0.1 %helloWorld: CEF:0|Check Point|VPN-1 & FireWall-1|Check Point|Log|Address spoofing|Unknown|act=Drop cs3Label=Protection Type cs3=IPS

values = CEF(originalData$)
OUTPUT1$= values[2]
OUTPUT2$= values["act"]
OUTPUT3$= values["Protection Type"]

# OUTPUT1$: VPN-1 & FireWall-1 (String)

# OUTPUT2$: Drop (String)

# OUTPUT3$: IPS (String)

LEEF(data, delimiterOverride)🔗

data をLEEF形式のメッセージとして解析します。ヘッダーフィールドは整数で、名前付きフィールドは名前でアクセスできます。オプションでデリミタの上書きが指定できます。LEEF拡張はタブ区切りであるべきか、ヘッダーの6番目のフィールドで代替デリミタを示す必要があります。デバイスが標準に準拠していない場合は、overrideパラメータを使用してください。

DATETIME(data, fmt, handle2DigitYear)🔗

文字列を EventTimeUsec$ などのフィールド用の時刻値に変換します。time.Parse のフォーマット文字列も受け付けます(オプション)。handle2digitYear がTRUEの場合、適切な年が選択されます。通常は現在の年ですが、年末年始の境界ケースがあります。

🔗

data = "Sep 21 2018 17:35:54"
OUTPUT1$ = DATETIME(data, "Jan 02 2006 15:04:05")
OUTPUT2$ = data

# OUTPUT1$: 2018-09-21 17:35:54 +0000 UTC (time)

# OUTPUT2$: Sep 21 2018 17:35:54 (String)

IS_PRIVATE_IP(string)🔗

渡された(IPアドレスの)文字列がプライベートIP範囲内かどうかをブール値で返します。現在はIPv4のみ対応し、RFC1918 で定義されたプライベートIP範囲に対してテストします。

🔗

data1 = "10.0.0.1"
data2 = "11.0.0.1"
OUTPUT1$ = IS_PRIVATE_IP(data1)
OUTPUT2$ = IS_PRIVATE_IP(data2)

# OUTPUT1$: true (bool)

# OUTPUT2$: false (bool)

IS_VALID_IP(string)🔗

渡された文字列が有効なIPアドレスかどうかをブール値で返します。net.ParseIP を利用しています。

🔗

data1 = "10.0.0.1"
data2 = "999.255.255.255"
data3 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
OUTPUT1$ = IS_VALID_IP(data1)
OUTPUT2$ = IS_VALID_IP(data2)
OUTPUT3$ = IS_VALID_IP(data3)

# OUTPUT1$: true (bool)

# OUTPUT2$: false (bool)

# OUTPUT3$: true (bool)

REPLACE(data, oldString, newString)🔗

oldString のすべての出現箇所を newString に置換します。

🔗

data = "aaaBBBaaaCCC"
OUTPUT$ = REPLACE(data, "aaa", "zzz")

# OUTPUT$: zzzBBBzzzCCC (String)

REPLACE_REGEX(data, pattern, newString)🔗

oldPattern のすべての出現箇所を newString に置換します。

🔗

data = "aaaBBBaaaCCC"
OUTPUT$ = REPLACE_REGEX(data, "a+", "z")

# OUTPUT$: zBBBzCCC (String)

STRLEN(string)🔗

渡された文字列の長さを返します。エラー時は-1、NULL型が渡された場合は0(エラーなし)を返します。

🔗

data = "1234567890"
OUTPUT$ = STRLEN(data)

# OUTPUT$: 10 (int)

UPPERCASE(string)🔗

渡された文字列のすべてのUnicode文字を大文字に変換して返します。strings.ToUpper() のインターフェース/ラッパーです。

🔗

data = "aaabbbccc acme"
OUTPUT$ = UPPERCASE(data)

# OUTPUT$: AAABBBCCC ACME (String)

LOWERCASE(string)🔗

渡された文字列のすべてのUnicode文字を小文字に変換して返します。strings.ToLower() のインターフェース/ラッパーです。

🔗

data = "AAABBBCCC ACME"
OUTPUT$ = LOWERCASE(data)

# OUTPUT$: aaabbbccc acme (String)

SANITIZE_ALL()🔗

イベントフィールド変数のnull/空値をクリーンアップします。例えば、" "、"N/A"、"n/a"、"null"、"nil"、"-" はすべてnullに設定されます。この関数は、!SANITIZEALLOFF で無効化しない限り、すべてのパーサーでデフォルトで実行されます。

🔗

data = "N/A"
OUTPUT$ = data

# OUTPUT$: NULL (null)
!SANITIZEALLOFF
data = "N/A"
OUTPUT$ = data

# OUTPUT$: N/A (String)

TRIM(data)🔗

空白、引用符、中括弧などを削除します。

🔗

data = " aaa bbb bcc "
OUTPUT1$ = "---" + data
OUTPUT2$ = "---" + TRIM(data)

# OUTPUT1$: --- aaa bbb bcc (String)

# OUTPUT2$: ---aaa bbb bcc (String)

TRIM_ALL()🔗

すべてのイベントフィールド変数の先頭/末尾の空白を削除します。この関数は、!TRIMALLOFF で無効化しない限り、すべてのパーサーでデフォルトで実行されます。

🔗

!TRIMALLOFF
data = " aaa bbb bcc "
OUTPUT2$ = data

# OUTPUT2$: aaa bbb bcc (String)

ADDFIELD(collection, fieldName, fieldValues)🔗

オブジェクトの配列にフィールドを追加します。各オブジェクトのフィールド値は fieldValues(配列)で指定します。新しいフィールド名は fieldName で指定します。collection がNULLの場合、各値(fieldName付き)を持つ新しいオブジェクト配列が作成されます。

🔗

keys = ["httpSourceName", "httpSourceId"]
values = [json["$.httpSourceName"], json["$.httpSourceId"]]

eventMetadata$.record$ = ADDFIELD(NULL, "key$", keys)
eventMetadata$.record$ = ADDFIELD(eventMetadata$.record$, "value$", values)

# event_metadata = {

#     "httpSourceName": json["$.httpSourceName"]

#     "httpSourceId": json["$.httpSourceId"]

# }

URL_PARSE(url, silent)🔗

URLを解析します。

ヒント

パース処理の詳細については、XDR でのカスタムパーサーの作成・編集・有効化 を参照してください。

silent がtrueの場合、URLが無効な場合でもエラーをスローせず、すべてのフィールドをnullにします。不正な形式のURLの場合、可能な限り多くの情報を抽出しようとします。想定される入力URL形式は以下のいずれかです:

scheme:opaque?query#fragment
scheme://userinfo@host/path?query#fragment

🔗

http://user:password@192.1.1.1:8080/1/asdfasdfasdf.html?key=value&key2=value2#topOfTheMorning
hTtps://Example.com:443/here//is/path.html?a=1+6&x=%2f%2Fkey=%41%0Avalue&b=ddd#top
https://example.com/foo/bar/bar/../baz.html?a=1&b=2
example.com/foo/bar/bar/../baz.html?a=1&b=2
スキームが指定されていない場合(例:example.com/index.html のように http://example.com/index.html ではない場合)、http が仮定され、scheme 値として返されます。

結果のコレクションオブジェクトには、可能な限り以下の値が含まれます:

  • scheme - 正規化済み。指定されたスキームを小文字に変換、または未指定の場合は http
  • user - 指定されていれば、渡されたユーザー。
  • host_raw - 未正規化。ポートを含む渡されたホスト(例:Example.com:443)。
  • host - 正規化済みホスト。すべて小文字、ポートを含まない(例:example.com)。
  • port - 抽出されたポート(存在する場合)。
  • path_raw - 未正規化。渡されたパス(例:/foo/bar/bar/../baz.html)。URIのクエリ部分があっても末尾に ? は含まれません。
  • path - 正規化済みパス(例:/foo/bar/baz.html)。URIのクエリ部分があっても末尾に ? は含まれません。正規化内容:
    • 文字はURIデコードされます。(単一パスなので %253D%3D= にはなりません)
      • /fo%6F/bar.html/foo/bar.html
    • 複数のスラッシュは1つにまとめられます。
      • /foo///bar.html/foo/bar.html
    • ディレクトリトラバーサルシーケンスは削除されます。
      • /foo/../bar//bar/
      • /foo/./bar//foo/bar/
  • query_raw - 未正規化。渡されたクエリ文字列(例:a=1+6&x=%2f%2Fkey=%41%0Avalue&b=ddd)。先頭に ? は含まれませんが順序は保持されます。(単一パスなので %253D%3D= にはなりません)
  • query - 正規化済みクエリ文字列。先頭に ? は含まれませんが順序は保持されます。URIデコードが行われ、失敗した場合は該当する名前と値のペアがそのまま順序通りに含まれ、正規化は行われません。
    • a=1&b=%44%57a=1&b=DW
  • raw_query - 非推奨。使用しないでください。query_raw と同じで、レガシー互換性のために提供されていますが、パーサーが更新され次第廃止されます。
  • password - 指定されていれば、渡されたパスワード。
  • fragment - 指定されていれば、渡されたフラグメント。

🔗

data = "hTtps://Example.com:443/here//is/path.html?a=1+6&x=%2f%2Fkey=%41%0Avalue&b=ddd#top"
urlParts = URL_PARSE(data, FALSE)
OUTPUT$ = urlParts["path_raw"]

# OUTPUT$: /here//is/path.html (String)

CONTAINS(string, substring)🔗

golangの strings.Contains(string,subString) をラップし、ブール値を返します。

🔗

data     = "aaabbbccc acme"
OUTPUT1$ = CONTAINS(data, "roadrunner")
OUTPUT2$ = CONTAINS(data, "acme")

# OUTPUT1$: false    (bool)

# OUTPUT2$: true    (bool)

IDX_OF_TLD(string)🔗

indexOfTopPrivateDomain$ 用に、文字列内でトップレベルドメインがどこにあるかを示すint64値を返します。-1が返された場合は IsTopPrivateDomainParsed$ をfalseに、それ以外はtrueに設定してください。

🔗

OUTPUT0$ = IDX_OF_TLD("aaa http://example.com")
OUTPUT1$ = IDX_OF_TLD("http://example.com")
OUTPUT2$ = IDX_OF_TLD("")

# OUTPUT0$: 0    (int)

# OUTPUT1$: 0    (int)

# OUTPUT2$: -1    (int)

PARSE_ERROR(string,string)🔗

ParseError は、パーサー .parameters[0] errText で明示的にエラーを発生させます。parameter[1] はオプションの文字列で、ParserValue.BoolValue() を使ってブール値にキャストされ、汎用イベントを作成するかどうかを示します。指定しない場合はデフォルトでtrueです。このメッセージは他のスキーマには正規化されません。

🔗

Genericイベントを作成🔗
test = IF someVal != "Expected_value" THEN PARSE_ERROR("bad data received") ELSE "ok"
Genericイベントを作成しない🔗
tenantId$ = TENANT_LOOKUP("ngav_id", vals["Account"], PARSE_ERROR("Unable to find Taegis tenant id for Deep Armor account " + vals["Account"],"False"))

TENANT_LOOKUP(label, value, default)🔗

メッセージのラベルと値に基づいて、Taegis Tenant ManagerでテナントIDを検索します。テナントが見つからない場合、指定したデフォルト式が評価されます。テナントが見つかった場合、3番目のパラメータは評価されません。呼び出し元はデフォルト値を指定するか、PARSE_ERROR() 関数を使ってエラーを発生させることができます。

🔗

tenantId$ = TENANT_LOOKUP("VendorName", messageValues["customerId"], PARSE_ERROR("Customer Id not on file"))

BASE64_DECODE(string)🔗

base64エンコードされた文字列入力をプレーンテキスト文字列に変換して返します。

🔗

OUTPUT$ = BASE64_DECODE("aG1lZXBcISBobWVlcFwh")

#OUTPUT$: hmeep\! hmeep\!    (String)

INT(string, base)🔗

特定の基数で表された数値文字列を整数に変換して返します。

🔗

OUTPUT$ = INT("4e0", 16)

#OUTPUT$: 1248    (int)

STRING(valueType)🔗

変数入力を文字列表現にキャストしようとします。

# In some cases "key" can be a string, empty (NULL), an array, or even map.

key = json["$.requestParameters.key"]
# By calling STRING() you guarantee objectKey is set with a value.

objectKey$ = STRING(key)
# Note: ParserValue.StringValue() isn't used directly because addition logic breaks when appending two valuetype.OBJECT to make a list (addition operator).

# valuetype.LIST, valuetype.OBJECT, and valuetype.JSONDATA returns the json string representation all others are cast to their string analogs.

OBJKEYS(value)🔗

マップまたはjsonオブジェクトのキーのリストを返します。

🔗

 # Suppose the original json was:

 { 
    "values" : {
        "c" : "x", 
        "b" : "y", 
        "a" : "z"
    }
 }
keys = OBJKEYS(json["$.values"])
# keys is now an array of ["a", "b", "c"]

# NOTE: this function puts the values in alphabetical order

OBJVALUES(value)🔗

マップまたはjsonオブジェクトの値のリストを返します。

🔗

 # Suppose the original json was:

 {
    "values" : {
        "c" : "x", 
        "b" : {
            "foo" : "bar"
        }, 
        "a" : "z"
    }
 }
vals = OBJVALS(json["$.values"])
# vals is now an array of ["z", "{ 'foo' : 'bar' }", "x"]

# NOTE: this function puts the values in alphabetical order by their key.  This assures that OBJKEYS and OBJVALS output their elements in the same order which is important when combining these functions with ADDFIELD().

FLATTEN(json, keyLabel, valueLabel)🔗

任意のjsonをオブジェクトのリストに変換します。

各オブジェクトには2つのフィールドがあります:キーと値(どちらも文字列型)。パラメータ keyLabel と valueLabel は省略可能で、デフォルト値は "key" および "value" です。この関数は、jsonデータをKeyValuePairsIndexed型のスキーマフィールド(例:genericスキーマのtagsフィールドやThirdPartyAlertのevidence.sourceData.recordフィールド)に格納するための便利な方法を提供します。

🔗

 # Suppose the original json was:

{
    "val" : { 
        "x": [
            "1",
            "2",
            "3"
        ] 
    }
}

# The output would be:

[
    {
        "key$": "val.x.0",
        "value$": "1"
    },         _
    {
        "key$": "val.x.1", 
        "value$": "2"
    },        _
    {
        "key$": "val.x.2", 
        "value$": "3"
    }
]