开源HIDS之Osquery详解

Osquery 是跨平台 Windows、OS X (macOS) 和 Linux 的威胁检测框架,由FaceBook开源,用于对系统进行查询、监控以及分析的一款软件,核心特性是支持SQL的方式来获取操作系统的数据,本文介绍Osquery使用方法、检测原理及安全能力...

0x01 基础使用

1. 安装部署

记录Osquery在Linux平台下安装方法

目录文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/etc/init.d/osqueryd
/etc/osquery/
/etc/sysconfig/osqueryd
/opt/osquery/bin/osqueryctl
/opt/osquery/bin/osqueryd
/usr/local/bin/osqueryi -> /opt/osquery/bin/osqueryd
/usr/local/bin/osqueryctl -> /opt/osquery/bin/osqueryctl
/usr/lib/systemd/system/osqueryd.service
/opt/osquery/share/osquery/certs/certs.pem
/opt/osquery/share/osquery/lenses/{*}.aug
/opt/osquery/share/osquery/packs/{*}.conf
/opt/osquery/share/osquery/osquery.example.conf
/var/log/osquery/
/var/osquery/

PRM安装

1
2
3
4
curl -L https://pkg.osquery.io/rpm/GPG | sudo tee /etc/pki/rpm-gpg/RPM-GPG-KEY-osquery
sudo yum-config-manager --add-repo https://pkg.osquery.io/rpm/osquery-s3-rpm.repo
sudo yum-config-manager --enable osquery-s3-rpm-repo
sudo yum install osquery

运行模式

  • osqueryi:启动一个独立的 osquery,交互式查询控制台 osqueryi 提供了一个 SQL 界面来尝试新的查询并探索您的操作系统

  • osqueryd:启动守护进程,使用它来深入了解整个基础架构的安全性、性能、配置和状态

1
2
3
sudo cp /opt/osquery/share/osquery/osquery.example.conf /etc/osquery/osquery.conf

sudo systemctl start osqueryd

2. osqueryi

osqueryi 是 osquery 交互式查询控制台/shell。在这种模式下,它是完全独立的,不与守护进程通信,也不需要以管理员身份运行。在这种模式下,它是完全独立的,不与守护进程通信,也不需要以管理员身份运行。

执行 SQL 查询

示例:

1
2
3
4
5
6
7
8
9
$ osqueryi
osquery> SELECT DISTINCT
...> process.name,
...> listening.port,
...> process.pid
...> FROM processes AS process
...> JOIN listening_ports AS listening
...> ON process.pid = listening.pid
...> WHERE listening.address = '0.0.0.0';

输出 JSON 或 CSV 值:

1
$ osqueryi --json "SELECT * FROM routes WHERE destination = '::1'"

使用管道符(注意添加’;’使用标准输入时的查询):

1
echo "SELECT * FROM routes WHERE destination = '::1';" | osqueryi --json

元命令

osqueryi 是 SQLite shell 的修改版本。它接受几个以”.”为前缀的元命令:

  • 列出所有表:.tables
  • 列出特定表的架构(列、类型):.schema table_name 或 pragma table_info(table_name)
  • 列出所有可用命令:.help
  • 退出控制台:.exit 或 ^D

示例:

1
2
3
4
osquery> .tables
osquery> .schema routes
osquery> PRAGMA table_info(routes);
osquery> .exit

osqueryi 默认使用内存数据库。要连接到现有的事件数据库,需使用标志 --database_path=/var/osquery/osquery.db

3. osqueryd

osqueryd 是主机监控守护进程,允许管理查询和记录操作系统状态更改,守护进程随时间聚合查询结果并生成日志,这些日志根据每个查询指示状态变化。守护进程还使用操作系统事件 API 来记录受监控的文件和目录更改、硬件事件、网络事件等。

配置定时查询

守护程序主要功能是执行定时查询,这个调度在 osquery 配置中定义,可配置查询时间频率

1
2
3
4
5
6
{
"usb_devices": {
"query": "SELECT vendor, model FROM usb_devices;",
"interval": 60
}
}

上述usb_devices 查询将在运行 osqueryd 的主机上大约每 60 秒运行一次。

记录和报告

每个查询代表您的操作系统的一个监控视图。计划查询第一次运行时,它会使用”added”操作记录结果表中的每一行。

如果设备上没有添加或移除 USB 设备,则此查询将永远不会再次记录结果。查询仍将每 60 秒运行一次,但结果将与上一次运行相匹配,因此不会检测到任何状态更改。

4. SQL查询

osquery 以 SQL 为中心, SQL 支持入侵检测、事件响应、流程审计、文件完整性监控等。

查询示例

1
2
3
4
5
6
7
8
9
10
11
12
osquery> SELECT pid, name, path FROM processes LIMIT 1;
osquery> .mode line // 使用 .mode 行在较小的终端视图中获得最佳输出
osquery> SELECT pid, name, path FROM processes LIMIT 1;
osquery> SELECT * FROM osquery_info;
osquery> .mode pretty

osquery> SELECT pid, name, path FROM osquery_info JOIN processes USING (pid); //join查询
osquery> SELECT p.pid, name, p.path as process_path, pf.path as open_path
...> FROM osquery_info i
...> JOIN processes p ON p.pid = i.pid
...> JOIN process_open_files pf ON pf.pid = p.pid
...> WHERE pf.path LIKE '/dev/%';

带参数的表

文件表操作,使用 file 表中的 path 和 directory 作为输入参数:

1
2
3
4
5
6
7
8
9
osquery> .mode line
osquery> SELECT * FROM file;
osquery> SELECT * FROM file WHERE path = '/dev/zero';
osquery> SELECT count(1) FROM file WHERE path LIKE '/dev/%';

osquery> .mode line
osquery> SELECT path, inode, size, type
...> FROM file
...> WHERE path IN (SELECT '/dev/zero');

hash表,查询/etc 中最后修改的文件的哈希值:

1
2
3
4
5
6
osquery> .mode line
osquery> SELECT path, mtime, sha256
...> FROM file
...> JOIN hash USING (path)
...> WHERE file.directory = '/etc'
...> ORDER BY mtime DESC LIMIT 1;

