文件结构
文件结构如下:
logs文件夹下是log文件相关信息,注意,只有在zeek运行时才会有这些文件:
并且在2024-11-04(当天日期)文件夹下,有各类log文件的压缩包。
bin目录下为二进制文件,而share目录下包含zeek附带的所有脚本。
Zeek 附带了一个用于本地自定义的脚本local.zeek。后续升级不会覆盖此文件,要使用该脚本,只需将其添加到命令行(zeek -i en0 local
)或通过 @load加载它。如果是 ZeekControl,会自动加载。
日志
日志格式
TSV
生成默认日志:
通过awk处理日志:
通过zeek-cut处理日志:
JSON
生成json日志:
jq处理日志:
日志内容
以下面的conn.log为例:
{
"ts":1729659418.809245,
"uid":"ChNfYL1dVVR827J2z1",
"id.orig_h":"10.21.9.50",
"id.orig_p":50256,
"id.resp_h":"10.21.15.21",
"id.resp_p":8300,
"proto":"tcp",
"duration":10.472655773162842,
"orig_bytes":208211,
"resp_bytes":252951,
"conn_state":"SF",
"local_orig":true,
"local_resp":true,
"missed_bytes":0,
"history":"DdAaFf",
"orig_pkts":110,
"orig_ip_bytes":213931,
"resp_pkts":98,
"resp_ip_bytes":258047,
"tunnel_parents":[
"CAcDHN3mvd29FiHQY7"
]
}
ts为时间戳,解析结果如下:
id.orig_h,id.orig_p,id.resp_h,id.resp_p分别代表源ip,port和目的ip,port。
duration表示会话持续时间,为10.472655773162842秒。
10.21.9.50在其应用层发送了208211字节的数据,在IP层发送了213931字节数据。
可通过如下命令查看具体内容:
tshark -V -r log.pcap http and ip.src==ip //显示指定源IP的HTTP数据包的详细解析信息
tshark -x -r log.pcap http and ip.src==ip //以十六进制和ASCII格式输出指定源IP的HTTP数据包内容
更多日志信息见:https://docs.zeek.org/en/master/logs/index.html
脚本
脚本基础
##! Detect file downloads that have hash values matching files in Team
##! Cymru's Malware Hash Registry (http://www.team-cymru.org/Services/MHR/).
@loadbase/frameworks/files
@loadbase/frameworks/notice
@load frameworks/files/hash-all-files
moduleTeamCymruMalwareHashRegistry;
export{
redef enumNotice::Type+={
## The hash value of a file transferred over HTTP matched in the
## malware hash registry.
Match
};
## File types to attempt matching against the Malware Hash Registry.
option match_file_types =/application/x-dosexec/|
/application/vnd.ms-cab-compressed/|
/application/pdf/|
/application/x-shockwave-flash/|
/application/x-java-applet/|
/application/jar/|
/video/mp4/;
## The Match notice has a sub message with a URL where you can get more
## information about the file. The %s will be replaced with the SHA-1
## hash of the file.
option match_sub_url ="https://www.virustotal.com/en/search/?query=%s";
## The malware hash registry runs each malware sample through several
## A/V engines. Team Cymru returns a percentage to indicate how
## many A/V engines flagged the sample as malicious. This threshold
## allows you to require a minimum detection rate.
option notice_threshold =10;
}
function do_mhr_lookup(hash:string,fi:Notice::FileInfo)
{
local hash_domain = fmt("%s.malware.hash.cymru.com", hash);
when(local MHR_result = lookup_hostname_txt(hash_domain))
{
# Data is returned as "<dateFirstDetected> <detectionRate>"
local MHR_answer = split_string1(MHR_result,/ /);
if(|MHR_answer|==2)
{
local mhr_detect_rate = to_count(MHR_answer[1]);
if( mhr_detect_rate >= notice_threshold )
{
local mhr_first_detected = double_to_time(to_double(MHR_answer[0]));
local readable_first_detected = strftime("%Y-%m-%d %H:%M:%S", mhr_first_detected);
local message = fmt("Malware Hash Registry Detection rate: %d%% Last seen: %s", mhr_detect_rate, readable_first_detected);
local virustotal_url = fmt(match_sub_url, hash);
# We don't have the full fa_file record here in order to
# avoid the "when" statement cloning it (expensive!).
local n:Notice::Info=Notice::Info($note=Match, $msg=message, $sub=virustotal_url);
Notice::populate_file_info2(fi, n);
NOTICE(n);
}
}
}
}
event file_hash(f: fa_file, kind:string, hash:string)
{
if( kind =="sha1"&& f?$info && f$info?$mime_type &&
match_file_types in f$info$mime_type )
do_mhr_lookup(hash,Notice::create_file_info(f));
}
借助zeek自带的脚本detect-MHR.zeek开始介绍。
该脚本可以分为3部分:
•模块和导入语句•配置选项•函数和事件处理
模块和导入语句
这部分包括:
@loadbase/frameworks/files
@loadbase/frameworks/notice
@load frameworks/files/hash-all-files
使用 @load
指令导入所需的框架和模块,处理正在加载的相应目录中的 __load__.zeek
脚本,允许脚本使用 Zeek 的基本功能和特定的库。
配置选项
这部分包括:
export{
redef enumNotice::Type+={
## The hash value of a file transferred over HTTP matched in the
## malware hash registry.
Match
};
## File types to attempt matching against the Malware Hash Registry.
option match_file_types =/application/x-dosexec/|
/application/vnd.ms-cab-compressed/|
/application/pdf/|
/application/x-shockwave-flash/|
/application/x-java-applet/|
/application/jar/|
/video/mp4/;
## The Match notice has a sub message with a URL where you can get more
## information about the file. The %s will be replaced with the SHA-1
## hash of the file.
option match_sub_url ="https://www.virustotal.com/en/search/?query=%s";
## The malware hash registry runs each malware sample through several
## A/V engines. Team Cymru returns a percentage to indicate how
## many A/V engines flagged the sample as malicious. This threshold
## allows you to require a minimum detection rate.
option notice_threshold =10;
}
这里首先重定义了 Notice::Type
,增加了一个新的通知类型 Match
。这表示当通过 HTTP 传输的文件哈希值与恶意软件哈希注册表中的某个条目匹配时,将触发一个新的通知。
match_file_types处定义了部分文件类型,脚本将重点检查这些特定类型的文件,以查找潜在的恶意内容。
当生成匹配的通知时,match_sub_url定义了一个包含文件 SHA-1 哈希值的 URL。
notice_threshold选项定义了一个阈值,表示在判断恶意软件时需要满足的最低检测率。
函数和事件处理
这部分包括:
function do_mhr_lookup(hash:string,fi:Notice::FileInfo)
{
local hash_domain = fmt("%s.malware.hash.cymru.com", hash);
when(local MHR_result = lookup_hostname_txt(hash_domain))
{
# Data is returned as "<dateFirstDetected> <detectionRate>"
local MHR_answer = split_string1(MHR_result,/ /);
if(|MHR_answer|==2)
{
local mhr_detect_rate = to_count(MHR_answer[1]);
if( mhr_detect_rate >= notice_threshold )
{
local mhr_first_detected = double_to_time(to_double(MHR_answer[0]));
local readable_first_detected = strftime("%Y-%m-%d %H:%M:%S", mhr_first_detected);
local message = fmt("Malware Hash Registry Detection rate: %d%% Last seen: %s", mhr_detect_rate, readable_first_detected);
local virustotal_url = fmt(match_sub_url, hash);
# We don't have the full fa_file record here in order to
# avoid the "when" statement cloning it (expensive!).
local n:Notice::Info=Notice::Info($note=Match, $msg=message, $sub=virustotal_url);
Notice::populate_file_info2(fi, n);
NOTICE(n);
}
}
}
}
event file_hash(f: fa_file, kind:string, hash:string)
{
if( kind =="sha1"&& f?$info && f$info?$mime_type &&
match_file_types in f$info$mime_type )
do_mhr_lookup(hash,Notice::create_file_info(f));
}
这段代码定义了一个用于检测和生成恶意软件通知的功能块。当 Zeek 的文件分析框架生成一个文件哈希时,会触发 file_hash
事件处理器。这个处理器将会检查生成的哈希是否是 SHA1,并且判断该文件的 MIME 类型是否符合已定义的特定类型。如果都满足,将调用 do_mhr_lookup
函数,查询 Team Cymru 的恶意软件哈希注册表,获取关于这个哈希的检测信息。如果该哈希在恶意软件数据库中被检测到,并且检测率达到了预设的阈值,则生成一条通知,包括检测率和最后检测时间。
语法
$是分隔符,例如,源主机通过 c$id$orig_h
进行引用,其中 id
是一个成员,而 orig_h
是 id
的一个成员。这个 id
是传递给事件处理程序 c
的数据结构的一部分。
scope
变量声明可以带SCOPE name或者不带,形式如下:
•SCOPE name: TYPE•SCOPE name = EXPRESSION
event zeek_init()
{
local a:int;
a =10;
local b =10;
if(a == b)
print fmt("A: %d, B: %d", a, b);
}
全局变量
全局变量在没有命名空间时可被所有脚本访问。而在命名空间中声明的全局变量仅对该命名空间内的脚本可用。如果在 export { ... }
块中声明,则须通过 <module name>::<variable name>
形式来访问。
常量
在 Zeek 中,常量通过 const
关键字来定义。常量与全局变量不同,它们只能在解析时(parse time)被设置或修改,除非使用了 &redef
属性。在运行时,常量是不可改变的。常见情况下,重定义常量用于 Zeek 脚本中的配置选项,例如控制日志记录、指定选项和其他参数。 常量可以声明如下:
module HTTP;
export{
## 此设置决定是否捕获 Basic-Auth 中使用的密码。
const default_capture_password = F &redef;
}
这里,default_capture_password
是一个配置选项,默认值为 F
(假)。其后,如果需要全局开启捕获密码选项,可以在 site/local.zeek
文件中添加以下行:
redef HTTP::default_capture_password = T;
局部变量
局部变量在函数处理完毕后便会被删除,从而释放占用的内存。
以下是一个使用局部变量的示例:
function add_two(i: count): count
{
local added_two = i +2;# 定义局部变量 added_two
print fmt("i + 2 = %d", added_two);# 打印结果
return added_two;# 返回 added_two
}
event zeek_init()
{
local test = add_two(10);# 在此处调用 add_two 函数
}
数据结构
Data Type | Description |
int | 64 bit signed integer |
count | 64 bit unsigned integer |
double | double precision floating precision |
bool | boolean (T/F) |
addr | IP address, IPv4 and IPv6 |
port | transport layer port |
subnet | CIDR subnet mask |
time | absolute epoch time |
interval | a time interval |
pattern | regular expression |
sets
event zeek_init()
{
local ssl_ports:set[port];
local non_ssl_ports =set(23/tcp,80/tcp,143/tcp,25/tcp );
# SSH
add ssl_ports[22/tcp];
# HTTPS
add ssl_ports[443/tcp];
# IMAPS
add ssl_ports[993/tcp];
# Check for SMTPS
if(587/tcp !in ssl_ports )
add ssl_ports[587/tcp];
for( i in ssl_ports )
print fmt("SSL Port: %s", i);
for( i in non_ssl_ports )
print fmt("Non-SSL Port: %s", i);
}
tables
event zeek_init()
{
local samurai_flicks: table[string,string, count,string] of string;
samurai_flicks["Kihachi Okamoto","Toho",1968,"Tatsuya Nakadai"]="Kiru";
samurai_flicks["Hideo Gosha","Fuji",1969,"Tatsuya Nakadai"]="Goyokin";
samurai_flicks["Masaki Kobayashi","Shochiku Eiga",1962,"Tatsuya Nakadai"]="Harakiri";
samurai_flicks["Yoji Yamada","Eisei Gekijo",2002,"Hiroyuki Sanada"]="Tasogare Seibei";
for([d, s, y, a]in samurai_flicks )
print fmt("%s was released in %d by %s studios, directed by %s and starring %s", samurai_flicks[d, s, y, a], y, s, d, a);
}
vectors
event zeek_init()
{
local v1: vector of count;
local v2 = vector(1,2,3,4);
v1 +=1;
v1 +=2;
v1 +=3;
v1 +=4;
print fmt("contents of v1: %s", v1);
print fmt("length of v1: %d",|v1|);
print fmt("contents of v2: %s", v2);
print fmt("length of v2: %d",|v2|);
}
数据类型
addr
在 Zeek 中,addr
数据类型用于表示网络地址,包括 IPv4、IPv6 和主机名常量。IPv4 地址采用点分十进制格式,而 IPv6 地址使用 RFC 2373 的表示法,并用方括号括起来。对于主机名常量,Zeek 会自动执行 DNS 查询,将其转换为一组地址(set[addr]
)。
event zeek_init()
{
local google:set[addr];
google = www.google.com;
print(google);
}
port
port的格式为:<unsigned ingeger>/<protocal name>,例如22/tcp
subnet
event zeek_init()
{
local subnets = vector(172.16.0.0/20,172.16.16.0/20,172.16.32.0/20,[2001:db8:b120::]/64);
local addresses = vector(172.16.4.56,172.16.47.254,172.16.1.1,[2001:db8:b120::1]);
for( a in addresses )
{
for( s in subnets )
{
if( addresses[a]in subnets[s])
print fmt("%s belongs to subnet %s", addresses[a], subnets[s]);
}
}
}
time
在 Zeek 中,current_time
和 network_time
是两个内置函数,用于处理时间数据类型。current_time
返回操作系统的时间,而 network_time
返回最后处理的数据包的时间戳。
interval
在 Zeek 中,interval
数据类型表示相对时间,通过一个数字常量和一个时间单位表示,如 2.2sec
或 31days
,interval
还可以执行各种数学运算,如加法、减法和比较操作。
pattern
event zeek_init()
{
local test_string ="The quick brown fox jumps over the lazy dog.";
local test_pattern =/quick|lazy/;
if( test_pattern in test_string )
{
local results = split_string(test_string, test_pattern);
print results[0];
print results[1];
print results[2];
}
}
record
record 类型和 type 关键字,类似于在 C 语言中使用 typedef 和 struct 关键字,来定义新的数据结构。
当与 type 关键字结合使用时,record 可以生成一个复合类型。例如下面的 Conn::Info:
moduleConn;
export{
## The record type which contains column fields of the connection log.
type Info: record {
ts: time &log;
uid:string&log;
id: conn_id &log;
proto: transport_proto &log;
service:string&log &optional;
duration: interval &log &optional;
orig_bytes: count &log &optional;
resp_bytes: count &log &optional;
conn_state:string&log &optional;
local_orig:bool&log &optional;
local_resp:bool&log &optional;
missed_bytes: count &log &default=0;
history:string&log &optional;
orig_pkts: count &log &optional;
orig_ip_bytes: count &log &optional;
resp_pkts: count &log &optional;
resp_ip_bytes: count &log &optional;
tunnel_parents:set[string]&log;
};
}
由于这个类型定义在一个 export 块内,因此实际上定义的是 Conn::Info。
在 Zeek 中,record类型声明的格式包括:(被定义类型的描述性名称)+(构成该记录的各个字段)。
构成新记录的单个字段在类型或数量上没有限制,只要每个字段的名称是唯一的。
type Service: record {
name:string;
ports:set[port];
rfc: count;
};
function print_service(serv:Service)
{
print fmt("Service: %s(RFC%d)",serv$name, serv$rfc);
for( p in serv$ports )
print fmt(" port: %s", p);
}
event zeek_init()
{
local dns:Service=[$name="dns", $ports=set(53/udp,53/tcp), $rfc=1035];
local http:Service=[$name="http", $ports=set(80/tcp,8080/tcp), $rfc=2616];
print_service(dns);
print_service(http);
}
record还可以作为另一个record中的字段。例如:
type Service: record {
name:string;
ports:set[port];
rfc: count;
};
type System: record {
name:string;
services:set[Service];
};
function print_service(serv:Service)
{
print fmt(" Service: %s(RFC%d)",serv$name, serv$rfc);
for( p in serv$ports )
print fmt(" port: %s", p);
}
function print_system(sys:System)
{
print fmt("System: %s", sys$name);
for( s in sys$services )
print_service(s);
}
event zeek_init()
{
local server01:System;
server01$name ="morlock";
add server01$services[[ $name="dns", $ports=set(53/udp,53/tcp), $rfc=1035]];
add server01$services[[ $name="http", $ports=set(80/tcp,8080/tcp), $rfc=2616]];
print_system(server01);
}
一般还使用record为数据结构创建一个更具描述性的名称,例如:
type string_array: table[count] of string;
type string_set:set[string];
type addr_set:set[addr];
自定义日志
用到最多的就是日志框架(Logging Framework),其中包括了日志流(Log Streams)、过滤器(Filters)和写入器(Writers)。
数据基于 Zeek 脚本中的决策过程写入日志流,然后,这些数据可以通过日志过滤器过滤、修改或重定向。过滤器可用于将日志文件拆分为子集或将该信息复制到另一个输出。数据的最终输出由写入器定义。Zeek 的默认写入器是简单的以制表符分隔的 ASCII 文件,但 Zeek 还支持 DataSeries 和 Elasticsearch 输出。
下面的例子将记录数字 1 到 10 及其对应的阶乘到默认的 ASCII 日志写入器中。
这是希望得到的结果:
moduleFactor;
function factorial(n: count): count
{
if( n ==0)
return1;
else
return( n * factorial(n -1));
}
event zeek_init()
{
local numbers: vector of count = vector(1,2,3,4,5,6,7,8,9,10);
for( n in numbers )
print fmt("%d", factorial(numbers[n]));
}
整合日志框架:
moduleFactor;
export{
redef enumLog::ID +={ LOG };
type Info: record {
num: count &log;
factorial_num: count &log;
};
}
function factorial(n: count): count
{
if( n ==0)
return1;
else
return( n * factorial(n -1));
}
event zeek_init()
{
Log::create_stream(LOG,[$columns=Info, $path="factor"]);
}
event zeek_done()
{
local numbers: vector of count = vector(1,2,3,4,5,6,7,8,9,10);
for( n in numbers )
Log::write(Factor::LOG,[$num=numbers[n],$factorial_num=factorial(numbers[n])]);
}
首先定义了一个模块 Factor
,用于计算并记录数字的阶乘。接着通过 redef enum Log::ID += { LOG };
创建了一个新的日志标识符 LOG
,用于识别即将创建的日志流。然后,定义了一个记录类型 Factor::Info
,包含输入的数字和其阶乘,两个字段均带有 &log
属性,代表它们能够被 Logging Framework 所记录。脚本在初始化事件中调用 Log::create_stream(LOG, [$columns=Info, $path="factor"]);
创建一个名为 factor
的日志流,并在zeek_done中遍历 1 到 10 的数字,计算其阶乘并通过 Log::write(Factor::LOG, ...)
方法将结果写入日志,从而实现了数字及其相应阶乘的记录功能。
如图所示,最终执行脚本没没有标准输出,而是将结果写入了factor日志。
过滤器为 Zeek 脚本提供了自定义的能力。过滤器可以选择性地包含或排除日志字段,甚至修改日志文件的保存路径。每个日志流在创建时都会默认有一个名为 default
的过滤器。当使用该默认过滤器时,所有带有 &log
属性的键值对都会被写入到同一个文件中。
下面尝试将阶乘为5的因子的日志写入另一个文件:
moduleFactor;
export{
redef enumLog::ID +={ LOG };
type Info: record {
num: count &log;
factorial_num: count &log;
};
}
function factorial(n: count): count
{
if( n ==0)
return1;
else
return(n * factorial(n -1));
}
event zeek_done()
{
local numbers: vector of count = vector(1,2,3,4,5,6,7,8,9,10);
for( n in numbers )
Log::write(Factor::LOG,[$num=numbers[n],
$factorial_num=factorial(numbers[n])]);
}
function mod5(id:Log::ID, path:string, rec:Factor::Info):string
{
if( rec$factorial_num %5==0)
return"factor-mod5";
else
return"factor-non5";
}
event zeek_init()
{
Log::create_stream(LOG,[$columns=Info, $path="factor"]);
local filter:Log::Filter=[$name="split-mod5s", $path_func=mod5];
Log::add_filter(Factor::LOG, filter);
Log::remove_filter(Factor::LOG,"default");
}
跟前一个脚本相比,差异主要在于func mod5和event zeek_init:
•mod5:该方法根据factorial_num的不同结果,返回两个路径。•zeek_init:同样是先创建一个日志流,但是随后定义了一个过滤器,名为split-mod5s,路径函数为mod5,并在最后移除了默认过滤器并添加了自定义过滤器。
再次执行脚本,日志的输出符合预期。
在下面的脚本中,相较于前一个版本,新增了自定义事件 log_factor
,用于实时处理日志记录。通过在导出模块中定义该事件,并在调用 Log::create_stream
时将其设置为 $ev=log_factor
,脚本能够在每次将数字及其阶乘写入日志时触发该事件。这种设计允许用户在日志生成的同时实施进一步的数据处理,增强了日志系统的灵活性和可扩展性。而之前的脚本仅通过 Log::write
将数据写入日志文件,缺乏动态交互和实时处理能力。
moduleFactor;
export{
redef enumLog::ID +={ LOG };
type Info: record {
num: count &log;
factorial_num: count &log;
};
global log_factor:event(rec:Info);
}
function factorial(n: count): count
{
if( n ==0)
return1;
else
return(n * factorial(n -1));
}
event log_factor(rec:Info){
print fmt("log_factor triggered: num=%d, factorial_num=%d", rec$num, rec$factorial_num);
}
event zeek_init()
{
Log::create_stream(LOG,[$columns=Info, $ev=log_factor, $path="factor"]);
}
event zeek_done()
{
local numbers: vector of count = vector(1,2,3,4,5,6,7,8,9,10);
for( n in numbers )
Log::write(Factor::LOG,[$num=numbers[n],
$factorial_num=factorial(numbers[n])]);
}
function mod5(id:Log::ID, path:string, rec:Factor::Info):string
{
if( rec$factorial_num %5==0)
return"factor-mod5";
else
return"factor-non5";
}
event zeek_init()
{
local filter:Log::Filter=[$name="split-mod5s", $path_func=mod5];
Log::add_filter(Factor::LOG, filter);
Log::remove_filter(Factor::LOG,"default");
}
创建通知
Zeek的通知框架(Notice Framework)允许脚本编写者以明确的方式来发出通知。只需要通过export声明一个特定的通知类型(Notice::Type),然后调用 NOTICE
函数,并提供适当的 Notice::Info
记录来提拉起通知即可。
在 Notice::Info
中,唯一的必需字段是 note
字段,但是最好在 msg
中包含简单的描述。如果提供了 $conn
变量,通知框架还会自动填充 $id
和 $src
字段。
核心概念
1.NOTICE
事件: Zeek 中的通知都是通过 NOTICE
事件实现的。用户可以调用 NOTICE
事件来生成一条告警日志。2.告警类型 (Notice::Type
): 每条 NOTICE
都有一个类型,表示这条通知的具体含义。Zeek 默认提供了很多类型,也可以自定义。3.通知处理:•NOTICE
事件触发后,会生成记录到 notice.log
。•根据配置,可以执行额外操作,如发送电子邮件或触发外部系统响应。
基础结构
触发 NOTICE
事件的基本代码如下:
NOTICE([$note=Notice::SSH_Brute_Force,
$msg="Detected possible SSH brute force attack",
$sub=fmt("Attacker IP: %s", c$id$orig_h),
$conn=c]);
其中:
•$note
:告警的类型,通常用枚举值(Notice::Type
)表示。•$msg
:告警的主要信息,用于描述事件的内容。•$sub
:告警的附加信息,可以提供更多细节。•$conn
:连接信息,方便关联流量日志。•$peer_descr
:来源描述(可选,用于标识产生事件的 Zeek 节点)。
Notice 类型 (Notice::Type
)
1. 内置类型
Zeek 提供了一些常见的告警类型,例如:
•Notice::SSH_Brute_Force
:SSH 暴力破解。•Notice::DNS_Spoofed
:DNS 被篡改。•Notice::HTTP_XSS_Attack
:HTTP XSS 攻击。
2. 自定义类型
用户可以根据需求定义自己的告警类型:
redef enumNotice::Type+={MyCustomNotice::Malicious_Activity};
Zeek 的 Notice 生命周期
1.生成: 脚本中通过 NOTICE
函数生成告警。2.记录: 告警会被写入 notice.log
文件。3.响应: 根据 policy/frameworks/notice/main.zeek
的配置,可以触发进一步的响应操作,如电子邮件通知。
下面的脚本在检测特定行为的同时,会通过notice进行通知:
1、捕获 SSH 登录行为,并生成通知告警:
@loadbase/frameworks/notice
moduleSSHDetection;
export{
redef enumNotice::Type+={SSHDetection::SSH_Login };
}
event SSH::log_ssh(rec: SSH::Info){
NOTICE([$note=SSHDetection::SSH_Login,
$msg=fmt("SSH login detected from %s to %s", rec$id$orig_h, rec$id$resp_h),
$conn=rec$id]);
}
•SSH::log_ssh
是 Zeek 的一个事件,用于捕获 SSH 连接日志。•每次触发该事件,脚本会调用 NOTICE
,生成一条包含来源 IP 和目标 IP 的告警。
2. 检测 HTTP 中的恶意 User-Agent
检测 HTTP 请求中包含特定恶意标识的 User-Agent 字段。
@loadbase/frameworks/notice
moduleHTTPDetection;
export{
redef enumNotice::Type+={HTTPDetection::Malicious_User_Agent};
}
event http_header(c: connection, is_orig:bool, name:string, value:string){
if(name =="user-agent"&&/EvilBot/i in value){
NOTICE([$note=HTTPDetection::Malicious_User_Agent,
$msg=fmt("Malicious User-Agent detected: %s", value),
$conn=c]);
}
}
•使用 http_header
事件捕获 HTTP 请求头信息。•如果 User-Agent
字段中包含 "EvilBot",则生成告警。
3. 检测 DNS 中的可疑域名
检测 DNS 查询中是否包含某些可疑域名。
@loadbase/frameworks/notice
moduleDNSDetection;
export{
redef enumNotice::Type+={DNSDetection::Suspicious_Domain_Query};
}
event dns_request(c: connection, msg: dns_msg, query:string){
if(query in{"malicious.com","badguy.org"}){
NOTICE([$note=DNSDetection::Suspicious_Domain_Query,
$msg=fmt("Suspicious domain query detected: %s", query),
$conn=c]);
}
}
•使用 dns_request
事件捕获 DNS 查询。•如果域名在可疑列表中,生成通知。
事件组
基于属性
当事件或者hook具有&group属性时,就会有基于属性的事件。基于属性的事件组有一个全局命名空间,不同文件或模块中的事件处理程序,如果有相同的group属性值,就属于同一组。事件和hook可以有多个group属性。
event http_request(c: connection, method:string, original_URI:string, unescaped_URI:string, version:string)&group="http-print-debugging"
{
print fmt("HTTP request: %s %s (%s->%s)", method, original_URI, c$id$orig_h, c$id$resp_h);
}
event http_header(c: connection, is_orig:bool, original_name:string, name:string, value:string)&group="http-print-debugging"
{
if( name !="USER-AGENT"&& name !="SERVER")
return;
local snd = is_orig ? c$id$orig_h : c$id$resp_h;
local rcv = is_orig ? c$id$resp_h : c$id$orig_h;
print fmt("HTTP header : %s=%s (%s->%s)", original_name, value, snd, rcv);
}
event http_reply(c: connection, version:string, code: count, reason:string)&group="http-print-debugging"
{
print fmt("HTTP reply: %s/%s version %s (%s->%s)", code, reason, version, c$id$resp_h, c$id$orig_h);
}
基于模块
除了基于属性的事件组外,Zeek 还支持基于模块的事件组。内置函数 disable_module_events
和 enable_module_events
可用于禁用和启用模块中的所有事件和hook处理程序。
原文始发于微信公众号(Crush Sec):Zeek0x02_Zeek文件结构&日志&脚本基础
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论