传统的IT安全行业比较注重用户行为监控,在linux上如何做行为监控呢?首先用户行为可以分作两大类:本地行为和网络行为,本地行为就是对文件的打开、读写等,而网络行为主要是浏览网页,发送邮件,下载文件等,这些行为又是如何监控的呢?
在linux系统中分为用户空间和内核空间,完成用户任务的主要是多种多样的程序,属于用户空间的程序,不同的程序运行起来叫进程,进程都有属于自己的地址空间,不同的进程是不能随便互相访问的,除非通过系统提供的接口将地址空间暴露出来,例如mmap,简单说进程是隔离的,所以如果单纯的监控进程很难监控到其他进程的行为。所以基本是有两种方法来达到监控的目的:
1.通过/proc和/sys提供了很多有用的接口,j监控进程可以通过这些接口提取信息,一般来说这类监控程序的行为模式主要是周期性扫描,通过这些接口来获取进程的信息,一般来说会有很多的滞后性和遗漏
2.在内核中搞,用户空间的程序访问文件,网络等这些资源都必须通过内核,进程本身没有权限操作硬件,需要借助内核。其次内核有最高优先级,能够访问所有进程的数据。所以大部分的安全厂商通常借助于动态加载内核模块,也就是常说的hook。这部分的工作在主流开发人员中一直不被接受,通常都是out-tree中单独自己维护的,对于维护众多内核版本的功能需要大量的开发人员,工作量非常之大.

不过毕竟监控的市场需求不小,所以有一部分用来做监控的功能合并到了内核主线中,这就是notify子系统,该子系统家族中中主要有三弟兄dnotify,inotify,fanotify,我不是非常了解这个历史,就不献丑了。

下面主要是简单分析一下fanotify在监控中能够扮演什么角色?他能做什么,不能做什么?目前的边界在哪里?大概的实现及其原理。里面会有和inotify的一些简单比较

fanotify能做什么

fanotify主要有下面两种用法,一种是基本的文件系统事件通知,一种是对文件系统通知事件做出回应,允许还是不允许。

  • 1.文件系统事件通知

notify的基本功能,当一个文件被打开,关闭,读写等操作时会产生事件,监控程序可以通过注册的方式告诉系统自己感兴趣的文件和事件,之后文件操作会触发系统通知,系统将这些事件上报上来,监控程序可以读取到这些事件,知道哪些文件被进行了什么样的操作.它的基本事件列表如下:

Fanotify 定义

含义

FAN_ACCESS

File was accessed

FAN_MODIFY

File was modified

FAN_CLOSE_WRITE

Writtable file closed

FAN_CLOSE_NOWRITE

Unwrittable file closed

FAN_OPEN

File was opened

FAN_OPEN_PERM

File open in perm check

FAN_ACCESS_PERM

File accessed in perm check

  • 2.权限访问控制功能

相对于inotify而言,这是增强的部分功能.当文件被打开的时候,monitor监控到了事件并且还能进行用户层的控制,这个是用户层控制的一种典型模型。在用户层可以有很复杂的策略决策,主要是因为如果出现bug的话也不会影响整个系统,最多是监控进程本身挂掉和被监控的某些程序异常。相对的在内核中做策略决策,如果出现bug可能会引起整个系统的崩溃,所有人都不能接受自己的机器异常死机。这种模型唯一的缺点就是拉低了原始应用的的速度,毕竟中间需要事件上报-进程切换出去-事件决策-结果下发-进程切换回来整个的一个过程,对于服务器来说可能这样的模型对于高吞吐来说很难接受,毕竟谁都不想自己花了服务器的钱却只能跑出来PC的结果.

fanotify使用和实现

1.fanotify在内核中的对象还是比较简单的,主要有面向监控进程的fsnotify_group,面向被监控对象文件的fsnotify_mark,事件对象fsnotify_event.下面一幅图摘出来了一些他的主要对象之间的关系,然后简单介绍一下注册和事件发生时如何上来的.

程序监控警告 监控程序行为_监控程序


初始化:

当我们通过fsnotify_init创建一个监控对象时,会创建一个fsnotify_group对象.

通过fsnotify_mark添加一个文件路径和对应的权限时,会找到对应的inode,创建fsnotify_mark对象,分别挂到inode的fsnotify_mark_connector上的hash链表上和fsnotify_group的marks_list上,将inode和fsnotify_group关联起来,这样当inode发生事件时可以知道应该通知谁.当有多个fsnotify_mark挂到同一个文件上时,即多个monitor进程关心同一个文件时,会根据fsnotify_init的优先级(FAN_CLASS_PRE_CONTENT>FAN_CLASS_CONTENT>FAN_CLASS_NOTIF)在插入时对其排序,相同优先级按照添加的先后顺序,后加入的反而在前面.fsnotify_mark支持两种事件类别:单个的inode(包含文件和目录)和mount点,当使用FAN_MARK_MOUNT时,当前路径的挂载点下面所有文件的操作都会上报上来.这两种的通知顺序是先inodemount,即先具体后抽象.

mark完之后那就需要监听了,fsnotify支持poll模式,所以可以配合epoll/select一起,然后会在notification_waitq睡眠,等待事件通知.

事件触发:

当文件事件触发,会遍历inode的fsnotify_mark对象,找到对应的fsnotify_group对象.事件类型包括两种:单纯的监听型(FAN_OPEN_PERM,FAN_ACCESS_PERM之外的其他所有类型)和控制型事件(FAN_OPEN_PERM,FAN_ACCESS_PERM),对于事件的处理有细微的不同.

单纯的监听型:内核会构建一个fsnotify_event对象并且挂到notification_list上,并且唤醒notification_waitq上的进程,告诉它来读事件,事件读完就销毁了.

控制型:内核同样会构建一个fsnotify_event对象,挂到notification_list上,并且唤醒notification_waitq上的进程,告诉它来读事件,事件读完没有销毁而是从notification_list上挪到了access_list,等待monitor进程response:ALLOW,DENY.当monitor进程下发response之后会找到access_list上的对象,进而唤醒access_waitq上对应的进程.

除了上面通常的过程,它还支持ignore mask,即monitor进程告诉添加某些文件的某些事件不再关心,在内核中通过fsnotify_mark对象的mask树型来保存.假如一个文件文件如果没有被修改只是被频繁的打开,每次都需要到monitor进程中决策一下,速度下降太多,所以给他增加了一个内核的cache机制,避免系统调用和进程切换的开销(个人感觉没啥用).
当文件第一次被打开,需要上报给monitor应用,monitor可以使用FAN_MARK_IGNORED_MASK标记后续的事件都不要上报了,使用这次的决策结果就好了.修改该文件时,fanotify会自动清除它的ignore mask标志位,然后对它进行正常的访问控制.如果不想被清除,它还提供了FAN_MARK_IGNORED_SURV_MODIFY标记,当文件被修改也不会清除ignore mask,直到文件被关闭.

如何确定自己感兴趣的event
刚开始使用的时候我们不知道应用怎么操作文件的,通常需要strace一下分析应用的行为模式.但是我们现在只关心事件类型.举个例子我们现在关心系统用户的添加和删除,用户信息会存放在/etc/passwd中,我们想通过监控这个文件来看什么时候增加和删除了用户,然后我们不知道到底是哪一个事件发生的时候我们可以扫描它,最笨的方法就是挨个尝试或者监控所有的事件.不过有个工具可以帮助我们减少很多的编码工作直接确定我们到底应该监控什么事件:inotify 首先我们可以通过inotifywait -m /xxx/yyyy打印出所有的事件,之后我们可以通过inotifywait -m -e open,delete /xxx/yyyy来逐渐缩小范围直至找到一个最小事件集合.在ubuntu通过安装inotify-tools就可以使用它了,这个通常是调试事件的利器.不过很可惜它报的是inotify的事件,你可以通过下面这张表来找到inotify和fsnotify的映射关系,建议还是仔细阅读一下man fanotify_mark:

文件系统事件

Inotify

Fanotify

ACCESS

Y

Y

MODIFY Y

Y

CLOSE_WRITE

Y

Y

CLOSE_NOWRITE

Y

Y

OPEN

Y

Y

fanotify的边界

下面是它的man中关于它自身限制的描述:
1.它只能抓取应用通过系统调用产生的事件,而无法感知到网络文件系统的变化,他也无法感知到proc,sys节点的事件,和inotify的一样的限制
2.当应用采用mmap来访问文件的时候,它无法感知到是否被修改,毕竟文件只mmap了一次,后续的读写不再经过系统调用,和inotify的一样的限制
3.如果监控目标是文件夹,文件夹下面的open,read,close会产生相应的事件,但是当它被mark之后,在它下面的增加,删除,修改并不会产生事件
4.它不能监控一个目录下所有的文件和文件夹,需要用户手动递归地添加文件监控,和inotify的一样的限制
5.如果事件非常多的话,一方面是消耗的内存就没有了限制,另一个是monitor是不是可以在短时间内处理完这些事件.基于这个原因设置了一个事件队列最大值,当队列满了话,直接丢弃.

工程中的一些实践:

  • 1.我们无法监控某些伪文件系统内容的变化,我曾经想通过监控proc下目录的增删来感知应用进程的创建和删除,但是proc创建并不经过fanotify过程,这个想法是实现不了的.不过我们可以监控哪些访问了/proc
  • 2.第二个:mmap这种方式是notify无能为力的地方,我们无法监控它是否进行了读写操作,对于审计和控制而言这就是漏洞了,这个不封闭的审计也是不完全可信的,那也就没有了意义.
  • 3.第三个:一个目录树下的监控需要用户手动递归添加监控,这个真心不方便.
  • 4.第四个:有可能利用它的缓存机制来逃避审计和控制

fanotify_mask中FAN_MARK_IGNORED_MASK可以添加ignore mask,常用的模式是第一次打开的时候检查哪个进程访问了文件,假如该进程是合法的,你想要忽略下面的文件访问,告诉内核忽略这个事件,但是下面的文件打开事件也会被忽略,这个就可以逃避审计。假如不忽略,每次都会检查,这个cache就完全无效了。所以说这个cache设计的本意是好的,但是让使用者如何使用呢?

改进:应该添加更加精确的ignore mask,例如忽略某个进程的该事件,而不是所有进程访问该文件的事件

参考

https://www.ibm.com/developerworks/cn/linux/l-cn-fanotify/index.htmlman fanotify