Math 函数

osquery 包括以下 C-math 函数:sqrt、log、log10、ceil、floor、power、pi

1
2
3
osquery> .mode line
osquery> select power(disk_size) as disk_size from disk_info;
osquery> select pi() * disk_size as disk_size from disk_info;

三角函数:sin、cos、tan、cot、asin、acos、atan 和 radians(弧度) 到 degrees(度数) 的转换

1
2
3
4
osquery .mode line 
osquery> select sin(30);
osquery> select radians(60);
osquery> select degrees(1.3);

String 函数

1)concate(ARG1, ARG2, ARG3…):连接参数,忽略空值。

1
2
osquery> .mode line
osquery> select concat('hello', NULL, ' ', 'world');

2)concat_ws(SEPARATOR, ARG1, ARG2, ARG3…):连接参数,忽略空值,并与 SEPARATOR 连接

1
2
osquery> .mode line
osquery> select concat_ws(' ', 'hello', NULL, 'world');

3)split(COLUMN, TOKENS, INDEX):使用 TOKENS 中的任何字符标记拆分 COLUMN 并返回 INDEX 结果。如果 INDEX 结果不存在,则返回 NULL 类型。

1
2
3
osquery> .mode line
osquery> select uid from users;
osquery> select split(uid, 1, 0) from users;

4)regex_match(COLUMN, PATTERN, INDEX):对列运行正则表达式匹配,并返回匹配的子组(0 索引是完全匹配,后面的数字是组)

1
2
osquery> .mode line
osquery> select regex_match('hello world. Goodbye', '(\w+) \w+', 0) as m0, regex_match('hello world. Goodbye', '(\w+) \w+', 1) as m1;

5)inet_aton(IPv4_STRING):返回 IPv4 字符串的整数表示

1
2
3
osquery> .mode line

osquery> select inet_aton("1.0.1.5") as ipInt;

哈希函数

添加了 sha1、sha256 和 md5 函数,它们采用单个参数并返回散列值

1
2
3
4
5
osquery> .mode line 
osquery> select username from users;
osquery> select sha1(username) as usernameHash from users;
osquery> select sha256(username) as usernameHash from users;
osquery> select md5(username) as usernameHash from users;

编码函数

1)to_base64: base64 编码一个字符串

1
2
3
4
5
osquery> .mode line

osquery> select device_id from cpu_info;

osquery> select to_base64(device_id) as device_id from cpu_info;

2)from_base64:解码 base64 编码的字符串。如果字符串不是有效的 base64,则返回一个空字符串

1
2
3
4
5
6
7
osquery> .mode line

osquery> select device_id from cpu_info;

osquery> select to_base64(device_id) as device_id from cpu_info;

osquery> select from_base64(to_base64(device_id)) as device_id from cpu_info;

3)conditional_to_base64:当且仅当字符串包含非 ASCII 字符时才对字符串进行编码

1
2
3
4
5
6
7
osquery> .mode line

osquery> select device_id from cpu_info;

osquery> select conditional_to_base64(device_id) as device_id from cpu_info;

osquery> select conditional_to_base64(device_id + char(183)) as device_id from cpu_info;

Network 函数

in_cidr_block(CIDR_RANGE, IP_ADDRESS):如果 IP 地址在 CIDR 块内,则返回 1,否则返回 0

1
2
3
4
5
osquery> .mode line

osquery> SELECT in_cidr_block('10.0.0.0/26', '10.0.0.24');

osquery> SELECT in_cidr_block('2001:db8::/48', '2001:db8:0:ffff:ffff:ffff:ffff:ffff');

5. 命令行标志

要查看 osquery 的完整标志列表,使用 –help 或从 osquery_flags 表中选择:

1
2
$ osqueryi
osquery> SELECT * FROM osquery_flags;

查看已配置、标志文件或 shell 更新的标志:

1
osquery> SELECT * FROM osquery_flags WHERE default_value <> value;

Flagfile

在 macOS 和 Linux 上,此 –flagfile 是添加/删除以下仅 CLI 初始化标志的推荐方法。

--flagfile /etc/osquery/osquery.flags

包括要解释和用作 CLI 标志的行分隔开关:

1
2
3
4
--config_plugin=custom_plugin
--logger_plugin=custom_plugin
--distributed_plugin=custom_plugin
--watchdog_level=0

如果未提供 –flagfile,osquery 将尝试在 /etc/osquery/osquery.flags.default 中查找并使用”default”标志文件。 shell 和守护进程都会发现并使用默认值。

配置控制标志

--config_plugin=filesystem:配置插件名称,配置检索的类型,默认文件系统插件从磁盘读取配置 JSON,内置选项包括filesystem, tls

--config_path=/etc/osquery/osquery.conf:文件系统配置插件的 JSON 文件路径。默认路径是 /var/osquery/osquery.conf,如果从多个配置路径读取,创建一个目录:/etc/osquery/osquery.conf.d/,该可选目录中的所有文件都将按词法顺序读取和合并

--config_refresh=0:可选的配置刷新间隔(以秒为单位)。默认情况下,配置仅在 osquery 加载时获取。如果配置应该自动更新,请将刷新时间设置为大于 0 的秒数。

--config_accelerated_refresh=300:如果使用配置刷新 (config_refresh > 0) 并且刷新尝试失败,则将使用加速刷新

--config_check=false:检查 osquery 配置的格式并退出。可以使用任意配置插件。如果解析失败,osquery 将返回非 0 退出。

--config_dump=false:请求在更新之前将配置 JSON 打印到标准输出,然后退出该过程。

守护进程控制标志

--force=false:强制 osqueryd 杀死以前运行的守护进程。

--pidfile=/var/osquery/osqueryd.pidfile:守护程序 pidfile 互斥锁的路径,该文件用于防止启动多个 osqueryd 进程。

--disable_watchdog=false:禁用用户态watchdog进程,osqueryd 使用watchdog进程来监视执行查询计划的线程的内存和 CPU 使用率

--watchdog_level=0:性能限制级别(0=正常,1=限制,-1=禁用)。watchdog进程使用”level”来配置性能限制,级别限制如下: 内存:默认200M,限制100M CPU:默认10%(持续12秒),限制5%(持续6秒)

