2009年3月6日星期五

ossec的新功能--预编译规则之三:对预处理规则的评价

对预处理规则的评价

插件化是很多开源软件采取的,非常流行的扩展方式,例如:

  • apache:采用动态共享对象的方式,提供扩展;利用Apache扩展工具(apxs),可以无需重新编译apache,即可加入apache插件;
  • nessus:nessus自创了一种叫做nasl的脚本语言来实现差价的扩展,由于其功能的局限,这种语言较受争议;
  • nmap:通过NSE(Nmap Script Engine,nmap脚本引擎)实现插件的扩展,使用目前非常流行的lua作为其寄生语言;
  • snort:在snort3的代码中,也引入了lua作为扩展语言;
  • bro-ids:采用了一种自定义的脚本语言,来扩展对网络流量的处理功能。

与上述开源产品相比,ossec的预处理规则显得非常粗糙,从技术上看,甚至有些画蛇添足,因为:

  • ossec的xml规则编写的难度比c语言编写的预处理规则更容易掌握;
  • 预处理规则只局限于检测规则,其提供的扩展能力,不见得比在xml规则中定义正则表达式更强大;
  • ossec的预处理规则不具备动态编译部署的能力,加入新的预处理规则,必须重新编译ossec的源代码;
  • 由于预处理规则c程序的引入,有可能会给ossec加入新的bug,反而造成ossec的不稳定;

而与上述问题相比,预处理规则只能满足对c语言的偏好,所以前景不会太乐观,除非效法nmap做改进。另外,预处理规则出现的真正原因或许是出于商业的考 虑,因为利用预处理规则,Third Brigade公司可以对某些检测功能封闭源代码,直接编译到其商业版中,不用担心技术的泄露。

2009年3月5日星期四

ossec的新功能--预编译规则之二:预编译规则

预编译规则

根据上面的介绍,我们知道ossec有两种规则,其中解码器负责日志解码、分类,规则实现入侵检测和日志分析。想了解预编译规则能干什么,我们现看看ossec处理日志(以syslog为例)的基本处理流程:

  1. ossec-logcollector(源代码在src/logcollector)监控各种日志的变更,ossec支持syslog、mysql、postgresql、snort、nmap,以及djbmultilog等日志;
  2. 一旦发现日志变动,就读取信产生的日志,根据日志类型的不同,分别调用read_syslog、read_djbmultilog、 read_nmapg、read_mysql_log、read_snortfull、read_postgresql_log等函数进行处理。在这里我 们只关注syslog的处理。
  3. read_syslog读取日志条目,调用SendMSG向analysisd(源代码在src/analysisd)投递。在ossec启动时,analysisd会打开一个UNIX套接字,接收事件日志,进行分析处理。
  4. ossec-analysisd调用OS_ReadMSG_analysisd函数接收事件信息,然后调用OS_CleanMSG将收到的数据转换为Eventinfo结构。

Eventinfo

我们先看一下Eventinfo:

typedef struct _Eventinfo
{
/* 从事件信息中提取的数据 */
char *log;
char *full_log;
char *location;
char *hostname;
char *program_name;

/* 从解码器中提取的数据 */
char *srcip;
char *dstip;
char *srcport;
char *dstport;
char *protocol;
char *action;
char *srcuser;
char *dstuser;
char *id;
char *status;
char *command;
char *url;
char *data;
char *systemname;

/* Pointer to the rule that generated it */
RuleInfo *generated_rule;

/* Pointer to the decoder that matched */
OSDecoderInfo *decoder_info;

/* Sid node to delete */
OSListNode *sid_node_to_delete;

/* Extract when the event fires a rule */
int size;
int p_name_size;

/* Other internal variables */
short int matched;

int time;
int day;
int year;
char hour[9];
char mon[4];
}Eventinfo;

