0x01 简介
简介
Bro
Bro是一款被动的开源流量分析器。它主要用于对链路上所有深层次的可疑行为流量进行一个安全监控。更通俗点说就是,Bro支持在安全域之外进行大范围的流量分析,分析包括性能评估和错误定位。
站点部署BRO所能获得的最直接好处就是获得日志文件的扩展集,这些文件在高层次记录网络的行为。这些日志文件不仅全方位记录了所有线路上可见的每个连接,还记录了应用层传输,例如HTTP会话以及请求的URL、关键头、MIME类型、服务器反馈,DNS请求及反应,SSL证书,SMTP会话的关键内容等等。默认情况下BRO将这些信息写进结构化,tab键分隔的日志文件中以供其他软件后续处理。当然我们也可以选择其他输出格式和后端来和数据库对接。
当然除了日志,bro还有内建函数来完成分析和检测任务,包括在HTTP会话中抽取文件,在与其他注册点对接时监测恶意软件,报告脆弱的软件版本,识别web应用,监测SSH暴力破解,认证SSL证书链等等。
但是,理解BRO的关键在于意识到,即使本系统自带开箱即用的强大功能,从根本上来看这仍代表一个全定制化和可扩展的流量分析平台。BRO为用户提供了特定域的,图灵完备的脚本语言用于表达任意的分析任务。你可以将BRO视作特定域的Python或者Perl。如同Python,BRO自带大量预建功能,即标准库。如此你将不受限于系统,而能以编写自己的代码的方式来使用BRO。事实上BRO的默认分析,如日志就是基于这些脚本的,核心系统中没有硬编码任何特定分析。
特征
- 部署
- Unix-like系统上运行
- 基于监控端口和网络窃听装置的全被动分析
- 标准libpcap的抓包接口
- 线上&线下分析
- 支持簇的大规模部署
- 统一管理框架
- 开源
- 分析
- 离线分析和取证的全面日志记录
- 应用层协议的独立端口分析
- 支持多种应用层协议 DNS FTP HTTP IRC SMTP SSH SSL
- 应用层协议交换的文件内容分析
- 全IPv6支持
- 隧道检测与分析 Ayiya Teredo GTPv1
- 扩展的检查
- 支持IDS的正则匹配
- 脚本语言
- 图灵完备
- 基于事件
- 特定域数据类型
- 追踪与管理网络状态
- 接口
- 结构化ASCII日志输出
- ElasticSearch和DataSeries后端
- 将输入实时整合到分析工具中
- bro将事件用C库与其他程序交换
- 以脚本语言触发任意处理
0x02 架构
从此图上来看我们可以看到BRO被划分成两大组件,其一是核心事件引擎,他会减少进入的数据包流,成为一系列高层事件。这些事件反映了网络活动。如每个HTTP请求会变成http_request事件,承载了IP地址和端口,请求的URI以及HTTP版本信息。事件不会表达任何深入的意义。
其二是脚本解释器,他会根据BRO的脚本语言编写的程序执行一系列事件处理。该脚本会表达站点安全规则,例如当监控装置侦测到不同的活动时BRO该采取什么样的动作。他们可以从输入流量中产生任意属性和统计数字。脚本语言自带特定域的类型和支持功能,并允许脚本维护状态。脚本能够生成实时告警也能按需执行任意外部程序来触发对攻击的响应动作。
0x03 Bro的簇架构
Bro不是多线程的,因此一旦到达单处理器核心的限制,唯一的选择就是将附在分摊到多核心或者多物理机上。簇部署就是针对这样大系统的场景设计的解决方案。伴随Bro的工具和脚本提供了易于管理许多Bro进程的结构来检查包处理相关动作,从而形成一个整体。
Bro簇的架构
Tap
划分数据流来形成一个可检测的拷贝。
前端
前端是一个分离的硬件设备或主机技术,能够将流量划分为许多的stream或flow。
管理端
管理端进程有两个主要任务,即从该簇的其他节点接收日志消息以及提示。其输出是一个单一的日志,你需要将某行为和后期处理相结合,你当然可以选择重复提示。
管理端进程首先由BroControl启动,仅开放特定端口等待连接,他不会初始任何对其他簇的连接,一旦工作端启动并连接到管理端,日志和提示将开始从工作端抵达管理端进程。
代理端
管理同步状态的进程。变量能够跨进程自动同步。代理端帮助工作端,减轻工作端之间彼此的直连需求。
工作端
工作端嗅探网络流量并做协议分析。活动簇的绝大部分工作发生在工作端上,因此工作端需要最快的内存和CPU速度。但是其硬盘需求不大,因为日志直接发给了管理端。
前端选项
设置前端流分发器的时候存在许多选项,多数情况下采取多步骤流分发具有益处。
cPacket-分离硬件
若是监控多个10G网卡,建议使用cFlow或者cVu设备。这些设备能通过重写目的mac地址引发与特定流关联的包具有相同的目的mac从而实现链路层负载均衡。
OpenFlow交换机-分离硬件
使用OpenFlow交换机来直接实现基于流的负载均衡。
PF_RING-主机负载均衡
PF_RING软件具有簇特征,它会通过大量嗅探同网卡的进程来进行基于流的负载均衡。这会使你轻松利用同一物理机上多核的优势,因为Bro的事件主循环是单线程,且不能简单利用所有的核心。
Netmap
基于流的负载均衡
Click!软路由
简单配置 基于流的负载均衡。
0x04 安装
在ubuntu 14.04中安装较为方便,只需输入以下代码即可。
sudo sh -c "echo 'deb http://download.opensuse.org/repositories/network:/bro/xUbuntu_14.04/ /' >> /etc/apt/sources.list.d/bro.list"
sudo apt-get update
sudo apt-get install bro
具体其他系统关注 Bro安装手册
0x05 快速使用bro
以上方法去安装的bro,root用户的环境变量中不会存在bro的可执行程序所在的路径,而bro的安装目录的权限只允许root以及root所在的组访问,因此我们选择将可执行文件所在的路径添加进root用户的环境变量PATH中。
export PATH=/opt/bro/bin:$PATH
如此一来只需对三个配置文件进行修改就可以完成BroControl的最小安装,从而完成在本机上的独立使用。
- 修改
$PREFIX/etc/node.cfg
,设置监听网卡 - 修改
$PREFIX/etc/network.cfg
,注释默认设置,添加本地网络作为监控对象 - 修改
$PREFIX/etc/broctl.cfg
,设置MailTo
属性,改为希望发送告警信息的邮箱,设置LogRotationInterval
属性,改为将当前日志文件夹内的文件压缩的指定频率,归档文件夹以日期命名。
PS:$PREFIX
是我们当时最初安装bro所在的目录。
随后以root用户执行命令
broctl
就可以进入broctl的shell,初次使用broctl需要先在shell中执行install
和以start
启动bro实例。由于抓包需要操作系统的root权限,所以最好是赋予你的账户抓包的权限。
[BroControl] > install
[BroControl] > start
你若是要停止bro实例。
[BroControl] > stop
随后我们打开浏览器,打开网页,查看$PREFIX/logs/current/http.log
就可以看到了Bro对HTTP协议的分析结果。见下图。
基本可以看到其中的时间戳,uid,源地址&端口,目的地址&端口等信息。
部署Bro是一个更新规则来对不同的时间来采取不同的行为以及使用其脚本语言以一个精确的方式扩展器流量分析能力的递归过程。定制Bro过程的第一步是熟悉Bro的notice日志。
当我们已经定义我们要做的事之后,我们需要知道我们在哪做这些事了。使用Bro的脚本即可完成我们需要的任务。
Bro脚本
脚本一般装在$PREFIX/share/bro
下且使用.bro扩展名。该目录下的文件不应该被直接编辑因为所做的修改会在升级新版本时丢失。而目录$PREFIX/share/bro/site
下的文件是个例外,指定站点的文件能够被放置而不必担心失败。
$PREFIX/share/bro/base
和$PREFIX/share/bro/policy
目录也是主要的脚本目录。Bro默认加载base
文件夹下的脚本,除非使用-b
来设定,该目录下的脚本处理收集网络活动的基础/有用状态或者提供框架/工具来拓展Bro的功能而不损失性能。policy
目录下的脚本更加有条件或存在耗费,因此我们要选择是否加载这些脚本。
用于BroControl管理的独立Bro实例的配置的主入口点是脚本$PREFIX/share/bro/site/local.bro
,基于他我们可以做一系列修改。
重定义脚本变量
简单的定制仅需要以自己的值来重定义标准Bro脚本中的变量,使用redef
操作符来完成。脚本发布可调整的选项的方法是定义有&redef
属性和const
修饰的变量。可重定义的常量看起来很奇怪吧,但这意味着该变量的值不能在运行时被修改,但是其初始值能通过redef
操作符在解析时修改。
$PREFIX/share/bro/base/frameworks/notice/main.bro
中的Notice命名空间在此是必要的,因为变量在模块Notice中声明并导出,在模块外被引用。既然仍在模块内,若要引用变量,模块内被声明和引用的变量不会被纳入范围。
在对该脚本语言记录域成员的访问采用$
而非.
。
需要记住的是,若要完成配置的修改需在BroControl中按顺序输入命令check
、install
以及restart
。
0x06 Bro日志分析
使用日志文件
在Bro日志文件夹下存在多个文件分别对应不同的类型的日志。
文件名 | 作用 |
---|---|
conn.log | 关于连接的日志 |
dpd.log | 非标准端口协议的日志 |
dns.log | dns活动日志 |
ftp.log | ftp会话活动日志 |
files.log | HTTP FTP SMTP 文件日志 |
http.log | http请求和响应日志 |
known_certs.log | SSL证书 |
smtp.log | SMTP活动日志 |
ssl.log | SSL会话,包括使用的证书 |
weird.log | 意料外的协议层活动日志 |
使用bro-cut工具
这个工具是读取日志输出,根据日志中各项属性的名字来提取对应的值。
处理时间戳
施工中
使用Unique IDs
施工中
0x07 以Bro监听HTTP流量
Bro能够将整个HTTP流量记录到http.log
日志中,该日志能够用于随后的分析和审计。
这部分的思想和技术当然也可适用于检测其他不同的协议。
初识HTTP日志
本日志包含所有的HTTP请求以及响应。如下图所示。
每行都由时间戳,uid,源地址,源端口,目的地址,目的端口组成。UID用于唯一标识一个活动,与UID相对应的是一个连接生命周期内的源地址-源端口-目的地址-目的端口四值对。其余部分则详述了这个活动。
侦测代理服务器
人们配置代理服务器来请求来自第三方系统的服务。代理用于管理网络并提供更好的封装特性。代理本身不存在安全威胁,但是一个错误配置的或者无鉴权的代理将使得网络内外的用户访问到任何站点,甚至是进行恶意活动。
代理服务器间的流量特征
与代理的对话流量长啥样呢?一般来说流量包含两部分:
GET请求
-
HTTP响应
Request: GET http://www.baidu.com/ HTTP/1.1 Reply: HTTP/1.0 200 OK
这就将代理的流量与浏览器同Web服务器的流量区分出来了,因为浏览器发出的GET请求不应该会有'http'字段。
如此一来我们就可以根据这个去识别代理服务器了。
文件检查
0x08 动手写脚本
如何理解Bro脚本语言
Bro拥有一个事件驱动的脚本语言,它能够为组织提供对Bro功能进行扩展和定制的方法。Bro脚本能够高效地知会Bro有我们定义的事件发生了,让我们获得连接的信息,因此我们可以对其进行操作。
一般的Bro脚本划分为三个部分。首先是无缩进的加载库指令,以@load
来加载module
中定义的命名空间。随后是格式化缩进的自定义变量,使用标识符export
来将变量写入脚本的命名空间。最终是对特定时间采取的特定指令。
@load base/frameworks/files
@load base/frameworks/notice
@load frameworks/files/hash-all-files
第一部分由@load
命令组成,这些命令能够执行__load__.bro
脚本。这些命令能够加载Files框架,Notice框架以及hash文件框架。
export {
redef enum Notice::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.
const 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/ &redef;
## 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.
const match_sub_url = "https://www.virustotal.com/en/search/?query=%s" &redef;
## 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.
const notice_threshold = 10 &redef;
}
第二部分定义了枚举常量,该常量描述了我们会从Notice框架产生的Notice类型。Bro允许重定义常量,这些常量都是非直观的。Notice类型允许使用NOTICE
函数产生Match
类型的Notice。Notice是Bro产生的额外通知。
接下来最后部分本脚本开始定义针对特定事件的指令。
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 " "
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));
}
首先这段脚本中提供了file_hash
的事件处理器。file_hash
事件允许脚本访问文件信息中的hash值,其中文件是f,hash函数是kind,hash值是hash。在file_hash
事件处理函数中存在一个if语句用于检查hash函数类型和mime类型是否符合我们感兴趣的类型,随后调用do_mhr_lookup
函数。其中when
语句可以执行异步操作,而不影响性能。
事件队列与处理器
Bro的脚本是事件驱动的,它依赖于处理由Bro生成的事件,会修改事件的数据结构状态,根据信息做出决策。
Bro核心将事件放置到有序的事件队列中,允许处理器按先到先服务的原则处理他。事件和事件处理方式对于理解Bro以及脚本语言很关键。Bro产生的事件可以通过在线事件文档查阅。
连接记录数据类型
连接记录数据类型传递大量Bro定义的事件,连接记录本身是嵌套数据类型,用于追踪连接在其生命周期中的状态。由于Bro能够进行包层的处理,其强项在于源点和目的点之间的连接上下文。
数据类型和数据结构
Scope 域:Local和Global
要了解Bro的原生数据类型和数据结构得先掌握可用的scope和正确的使用时刻。Bro里有两种声明变量的方法(1. SCOPE name: TYPE
2. SCOPE name = EXPRESSION
),变量声明的时候可以带也可以不带定义。若EXPRESSION
的结果是TYPE
类型的,那么两者效果相同。
全局变量
某脚本中的使用Global声明的变量能够在其他脚本中被访问。脚本使用module
关键字会给脚本一个命名空间,我们必须更加关注全局变量的声明。当全局变量在有用命名空间的脚本中声明时,我们的变量将会有两个输出。其一是命名空间的输出,使用同样命名空间的脚本将可以访问声明的变量,而使用不同命名空间或无命名空间的将不能访问变量。若全局变量以export {...}
声明,变量就可以被任何脚本以MODULE::variable_name
的方式来访问了。
当module
关键字在脚本中使用时,就是说脚本中export出来的的变量被加入到了module
后的命名空间中了。当全局变量未在模块内声明时,它可以按其名字来访问,而声明在模块内部的全局变量就必须导出且通过MODULE_NAME::VARIABLE_NAME
来访问。
常量
Bro利用const
关键字来定义常量。不同于全局变量的是,若使用了&redef
,常量只能在解析时设置与修改。运行时,常量是无法改动的。重定义的常量是用作配置选项的容器。
局部变量
数据结构
基本数据类型
数据类型 | 描述 |
---|---|
int | 64位有符号整数 |
count | 64位无符号整数 |
double | 双精度浮点数 |
bool | 布尔型 |
addr | IPv4/v6地址 |
port | 传输层端口 |
subnet | 子网掩码 |
time | 绝对时间 |
interval | 时间间隔 |
pattern | 正则表达式 |
sets
Sets用于存储统一数据类型不可重复的元素,是集合的概念。Sets的声明方式为SCOPE var_name : set[TYPE]
。使用add
和delete
语句可以添加和删除集合中的元素。可以使用in
操作符来遍历集合中的元素。
tables
Tables里是键值对的映射关系,键唯一而值不唯一。
Tables的键可以是由多个数据类型的变量,或者是名为'tuple‘的元素序列构成。Bro因表的复杂变得灵活,人们也为其灵活付出了巨大的代价。
vectors
向量为我们提供了以下标访问unsigned integer的数组的方法,然而他更加高效,且能够允许顺序访问。当我们需要连续存储相同类型的数据时,就可以使用向量。因为向量对其中元素采取连续存储的方法,对于向量中的元素我们就能以从0开始的偏移量来进行访问了。
声明一个向量需这么做SCOPE v: vector of T
,其中v是变量名,T是数据类型。
使用for语句可以快速遍历向量。
for (i in vector)
print vector[i] ;
详述基本数据类型
addr
地址类型。IPv4用默认的点分隔四段格式类型,IPv6则用方框包住了整个地址。
port
端口类型。
如22/tcp
,53/udp
。Bro支持TCP,UDP,ICMP以及UNKNOWN作为支持的协议类型。ICMP实际并不具有实际的端口,BRO以ICMP消息类型和ICMP消息码的方式支持ICMP端口的概念。
端口号可以用==或者!=来进行比较或者排序,协议之间也有顺序,即unknown
<tcp
<udp
<icmp
,如65535/tcp
就比0/udp
小。
subnet
子网类型。Bro对CIDR标记的子网类型能够完全支持,这样我们就无需以两个独立的实体来管理IP以及子网掩码了。
在Bro脚本里,通常不需要任何网络分析,只需要使用address in subnet
就可以对IP地址是否属于该子网进行判断,如10.0.0.1 in 10.0.0.0/8
就会返回true。
time
我们不能自己创建time
常量,但是可以通过两个函数network_time
和current_time
来返回一个time
数据类型,这两个返回的time
标准不同:current_time
返回OS设置的墙上时间;而network_time
返回的是在线数据流或离线数据包中处理的最后一个报文中的时间戳。这两个时间的表达形式都是以秒计数的,使用函数strftime
可以将其转换成可阅读的形式,就像这样:strftime("%Y/%M/%d %H:%m:%S",network_time())
。这里用到了时间戳的格式化字符串,具体有哪些格式化字符串和都代表啥意思,就得看我在justniffer使用手册
里介绍的部分了。
interval
时间间隔,作为一个数据类型它代表着时间差,表示方法就是数值常量后跟着一个时间单位,如3 hr
。Bro支持以下时间单位
符号 | 含义 |
---|---|
usec |
微秒 |
msec |
毫秒 |
sec |
秒 |
min |
分 |
hr |
时 |
day |
日 |
时间间隔变量中间的空格可有也可无。当然时间单位也分为单复数,虽然都可以表示。和上一句话的意思结合起来就是42hrs
和42 hr
是一个意思,都对。时间间隔可为负数,- 10 mins
表示10分钟之前。
在Bro里时间间隔可以加减乘除,也可以进行比较。两个time
类型相减会返回一个时间间隔。
Pattern
正则表达式。Bro当然支持在脚本中采用正则表达式来对文本进行快速的搜索,我们生声明的正则表达式对象以两个/
来包裹正则文本。常用的匹配方式是采用in
操作符,如pattern in string
。
split
函数则是通过匹配的正则表达式来划分字符串,输出一个表,和python里差不多。
正则表达式和字符串之间可以使用==
与!=
来进行比较,当字符串完全与正则表达式匹配时,==
返回true,否则就返回false,另一个操作符反之。
record数据类型
Bro支持多种数据类型和数据结构,当然也支持创建自定义的数据类型,这个数据类型由基本数据类型以及数据结构组成。这样的自定义类型采用type
关键字来定义record
数据类型,就像在c里用typedef和struct定义新的结构体。
声明一个record数据类型时,应采用一个类型描述名和几个独立的域。如:
type Service: record {
name: string;
ports: set[port];
rfc: count;
};
当然record数据类型也可以进行嵌套,如:
type System: record {
name: string;
services: set[Service];
};
type
也可以用于给某数据结构改名,如type addr_set: set[addr];
定制日志
为了理解Bro中的数据类型和数据结构,最好的办法自然是阅读bro的框架。框架里最吸引人的是Logging框架。为了设计一个能够抽象创建文件并添加有序有组织的数据到这个文件中的过程,Logging框架使用潜在的命名系统。Log Streams,Filters和Writers用于维护高速进入的日志。
我们将基于脚本中的决策将数据写入Log Stream,其中的一条日志记录将以 名-值 对填充到其值域中。随后数据将被过滤,修改,重定向到Logging Filter。Filter能够用于截断日志文件或者复制信息到其他输出。数据的最终输出由Writer来定义,默认情况下日志输出是以tab
来分割的ASCII文件,当然日志也支持DataSeries
和Elasticsearch
(其writer还在开发中)。
看到这么多的新名词和新思路是不是有点晕了呢?是不是觉得Logging Framework很难用很难学呢?
其实按En Wiki的说法,Logging Framework的学习曲线不是很陡峭。Logging Framework的大部分脚本对于初学者来说不一定要全部读完。实际上,写入日志文件,定义数据格式,让Bro知道要创建一个新的日志,调用Log::write
方法输出日志都很简单。
所以说日志框架的话,你照着他写的越多,自己写的越多,你就越会把他当成你的第二语言。
在Logging Framework中每个默认log stream都会产生一个自定义时间,该事件能够被任何想基于写入的数据做些事的人处理。这些事件是log_x
的格式,其中x是logging stream的名字。例如由HTTP解析器发送给LoggingFramework的日志所引发的的事件都被称作log_http
。
我们可以在创建logging stream的时候对流进行配置,设置他可以引发的事件,事件一般以global log_x:event(var:Type);
在export中声明。随后在其他脚本中使用以下脚本来处理事件。
event log_x( var : Type){}
这样就可以完成指定事件的处理了。具体应用到我们的流量事件触发处理操作还需进一步研究。
发出提醒
学习中
0x09 Bro IDS
学习中
0x0A 获取HTTP请求中的POST数据
我们想在输出的HTTP日志中添加一列,该列为HTTP请求中的POST数据。这样我们就在PREFIX/share/bro/site/local.bro
中添加一部分脚本,用以实现该功能。添加的代码为:
@load base/protocols/http
module HTTP_PostBody;
export {
## The length of POST bodies to extract.
const extract_length = 100 &redef;
}
redef record HTTP::Info += {
post_body: string &log &optional;
};
event log_post_bodies(f: fa_file, data: string)
{
for ( cid in f$conns )
{
local c: connection = f$conns[cid];
if ( ! c$http?$post_body )
c$http$post_body = "";
c$http$post_body = c$http$post_body + data;
if ( |c$http$post_body| > extract_length )
{
c$http$post_body = c$http$post_body[0:extract_length] + "...";
Files::remove_analyzer(f, Files::ANALYZER_DATA_EVENT, [$stream_event=log_post_bodies]);
}
}
}
event file_over_new_connection(f: fa_file, c: connection, is_orig: bool)
{
if ( is_orig && c?$http && c$http?$method && c$http$method == "POST" )
{
Files::add_analyzer(f, Files::ANALYZER_DATA_EVENT, [$stream_event=log_post_bodies]);
}
}
如下图
0x0B Bro优缺点
优势
- 开源
- 有脚本能够对其输出的日志进行定制
- 存在控制台可以让系统作为守护进程,通过读取配置文件以及脚本完成日常的流量分析和日志记录工作
- 脚本由事件驱动,可以根据特殊的系统日志情况进行有针对性的操作
- 能够以分布式部署在多台机器上,对多个网络进行监控,并通过网络将数据集中到服务器,统一筛选查看
劣势
- 脚本语言复杂,初次接触难以很快上手
- 系统复杂,要对其进一步定制需要大量时间