--watchdog_memory_limit=0:如果此值 >0,则覆盖最大内存的watchdog级别 (–watchdog_level)。

--watchdog_utilization_limit=0:如果此值 >0,则覆盖最大持续 CPU 使用率的watchdog级别 (–watchdog_level)。

事件控制标志

--disable_events=false:禁用或启用 osquery 操作系统事件发布-订阅 API。将此设置为 true(这是默认值)会禁用报告事件数据的表(名称以 _events 结尾的表)并且查询它们将生成警告

--events_expiry=3600:事件数据的过期时间(以秒为单位),在查询数据后应用。

--events_optimize=true:在 osquery 守护程序的查询计划中进行优化。

--events_max=50000:在等待查询”耗尽”它们时要在后备存储中缓冲的最大事件数

--events_enforce_denylist=false:控制是否对使用”*_events”(基于事件的)表的查询强制执行 watchdog 拒绝列表

Logging/results标志

--logger_plugin=filesystem:记录器插件名称,默认记录器是文件系统。这会将各种日志类型作为 JSON 写入特定文件路径

--logger_plugin=filesystem,syslog:指定多个记录器插件

--disable_logging=false:禁用 ERROR/WARNING/INFO(又名状态日志)和查询结果日志记录

--logger_event_type=true:将预定结果记录为事件

--logger_snapshot_event_type=false:将计划的快照结果记录为事件,类似于差异结果

--logger_min_status=0:状态日志记录的最低级别。使用以下值:INFO = 0、WARNING = 1、ERROR = 2。要禁用所有状态消息,请使用 3 或更高值。使用 –verbose 时,该值将被忽略

--logger_min_stderr=0:写入 stderr 的状态日志的最低级别。使用以下值:INFO = 0、WARNING = 1、ERROR = 2

--logger_stderr=true:默认行为是还将状态日志写入 stderr。将此标志设置为 false 以禁止将状态日志写入(复制)到 stderr

--logger_path=/var/log/osquery/:文件系统插件记录 ERROR/WARN/INFO 和查询结果的目录路径

--logger_mode=0640:文件系统插件输出日志文件的文件模式,以八进制字符串形式提供

--logger_kafka_brokers:要连接的 Kafka 代理的逗号分隔列表。格式可以是 protocol://host:port

--logger_kafka_topic:将日志发布到的 Kafka 主题。当使用多个topic时,此配置成为未配置查询回退到的基本topic

--host_identifier=hostname:用于标识运行 osquery 的主机的字段(hostname、uuid、ephemeral、instance、specified)

0x02 Osquery配置

1. 配置组件

osquery”配置”是从配置插件中读取的。该插件是一种数据检索方式,默认设置为文件系统。其他检索和运行时更新方法可能包括使用 tls 配置插件的 HTTP/TLS 请求。在所有情况下,响应数据都必须是 JSON 格式。

配置相关的组件:

  • 守护进程选项和功能设置
  • 查询计划任务:一组 SQL 查询和间隔
  • 文件变更监控:监控的文件和目录的类别和路径

默认配置文件:

1
2
3
Linux: /etc/osquery/osquery.conf 和 /etc/osquery/osquery.conf.d/
macOS: /var/osquery/osquery.conf 和 /var/osquery/osquery.conf.d/
Windows: C:\Program Files\osquery\osquery.conf

可以使用 --config_path=/path/to/osquery.conf 覆盖配置文件的路径

2. 查询Packs

配置支持帮助定义计划任务的查询集(称为Packs)。Packs使用 osquery 分发,并根据广泛的信息类别和可见性进行标记。

例如,”compliance”包将包括检查锁定操作系统功能和用户设置更改的查询;”vulnerability management”包可以执行一般资产管理查询,这些查询围绕包和软件安装更改构建事件日志。

在 osquery 配置 JSON 中,包被定义为top-level-key ,由包名称组成,用于打包内容 JSON 数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"schedule": {...},
"packs": {
"internal_stuff": {
"discovery": [
"SELECT pid FROM processes WHERE name = 'ldap';"
],
"platform": "linux",
"version": "1.5.2",
"queries": {
"active_directory": {
"query": "SELECT * FROM ad_config;",
"interval": "1200",
"description": "Check each user's active directory cached settings."
}
}
},
"testing": {
"shard": "10",
"queries": {
"suid_bins": {
"query": "SELECT * FROM suid_bins;",
"interval": "3600"
}
}
}
}
}

pack值也可以是字符串,如下:

1
2
3
4
5
6
7
8
{
"packs": {
"external_pack": "/path/to/external_pack.conf",
"internal_stuff": {
[...]
}
}
}

如果使用字符串而不是内联 JSON 字典,将要求配置插件”生成”该资源。在默认文件系统插件的情况下,这些字符串被视为路径。

文件系统插件支持另一种添加包目录的约定:

1
2
3
4
5
{
"packs": {
"*": "/path/to/*",
}
}

这里的 * 要求插件将值全局化并构造一个multi-pack

Discovery queries

Discovery queries是查询包的一项功能,可以更轻松地大规模监控服务。

示例:该包将仅在运行名为”foobar”的进程并且具有以”www”开头的用户的主机上执行

1
2
3
4
5
6
7
{
"discovery": [
"SELECT pid FROM processes WHERE name = 'foobar';",
"SELECT 1 FROM users WHERE username like 'www%';"
],
"queries": {}
}

3. 配置说明

介绍所有(read:most)默认配置键,称为默认规范。提到”default”是因为可以使用 ConfigParser 插件扩展配置。

Options

options 键定义选项名称到选项值的映射,名称必须是”osquery 配置选项”集中的 CLI 标志

1
2
3
4
5
6
7
{
"options": {
"read_max": 100000,
"events_max": 100000,
"host_identifier": "uuid"
}
}

Schedule