Eventinfo是ossec检测的核心对象,所有的检测都是针对Eventinfo进行的。对Eventinfo的处理又分以下阶段:

  1. 预处理阶段。OS_CleanMSG函数从接收到的信息中,提取相关数据,填写Eventinfo的第一部分;
  2. 解码处理阶段。调用DecodeEvent函数,完成解码处理。
  3. 检测阶段。剩下的检测过程全部由OS_ReadMSG_analysisd调度完成。

预编译规则

ossec预编译规则的位置是源代码的src/analysisd/compiled_rules目录。在上述阶段中,预处理规则只能用于检测阶段,与xml定义的检测规则等同。下面我们看一下ossec源代码中的一段示例,检测源用户和目标用户是否相同。

void *comp_srcuser_dstuser(Eventinfo *lf)
{
if(!lf->srcuser || !lf->dstuser)
{
return(lf);
}

/* 对比Eventinfo中,srcuser是否等于dstuser */
if(strcmp(lf->srcuser, lf->dstuser) == 0)
{
return(lf);
}


/* In here, srcuser and dstuser are present and are different. */
return(NULL);
}

编写预处理插件时,我们只需要编写我们需要的功能即可,剩下的相关工作可以由register_rule.sh脚本完成,然后重新编译ossec的源代码,即可完成安装。register_rule.sh支持以下选项:

  • add <函数明> #添加函数
  • list #列出加入的函数
  • build #生成头文件 compiled_rules.h
  • save #备份函数列表
  • restore #恢复函数列表

2009年3月4日星期三

ossec的新功能--预编译规则之一:ossec的规则

OSSEC的预编译规则(Compiled Rules)为了方便不喜欢编写xml格式规则的用户。利用这项功能,用户可以直接使用c语言来编写日志处理规则,在编译的时候,连接到 analysisd程序。并非一种动态的规则扩展机制。为了了解预编译规则,我们先看一下ossec的规则定义。

1. ossec的规则

ossec的规则是XML格式的,主要有两种:

  • 解码规则:用于判断日志的种类和格式,所有的内容都在decode.xml文件中;
  • 检测规则:依赖于检测规则,主要用于检测日志中记录的安全问题。

1.1. 解码规则

根据decoder.xml的定义,ossec的日志分析程序会从被监控的日志中提取某些字段,根据这些字段判断日志的种类,例如:sshd日志、 web日志,然后在根据其中的某些字段,确定日志记录了那种信息,例如:sshd登录成功、失败。下面我们看一段linux sshd日志的片段(来源于ossec wiki的日志样本):

  • 登录成功:

May 21 20:22:28 slacker2 sshd[8813]: Accepted password for root from 192.168.20.185 port 1066 ssh2
May 21 20:22:28 sol2 sshd[23857]: [ID 702911 auth.notice] User test1, coming from 192.168.2.185, - authenticated.
Oct 11 08:05:46 hostname auth|security:info sshd[323808]: Accepted publickey for usr1 from 2.3.4.5 port 37909 ssh2


  • 登录失败:

May 21 20:22:28 slacker sshd[21487]: Failed password for root from 192.168.20.185 port 1045 ssh2

  • 无效的用户登录企图:

Jul 7 10:51:24 chaves sshd[19537]: Invalid user admin from spongebob.lab.ossec.net
Jul 7 10:53:24 chaves sshd[12914]: Failed password for invalid user test-inv from spongebob.lab.ossec.net
Jul 7 10:53:24 kiko sshd[3251]: User dcid not allowed because listed in DenyUsers



decoder.xml文件中,相关规则如下:

<!-- 判断sshd日志的规则,从日志条目的开头匹配是否存在字符:sshd。 -->
<!-- 也是其它sshd日志解析规则的上级规则。 -->
<decoder name="sshd">
<program_name>^sshd</program_name>
</decoder>


<decoder name="sshd-success"><!-- 解析sshd登录成功日志 -->
<parent>sshd</parent><!-- 只有符合sshd的条件才启用这条规则 -->
<prematch>^Accepted</prematch><!-- 判断是否为登录成功,条件是从头搜索,发现Accepted字符 -->

