从 SPL 过渡到 YARA-L 2.0
本指南适用于已熟悉 Splunk 搜索处理语言 (SPL) 的用户。本课程简要介绍了 YARA-L 2.0,这是在 Google Security Operations 中构建搜索、信息中心和检测规则的核心语言。
了解 YARA-L 2.0 结构
YARA-L 2.0 是一种统一的查询语言,可在 Google SecOps 中使用,用于执行强大的威胁搜索、构建实时信息中心,以及在提取所有企业日志数据时创建高保真度检测规则。
该语言与 Google SecOps Detection Engine 配合使用,让您可以在大量数据中搜索威胁和其他事件。
SPL 和 YARA-L 之间的基本结构差异
SPL 使用一系列通过竖线 (|) 字符链接在一起的命令,而 YARA-L 基于部分。您可以使用不同的部分(例如 events、outcome 和 condition)来定义查询,以描述要搜索、检测或可视化的模式。
下表比较了 SPL 和 YARA-L 之间的基本结构:
| 功能 | SPL(程序性) | YARA-L(声明式) |
|---|---|---|
| 核心概念 | 使用命令流水线逐步转换数据流。 | 分析并应用条件和转换的多部分结构来处理安全和运营数据流,从而识别威胁并提取有价值的数据洞见。 |
| 数据流 | 程序化。一个命令的结果通过管道传输到下一个命令作为输入。 | 声明性结构,可大规模优化处理和关联模式。无需考虑效率问题。 |
| 事件关联 | 依赖于 join、transaction 和 stats 等显式命令。 |
通过定义多个事件并在查询逻辑中基于通用字段关联这些事件来实现。 |
| 时间窗口化 | 作为静态搜索窗口(例如 last
60m)处理。每次新搜索都是一次新请求。 |
作为在查询中定义的连续滑动时间窗口进行处理(例如 over 5m)。 |
| 语法 | 由命令驱动(例如 index=web)。 |
简洁且由逻辑驱动(例如 metadata.event_type=
"USER_LOGIN")。 |
查询的具体结构
YARA-L 会强制执行特定的查询结构,这与 SPL 的顺序管道命令不同。虽然 SPL 通过链接命令来构建结果,但 YARA-L 在不同的部分中定义了查询的不同方面。
| 命令 | 操作 | 必需 | 可选 |
|---|---|---|
meta
|
为规则设置描述性元数据,例如作者、说明和严重程度。 | 仅对规则是必需的。 |
events
|
定义和过滤事件。 | 必需(查询的核心逻辑)。 |
match
|
按事件分组,并允许您指定时间窗口(例如 by 5m)。 |
可选。仅对于多事件相关性查询是必需的。 |
outcome |
计算关键指标并获取数据洞见。 | 可选。 |
condition
|
评估查询变量条件,以确定结果是否适用(例如,#event >5)。 |
仅对规则是必需的。在搜索和信息中心中可选。 |
dedup |
通过基于关键变量(例如 target.user.userid、target.ip>、principal.hostname)对重复事件进行分组,从而移除这些事件。 |
可选。 |
order
|
按特定字段(例如 asc)对收集的事件结果进行排序。 |
可选。 |
limit |
限制查询返回的最大事件数。 | 可选。 |
SPL 和 YARA-L 中的常见命令
YARA-L 部分结构可让您使用 SP 中相同的常用命令。本部分将概述两者之间的异同。
SPL search 命令 = YARA-L events 部分
SPL 中的 search 命令相当于 YARA-L 中的 events 部分。events 部分定义了事件以及如何对这些事件进行初始过滤。虽然其他部分(例如 match 或 outcome)是可选的,但 events 部分是每条规则的基础。
例如:
metadata.event_type = "USER_LOGIN"
或者:
principal.hostname != "" AND metadata.event_type = "NETWORK_CONNECTION"
在规则(和高级查询)的 events 部分中,您可以使用事件变量来简化逻辑。
事件变量充当过滤条件的逻辑分组,表示符合特定条件的特定事件或一组事件。
例如,如需定义事件变量(例如 $e),请在查询的 events 部分中,将该变量用作所有相关事件和过滤条件的前缀。然后,您可以在查询的其他部分(例如 match 或 outcome)中使用该变量来引用该特定事件组及其数据字段。
事件变量最常见的应用场景是在检测规则中。以下示例展示了如何在规则中使用事件变量 ($e) 来对事件进行分组,并查找用户在一天内的登录失败次数。如果超出定义的阈值,则触发相应规则。
在规则示例中,每个事件都使用事件变量 ($e) 进行定义。metadata.id 中也引用了 $e 变量,以将规则元数据与定义的事件相关联。
rule DailyFailedLoginAttempts {
meta:
author = "Alex"
description = "Detects a high number of failed login attempts for a single user within a day."
events:
// Alias for each event
$e.metadata.event_type = "USER_LOGIN"
$e.security_result.action = "FAIL"
$e.principal.user.userid != ""
$userid = $e.principal.user.userid
match:
// Group events by principal.user.userid within a 24-hour window
$userid over 1d
outcome:
// Count the number of unique event IDs for each user per day
$daily_failed_login_count = count($e.metadata.id)
condition:
// Trigger a detection if the daily failed login count exceeds a threshold
// You should adjust this threshold based on your environment's baseline.
#e > 0
}
为确保规则触发,您通常需要检查分组后事件的计数。您可以使用事件变量在 condition 部分中指定最小数量。例如,条件 (#e > 0) 会检查是否存在至少一个符合条件的事件。
SPL eval 命令 = YARA-L outcome 部分
eval 命令是一种基本的 SPL 函数,用于在搜索结果中处理和定义字段值。
- 用途:用于计算和定义新的字段值。
- 功能:用于对数学、字符串或布尔表达式求值。
- 结果:评估结果会创建一个新字段或覆盖现有字段的值。
- 链式调用:可以使用英文逗号将多个表达式链接在一起(例如
| eval A=1, B=A+1)。 - 顺序处理:链中的表达式按顺序处理,因此后面的计算可以引用并基于前面的表达式创建或修改的字段进行计算。
下表(以及后续内容)中的示例说明了此命令结构:
| 功能 | 说明 | YARA-L 示例 |
|---|---|---|
| 布尔运算符 | 在 events 和 condition 中使用。请参阅在条件部分中使用 OR。 |
|
| 计算字段 | 在 outcome 部分中使用。 |
|
| 动态字段名称创建 | 在 outcome 部分中使用。 |
如需查看示例,请参阅将 SPL 与 YARA-L 进行比较。 |
示例:创建包含计算结果的新字段
使用 YARA-L 在每个事件中创建一个新字段 bytes。通过将发送的 bytes 字段中的值与接收的 bytes 字段中的值相加来计算字节数。
metadata.event_type = "SCAN_NETWORK"
principal.hostname != ""
$host = principal.hostname
match:
$host
outcome:
$bytes = cast.as_int(sum(network.sent_bytes))
示例:串联两个字段中的值
使用句点 (.) 字符将 first_name 字段中的值与 last_name 字段中的值串联起来。使用英文引号 ("") 在两个字段之间插入空格字符。在串联时,无论实际值是什么,系统都会将值读取为字符串。
在 SPL 中,查询将如下所示:
| eval full_name = first_name+" "+last_name
在 YARA-L 中,搜索查询将类似于以下内容:
principal.user.first_name = $first_name
principal.user.last_name = $last_name
outcome:
$full_name = strings.concat(principal.user.first_name = $first_name
principal.user.last_name = $last_name
outcome:
$full_name = strings.concat($first_name, " ", $last_name))
以登录失败查询为例,以下示例可让您查找在 10 分钟内(10m)连续登录失败 5 次或 5 次以上的用户(同时使用事件变量和占位变量):
metadata.event_type = "USER_LOGIN"
security_result.action = "FAIL"
target.user.userid = $userid
match:
$userid by 10m
outcome:
$login_count= count(metadata.id)
condition:
$login_count > 5
SPL where 命令 = YARA-L condition 部分
SPL where 命令相当于 YARA-L 中的 events、outcome 或 condition 部分的混合。您可以使用 events 部分声明事件并为其指定特定属性(例如 priniciapal.hostname = "xyz")。声明事件后,您可以使用布尔运算符、比较运算符和汇总函数结果(来自 outcome 部分)来定义事件必须满足的参数,以便查询返回结果。
以下示例演示了如何针对汇总计数设置阈值条件。该查询的结构旨在统计每个 User-ID 的登录失败事件总数,然后使用 condition 部分仅针对记录了 5 次或更多次登录失败的用户输出结果。
metadata.event_type = "USER_LOGIN"
security_result.action = "FAIL"
match:
target.user.userid
outcome:
// metadata.id counts all unique events associated with failed logins.
$count = count(metadata.id)
//metadata.id counts all unique events associated with blocked logins.
condition:
$count > 5
SPL dedup 命令 = YARA-L dedup 部分
SPL dedup 命令相当于 YARA-L 中的 dedup 部分。使用 dedup 部分可根据 events 部分中的事件对任何重复结果进行去重。
例如:
principal.hostname = "foo"
dedup:
target.ip
SPL stats 命令 = YARA-L match 或 outcome 部分(或两者兼有)
在 SPL 中,聚合通常由 stats 系列命令处理,这些命令用于指定聚合类型(例如 count、distinct count、max、min)和 "group by" 字段。
在 YARA-L 中,match 和 outcome 部分共同提供此功能:
聚合逻辑:
match部分通过定义要考虑的事件组 (match: $grouping_field by time) 来创建聚合。然后,outcome部分定义要针对该组计算的特定聚合函数(例如count()、min()、max())。时间窗口:
match部分支持指定时间窗口来对事件进行分组。使用over关键字(适用于规则)或by(适用于搜索和信息中心)(例如match: $userid by 1h)。此功能类似于 SPL,例如"timechart"、"streamstats"和"eventstats"。如需了解详情,请参阅时间窗口化。
示例:计算按主主机名和目标 IP 分组的字节数总和
以下示例使用 match 部分定义了一个聚合组,该组基于委托方主机名和目标 IP 地址,时间窗口为一天。然后在 outcome 部分计算发送的字节数的总和。
metadata.event_type = "NETWORK_CONNECTION"
network.sent_bytes > 0
principal.hostname != ""
target.ip != ""
// Define placeholder variables for grouping
$principal_hostname = principal.hostname
$target_ip = target.ip
// Group events by principal hostname, target IP, and day
match:
$principal_hostname, $target_ip by day
// Calculate the sum of sent bytes for each group
outcome:
$daily_bytes_sent = sum(network.sent_bytes)
将 SPL 映射到 YARA-L
SPL 通过管道命令逐步处理数据,而 YARA-L 使用声明式、基于部分的结构来定义模式和操作。尽管在方法上存在这些根本性差异,但 YARA-L 的表达能力可让您执行许多与 SPL 中相同的任务,从基本过滤到复杂的聚合和关联。
本部分通过将熟悉的 SPL 功能映射到 YARA-L 框架中的等效功能,来解释两者之间的区别。
将 SPL 与 YARA-L 进行比较
下表比较了常见 SPL 语言中的常见函数和概念与 YARA-L 2.0 中的等效函数和概念,或 YARA-L 查询结构中处理相应概念的方式。
| SPL 命令或概念 | 用途 | YARA-L 等效规则 | 说明和 YARA-L 映射 | YARA-L 实现示例 |
|---|---|---|---|---|
search |
初始数据过滤 | events 部分 |
定义事件字段和条件。无需在搜索或信息中心中使用 events 前缀。请参阅示例。 |
|
where |
进一步过滤结果 | events 和 condition 部分 |
应用布尔逻辑,通常应用于汇总结果。查看示例。 |
|
eval |
根据现有字段、汇总、数据查找计算新值 | outcome或 events部分 |
请参阅 SPL eval 示例。
|
|
stats |
聚合(count、sum、平均值) |
match 或 outcome |
按 match 中的字段分组。在 outcome 中计算聚合。请参阅汇总和统计查询以及 SPL stats 命令中的示例。 |
|
dedup |
根据一个或多个字段移除重复事件 | dedup 部分 |
指定要用于去重的字段。 |
|
table |
定义表列输出 | select 或 unselect
|
用于信息中心。在搜索中,显示 outcome 变量。 |
|
sort |
按升序或降序列出结果 | order 部分 |
请参阅下一个表格单元格中的示例。 |
|
limit |
限制返回的结果数 | limit 部分 |
请参阅下一单元格中的示例。 |
|
| 多值函数 | 通过 mv* 函数(mvexpand、mvfilter)处理 |
支持内置 | YARA-L 会自动取消嵌套事件部分中的数组。 如果需要,可在 outcome 中使用数组函数。 |
请参阅多值函数示例。 |
| 时间窗口化 | earliest=-5m, latest=now |
match 部分,over,by |
如需进行持续检测,请使用 match: $field over 5m or by 5m。对于搜索界面中的信息中心,请使用 match: $field by 5m。 |
请参阅时间窗口化中的示例。 |
聚合查询和统计查询
在 YARA-L 中,聚合函数和统计函数通常放在 outcome 部分,而聚合基于 match 部分。
stats 命令是在 YARA-L 查询中实现数据聚合的主要机制。它将原始事件数据转换为汇总的安全指标。eval 命令处理字段级、逐行转换(类似于 SELECT 表达式),而 stats 执行集级聚合(类似于 SQL 中的 GROUP BY)。
下表提供了核心语法和用法,演示了如何根据数据模式和统计离群值有效地使用 stats 来实现复杂的安全逻辑。
| SPL 函数 | 说明 | YARA-L 等效功能 | YARA-L 实现示例 |
|---|---|---|---|
count |
统计事件数量。 | count() |
|
dc (count_distinct) |
统计字段的唯一值数量。 | count_distinct() |
|
sum |
计算某个字段的值的总和。 | sum() |
|
avg |
计算字段的平均值。 | avg() |
|
min/max |
查找字段的最小值或最大值。 | min() 或 max() |
|
median() |
查找中位数值。 | window.median |
|
first() and last() |
根据搜索结果中事件的顺序返回值。 | window.first/window.last |
|
STDDEV() |
计算标准差,用于衡量数据集的离散程度。 | window.stddv |
|
如需了解详情,请参阅其他函数。
例如,多阶段查询可以通过分层聚合来跟踪多次登录失败的情况。如需了解详情,请参阅多阶段聚合示例和在 YARA-L 中创建多阶段查询。
多阶段聚合(从每小时平均值到每周平均值)
此多阶段示例最初会汇总数据,以查找每个主机每小时传输的字节数。然后,它会使用该汇总数据来计算过去 7 天内这些小时段的总体平均值。
stage bytes_per_host {
metadata.event_type = "SCAN_NETWORK"
principal.hostname != ""
$host = principal.hostname
match:
$host by hour
outcome:
$bytes = cast.as_int(sum(network.sent_bytes))
}
$host = $bytes_per_host.host
match:
$host
outcome:
$hour_buckets = array_distinct(timestamp.get_timestamp($bytes_per_host.window_start))
$num_hour_buckets = count_distinct($bytes_per_host.window_start)
$avg_hourly_bytes = avg($bytes_per_host.bytes)
多值函数(读取数组)
YARA-L 的语法旨在理解一个字段可以有多个值。如果您编写的查询在 events 部分中包含具有多值字段的事件,该语言会自动检查数组中的每个值。您无需使用特殊函数来过滤数组,只需声明要匹配的条件即可。例如,如果日志事件的 principal.ip 字段包含以下内容,YARA-L 引擎会自动检查 principal.ip 数组中的每个值。如果任何值为 "10.1.1.5",则满足条件。
["10.1.1.5", "10.2.2.6", "10.3.3.7"]
下表比较了 YARA-L 和 SPL 在如何管理日志数据中的多值字段方面的差异。多值字段(例如 IP 地址数组或用户群组列表)是结构化日志中的常见功能。
| SPL 函数 | 用途 | YARA-L 等效功能 | YARA-L 实现示例 |
|---|---|---|---|
mvfilter() |
过滤多值字段,仅保留匹配的值。 | 在 YARA-L 查询的 events 部分中使用时,列出要匹配的字段。YARA-L 会自动检查 groups 数组中的任何值是否与“`admin`”匹配。 |
|
mvcount() |
计算多值字段中的值数量。 | count() 应用于 outcome 查询部分中的字段。无需先取消嵌套任何值。 |
请参阅统计属于 IT 员工群组的用户数量示例。 |
mvexpand |
为多值字段中的每个值创建一个新事件。 | 可原生且隐式地处理多值字段;自动进行取消嵌套。 | 请参阅统计属于 IT 员工群组的用户数量示例。 |
mvjoin |
将多值字段中的所有值联接成一个字符串,以用于数据格式设置。 | 这些值会自动以数组形式存储在结果中。YARA-L 的输出是结构化数据,而不是扁平的字符串。如果需要进一步处理数组,则将相应字段显示为数组。如需了解详情,请参阅数组函数。 |
示例:统计 admin 登录次数
在以下示例中,条件 $metadata.event_type = "USER_LOGIN" 会过滤 event_type 为 "USER_LOGIN" 的事件:
events:
metadata.event_type = "USER_LOGIN" // Changed to a more appropriate event type for login
principal.user.group_identifiers = "admin"
outcome:
// This counts each unique event ID where the principal user is in the `"admin"` group.
$admin_login_count = count(metadata.id)
$principal.user.group_identifiers= "admin" 字段是重复字段(数组)。
- 隐式取消嵌套:YARA-L 会在查询评估期间自动在内部取消嵌套此字段。
- 条件检查:如果
$principal.user.group_identifiers数组中的任何值等于"admin",则事件将满足条件。 - 无需显式命令:与 SPL 不同,您不需要像
mvexpand这样的特定取消嵌套命令。
“对聚合的影响 (outcome)”部分表示,隐式取消嵌套在 outcome 部分(例如 outcome: $admin_login_count = count(metadata.id)))中至关重要。请注意以下影响:
- 如果单个 UDM 事件的重复字段中包含多个匹配值,则可以生成多个内部行以用于查询评估。
- 由于
events部分已根据$principal.user.group_identifiers中的每个匹配值有效地取消嵌套事件,因此count(metadata.id)聚合会统计每个取消嵌套的实例。
示例:统计属于 IT 员工群组的用户数量
SPL:
index=<your_index_name> user_id="jsmith"
| where match(memberOf, "Domain Admins|IT Staff|HR")
| mvexpand memberOf
| stats count by memberOf
| mvexpand actions
| table memberOf, count, actions
YARA-L(搜索):
principal.user.userid = "jsmith"
additional.fields["memberOf"] = $group
$group = /Domain Admins|IT Staff|HR/ nocase
match:
$group by 1h
outcome:
$group_count = count_distinct(metadata.id)
$memberOf = array_distinct($group)
$risk_score = max(50)
示例:创建一条规则,以便在出现特定文件哈希时触发提醒
SPL:
| eval file_hashes="hash12345,hash67890,hashABCDE"
| makemv delim="," file_hashes
| mvexpand file_hashes
| search file_hashes="hash67890"
| table _time, file_hashes
YARA-L(规则):
rule specific_file_hash_detected {
meta:
rule_name = "Specific File Hash Detected"
description = "Detects events where a specific file hash is present."
severity = "Medium"
events:
$e.target.file.sha256 == "hash67890"
outcome:
$time = array_distinct($e.metadata.event_timestamp)
$file_hashes = array_distinct($e.target.file.sha256)
condition:
$e
}
时间窗口化
在 YARA-L 中,时间窗口化是一种在特定滚动时间段内关联事件的方法。在规则中使用时,此窗口会随着传入的数据不断移动,从而实现对随时间推移而出现的模式进行持续的实时检测。
此流程是自动化检测设计中的关键部分,也是使用 YARA-L 的优势之一。指定时间窗口后,检测和信息中心会持续使用实时数据。
| 功能 | SPL | YARA-L |
|---|---|---|
| 主要目标 | 静态搜索、临时分析 | 持续检测,自动关联 |
| 主要功能 |
earliest, latest, span, transaction
|
over,by |
| 5 分钟示例 | earliest=-5m(静态搜索)或
transaction maxspan=5m
|
match:[event] over 5m(规则中的持续检测)或
[event] by 5m(搜索和信息中心)
|
本部分中的示例说明了 YARA-L 中滚动时间窗口(使用 by)与滑动时间窗口(使用 over)之间的区别。
翻滚时间窗口 (by <time_unit>)
概念:在 YARA-L 搜索中使用,滚动窗口会创建固定且不重叠的固定大小的时间间隔。系统会根据每个事件的时间戳,将其分配给一个且仅一个特定的时间段,从而处理每个事件。这些固定间隔是绝对的,并且与标准时间标记(例如天、小时或分钟)严格对齐。
用法:通常在 Google SecOps 搜索查询和信息中心中使用,用于将数据汇总到离散的时间段。
示例:每位用户的每日成功登录次数
此搜索查询按每个日历天内每个唯一身份用户对成功登录事件进行分组。以下示例展示了 YARA-L 搜索滚动窗口 (by day):
events:
//Filter for successful login events
metadata.event_type = "USER_LOGIN"
principal.user.userid != ""
match:
//Group by each unique user ID, aggregated over a calendar day.
principal.user.userid by day
outcome:
//Count how many successful logins occurred for this user on this specific day.
$daily_success_count = count(metadata.id)
//Get the timestamp of the FIRST event within this daily group.
$first_event_time = window.first(metadata.event_timestamp.seconds, timestamp.get_timestamp(metadata.event_timestamp.seconds))
//Get the timestamp of the LAST event within this daily group.
$last_event_time = window.last(metadata.event_timestamp.seconds, timestamp.get_timestamp(metadata.event_timestamp.seconds))
运作方式:如果用户 jdoe 在 Nov 17 成功登录 10 次,在 Nov 18 成功登录 15 次,则此查询会为 jdoe 生成两个单独的行,分别对应这两天,并包含各自的计数。Nov 17 类别包含来自 2025-11-17 00:00:00 to 23:59:59 UTC 的活动。
滑动时间窗口 (over <duration>)
概念:滑动窗口用于 YARA-L 规则,是指指定时长的移动时间窗口(可能会重叠)。它们非常适合用于关联在一定邻近范围内发生的事件。
用法:主要用于 YARA-L 规则中,以检测连续时间范围内的模式或事件序列。
示例:检测 5 分钟内多次登录失败
此 YARA-L 规则示例会在单个用户在任何滚动 5-minute 时间段内尝试登录的次数超过 5 failed logins 次时生成检测结果:
rule TooManyFailedLogins_SlidingWindow {
meta:
author = "Alex"
description = "Detects when a user has more than 5 failed logins within a 5-minute sliding window."
severity = "Medium"
events:
// Define an event variable $e for failed login attempts
$e.metadata.event_type = "USER_LOGIN"
$e.security_result.action = "FAIL"
$e.principal.user.userid != ""
$userid = $e.principal.user.userid
match:
// Group events by userid over a continuous 5-minute sliding window.
// Any events for the same $userid within 5 minutes of each other are grouped.
$userid over 5m
outcome:
// Count the number of failed login events within each 5-minute window for the grouped userid.
$failed_count = count($e.metadata.id)
condition:
// Trigger a detection if the count of failed logins in ANY 5-minute window is greater than 5.
#e > 5
}
运作方式:系统会持续监控登录失败情况。在任何给定时刻,它都会考虑每位用户在过去 5 分钟内的事件。例如,如果用户 jdoe 在 10:02:30 到 10:07:30 之间累计登录失败 6 次,系统就会触发检测。此窗口会不断向前滑动,从而实现实时模式检测,而无需考虑日历边界。
后续步骤
- 深入了解 YARA-L 2.0 概览
- 了解完整的 YARA-L 2.0 语言语法
- 了解如何编写检测引擎规则
- Google SecOps 新手博客
需要更多帮助?获得社区成员和 Google SecOps 专业人士的解答。