schedule 键定义计划查询名称到查询详细信息的映射,每个schedule的值也是一个映射,我们称这些计划查询,它们的键是显示在结果日志中的名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"schedule": {
"users_browser_plugins": {
"query": "SELECT * FROM users JOIN browser_plugins USING (uid);",
"interval": 60
},
"hashes_of_bin": {
"query": "SELECT path, hash.sha256 FROM file JOIN hash USING (path) WHERE file.directory = '/bin/';",
"interval": 3600,
"removed": false,
"platform": "darwin",
"version": "1.4.5",
"shard": 1
}
}
}

基本的 scheduled 查询规范包括:

  • query:要运行的 SQL 查询
  • interval:运行查询的间隔(以秒为单位)(取决于splay/smoothing)
  • removed:一个布尔值,用于确定是否应记录”removed”操作,默认为 true
  • snapshot:设置”snapshot”模式的布尔值,默认为 false
  • platform:将此查询限制在给定平台,默认为”所有”平台;您可以使用逗号来设置多个平台
  • version:仅在大于或等于此版本字符串的 osquery 版本上运行
  • shard:将此查询限制为目标主机的百分比(1-100)
  • denylist:一个布尔值,用于确定此查询是否可能被列入拒绝列表(当看门狗因资源消耗过多而停止时),默认为 true

platform key 可以是:

  • darwin:macOS 主机
  • linux:RedHat 或 Debian-based 主机
  • posix:darwin 和 linux 主机
  • windows:Windows desktop 或 server 主机
  • any/all:全部平台

Packs

Packs 使用 packs 键,一个映射,其中键是包名称,值可以是字符串或字典(对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"packs": {
"pack_name_1": "/path/to/pack.json",
"pack_name_2": {
"queries": {},
"shard": 10,
"version": "1.7.0",
"platform": "linux",
"discovery": [
"SELECT * FROM processes WHERE name = 'osqueryi';"
]
}
}
}

如上,每个pack 都用了platform, version, 和 shard选择器和限制。queries 键模仿配置的 schedule 键,上面的包部分详细描述了 discovery 查询集功能。

File Paths

file_paths 键定义文件完整性监控 (FIM) 类别到文件系统 globbing 行集的映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"file_paths": {
"custom_category": [
"/etc/**",
"/tmp/.*"
],
"device_nodes": [
"/dev/*"
]
},
"file_accesses": [
"custom_category"
]
}

文件路径集有一个姊妹键:file_accesses,它包含一组选择加入文件系统访问监控的类别名称

YARA

yara 键使用两个子键来配置 YARA 签名:signatures,并定义签名集到”file paths”配置中定义的file_paths类别的映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"yara": {
"signatures": {
"signature_group_1": [
"/path/to/signature.sig"
]
},
"file_paths": {
"custom_category": [
"signature_group_1"
]
}
}
}

Prometheus

prometheus_targets 键可用于配置要查询的 Prometheus 目标。接收到目标响应时取毫秒精度的度量时间戳,prometheus_targets 父键由子键 urls 组成

1
2
3
4
5
6
7
8
9
{
"prometheus_targets": {
"timeout": 5,
"urls": [
"http://localhost:9100/metrics",
"http://localhost:9101/metrics"
]
}
}

Views

视图是表示为表的已保存查询。大型子查询或复杂的连接逻辑通常可以移动到视图中,使查询更简洁

1
2
3
4
5
{
"views": {
"kernel_hashes" : "SELECT hash.path AS kernel_binary, version, hash.sha256 AS sha256, hash.sha1 AS sha1, hash.md5 AS md5 FROM (SELECT path || '/Contents/MacOS/' AS directory, name, version FROM kernel_extensions) JOIN hash USING (directory);"
}
}
1
SELECT * FROM kernel_hashes WHERE kernel_binary NOT LIKE "%apple%";

Decorator queries

装饰器查询存在于 osquery 版本 1.7.3+ 中,用于向结果和快照日志添加额外的”装饰”,根据需要装饰数据的时间和方式,存在三种类型的装饰器查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"decorators": {
"load": [
"SELECT version FROM osquery_info;",
"SELECT uuid AS host_uuid FROM system_info;"
],
"always": [
"SELECT user AS username FROM logged_in_users WHERE user <> '' ORDER BY time LIMIT 1;"
],
"interval": {
"3600": [
"SELECT total_seconds AS uptime FROM uptime;"
]
}
}
}

装饰器的类型:

  • load:在配置加载(或重新加载)时运行这些装饰器
  • always:在计划中的每个查询之前运行这些装饰器
  • interval:定义间隔时间映射的特殊键

每个装饰器查询最多应返回 1 行。如果返回超过 1 行,将生成警告,

Events

“Events”是指基于事件的表。事件通过操作系统或特定于应用程序的 API 发布到 osquery;在 osquery 中,某些表”订阅”这些发布者。发布者和订阅者之间通常是一对多的关系。

该配置支持一种显式允许和拒绝事件订阅者的方法,如果选择明确允许订阅者,则除允许列表中指定的订阅者外,所有订阅者都将被禁用;如果选择明确拒绝订阅者,则除了拒绝列表中指定的订阅者外,所有订阅者都将被启用。

示例配置:

1
2
3
4
5
6
{
"schedule": {...},
"events": {
"disable_subscribers": ["yara_events"]
}
}

可以使用查询 SELECT * FROM osquery_events where type = 'subscriber'; 检查订阅者列表,如果启用订阅者,此表将显示 1 作为活动列。

Chef 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Service name installed by the osquery package.
service_name = 'osqueryd'

cookbook_file '/etc/osquery/osquery.conf' do
source 'osquery.conf'
mode 0444
owner 'root'
group 'wheel'
notifies :restart, "service[#{service_name}]"
end

service service_name do
action [:enable, :start]
end

osqueryctl 助手

为了测试部署或配置,我们包含了一个名为 osqueryctl 的简短帮助脚本。有几个操作包括适用于 macOS 和 Linux 的start、stop和config-check

0x03 日志记录

osquery 守护进程使用默认的文件系统记录器插件。与配置一样,文件系统插件的输出以 JSON 格式编写。查询计划的结果写入 /var/log/osquery/osqueryd.results.log

有两种类型的日志:

  • 状态日志 (INFO, WARNING, ERROR)
  • 查询计划结果日志,包括来自快照查询的日志

日志文件位置