<!-- 从日志条目中,利用正则表达式提取变量 -->
<regex offset="after_prematch">^ \S+ for (\S+) from (\S+) port </regex>

<!-- 两个(\S+)提取的值分别赋给变量:user(用户名)和srcip(源IP地址) -->
<order>user, srcip</order>

<!-- 根据name, user, location判断fts??? -->
<!-- fts是首次看到(First Time Seen)的简写 -->
<fts>name, user, location</fts>
</decoder>

<!-- 无效的用户或者失败的登录 -->
<decoder name="ssh-invfailed">
<parent>sshd</parent>

<!-- 判断 -->
<prematch>^Failed \S+ for invalid user|^Failed \S+ for illegal user</prematch>

<!-- 提取源IP地址 -->
<regex offset="after_prematch">from (\S+) port \d+ \w+$</regex>
<order>srcip</order>
</decoder>

<!-- 登录失败 -->
<decoder name="ssh-failed">
<parent>sshd</parent>
<prematch>^Failed \S+ </prematch>
<regex offset="after_prematch">^for (\S+) from (\S+) port \d+ \w+$</regex>
<order>user, srcip</order>
</decoder>

<!-- 无效用户 -->
<decoder name="ssh-invalid-user">
<parent>sshd</parent>
<prematch>^Invalid user|^Illegal user</prematch>
<regex offset="after_prematch"> from (\S+)$</regex>
<order>srcip</order>
</decoder>


1.2. 检测规则

检测规则位于etc/rules/目录中,还是以sshd的规则为例:

<group name="syslog,sshd,"> <!-- 定义规则组 -->
<rule id="5700" level="0" noalert="1">
<decoded_as>sshd</decoded_as> <!-- 判断是否为sshd日志 -->
<description>SSHD messages grouped.</description>
</rule>

<rule id="5701" level="8">
<if_sid>5700</if_sid> <!-- 只处理sshd日志 -->
<match>Bad protocol version identification</match>
<description>Possible attack on the ssh server </description>
<description>(or version gathering).</description>
</rule>

<!-- 其余规则省略 -->
</group>

2009年2月19日星期四

netmate在x86_64平台编译遇到的问题和解决方案。

在x86_64平台上编译netmate时,会遇到以下错误信息:

...
../../src/include/stdincpp.h:46:22: error: multimap.h: No such file or directory
../../src/include/stdincpp.h:47:22: error: hash_map.h: No such file or directory
../../src/include/stdincpp.h:58:21: error: iomanip.h: No such file or directory
../../src/include/stdincpp.h:59:23: error: streambuf.h: No such file or directory
In file included from ../../src/netmate/FlowRecord.h:35,
from ../../src/include/ExportModuleInterface.h:39,
from ./ExportModule.h:34,
from testexp.cc:33:
../../src/netmate/MetricData.h: In member function 'char* MetricData::align(char*, DataType_e)':
../../src/netmate/MetricData.h:101: error: cast from 'char*' to 'unsigned int' loses precision
testexp.cc: In function 'char* getErrorMsg(int)':
testexp.cc:121: warning: deprecated conversion from string constant to 'char*'
testexp.cc: In function 'char* getModuleInfo(int)':
testexp.cc:127: warning: deprecated conversion from string constant to 'char*'
make[3]: *** [testexp.lo] 错误 1
...

g++版本和操作系统分别是gcc版本 4.3.2 (Ubuntu 4.3.2-1ubuntu12)或gcc version 4.1.2 20071124 (Red Hat 4.1.2-42)。

这个问题可以分为两部分:

  1. error: cast from 'char*' to 'unsigned int' loses precision
  2. 头文件找不到的问题。

error: cast from 'char*' to 'unsigned int' loses precision是由于指针的长度造成的,在传统32位系统中,指针的长度是32为,与int的长度相同,但是在64位Unix系统中,指针的长度已是64位(LP64)。所以char指针转换为unsigned int会出现问题。相关代码如下:

    inline char *align(char *var, DataType_e type)
{
return (DataTypeSize[type] == 0) ? var :
(char*) (((unsigned int)(var) + DataTypeSize[type]-1) /
DataTypeSize[type] * DataTypeSize[type]);
}