1
2
3
/var/log/osquery/  # Linux

C:\Program Files\osquery\log # Windows

1. Logger插件

osquery 包括支持可配置记录到各种接口的记录器插件。内置的记录器插件是filesystem(默认)、tls、syslog(适用于POSIX)、windows_event_log(适用于Windows)、kinesis、firehose 和 kafka_producer。

可以同时使用多个记录器插件,有效地将日志复制到每个接口,要启用多个记录器,将 --logger_plugin 选项设置为以逗号分隔的请求插件列表,不包括空格。通过 osquery flagfile 设置记录器插件和记录器设置。

a. Status logs

状态日志由 Glog 日志记录框架生成,默认的文件系统记录器插件以与 Glog 相同的方式将这些日志写入磁盘,记录器插件可能会拦截这些状态日志并将它们写入系统或其他地方。

osqueryd.INFO 是指向最近执行的 INFO 日志的符号链接,WARNING、ERROR 和 FATAL 日志也是如此。

注:osqueryi shell 只显示 WARNING 和 ERROR 状态日志,INFO 日志被禁用。

默认情况下,osqueryd 守护进程将 INFO、WARNING 和 ERROR 日志发送到配置的记录器插件和进程的 stderr。可以使用 CLI flags 中记录的几个标志来配置此行为

  • 禁止将状态日志写入 stderr,使用 --logger_stderr=false
  • 设置写入 stderr 的最低状态日志严重性 (INFO=0),使用 --logger_min_stderr=0
  • 设置写入 stderr 和记录器插件的最小状态日志,使用 --logger_min_status=0

住:在 osquery 包中提供的 LaunchDaemon、systemd 和 initscript 中,最小的 stderr 报告仅限于 WARNING 以帮助最小化复制到 syslog 的内容

b. Results logs

Differential logs

预定查询的结果将记录到”results log”中。这些是上次(最近)查询执行和当前执行之间的差异变化。每个日志行都是一个 JSON 字符串,指示哪个查询已添加/删除了哪些数据。

1
2
3
4
5
6
7
8
9
{
"schedule": {
"osquery_monitor": {
"query": "SELECT ... t.minutes AS c FROM time t WHERE ...",
"interval": 60,
"removed": false
}
}
}

Snapshot logs

快照日志是查询结果日志记录的另一种形式,快照是一组”精确时间点”的结果,没有差异。如果想要一个挂载列表,而不是添加和删除的挂载列表,可使用快照。

文件系统记录器插件将快照结果写入 /var/log/osquery/osqueryd.snapshots.log

定时启动快照查询:

1
2
3
4
5
6
7
8
9
{
"schedule": {
"mounts": {
"query": "SELECT * FROM mounts;",
"interval": 3600,
"snapshot": true
}
}
}

c. 日志接入kafka生产者

用户可以将日志配置为直接发布到 Kafka Topic

配置

有 3 个 Kafka 配置作为选项公开:一个逗号分隔的代理列表,有或没有端口(默认 9092)[default value: localhost],默认topic [default value: “”] 和 acks(记录器在考虑请求完成之前要求 Kafka leader确认的数量)[default: all; valid values: 0, 1, all]

要发布对特定主题的查询,请在 osquery.conf 的顶层添加一个 kafka_topics 字段,建议在使用多个主题时显式配置 kafka_topics 中的所有计划查询。

示例文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
"options": {
"logger_kafka_brokers": "some.example1.com:9092,some.example2.com:9092",
"logger_kafka_topic": "base_topic",
"logger_kafka_compression": "gzip",
"logger_kafka_acks": "1"
},
"packs": {
"system-snapshot": {
"queries": {
"some_query1": {
"query": "select * from system_info",
"snapshot": true,
"interval": 60
},
"some_query2": {
"query": "select * from md_devices",
"snapshot": true,
"interval": 60
},
"some_query3": {
"query": "select * from md_drives",
"snapshot": true,
"interval": 60
}
}
}
},
"kafka_topics": {
"test1_topic": [
"pack_system-snapshot_some_query1"
],
"test2_topic": [
"pack_system-snapshot_some_query2"
],
"test3_topic": [
"pack_system-snapshot_some_query3"
],
}
}

使用的客户端 ID 和消息密钥是操作系统主机名和二进制名称 (argv[0]) 的串联。目前只能将一个Topic传递到配置中,因此所有日志都将发布到同一Topic。

2. Schedule 结果

a. Event格式

事件是默认的结果格式,每条日志行代表一个状态变化。这种格式适用于 Logstash 或 Splunk 等日志聚合系统。

SELECT name, path, pid FROM processes 的示例输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"action": "added",
"columns": {
"name": "osqueryd",
"path": "/opt/osquery/bin/osqueryd",
"pid": "97830"
},
"name": "processes",
"hostname": "hostname.local",
"calendarTime": "Tue Sep 30 17:37:30 2014",
"unixTime": "1412123850",
"epoch": "314159265",
"counter": "1",
"numerics": false
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"action": "removed",
"columns": {
"name": "osqueryd",
"path": "/opt/osquery/bin/osqueryd",
"pid": "97650"
},
"name": "processes",
"hostname": "hostname.local",
"calendarTime": "Tue Sep 30 17:37:30 2014",
"unixTime": "1412123850",
"epoch": "314159265",
"counter": "1",
"numerics": false
}

这告诉我们一个名为 osqueryd 的二进制文件已停止并启动了一个具有相同名称的新二进制文件(注意不同的 pid)。数据是通过保留先前查询结果的缓存并仅在缓存更改时记录来生成的。如果没有启动或停止新进程,查询将不会记录任何结果。

b. Snapshot格式

快照查询试图模仿差异事件格式,而不是发出”列”,快照数据使用”快照”存储

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"action": "snapshot",
"snapshot": [
{
"parent": "0",
"path": "/sbin/launchd",
"pid": "1"
},
{
"parent": "1",
"path": "/usr/sbin/syslogd",
"pid": "51"
},
{
"parent": "1",
"path": "/usr/libexec/UserEventAgent",
"pid": "52"
},
{
"parent": "1",
"path": "/usr/libexec/kextd",
"pid": "54"
}
],
"name": "process_snapshot",
"hostIdentifier": "hostname.local",
"calendarTime": "Mon May 2 22:27:32 2016 UTC",
"unixTime": "1462228052",
"epoch": "314159265",
"counter": "1",
"numerics": false
}

c. Batch格式

如果查询识别出多个状态更改,则批处理格式会将所有结果包含在单个日志行中。如果您以编程方式解析行并将它们加载到后端数据存储中,这可能是最好的解决方案。

要启用批处理日志行,需使用 --logger_event_type=false 参数启动 osqueryd。

SELECT name, path, pid FROM processes 的示例输出;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"diffResults": {
"added": [
{
"name": "osqueryd",
"path": "/opt/osquery/bin/osqueryd",
"pid": "97830"
}
],
"removed": [
{
"name": "osqueryd",
"path": "/opt/osquery/bin/osqueryd",
"pid": "97650"
}
]
},
"name": "processes",
"hostname": "hostname.local",
"calendarTime": "Tue Sep 30 17:37:30 2014",
"unixTime": "1412123850",
"epoch": "314159265",
"counter": "1",
"numerics": false
}

3. 特殊top-level字段

Schedule epoch

osquery 维护一个 epoch 标记以及每个计划的查询执行,并且仅当最后一次运行的epoch与当前epoch匹配时才计算差异,如果没有,则它将当前执行的查询视为初始运行。

可以通过使用 --schedule_epoch=<some 64bit int> 标志启动 osquery 或通过从 TLS 后端远程更新 schedule_epoch 标志来设置epoch标记,epoch 与每个日志结果一起传输,因此很容易识别哪些结果属于计划查询的哪些执行

Schedule counter

为差异日志数据设置警报时,可能希望跳过最初添加的记录。counter可用于识别添加的记录是否都是来自初始查询的记录,如果它们是新记录。

Numerics

这是所有结果的指示符,如果 osquery 尝试将数字记录为数字,则为 true,否则为 false 表示它们被记录为字符串

Unique host identification

如果您需要一种方法来唯一标识嵌入到 osqueryd 结果日志中的主机,可用 --host_identifier 标志。默认情况下,host_identifier 设置为”hostname”。

主机的主机名将用作结果日志中的主机标识符。如果主机名在您的环境中不唯一或不一致,可以使用 --host_identifier=uuid 启动 osqueryd

4. 日志聚合

a. Logstash

LogStash 是一个开源工具,能够收集、解析、索引和转发日志,Logstash 能够使用其文件输入插件摄取 osquery 日志,然后通过输出插件列表将数据发送到聚合器。

ElasticSearch 配置的 Logstash 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
input {
file {
path => "/var/log/osquery/osqueryd.results.log"
type => "osquery_json"
codec => "json"
}
}

filter {
if [type] == "osquery_json" {
date {
match => [ "unixTime", "UNIX" ]
}
}
}

output {
stdout {}
elasticsearch {
hosts=> "127.0.0.1:9200"
}
}

b. Splunk