将其中的unsigned int修改为unsigned long,编译通过。

而第二个问题,在ubuntu 8.10中无法解决,这是libstdc++的版本造成的,在centos5中,安装compat-libstdc++-33即可解决问题。

2008年12月10日星期三

“百姓”古今意义之别对阅读《道德经》造成的误解

中国与西方在思维、表达方式存在巨大的差异。西方从很早就形成了严密的形式逻辑体系(在欧儿里得几何学中)。而中国从历史上就没有形成这方面的体系,走的是另外一条道路,就是观象取意,在思维上叫做像思维,以《易经》为代表。这种表达方式不对事物下定义,而是从不同角度取象来对事物来进行描述,因此周易中讲“观物取象、象以尽义”。“象思维”大致可以分为三个层次:具象、意象、体悟之象。

作为古代经典之一的《道德经》也是采用这种描述,书中涉及治国、修身、谈兵、自然等很多的象,而几乎没有任何概念性的内容。采用这种描述方式的直接后果是,由于某些词在演变过程中发生了很大的变化,给后人造成了很大的误解,道德经中的“百姓”就是一例。下面我们来看这两段内容:

第五章

天地不仁,以万物为刍狗;圣人不仁,以百姓为刍狗。天地之间,其犹橐龠乎?虚而不屈,动而愈出。多闻数穷,不如守中。

第四十九章

圣人常无心,以百姓心为心。善者吾善之,不善者吾亦善之,得善。信者吾信之,不信者吾亦信之,得信。圣人在天下,歙歙焉,为天下浑其心,圣人皆孩之。


我们先说第五章的“天地不仁,以万物为刍狗;圣人不仁,以百姓为刍狗”,按照现在的理解,百姓当然指的是人民大众了,所以有一些注家在点评这段时认为,这是老子对天地和圣人的控诉,当然现在这种论点极少。而大部分注家认为这一句的意思是天地和圣人无私心,任万物和百姓自生自灭,这种观点与《道德经》全文的意思相近,所以是主流的。

其实,这也是有问题的。问题就在对于“百姓”的理解。在古代庶民无姓,有土有官爵者才有姓。所以“百姓”作贵族的通称,而战国之后才泛指平民。我们知道老子应该是春秋晚期的人,而且是周王室的史官,因此他不可能将“百姓”指代平民,在《道德经》中百姓只能是有官爵的人,或者是贵族。刍狗是古代祭祀时用草扎成的狗,在祭祀时披红挂绿,备受尊崇,而祭祀完成之后,被任意丢弃。因此,第五章这段话的意思应该是这样的:

天地没有任何偏爱,把万物当成刍狗,需要其繁盛时,就让其繁盛,需要其枯萎时,就使其枯萎。
圣人没有任何偏爱,把百官当成刍狗,适合就任用他,不适合就罢免他。


这不就是最佳的管理诀窍吗?选择合适的时机,任用合适的人选作合理的事情,而且官员能上能下。:)

同样,第四十九章中的意思也就好理解了:

圣人不固执己见,在做事之前,先听取百官的不同意见。

我们知道,老子是崇尚远古的,这段话其实反应了远古原始社会的一种民主方式。

2008年12月5日星期五

ElementTree如何输出XML的版本

默认的lxml.etree是用ascii编码,在输出的XML文件中没有,如果需要在输出的XML文件中包含,需要在tree.write()函数中设置参数。例如:

import lxml.etree as et
tree = et.parse('sample-ns.xml')
tree = tree.write('test.xml', encoding='utf-8', xml_declaration=True)

原始文件是:

<root xmlns='namespace'>
<element key='value'>text</element>
<element>text</element>tail
<empty-element/>
</root>


输出的文件是:

<?xml version='1.0' encoding='utf-8'?>
<root xmlns="namespace">
<element key="value">text</element>
<element>text</element>tail
<empty-element/>
</root>