Splunk 转发器配置 (inputs.conf) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[monitor:///var/log/osquery/osqueryd.results.log]
index = main
sourcetype = osquery:results

[monitor:///var/log/osquery/osqueryd.*INFO*]
index = main
sourcetype = osquery:info

[monitor:///var/log/osquery/osqueryd.*ERROR*]
index = main
sourcetype = osquery:error

[monitor:///var/log/osquery/osqueryd.*WARNING*]
index = main
sourcetype = osquery:warning

0x04 安全能力

1. 文件完整性监控

文件完整性监控 (FIM) 可用于 Linux(在 file_events 中使用 inotify 子系统,在 process_file_events 中使用 Audit 子系统),Windows(在 ntfs_journal_events 中,使用 NTFS Journaling)和 macOS(在 file_events 中,使用 FSEvents)。

配置

在 osquery 中收集文件事件需要先指定要从 osquery 配置中监视的文件/目录列表。然后,与这些选定文件相关的事件将填充每个平台上的相应表格。

FIM 在 osquery 中也是默认禁用的。要启用它,首先确保在 osquery (--disable_events=false) 中启用了事件,然后确保使用相应的 CLI 标志启用所需的 FIM 表(–enable_file_events=true 对 file_events,–disable_audit=false 对 process_file_events,–enable_ntfs_event_publisher=true 对 ntfs_journal_events)

要指定要监视的文件和目录,必须使用 fnmatch 样式或文件系统通配模式来表示目标路径。您可以使用标准通配符 */** 或 SQL 样式通配符 *%*

匹配通配符规则

  • %:匹配一级的所有文件和文件夹。
  • %%:递归匹配所有文件和文件夹。
  • %abc:匹配所有以”abc”结尾的内容。
  • abc%:匹配所有以”abc”开头的内容。

匹配实例

osquery 中 FIM 配置的三个元素:(a) 针对 file_events 的计划查询,(b) 添加的 file_paths 部分,以及 (c) exclude_paths 部分。

file_events 查询计划以五分钟的间隔收集在 file_paths 中指定的路径内的任何文件上发生的所有 FIM 事件,但不包括 exclude_paths 中指定的路径。

确定要监视的文件和目录后,将它们的匹配规则添加到 osquery FIM 配置中的 file_paths 部分:

  • /Users/%/Library:监视对每个用户的 Library 文件夹的更改,但不监视其中的内容。
  • /Users/%/Library/:监视每个库文件夹中文件的更改,但不监视其子目录的内容
  • /Users/%/Library/%:相同,对每个 Library 文件夹中的文件进行更改。
  • /Users/%/Library/%%:在每个库中递归地监视更改。
  • /bin/%sh:监控bin目录是否有以sh结尾的变化

FIM监控示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
"schedule": {
"crontab": {
"query": "SELECT * FROM crontab;",
"interval": 300
},
"file_events": {
"query": "SELECT * FROM file_events;",
"removed": false,
"interval": 300
}
},
"file_paths": {
"homes": [
"/root/.ssh/%%",
"/home/%/.ssh/%%"
],
"etc": [
"/etc/%%"
],
"tmp": [
"/tmp/%%"
]
},
"exclude_paths": {
"homes": [
"/home/not_to_monitor/.ssh/%%"
],
"tmp": [
"/tmp/too_many_events/"
]
}
}

不要在 exclude_paths 节点下使用任意类别名称;只允许使用有效名称

  • 有效类别: 在 file_paths 节点下引用的类别。在上面的示例中,config、homes 等和 tmp 都是有效的类别
  • 无效类别: 任何未在 file_paths 下引用的名称。在上面的示例中,除 homes 等和 tmp 之外的任何名称都是无效的

除file_paths 之外,还可以使用 file_paths_query 将要监视的文件路径指定为给定查询结果的路径列:

1
2
3
4
5
6
7
{
"file_paths_query": {
"category_name": [
"SELECT DISTINCT '/home/' || username || '/.gitconfig' as path FROM last WHERE username != '' AND username != 'root';"
]
}
}

示例事件输出

当文件发生变化时,事件将出现在 file_events 表中。在文件更改事件期间,可能计算文件的 md5、sha1 和 sha256

1
2
3
4
5
6
7
8
9
10
{
"action":"ATTRIBUTES_MODIFIED",
"category":"homes",
"md5":"bf3c734e1e161d739d5bf436572c32bf",
"sha1":"9773cf934440b7f121344c253a25ae6eac3e3182",
"sha256":"d0d3bf53d6ae228122136f11414baabcdc3d52a7db9736dd256ad81229c8bfac",
"target_path":"\/root\/.ssh\/authorized_keys",
"time":"1429208712",
"transaction_id":"0"
}

调整 Linux inotify 限制

Linux 上,osquery 中的 file_events 表使用 inotify 来订阅文件更改,调整限制可以帮助增加文件限制

sysctl.conf 修改:

1
2
3
4
5
6
7
8
#/proc/sys/fs/inotify/max_user_watches = 8192
fs.inotify.max_user_watches = 524288

#/proc/sys/fs/inotify/max_user_instances = 128
fs.inotify.max_user_instances = 256

#/proc/sys/fs/inotify/max_queued_events = 16384
fs.inotify.max_queued_events = 32768

文件访问监控

除了在创建/修改/删除文件时生成事件的 FIM 之外,osquery 还支持文件访问监控,如果文件被访问则可以生成事件。

在 file_paths 中定义监控特定的文件,减少性能开销:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"file_paths": {
"homes": [
"/root/.ssh/%%",
"/home/%/.ssh/%%"
],
"etc": [
"/etc/%%"
],
"tmp": [
"/tmp/%%"
]
},
"file_accesses": ["homes", "etc"]
}

上面的配置将为 homes 等和 tmp 启用文件完整性监控,但只为 homes 和 etc 目录启用访问监控

2. 进程/socket审计

osquery 可以利用 BPF、Audit、OpenBSM 或 EndpointSecurity 子系统在 Linux 和 macOS 系统上近乎实时地记录进程执行和网络连接。

不同的平台有不同的选择来收集实时事件数据,osquery 根据来源和平台有多个表格来呈现这些信息:

事件类型 osquery表 数据源 支持平台
Process events process_events Audit (Linux), OpenBSM (macOS) Linux, macOS (10.15 and older)
Process events bpf_process_events BPF Linux (kernel 4.18 and newer)
Process events es_process_events EndpointSecurity macOS (10.15 and newer)
Socket events socket_events Audit (Linux), OpenBSM (macOS) Linux, macOS (10.15 and older)
Socket events bpf_socket_events BPF Linux (kernel 4.18 and newer)

要采集进程事件,可以将如下查询添加到查询 schedule 或查询 pack 中:

1
SELECT * FROM process_events;

这些审计功能中的每一个都是使用额外的 osquery 配置设置在每个源的基础上启用的。

检查配置标志

验证 osquery 的标志是否设置正确,可以查询 osquery_flags 表

1
osquery> select * from osquery_flags where name in ("disable_events", "disable_audit");

检查事件表

osquery 在 osquery_events 表中保存关于事件子系统的状态,注意 events 列,尝试触发一个事件,然后确认事件计数不为 0:

1
osquery> select * from osquery_events;

使用 Audit 进行 Linux 进程审计

在 Linux 上,osquery 可以通过 Audit 系统来收集和处理事件。它通过监视 execve() 和 execveat() 等系统调用来实现,auditd 在使用 osquery 的进程审计时不应运行,因为它会与 osqueryd 在访问审计 netlink 套接字时发生冲突。不需要安装 auditd 或 libaudit,Osquery 仅使用内核中存在的审计功能。

process_events示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"action": "added",
"columns": {
"uid": "0",
"time": "1527895541",
"pid": "30219",
"path": "/usr/bin/curl",
"auid": "1000",
"cmdline": "curl google.com",
"ctime": "1503452096",
"cwd": "",
"egid": "0",
"euid": "0",
"gid": "0",
"parent": ""
},
"unixTime": 1527895550,
"hostIdentifier": "vagrant",
"name": "process_events",
"numerics": false
}

关注4个配置选项,这些标志可以在命令行设置或放入 osquery.flags 文件中:

  1. --disable_audit=false 默认设置为 true 并防止 osquery 打开内核审计的 n​ettlink 套接字。通过将其设置为 false,告诉 osquery 启用审计功能。

  2. --audit_allow_config=true 默认设置为 false 并防止 osquery 更改审计配置设置

3)--audit_persist=true 但默认情况下这是 true 并指示 osquery 在另一个进程也访问它时”重新获得”审计 netlink 套接字

4)--audit_allow_process_events=true 这个标志表示想记录进程事件

使用 Audit 进行 Linux 套接字审计

Osquery 也可以通过启用 socket_events 来记录网络连接。此表使用系统调用 bind() 和 connect() 来收集有关网络连接的信息。要启用套接字事件,使用 --audit_allow_sockets 标志

socket_event示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"action": "added",
"columns": {
"time": "1527895541",
"status": "succeeded",
"remote_port": "80",
"action": "connect",
"auid": "1000",
"family": "2",
"local_address": "",
"local_port": "0",
"path": "/usr/bin/curl",
"pid": "30220",
"remote_address": "172.217.164.110"
},
"unixTime": 1527895545,
"hostIdentifier": "vagrant",
"name": "socket_events",
"numerics": false
}

使用以下布尔值更改 socket_events 表的行为:

标志 说明
–audit_allow_sockets 允许审计者安装套接字相关规则
–audit_allow_unix 允许套接字事件收集domain套接字
–audit_allow_failed_socket_events 包括失败的套接字事件的行
–audit_allow_accept_socket_events 包括接受套接字事件的行
–audit_allow_null_accept_socket_events 允许返回 EAGAIN/EWOULDBLOCK 的非阻塞 accept() 系统调用

使用 BPF 进行 Linux 进程和套接字审计

3. 集成 YARA 扫描

osquery中有两个YARA相关的表,第一个表,称为 yara_events,使用 osquery 的事件框架来监视文件系统更改,并在文件更改事件触发时执行 YARA。第二个表,称为 yara,是一个用于执行按需 YARA 扫描的表。

YARA 配置

示例配置,将本地文件系统中的一些 YARA 规则文件分组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
// Description of the YARA feature.
"yara": {
"signatures": {
// Each key is an arbitrary group name to give the signatures listed
"sig_group_1": [ "/Users/wxs/sigs/foo.yar", "/Users/wxs/sigs/bar.yar" ],
"sig_group_2": [ "/Users/wxs/sigs/baz.yar" ]
},
"file_paths": {
// Each key is a key from file_paths
// The value is a list of signature groups to run when an event fires
// These will be watched for and scanned when the event framework
// fire off an event to yara_events table
"system_binaries": [ "sig_group_1" ],
"tmp": [ "sig_group_1", "sig_group_2" ]
}
},

// Paths to watch for filesystem events
"file_paths": {
"system_binaries": [ "/usr/bin/%", "/usr/sbin/%" ],
"tmp": [ "/Users/%/tmp/%%", "/tmp/%" ]
}
}
  • file_paths 部分,该部分用于描述要监视哪些路径的变化;

  • yara 部分,它包含在 osquery 中用于 YARA 的配置,yara 部分包含两个键:signatures 和 file_paths,signatures包含一组任意key名称,称”签名组”,每个组的值都是将在 osquery 中编译和存储的签名文件的路径;file_paths将全局 file_paths 部分中描述的事件的类别名称映射到签名分组

在运行时检索 YARA 规则

yara 表的默认行为是使用 osquery 主机上文件中指定的 YARA 规则。可使用 signature_urls 部分配置 osquery 以允许在运行时获取 YARA 规则。这是一个数组,可以是指向单个 Yara 规则的完整 URL 或部分 URL 的混合,其中路径部分可以是将用于匹配多个 URL 和规则的正则表达式。

配置示例:

1
2
3
4
5
6
7
8
"yara": {
"signature_urls": [
"https://raw.githubusercontent.com/Yara-Rules/rules/master/cve_rules/CVE-2010-0805\\.yar",
"https://raw.githubusercontent.com/Yara-Rules/rules/master/crypto/crypto_signatures\\.yar",
"https://raw.githubusercontent.com/Yara-Rules/rules/master/malware/APT_APT3102\\.yar",
"https://raw.githubusercontent.com/Yara-Rules/rules/devel/CVE_Rules/CVE-.*"
]
}

查询示例:

1
2
3
4
5
6
7
8
# This is valid
SELECT * FROM yara WHERE path="/usr/bin/ls" AND sigurl='https://raw.githubusercontent.com/Yara-Rules/rules/master/cve_rules/CVE-2010-0805.yar';

# This too
SELECT * FROM yara WHERE path="/usr/bin/ls" AND sigurl='https://raw.githubusercontent.com/Yara-Rules/rules/devel/CVE_Rules/CVE-2010-0805.yar';

# This is not allowed
SELECT * FROM yara WHERE path="/usr/bin/ls" AND sigurl='https://raw.githubusercontent.com/Yara-Rules/rules/devel/malware/APT_APT3102.yar';

YARA 规则字符串默认从输出中省略,以防止在 osquery 的结果和日志中泄露。要在 sigrule 列中包含 YARA 规则,需将 enable_yara_string 标志设置为 true。

使用 yara_events 表持续监控

1
2
3
osquery> SELECT * FROM file_events;

osquery> SELECT * FROM yara_events;

按需 YARA 扫描

yara 表用于按需扫描,使用此表,可以使用配置中的任何可用签名文件或签名组任意 YARA 扫描文件系统上的任何可用文件。如果要使用通配符模式,则必须使用 LIKE。

一旦 where 被排除在外,您必须指定”what”部分,这是通过 sigfile 或 sig_group 约束来完成的。sigfile 约束是文件系统上签名文件的绝对路径,sig_group 约束必须包含配置文件中的命名签名分组。

yara应用示例:

1
2
3
4
5
6
7
8
9
// 使用两个不同的签名组扫描同一个文件并得到不同的结果
osquery> SELECT * FROM yara WHERE path="/bin/ls" AND sig_group="sig_group_1";
osquery> SELECT * FROM yara WHERE path="/bin/ls" AND sig_group="sig_group_2";

// 使用路径 LIKE 约束扫描带有签名组的 /bin/%sh
osquery> SELECT * FROM yara WHERE path LIKE "/bin/%sh" AND sig_group="sig_group_1";

// sigfile使用绝对路径结合路径LIKE示例
osquery> select * from yara where path LIKE 'C:\tmp\%' and sigfile = "C:\tmp\test.yar.txt";

使用 sirule 内联 YARA 规则

YARA 规则也可以与查询一起提供,使用隐藏列 sigrule 作为约束,YARA 规则采用rule rulename { condition: [whatever] }的形式

1
osquery> select * from yara where path = '/etc/passwd' and sigrule = 'rule always_true { condition: true }';

YARA 规则没有行终止符,要输入多行 YARA 规则,需使用换行符,也适用于 osqueryi:

1
2
3
4
5
osquery> select * from yara where path LIKE 'C:\tmp\%' and sigrule = 'rule hello_world {
...> strings:
...> $a = "Hello world"
...> condition: $a
...> }';

0xFF Reference