Confine 拍了拍你,问要不要强化下你的 docker 容器

  • A+
所属分类:安全开发
作者 | 绿盟科技 NS-SRC 潘雨晨

Confine 拍了拍你,问要不要强化下你的 docker 容器

刚才那个拍了拍你的 Confine 是一个可为Docker镜像生成Seccomp配置文件的框架,它通过减小容器可能遭受的Linux内核攻击面来强化容器。总体目标是在保证容器及其内部应用正常运行的前提下,过滤掉容器不需要的所有系统调用(syscalls),以缓解来自内核的漏洞攻击。
那么问题来了, docker 已经有默认的 Seccomp 配置文件,为什么还需要用Confine 专门生成呢?据 Confine 开发者表示,他们对150个热门容器镜像进行了分析,通过使用Confine,其中大约一半的容器镜像被屏蔽了至少145+ syscalls,即使在最差的情况下,也屏蔽了100个以上的syscalls,而Docker默认的Seccomp filter只过滤了49个。光看数量,确实是迈出了一大步,不过过滤掉更多syscalls 所减轻的风险是否能够被量化?只取 150个镜像是否样本少了些?过滤了那么多syscalls 是否真的不会影响到容器及其内部应用?如果真不影响,他们又是如何实现筛选的呢?
带着这些疑问和好奇,咱们接下来就一起了解了解 Confine 吧,看最后能否说一句,真香。
本文将从三个方面入手,一是Confine 的使用实践、二是 Confine 有效性的验证和结论、三是设计思路和实现原理,咱们的疑问也将在其中逐个解开。
Tips
Q:什么是Seccomp,有什么作用,怎么用?
A:Seccomp 是 Secure computing mode 的缩写,它是Linux 内核在2.6.12版本(2005年3月8日)开始引入的一种安全机制,用于限制一个进程可以执行的系统调用,在/proc/${pid}/status文件中的Seccomp字段可以查看进程的Seccomp。当然,我们需要有一个配置文件来指明进程到底可以执行哪些系统调用,不可以执行哪些系统调用。那么在Docker中,就可以利用这个机制来限制容器中可以执行的系统调用。
Docker 官方文档中关于 Seccomp 的使用详见
https://docs.docker.com/engine/security/seccomp/。docker 默认使用的seccomp
配置文件可以在
https://github.com/moby/moby/blob/master/profiles/seccomp/default.json 查看。
在运行容器时,除非使用 --security-opt选项进行覆盖,否则都将使用默认配置文件。默认seccomp配置文件是白名单,白名单中指定了允许的调用。

一、使用实践

Confine下载链接:
https://github.com/shamedgh/confine。
本次实践环境如下表所示:
Confine 拍了拍你,问要不要强化下你的 docker 容器

1.1部署使用环境

项目文档中表示,Confine这个项目只在Ubuntu 18.04上测试过,它需要Linux内核v4.15。在其他操作系统上使用的话不能保证不出现问题。配套使用的python版本是3.7。执行下面命令安装环境需要的东西。
sudo apt updatesudo apt install -y python3.7sudo apt install -y sysdig
最后安装的 sysdig 据说是一款Linux超强的系统挖掘工具,相当于 strace +tcpdump + lsof + htop + iftop 以及其他工具的合集,它在Confine中也起到了重要作用,具体用处后面会提到。

1.2操作指南

1.2.1  主脚本confine.py

Confine 项目中所有文件如下图所示,其中的主脚本就是confine.py。

Confine 拍了拍你,问要不要强化下你的 docker 容器

总结来说,该文件使用镜像列表以及提前为musl-libc和glibc创建好的调用图,为每个镜像创建各自的Seccomp配置文件。至于 musl-libc和glibc的调用图,在3.5.1节会讲到,本节就只先关注它的使用。
下面是运行confine.py 时可附加的参数,这里有个印象就行

-l : 指定 glibc调用图

-m : 指定musl-libc调用图
-i : 包含镜像JSON信息的输入文件
-o : 存储从容器中提取的二进制文件和库的路径
-p : Docker默认seccomp配置文件的路径
-r : 存储结果的路径
-g : 特殊容器(例如golang容器)的路径
-d : 启用或禁用调试
--skip: [可选] 如果设置了这个选项,脚本将不会对之前运行的镜像进行分析,这些镜像的结果已插入到profile.results.csv 文件中
-f : [可选] glibc共享库
-n : [可选] musl-libc共享库
--finegrain : [可选]传递此参数可以生成细粒度策略。不建议使用此特性,因为它正在开发中。
--othercfgfolder : 如果您通过设置 --finegrain启用了细粒度分析,您也必须设置此选项。该文件夹应该包含细粒度分析中使用的其他库的调用图。
--allbinaries : 传递这个参数会导致提取所有二进制文件,而不是只提取30秒内运行的二进制文件。

1.2.2  操作1 - 使用Confine强化Docker容器

1、来到Confine 项目的目录下,创建一个新文件,文件名可以自拟,后缀名是 json,在本示例中使用 myimages.json。
2、在文件中写入以下内容:
{    "nginx": {        "enable": "true",        "image-name": "nginx",        "image-url": "nginx",        "dependencies": {}    }}
使用如下命令运行Confine,为Nginx生成Seccomp配置文件。
sudo python3.7 confine.py -l libc-callgraphs/glibc.callgraph -m libc-callgraphs/musllibc.callgraph -i myimages.json -o output/ -p default.seccomp.json -r results/ -g go.syscalls/
脚本运行后程序就开始分析Nginx Docker镜像了。接下来依照输出结果,介绍脚本执行中的每个步骤,大体流程可参考下图:
Confine 拍了拍你,问要不要强化下你的 docker 容器
a ) 脚本打印出下面内容,显示它已经开始分析了。
Confine 拍了拍你,问要不要强化下你的 docker 容器
b ) 然后,它将进入监控阶段,该阶段使用Sysdig来标识在容器中执行的所有二进制文件。如果是第一次执行这个操作,即之前没有提取过二进制文件和库的列表,它将首先打印出:
Confine 拍了拍你,问要不要强化下你的 docker 容器
然后它将通过运行sysdig来监视已执行的二进制文件,生成以下输出:
Confine 拍了拍你,问要不要强化下你的 docker 容器
c ) 在这一步中,脚本的执行输出结果可能会有所不同,这取决于之前是否成功提取了二进制文件。如果前一步动态分析中已经从容器中提取了二进制文件和库,它就不需要再次复制它们了。否则,它将首先生成容器中使用的二进制文件列表,然后开始复制它们。
Confine 拍了拍你,问要不要强化下你的 docker 容器
d ) 在提取出可执行文件之后,脚本就开始使用objdump提取其中所有直接系统调用(以下也将系统调用称为Syscalls)。它将检查从容器中复制到临时输出文件夹的所有文件,并识别其中直接的Syscalls。
Confine 拍了拍你,问要不要强化下你的 docker 容器
e ) 接着,脚本会分析每个二进制文件和库导入的函数列表。
Confine 拍了拍你,问要不要强化下你的 docker 容器
f ) 在提取出所有的直接Syscalls,并将导入的libc函数与这些libc函数所需的Syscalls统统整合在一起后,就生成了一组可被禁用的Syscalls列表,并打印如下行:
Confine 拍了拍你,问要不要强化下你的 docker 容器
g ) 现在,不必要的系统调用已经被提取出来,并生成相应的 Seccomp 配置文件了,接下来通过使用生成的Seccomp 配置文件启动容器来验证它是否可以正常工作。
Confine 拍了拍你,问要不要强化下你的 docker 容器
如果您看到形如:$$$ was hardened successfully! 的消息,这意味着Seccomp配置文件通过了我们的验证步骤。如果没有看到上面的信息,可以在 github 上寻求帮助。
h ) 最后,对Nginx Docker镜像的分析完成,打印如下:
Confine 拍了拍你,问要不要强化下你的 docker 容器
4、分析完成后,我们可以去查看为了正确运行nginx容器所需要的二进制文件和库了,它们存储在./output/nginx中:
Confine 拍了拍你,问要不要强化下你的 docker 容器
还可以查看到生成的 Seccomp配置文件,存储在./results/nginx.seccomp.json中,文件中部分内容如下:
Confine 拍了拍你,问要不要强化下你的 docker 容器
1.2.3  操作2 - 使用强化后的容器
在这部分实践中,我们将运行加固后的容器,看看在容器内的操作是否有受到了限制。
1、使用之前生成的Seccomp配置文件启动容器:
sudo docker run --name container-hardened --security-opt seccomp=results/nginx.seccomp.json -td nginx
2、获取这个容器的IP地址:
sudo docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container-hardened
ip 是 172.17.0.6
3、获取Nginx的默认索引页,看看是否能行得通?请用上一条命令保留的IP替换[IP-Address]
wget http://[IP-Address]cat index.html
4、结果输出如下,表示可以获取:
Confine 拍了拍你,问要不要强化下你的 docker 容器
5、尝试进入容器。
sudo docker exec -it container-hardened /bin/bash
执行后发现提示 exit:
Confine 拍了拍你,问要不要强化下你的 docker 容器
使用上面的命令无法进入容器,这是因为没有将/bin/bash标识为运行容器所必需的文件,并且它需要的Syscalls也不在提取的二进制文件所需的集合中。说明限制是有作用的,现在我们再用/bin/sh试试。
sudo docker exec -it container-hardened /bin/sh
能够进入容器了,执行下面这些命令也都ok。
Confine 拍了拍你,问要不要强化下你的 docker 容器

1.2.4  操作3 - 强化效果

1、来到 confine项目的主目录下,我们来看看哪些系统调用可以被过滤掉?它们总共有多少?
cat results/nginx.seccomp.json | grep namecat results/nginx.seccomp.json | grep name | wc -l
被过滤掉的Syscalls总共有 154 个,详细列表如下:
msync、mincore、shmctl、pause、getitimer、getpeernameforksemgetsemopsemctlmsggetmsgsndmsgrcvmsgctlflock、fdatasync、truncatesymlink、lchown、getrusage、ptrace、syslog、setreuid、setregid、getpgid、setfsuid、setfsgid、rt_sigpending、rt_sigqueueinfo、utime、mknod、uselib、personality、ustat、sysfs、getpriority、sched_rr_get_interval、munlock、mlockall、munlockall、vhangup、modify_ldt、pivot_root、adjtimex、chroot、sync、acct、settimeofday、swapon、swapoff、reboot、sethostname、setdomainname、iopl、init_module、delete_module、quotactl、readahead、tkill、set_thread_area、io_submit、io_cancel、get_thread_area、lookup_dcookie、remap_file_pages、restart_syscall、semtimedop、timer_create、timer_settime、timer_gettime、timer_getoverrun、timer_delete、clock_settime、clock_nanosleep、mbind、set_mempolicy、get_mempolicy、mq_open、mq_unlink、mq_timedsend、mq_timedreceive、mq_notify、mq_getsetattr、kexec_load、waitid、add_key、request_key、keyctl、ioprio_set、ioprio_get、inotify_init、inotify_add_watch、inotify_rm_watch、migrate_pages、mkdirat、mknodat、fchownat、futimesat、renameat、linkat、symlinkat、fchmodat、pselect6、ppoll、unshare、get_robust_list、tee、sync_file_range、vmsplice、move_pages、signalfd、timerfd_create、eventfd、fallocate、timerfd_settime、timerfd_gettime、signalfd4、epoll_create1、dup3、inotify_init1、preadv、rt_tgsigqueueinfo、perf_event_open、recvmmsg、fanotify_init、fanotify_mark、name_to_handle_at、open_by_handle_at、clock_adjtime、syncfs、sendmmsg、getcpu、process_vm_readv、process_vm_writev、kcmp、finit_module、sched_setattr、sched_getattr、renameat2、seccomp、memfd_create、kexec_file_load、bpf、execveat、userfaultfd、membarrier、mlock2、copy_file_range、preadv2、pwritev2、pkey_mprotect、pkey_alloc、pkey_free、statx
2、禁用了上述Syscalls后,要想确定哪些内核 CVE被缓解了的话,可以使用filterToCveProfile.py脚本将生成的Seccomp配置文件映射到被缓解了的CVE。
python3.7 filterProfileToCve.py -c cve.files/cveToStartNodes.csv.validated -f results/profile.report.details.csv -o results -v cve.files/cveToFile.json.type.csv --manualcvefile cve.files/cve.to.syscall.manual --manualtypefile cve.files/cve.to.vulntype.manual
-c:文件的路径,该文件包含每个CVE和所有可以到达内核调用图中脆弱点的起始节点之间的映射。
-f:这个文件是为一组容器运行了Confine之后生成的。它可以在存储库根目录的结果路径中找到。
-o:存储结果的文件前缀名称。
-v:一个CSV文件,包含CVEs和其对应漏洞类型的映射。
--manualcvefile:一些手动收集的cve,可以使用此选项指定。
--manualtypefile:包含人工识别的CVE与其各自漏洞类型对应关系的文件。
-d:启用/禁用调试模式,调试模式会打印更多的日志。
注意:用来生成内核函数和它们cve之间映射的脚本位于单独的仓库。使用的时候不需要重新创建这些结果。
执行上述命令后,将创建一个名为results.container.csv的文件。其中每一行都表示能够缓解的一个CVE。文件部分内容如下所示:
Confine 拍了拍你,问要不要强化下你的 docker 容器
这里以第一条为例:
  • Cveid,可缓解的 CVE 编号,值为 CVE-2015-8539;
  • 系统调用的名字(可以有多个),值为 add_key, keyctl;
  • CVE 的类型 (可以有多个),值为 Denial Of Service,,Gain privileges;
  • 是否可被docker默认的Seccomp策略缓解,值为 True;
  • 受影响 docker 镜像的个数,值为 1;
  • 受影响 docker 镜像的名字,值为 nginx;
3、查看results.container.csv中报告的所有已缓解的CVE。通过图中命令,还可以查看未被默认Docker Seccomp过滤器缓解,但现在被Confine缓解了的CVE子集。
Confine 拍了拍你,问要不要强化下你的 docker 容器

二、Confine的有效性验证

通过上面的实践,发现利用该工具不光可以生成Seccomp配置文件,还能查看被加固后的容器实际规避了的内核漏洞。不过它是否具有通用性还不是很清楚,毕竟每个镜像的情况各异。
那么本节就从开发者提供的论文中找找答案。原文请参考
https://www3.cs.stonybrook.edu/~sghavamnia/papers/confine.raid20.pdf。
为了方便,下文都将以第一人称的口吻进行叙述。

2.1公开容器的分析

2.1.1  数据集收集

Docker Hub提供了153个"official "容器镜像(这个数字统计于文章编写时),同时还有许多其他社区维护的镜像。为了这次评估,我们收集了一个数据集,包括:
i ) 153个官方维护的Docker镜像;
ii ) 前200个最受欢迎的社区维护镜像。
由于两个列表之间存在重叠,因此初始数据集由209个不重复的Docker镜像组成。
在这209个镜像中,有193个是免费提供的,可以在没有任何付费及许可要求的情况下使用,所以数据集进一步缩减至193。在这193个镜像中,有43个需要某种形式的手动设置,比如需要启动先决容器(如rocket-chat),或指定复杂的配置设置。由于运行这些镜像所需的手动步骤比较复杂,所以我们也将这些镜像排除在测试范围之外。最终的数据集包括150个Docker图像(122个official和28个unofficial),这些镜像可以由我们的系统自动处理。

2.1.2  容器统计

Docker Hub会根据每个官方镜像的拉取请求(下载)数量,为其分配一个流行度指标。我们通过官方Docker Hub API为153个官方镜像收集了这个数字(对于大多数非官方镜像,返回的值为零)。下图中(a)展示了153个官方镜像在下载数量上的流行度分布。可以看到,其中15%的Docker镜像占据了大部分的下载量,而其余大部分镜像则不那么受重视。这意味着,将更多的镜像加入到评估集中也不会增加数据集在容器流行度方面的重要性,那么也就没必要加入更多的镜像了。
我们会提取每个容器中执行的二进制文件列表,并检索它们的加载库。图中b、c图表分别展示了被测容器执行的二进制文件和加载库的数量分布。大约75%的容器执行的二进制文件少于16个,75%的容器需要的库少于47个。
Confine 拍了拍你,问要不要强化下你的 docker 容器
在3.5.3节中,我们讨论了如何处理C/C++以外语言编写的应用程序。为了深入了解这些语言的使用频率,我们统计了收集到的容器中所使用的编程语言。容器化应用中最常用的编程语言包括C/C++、Java和Go。如下表所示,许多常见的服务器应用程序(如Nginx、Apache Httpd和MySQL)都属于C/C++类别,其中有61%的容器。然而,还有22%的镜像中托管基于Java的应用程序,如Apache Cassandra(NoSQL数据库),Apache Solr(开源搜索引擎平台)和ApacheTomcat。由于Go被用于开发Docker生态系统中使用的许多管理工具,所以基于Go的容器也占了相当的数量(14%)。
我们根据容器中是否存在相应的语言运行时,将容器分为基于Java或基于Python/Perl的容器。由于大多数容器中包含小型实用工具,如sed、grep和find,这将导致容器属于C/C++类别,因此Confine只会在容器中没有运行任何其他编程语言编写的程序时,才会将其归入C/C++类别。例如,Cassandra[2]容器同时调用sed(一个C程序)和java应用程序。我们就不把这个容器归入C/C++和Java两个类别,而只把它视为下表结果中的Java容器。
Confine 拍了拍你,问要不要强化下你的 docker 容器

2.2过滤系统调用

我们使用150个Docker镜像集来评估我们的工具在筛选Syscalls方面的有效性。首先,Confine会自动分析每个容器(具体分析方法详见3.5.2节),分析提取二进制文件所需的系统调用列表。然后,它将生成一个Seccomp filter来禁用所有不需要的系统调用。最后,我们还会将容器与filter一起在Docker引擎上运行,以测试正确性。
我们通过度量每个容器过滤掉的Syscalls数量来评估我们方法的有效性。因为每个系统调用都是一些内核功能的入口点,所以禁用一个系统调用就相当于缓解了该内核功能相关代码中所有的漏洞(此外,还禁止了该系统调用成为恶意代码的一部分)。除了数量,还要关注质量,所以我们用已知缓解的CVE个数来量化攻击面减少的程度,并在2.3.2节中介绍最终结果。
下图显示了测试集里150个容器中被过滤的Syscalls个数的累计分布。对于大约一半的容器,Confine禁用了145+系统调用(Linux中目前可用326个系统调用)。即使在最坏的情况下(x轴的最左边),也有100多个系统调用被移除。这意味着与Docker默认的Seccomp filter禁用49个系统调用相比,至少禁用了两倍或更多。
Confine 拍了拍你,问要不要强化下你的 docker 容器
对于热度排名前15的Docker镜像,平均限制了148个系统调用。这些镜像包括Nginx,PostgreSQL,MySQL和MongoDB等。对于Nginx,我们观察到它正常操作需要160个系统调用(从326个中过滤掉了166个),而Wan等人[75]通过动态分析只识别出76个。完整的列表如表所示。
Confine 拍了拍你,问要不要强化下你的 docker 容器

2.2.1  可用性验证

为了确保生成的系统调用策略不会破坏任何正常功能,我们执行了额外的验证运行。
1、一般性验证
首先,我们检查容器是否能够用Seccomp配置文件正确地启动。除非我们把Docker框架本身需要的系统调用给过滤掉了,否则这一步都会成功。
Docker 镜像是用一个名为 Dockerfile 的容器文件来确定的。Dockerfile中的Entrypoint属性规定了容器在启动时必须调用的应用程序。如果这个应用程序退出(或崩溃),容器就会退出,经验证这种情况不会发生。
然而,即使应用程序仍然在运行,它也可能会遇到错误。例如,它可能会遇到被应用程序妥善处理了的异常,但这在正常操作中仍然会导致问题。为了捕获这些情况,我们检查容器生成的日志文件。Docker提供了一个简便的方式来读取由容器化应用程序生成的日志。我们对强化后的容器与默认容器生成的日志进行比较。由于日志中的值(如时间戳和进程id)可能在不同的执行过程中有所不同,因此我们忽略掉这些值。
2、深层验证
我们还通过使用基准测试套件(benchmarksuites)来做进一步的验证。对于这个验证工作,我们主要关注最流行的Docker镜像,因为需要手工操作。我们收集了一套适用于TOP-50 Docker镜像中10个镜像的基准测试工具。这些工具包括特定领域的基准测试工具、Selenium[24]脚本和CloudSuite benchmarks[60]。被测Docker镜像包括:MongoDB, PostgreSQL,MySQL, Redis, WordPress, PHP, Memcached, Nginx, Apache Httpd和MediaWiki。
CloudSuite[60]的web服务benchmark基于开源社交网站Elgg[10]。Elgg是一个基于php的应用程序,使用MySQL作为数据库。它还提供了一个运行在Nginx服务器上的流媒体服务器。最后,它还提供了Memcached的基准测试。我们为运行这些基准测试的容器生成了系统调用策略,并验证了基准测试能够成功运行。
对于MongoDB,我们使用mongo-perf[15],在一个线程上应用simple_insert和simple_query测试套件中的所有测试用例,持续10秒。对于PostgreSQL,我们使用pgbench[20],它首先创建一个新的数据库,然后运行测试用例。对于MySQL,我们使用sysbench工具[48],在初始化新数据库后,我们使用16个线程在10张表上运行OLTP读写测试。对于Redis,我们使用redis-benchmark[22],并在每个测试中改变请求的类型和数量、客户机数量和加载数据的大小,运行多个测试。
WordPress和Mediawiki运行在Apache Httpd上。我们运行了一个加固后的MySQL容器作为他们的数据库,并使用Selenium[24]通过自动用户交互来创建内容。我们使用了Azad等人发布的脚本[28]。通过这些脚本,执行了不同的操作,如创建用户、帖子、应用图像和修改它们。所有的操作都成功地进行了,通过日志或脚本输出也没有发现不正常。

2.2.2  可用性结果

基于上述过程,最终验证结果是150个加固容器中有146个运行成功,4个运行失败,原因如下。首先,由于存在大量依赖关系,Confine无法提取三个基于Go镜像的系统调用,对于 influxdb[12]和Chronograf[4]镜像,原因是Go调用图形工具无法应用于它们的源代码,Sematext[25]镜像是因为其源代码不可用。
其次,Java提供了通过Java原生接口直接访问操作系统接口的选项。因此我们需要分析每个程序的Java代码来提取JNI调用的系统调用。不过Elasticsearch[9]镜像是我们在Java数据集中看到的唯一使用此特性的示例,因此我们没有投入时间来实现针对它的分析。

2.3量化减小的攻击面

以往软件debloating工作[41,47,66]大多是将删除的代码量(以及ROP小工具的数量) 作为改进的主要度量。相比之下,我们的方法并没有删除任何代码,只是限制了恶意容器可以调用的Syscalls,即通过减少宿主机内核暴露给潜在恶意应用的系统调用数量来降低主机内核的攻击面。
为了证明我们的工具在减小攻击面方面的有效性,我们的出发点是Lin等人[54]之前研究中使用的漏洞[18]。这些漏洞都是可以利用的,即便使用了容器隔离机制,如命名空间、cgroups[3]和capabilities[1]。为了更好地了解过滤单个系统调用对缓解潜在内核漏洞的影响,我们将每个CVE映射到其对应的系统调用上。

2.3.1  将内核CVE映射到系统调用

为了执行分析,我们使用一个定制的自动化工具来抓取CVE网站[5]中的Linux内核漏洞。该工具会对Linux内核Git仓库中的每一次提交进行解析,以找到给定CVE对应的补丁,并检索被补丁修改的相关文件和函数。在将CVE映射到各自的函数后,我们建立了Linux内核调用图,并分析其中哪些部分可以被某个系统调用所独占。
我们使用KIRIN[78]构造了Linux内核的调用图。这允许我们映射内核中的哪些函数是从哪个Syscall调用的,从而推断出过滤掉一组系统调用时内核代码的哪一部分永远不会被调用。
我们发现,虽然只有少数的CVEs直接与被过滤掉的系统调用代码相关,但与许多CVEs 相关的文件和函数是被filtered 系统调用专门且唯一调用的。通过将CVEs与KIRIN创建的调用图相匹配,我们能够确定与给定容器系统调用集相关的所有漏洞。这为我们提供了一个攻击面减少的可量化属性。即,容器应用了Confine生成的系统调用限制策略后,能被缓解的 CVEs个数。

2.3.2  通过Confine缓解了的CVEs

结果汇总在下表中。所有Linux内核CVEs根据其对底层系统的影响会被分配一个类别。虽然提权的结果最严重,但其他类型的影响也是需要考虑的。使用拒绝服务攻击,攻击者有可能破坏所有运行在同一宿主机上的容器和应用程序。"绕过限制"类的漏洞,可能允许攻击者直接或间接绕过隔离机制实施攻击。同样,利用"获取信息"类别中的漏洞,可能会导致敏感内核数据的泄露,从而危及为容器提供的隔离保障。
根据我们的分析,除了Docker默认Seccomp策略缓解的25个CVE之外,通过应用生成的策略,所有研究容器中的51个CVE被有效地缓解了(即,攻击者无法触发这些漏洞)。这些CVEs包括可被用来对内核进行拒绝服务攻击的(CVE-2012-3375、CVE-2016-7911和CVE-2017-11176),进行提权攻击的(CVE-2017-5123、CVE- 016-7911和CVE-2015-7613),或者泄露敏感内核信息的 (CVE-2017-14954和CVE-2014-9903)。可以看出,在这51枚CVE中,前7个漏洞所在镜像个数都超过了130,不过这次被移除了。
Confine 拍了拍你,问要不要强化下你的 docker 容器

三、设计思路&实现细节

以上,Confine 的可用性得到了验证,减小的攻击面也可被量化了。那么它是如何实现的呢?本节就来详细介绍。(ps:下面内容较多,时间有限的情况下可以着重关注加粗字体的内容)

3.1需求来源

介于Kubernetes等编排器运行容器和管理容器的便利性,众多开发者和组织开始使用容器,因为它们提供了更低的成本和更高的灵活性。虚拟机会运行自己的操作系统(OS),而对于容器来说,多个租户可以在宿主机的同一个OS内核之上启动容器。这使得容器与虚拟机相比更加轻量级,从而可以在同一硬件上运行更多的实例。
然而,容器的性能提升是以弱于虚拟机的隔离为代价的。在同一主机上运行的容器之间的隔离完全是由底层操作系统内核在软件中强制执行的。因此,能够访问第三方宿主机上容器的攻击者可以利用内核漏洞来提升他们的权限,并完全攻陷主机(以及危害在其上运行的所有其他容器)。
容器环境中的可信计算基础基本上包含了整个内核,因此内核的所有入口点都有可能成为潜在攻击面的一部分。尽管使用了操作系统提供的严格软件隔离机制,如capabilities和namespaces,但恶意租户仍可以利用内核漏洞来绕过它们。例如,waitid系统调用中的一个漏洞就会允许恶意用户实施提权攻击,逃离容器以获得对宿主机的访问权限。
同时,Linux内核的代码库也在不断扩大,以支持新的功能、协议和硬件。这些年来,系统调用数量的增加表明了内核代码的 "膨胀"。Linux内核的第一个版本(1991年发布)只有126个系统调用,而4.15.0-76版本(2018年发布)支持326个系统调用。Kurmus等人[50]的研究表明,每一个新的内核函数都是访问整个内核代码中很大一部分的入口点,从而导致攻击面越来越大。
作为应对现代软件不断扩大代码库的一种措施,减少攻击面的技术开始受到重视。这些技术背后的主要思想是识别和删除(或缓解)那些虽然是程序的一部分,但1)完全无法访问(例如,共享库中的non-imported功能),或2)对特定的工作负载/配置不需要的代码。以前的大量工作在不同层面上应用了这个想法,包括从共享库中删除未使用的函数[56,58,66],甚至删除了所有不需要的库[47];根据应用需求来修改内核代码[50,80];或者限制容器的系统调用[8,68,69,75]。事实上,NIST容器安全指南[59]中的建议之一就是通过限制容器的功能来减少攻击面。
尽管这些方法的性质各不相同,但都面临着一个共同的挑战,那就是如何准确地识别,然后最大限度地删除那些可以安全删除的代码。基于静态代码分析的工具遵循一种比较保守的方式,为了保持兼容性,并不会删除所有实际上不需要的代码。另一些依靠动态分析和训练的方法[8,50,68,69,75,80],会使用真实工作负载来训练系统,并确定实际执行的代码,同时丢弃其余没用到的代码(即 "保留需要的东西")。对于给定的工作负载,这种方法可以最大限度地删除代码,但正如我们在 3.3节中所展示的那样,它并不能详尽地捕捉到不同工作负载可能需要的所有代码,更不用说那些很少执行的代码部分了,比如错误处理流程。
鉴于之前减小容器攻击面方面的努力大都集中在动态分析上,在Confine的设计中

[8,68,69,75],我们的目标是提供一个更通用、更实用的解决方案,以达到在不需要训练的情况下随时应用于任何容器的保护。

为此,我们提出了一种自动化技术,用于为任意容器生成限制性的系统调用策略,限制可能被滥用的底层内核暴露接口。通过静态代码分析,我们会检查容器化应用程序的所有执行路径及其所有依赖关系,并确定容器正确运行所需的系统调用超集。
我们工作的主要贡献包括: 
  • 提出了一种更通用的方法,用于自动生成任意容器的限制性Seccomp策略,而不必依赖于目标程序的源代码。
  • 对Linux内核CVE进行了彻底的分析,并将它们与内核代码里的功能建立了映射关系。我们确定了可以使用哪些系统调用来利用某个CVE,并以此映射为基础来评估我们方法的有效性。
  • 我们检查了200多个来自Docker Hub[7]中最流行的公开Docker镜像,并对它们的特点进行了分析。
  • 我们用上述镜像对系统进行了实验评估,并证明Confine在生成限制性系统调用策略方面的有效性,该策略使得之前披露的51个内核漏洞无法被利用。

3.2威胁场景

我们认为,本地攻击者可以完全访问在第三方宿主机上运行的容器。这种访问可能是合法授予的(例如,作为云服务的普通用户),也可能是由于破坏了在容器上运行的脆弱进程而获得的。潜在的受害者包括宿主机的操作系统内核,以及在其上运行的任何其他容器。不过我们只专注于防止攻击者逃离容器,所以防止在容器内运行的应用程序被利用并不是我们工作的重点。
Confine限制了攻击者可以调用的系统调用集。在准备利用漏洞的场景下,这意味着攻击者运行的漏洞利用代码(例如,shellcode或ROP有效载荷)或恶意程序的功能将受到更多限制,因为它们不能依赖于容器不需要的系统调用。更重要的是,通过禁止访问较少使用和较少测试的系统调用(这些系统调用的内核代码中很可能包含有导致提权的漏洞)[53],攻击者就无法通过触发这些漏洞来损害内核。

3.3静态分析的必要性

以前的相关工作[8,68,69,75]使用动态分析来推导出容器使用的系统调用列表。然而,动态分析并不健全,因此可能会错过一些执行路径。为了证明这个问题,我们手动分析了Nginx,发现只使用动态分析时遗漏了三个系统调用。在我们的评估中,我们使用的Nginx启用了缓存管理和自动索引功能。
Nginx会产生一个单独的cache-manager进程来处理缓存管理。当缓存满时,这个进程会使用unlink系统调用清除旧的缓存文件。动态分析Nginx可以捕捉到cache-manager进程的初始化,但很可能无法捕捉到删除旧缓存文件的过程,因此无法捕捉到unlink系统调用的使用。由于在程序的正常执行过程中,在其他任何地方都不会调用unlink系统调用,因此仅仅依靠动态分析会导致unlink被标记为未使用。此外,延长动态训练的持续时间并不能解决该问题,因为只有当缓存满了的时候才会触发对旧片段的删除。训练需要请求足够多的新片段来填满缓存。因此,要正确地设置训练过程来处理这种情况是比较困难的的。下图就展示了在训练过程中没有被发现的控制流的部分。
Confine 拍了拍你,问要不要强化下你的 docker 容器
Nginx中被动态分析遗漏的控制层示例
椭圆代表函数,而矩形代表基本块。虚线的分支和块在训练过程中没有被执行到,因此遗漏了部分系统调用。
另一个未能捕获系统调用的例子是在显示目录列表时使用lstat。除了这个功能之外,Nginx的其他部分都没有使用lstat。由于目录列表通常是由用户手动输入一个URL触发的,而不是通过网站上任何现有的URL触发,所以基于动态分析的方法不太可能捕捉到这个系统调用。
在另一种情况下,Nginx二进制文件可以在不删除客户端连接的情况下更新到新版本。系统调用getsockopt和getsockname用于将现有套接字连接移交给新进程,并且这两个调用在代码的其他地方没有使用,这也使得动态分析很难发现它们。
上述例子说明了动态分析和静态分析面临着脆弱性和过度逼近之间的权衡。单纯依靠动态分析,需要训练足够全面,才能够预测和捕捉到上述所有的特殊案例。相比之下,静态分析的结果是可以保证健全的,但可能包括某些工作负载从未调用的系统调用。由于我们的目标是寻求一个实用的通用解决方案,所以我们选择使用静态分析来捕捉应用所使用的Syscalls超集。

3.4设计

我们的目标是通过限制每个容器可用的Syscalls数量,来减少暴露给恶意租户的内核攻击面,这些系统调用有可能被用于恶意目的(有可能是作为利用代码的一部分,或是作为利用内核漏洞的通道)。为了达到这个目的,一旦容器镜像被用户完全配置,Confine就会对其进行 "强化",即在容器内只允许访问那些正常运行实际需要的系统调用。
要识别出容器正常执行中所必需的系统调用需要满足以下要求。
( 1 ) 识别可能在容器内运行的所有应用程序;
( 2 ) 识别每个应用程序导入的所有库函数;
( 3 ) 将库函数映射到系统调用;
( 4 ) 从应用程序和库中提取直接的系统调用。
下图是实现的整体思路,Confine目前支持运行在基于Linux的虚拟主机上的Docker容器,但类似的分析也可以针对其他容器环境和操作系统进行。
Confine 拍了拍你,问要不要强化下你的 docker 容器
Confine的系统调用提取过程概述
动态分析阶段,不需要执行任何应用程序特定的工作负载,其唯一目的是识别出在容器中运行的应用程序。然后对每个应用程序进行静态分析,识别它所使用的所有库函数,以及它所依赖的系统调用。

3.4.1  动态分析识别容器中运行的所有应用程序

虽然容器通常是专门用来运行单个应用程序或服务的,但它们通常会在执行主程序之前调用许多其他实用程序和支持程序。例如,默认的MongoDB Docker镜像[16]会调用以下支持程序来设置环境:bash、chown、find、id还有numactl。
我们必须识别出在容器的生命周期内可能运行的所有程序。Confine依靠有限时间的动态分析来捕获系统上创建的进程列表。分析工具Sysdig会记录自容器创建以来,在可定义的时间段内(默认情况下为 30 秒)启动的每一个应用程序,这个时间段足以捕获系统初始化,以及系统的 "稳定"状态。
我们的方法与前人的工作不同,之前的工作依赖于使用各种工作负载的动态训练来推导出系统调用列表[75]。而在我们的方法中,动态分析的目标只是识别要分析的二进制可执行文件集,然后再采用静态分析推导出这些程序所调用的Syscalls
上述动态分析是为了方便、自动化地对多个容器镜像进行批量分析。但是某些容器中可能会包含不是一开始就启动的应用程序,对于这类情况,Confine支持手动提供外部可执行文件列表,这些可执行文件也应该被包含在分析中。

3.4.2  静态分析

动态分析往往无法执行所有可能的代码路径,尤其是在训练期间无法获得全面工作负载的情况下。所有为了确保完整的代码路径覆盖,一旦我们有了在容器上执行的应用程序列表,我们就会进行静态分析,以提取正确执行每个应用程序所需的所有Syscalls。
1、Libc 
libc是Stantard C Library的简称,它是符合ANSI C标准的一个标准函数库。libc库提供C语言中所使用的宏,类型的定义,字符串操作符,数学计算函数以及输入输出函数等。
用户程序通常通过libc库调用系统调用,libc库提供了相应的封装函数(例如,libc函数read调用系统调用SYS_read)。Confine分析libc的源代码,得出了导出函数与其调用的Syscalls之间的映射关系。
一个libc函数可能有多个控制流路径通往实际的系统调用。因此,为了正确识别一个给定的libc函数调用了哪些系统调用,我们需要分析这些路径。为此,Confine静态地分析了libc的源代码,得出了其完整的调用图,并准确地将每个函数映射到其各自的系统调用。
函数指针在libc中应用非常广泛,但是,精确的指针分析(Points-to analysis)在可扩展性和性能上存在明显的问题[29,42]。所以为了避免执行Points-toanalysis,我们采用了一种更保守的方法,即保留了所有通过任何函数调用的系统调用。
有了libc函数和系统调用之间的精确映射,就可以直接分析每个程序(主可执行文件和库)了,识别程序中所有导入的libc函数,从而推导出程序可能调用的所有系统调用合集。需要强调的是,这个过程在每个libc版本中只执行一次,即推导出的映射会被保存并在所有容器中使用。
2、直接系统调用
除了使用libc包装器,应用程序和库也可以直接使用syscall()函数,或者使用syscall汇编指令来调用Syscalls。虽然使用这种方法的应用程序和库的数量有限,但为了完整起见,我们使用二进制代码反汇编来提取所有直接的系统调用。我们在3.5.2节详细描述了这个过程。一些用C/C++以外的语言开发的应用程序也需要特殊的考虑,我们在第3.5.3节中讨论。

3.4.3  强化容器镜像

一旦生成了容器运行所需的所有Syscalls列表,我们就可以对容器镜像进行加固了。Docker容器支持使用Seccomp filters来限制从容器中可访问到的系统调用。用户可以使用自定义规则集来启动容器,规则集规定了容器可以访问的系统调用。这个规则集可以是黑名单形式,也可以是白名单形式。对于Confine,我们使用一个黑名单列表,列出了容器内不允许调用的系统调用。
根据3.5.1和3.5.2节中执行的分析,我们最终使用一个自动脚本来推导出被禁止的系统调用列表,并构建相应的Seccomp 配置文件。如果在这个过程之后,有任何新的应用程序需要在容器上执行,管理员必须再次执行脚本来更新Seccomp 配置文件。

3.5实现

3.5.1  将Libc函数映射到系统调用

为了确保正确性,需要一个精确的函数调用图来识别和限定未使用的系统调用。根据我们对Docker Hub[7]中200多个流行Docker镜像的分析,我们发现尽管大多数容器使用流行的glibc库作为其主要的用户空间libc库,但也有12个使用到了musl-libc[17]。虽然musl-libc和glibc都提供了C标准库函数的实现,应用程序应该能够互换使用,但我们发现标准libc函数使用的系统调用有时在musl-libc和glibc之间存在差异。
为了最大限度地提高兼容性,我们对这两个库分别进行了分析,提取出它们的调用图,以及相应函数到系统调用的映射。此外,由于glibc和musl-libc之间的某些差异,我们不得不使用不同的工具链来分析这两个库,接下来将讨论这些差异。

3.5.1.1 Musl-Libc

Musl-libc[17]是一个轻量级的C标准库,与glibc相比,它的代码库更小。在我们的分析中,我们用LLVM编译器[14]工具链编译了musl-libc,并实现了一个LLVM pass来提取完整的调用图。这个pass对代码的中间表示(IR)进行操作,并记录每个函数调用。为了识别系统调用,除了记录每个函数调用外,我们还特别记录了对syscall函数的调用。利用提取的调用图,我们为musl-libc中每个导出的函数和它所调用的系统调用之间建立了映射。我们修改了编译器工具链,能够实现在任何优化之前都调用pass,以防止由于优化和代码转换造成的精度损失。
Musl-libc使用weak_alias宏来确定函数的弱符号(weaksymbol)。弱符号可以被具有相同名称的强符号覆盖,而不会出现名称重复错误。我们的LLVM pass也会跟踪这些别名。
强符号与弱符号
在C语言中,函数和初始化的全局变量(包括显示初始化为0)是强符号,未初始化的全局变量是弱符号。其规则如下:
同名的强符号只能定义一次,否则编译错误。
同名的强弱符号同时存在时,以强符号为准。
多个弱符号相同存在时,则从多个弱符号中任选一个。

3.5.1.2 Glibc

Glibc是大多数容器中最流行的libc实现。Glibc严重依赖于多个GCC[11]特性,而这些特性在LLVM中没有实现。由于这个原因,我们选择了基于GCC RTL(Register Translation Language,寄存器翻译语言)的中间表示法,实现了从glibc中提取调用图和系统调用信息的第二个pass。调用图提取的实现是基于Egypt[38]工具,该工具在GCC的RTL IR上运行。我们发现,glibc调用Syscalls主要有三种机制。
1、通过内联汇编和汇编文件进行系统调用
这是调用Syscalls最直接的机制。像 accept4()这样负责接收传入的套接字连接的函数,包含了使用 x86-64 syscall 指令的内联调用。给定源代码,Egypt工具可以为任何给定的应用程序或库构建函数调用图。我们增强了Egypt,以迭代RTL IR中的每一条调用指令,并记录任何原生的x86-64 syscall指令。同样,汇编文件也包含syscall指令。因此,我们分析汇编文件并提取所有的syscall指令。
2、系统调用包装器宏
除了直接使用syscall指令,glibc还使用宏扩展来生成系统调用的包装器。其他glibc例程使用这些包装器来调用系统调用。由于这些包装器是作为依赖于体系结构(在我们的例子中是x86-64)的宏来实现的,因此不能通过分析RTL IR来检索它们。此外,这些宏的参数是在编译时由bash脚本提供的。
syscall-template.S文件包含了宏T_PSEUDO、 T_PSEUDO_NOERRNO和T_PSEUDO_ERRVAL,这些宏为系统调用提供了封装。要生成的系统调用列表以及其他信息,如符号名和参数个数,都在syscalls.list文件中提供。Bash脚本make-syscalls.sh在编译时读取这个文件,生成正确的宏定义,并调用syscall-template.S中的宏的扩展,这个脚本作为glibc构建过程的一部分被调用。在编译glibc的过程中,我们跟踪这个脚本的执行,并记录在执行过程中观察到的相关宏定义。利用这些宏和宏定义,我们推导出这些封装器和它们各自的系统调用之间的映射。
3、弱符号和版本符号
与 musl- libc 类似,glibc 使用 weak_alias 宏为函数定义弱符号。GCC支持符号版本化(Symbol Versioning),glibc使用这个特性来支持glibc的多个版本。带版本号的符号是使用宏version_symbol去定义的。weak_alias 和 versioned_symbol 都提供了函数的别名。glibc中的其他函数,以及使用glibc的应用程序,可以通过原始函数名或其别名来调用这些别名函数。我们分析C源代码来提取这些别名,并将它们添加到调用图中。

3.5.2  二进制文件分析

为了捕捉所有被调用的可执行文件,我们利用Sysdig[26]来监控容器启动最初30秒(这个时间可配置)内的execve调用。在我们生成容器运行的程序列表后,我们进一步进行静态分析,提取正常执行容器所需的系统调用列表。

3.5.2.1 通过Libc进行的系统调用

在提取出二进制文件的列表后,我们递归查找被它们加载的所有其他库(除了libc),然后使用objdump提取所有主可执行文件和库中导入函数的超集。这个分析给我们提供了一个应用程序及其库导入的libc(glibc或musl-libc)函数列表。然后,使用第3.5.1节中描述的libc-to-syscall映射,我们得到应用程序所需的系统调用列表。除此以外,Docker框架本身也需要某些系统调用来运行。因此,在推导出容器内所有程序所需的系统调用后,我们将它们与Docker默认需要的系统调用列表结合起来。

3.5.2.2 直接系统调用

我们还遇到了一些库和应用程序,它们直接通过libc syscall()接口或原生syscall汇编指令调用系统调用,分析这样的调用需要推导出传递给系统调用的参数值。幸运的是,提取第一个参数,即系统调用号是很简单的,因为它通常由syscall指令或syscall()函数调用之前的(少数)指令设置。因此,我们使用二进制代码反汇编,通过提取分配给syscall指令的RAX/EAX寄存器和syscall()函数的RDI/EDI寄存器的值来识别系统调用号。

3.5.2.3 动态加载函数库

一个需要特别考虑的问题是动态加载,通过这种机制,应用程序可以在整个执行过程中按需加载模块。dlopen()、dlsym() 和dlclose() API函数分别用来加载一个库、检索其符号、关闭它。因为这些操作是在运行时执行的,所以任何以这种方式加载的库都不能通过查看应用程序的ELF二进制头来识别。例如,Apache Httpd使用此特性根据用户自定义的配置加载库。Quach和Prakash在[65]中指出,在他们的数据集中分析的3174个程序和4292个库中,只有大约3%的程序和2%的库使用了这些特性,所有这些程序在初始化时都加载了所需的库。
为了识别这种动态加载的库,我们通过/proc虚拟文件系统监视应用程序在运行时加载的库列表,该系统为每个进程提供这些信息。
这里需要考虑的一个问题是,应用程序是否会动态加载libc,如果会,那么我们就无法识别由应用程序导入的各个函数,那么就必须保留libc发起的所有系统调用。但是,libc不太可能以这种方式加载,因为动态加载是用于为应用程序提供额外的功能模块。我们在实验中还没有遇到过这样的情况。

3.5.3  编程语言方面的考虑因素

不同的语言有不同的软件堆栈,因此提取系统调用策略的分析技术也不同。容器化应用程序的编程语言对用于识别特定应用程序所需的系统调用的分析方法有重要影响。在本节中,我们将描述用于处理除C/C++以外编程语言编写的应用程序的不同技术,这些技术是我们在研究top 200 Docker镜像时遇到的。
1、GO
用Go语言编写的应用程序由命令包和实用的非主包组成。Go应用程序可以使用两种构建模式编译成可执行文件,即:默认模式和c-shared模式。当使用默认构建模式编译时,所有的主包都被构建成可执行文件,所有的非主包都被构建成一个静态的.archive,与可执行文件静态链接。Go应用程序使用Go syscall和运行时包提供的系统调用包装器来调用Syscalls。
用c-shared 模式编译时,主包依赖于标准libc库来调用系统调用包装器函数。
对我们数据集的分析表明,所研究的容器中大部分Go应用都是使用默认构建模式构建的,因此,与依赖glibc的C/C++应用不同,这些应用使用Go的核心包syscall和runtime进行系统调用。因此,对于包含Go应用程序的容器,我们需要所运行的Go应用程序的源代码来识别它们的系统调用。然后使用callgraph工具[19]来构建Go应用程序及其所有依赖关系的调用图。
2、Java/NodeJS
Java和NodeJS运行时应用程序都使用libc作为共享库来调用系统调用。Java编译器将Java源代码(.java文件)编译成Java字节码文件(.class文件),并使用JVM来解释执行字节码。Java程序不会被直接编译成机器代码,因此不会生成二进制。JVM由通过execve系统调用启动的java二进制文件提供。为了找到Java类型应用容器的系统调用,除了分析所有正在运行的二进制文件外,我们还分析了包含JVM的java二进制文件,以及所有其他动态加载的库。对于NodeJS运行时调用的Syscalls,我们也同样处理。
3、纯粹的解释型语言
脚本语言,如Python和Perl l等脚本语言,是纯解释语言,需要各自的解释器来运行。我们使用与其他二进制文件相同的方法提取这些类型容器所需的系统调用。

3.5.4  Seccomp配置文件生成

我们通过将所有未出现在所需系统调用列表中的系统调用分类为 " not-permitted ",并将其分配到拒绝列表中,从而自动生成Seccomp策略。不过Docker Seccomp规则集要求提供被过滤的(filtered)系统调用的名称,而我们对容器的分析结果只会生成系统调用号。所以我们通过使用procfs伪文件系统中与系统调用名称相关的符号信息,将内核中所有可用的系统调用名称映射到它们各自的编号上。基于sys/syscall.h头文件,我们将系统调用名称映射到它的编号上,并利用它将禁止的系统调用号码转换为它们的名字。最终用这些系统调用拒绝列表创建Seccomp配置文件,并将其应用到容器中。
Docker使用JSON文件来定义被允许的Syscalls。下图清单中展示了一个规则集示例,它只禁止pwrite64系统调用。这个规则集的默认操作是允许所有的系统调用,除了那些在syscalls标签下定义的会被禁用。每个系统调用都由三项来定义:名称、动作和参数。
Confine 拍了拍你,问要不要强化下你的 docker 容器

3.6讨论与局限

如上表所示,通过我们的技术过滤掉的Syscalls并不是很常用,但它减少了大量已公开内核漏洞的攻击面。我们必须强调,尽管系统调用(如execve和mmap)被用作用户空间利用的一部分,但任何与内核CVE相关的系统调用都可以用于攻击内核。对于试图从容器中逃逸的攻击者来说,利用常用的系统调用(如execve或mmap)不会比利用较少使用的系统调用(如waitid)有更多好处。
除了从脚本和命令行启动应用程序外,大多数编程语言还为程序员提供了使用特殊库调用(如execve)启动应用程序的能力。我们的方法可能无法分析以这种方式启动的可执行文件。目前,开发人员需要提供使用此类库调用执行的二进制文件列表。我们为用户提供了一个初始的应用程序列表,以便在此基础上进行构建,这可以减少所需的手动操作。
一个更好的选择是静态分析所有被调用的应用程序的源代码来识别进程。这对于用解释语言编写的应用程序来说很容易做到,因为通常有许多静态分析工具支持(例如,PHP的php-ast[62],或者Python的内置AST[21]功能)。我们在Wordpress Docker镜像上执行了php-ast,并验证了提取二进制文件路径的正确性,这些二进制文件可以传递给任何类似exec的函数(例如,php_exec ,shell_exec)。这可以很容易地扩展到用不同语言编写的应用程序。我们将这种能力的完整实现作为我们未来工作的一部分。
虽然不推荐,但一些Docker镜像确实在使用cron来运行容器中的定时任务。在这种情况下,我们希望用户提供通过cron执行的程序列表,尽管这种情况也可以通过解析crontab文件来自动处理。

3.7相关工作

在沙盒和基于主机的入侵检测领域,用于推导系统调用策略的静态源码分析已经是一种被广泛使用的方法了[33-35,43,49,61,67,74]。我们的工作主要属于软件debloating领域,因此我们在此背景下讨论相关工作。

3.7.1  Confine 类似的工具

Wan等人[75]使用动态分析来为容器上正在运行的应用程序生成相应的Seccomp过滤器。DockerSlim[8]是一个开源工具,它也依靠动态分析来生成Seccomp 配置文件,并从docker镜像中删除不必要的文件。正如在第3.3节中讨论的那样,仅通过动态分析无法可靠地提取出所有需要的系统调用,尤其是对于处理异常和错误的情况,它们通常不属于常见的执行路径。因此,动态分析不能保证完全覆盖每个应用所需的所有Syscalls,而Confine则提供了一种更全面的静态分析机制。
Speaker[52]将所需的系统调用分为两个主要阶段,即启动和运行时。它动态地提取每个阶段所需的系统调用,并根据每个状态的必要性进行筛选。Cimplifier[68]利用动态分析将运行多个应用程序的容器分割成多个单用途容器。Rastogi等人[69]提出通过符号执行对Cimplifier[68]进行改进。
之前的这些工作也是从软件保护机制和漏洞的角度来关注容器安全。Lin等人[54]提供了一个安全漏洞和利用的数据集,这些漏洞和利用有可能绕过Linux内核提供的软件隔离。他们的建议之一是对容器使用更严格的Seccomp策略。Shu等人[71]创建了一个框架,用于对DockerHub上发现的镜像上进行漏洞扫描。Combe等人[31]通过假定攻击者可以完全访问主机上一个容器的模型,探讨了使用容器的安全影响。

3.7.2  应用Debloating

之前在debloating领域的大部分工作都集中在从单个进程中删除不必要的代码。Mulliner和Neugschwandtner[58]提出了最早库专业化的一种方法,该方法会在加载时识别并删除所有非导入的库函数。Quach等人[66]开发了一种经过修改的加载器和编译器,在编译时删除通过调用依赖和函数边界识别提取的不必要函数来执行共享库的特殊化。Agadakos等人[27]也进行了类似的库特殊化工作,但只是在二进制层面。BlankIt[63]只在请求时加载库函数,在进程地址空间中只保留程序和该时刻需要的部分库。Song等人[72]使用数据依赖分析展示了静态链接库在细粒度库定制上的潜力。Shredder[56]和Saffire[57]将传递给关键系统API函数的参数限制为仅从每个应用程序的代码中提取的合法值。Qian等人[64]使用训练和启发式方法来识别可以从二进制中删除的基本块,而Ghaffarinia和Hamlen[36]则限制了二进制的控制范围,而不是使用类似的训练方法删除额外的代码。
Sysfilter[32]利用二进制分析来识别给定应用所需的系统调用集,并通过Egalito[76]执行的二进制重写来限制对它们的访问。Confine主要依赖于通过源代码分析生成的libc调用图,而Sysfilter依赖于二进制,在识别所需系统调用集时可能会造成精度损失和过度逼近。时间性系统调用特殊化[37]根据服务器应用的执行阶段来禁用系统调用,使得许多安全关键的系统调用(如execve)在应用完成初始化阶段后就被禁用了。

3.8小结

实现过程中的具体步骤,以及验证评估结果在证明Confine 具备实用性和有效性的同时,也提到了目前的局限性。不过局限性也可以当作是未来进一步精进的方向,
在容器安全被越来越多关注的情况下,这种细粒度系统调用策略生成工具的出现,还是很有效的为容器提供了一分安全!

参考链接

[1]Capabilities(7) - Linux Programmer’s Manual.http:

//man7.org/linux/man-pages/man7/capabiliti

es.7.html.

[2]Cassandra - Docker Hub. https://hub.docker.com/_/cassandra

[3]Cgroups(7) - Linux Programmer’s Manual.http://man7.org/linux/man-pages/man7/cgroups.7.html.

[4]Chronograf - Docker Hub. https://hub.docker.com/_/chronograf

[5]Common vulnerabilities and exposuresdatabase.

https://www.cvedetails.com.

[6]CVE-2017-5123. https://www.cvedetails.com/cve/CVE-2017-5123/.

[7]Docker Hub. https://hub.docker.com.

[8]DockerSlim. https://dockersl.im.

[9]Elasticsearch - Docker Hub.https://hub.docker.com/_/elasticsearch.

[10]Elgg. https://elgg.org/.

[11]GNU Compiler Collection.https://gcc.gnu.org.

[12]Influxdb - Docker Hub.https://hub.docker.com/_/influxdb.

[13]Kubernetes - Production-Grade ContainerOrchestration.https://kubernetes.io.

[14]The LLVM compiler infrastructure.http://llvm.org.

[15]Mongo-perf. https://github.com/mongodb/mongo-perf.

[16]MongoDB - Docker Hub.https://hub.docker.com/_/mongo/.

[17]Musl Libc. https://www.musl-libc.org.

[18]Namespaces(7) - Linux Programmer’s Manual.

http://man7.org/linux/man-pages/man7/namespaces.7.html.

[19]Package Callgraph - GoDoc.https://godoc.org/golang.org/x/tools/go/callgraph.

[20] Pgbench. https://www.postgresql.org/docs/10/pgbench.html.

[21]Python AST. https://docs.python.org/3/library/ast.html.

[22]Redis-benchmark. https://redis.io/topics/benchmarks.

[23]Seccomp BPF (SECure COMPuting withfilters).

https://www.kernel.org/doc/html/v4.16/userspace-api/seccomp_filter.html.

[24]Selenium. https://selenium.dev/.

[25]Sematext Agent Monitoring and Logging -Docker Hub.

https://hub.docker.com/_/sematext-agent-monitoring-and-logging.

[26] Sysdig. https://github.com/draios/sysdig.

[27]Ioannis Agadakos, Di Jin, David Williams-King,

Vasileios P Kemerlis, and Georgios Portokalidis.Nibbler: debloating binary shared libraries. In Proceedings of the 35th AnnualComputer Security Applications Conference (ACSAC), pages 70–83, 2019.

[28]Babak Amin Azad, Pierre Laperdrix, and NickNikiforakis. Less is more: Quantifying the security benefits of debloating webapplications. In Proceedings of the 28th USENIX Security Symposium, 2019.

[29]Lars Ole Andersen. Program analysis andspecialization for the C programming language. PhD thesis, University ofCophenhagen, 1994.

[30]Brandon Butler. Which is cheaper:Containers or virtual machines? https://www.networkworld.com/article/3126069/which-is-cheaper-containers-orvirtual-machines.html, September 2016.

[31]Theo Combe, Antony Martin, and Roberto DiPietro. To Docker or not to Docker: A security perspective. IEEE CloudComputing, 3(5):54–62, 2016.

[32]Nicholas DeMarinis, Kent Williams-King, DiJin, Rodrigo Fonseca, and Vasileios P. Kemerlis. Sysfilter: Automated systemcall filtering for commodity software.

In Proceedings of the International Conferenceon Research in Attacks, Intrusions, and Defenses (RAID), 2020.

[33]Henry Hanping Feng, Jonathon T Giffin, YongHuang, Somesh Jha, Wenke Lee, and Barton P Miller. Formalizing sensitivity instatic analysis for intrusion detection. In Proceedings of the IEEE Symposiumon Security & Privacy (S&P), pages 194–208, 2004.

[34]Stephanie Forrest, Steven A Hofmeyr, AnilSomayaji, and Thomas A Longstaff. A sense of self for Unix processes. In Proceedingsof the IEEE Symposium on Security & Privacy (S&P), pages 120–128, 1996.

[35]Tal Garfinkel, Ben Pfaff, and MendelRosenblum. Ostia: A delegating architecture for secure system callinterposition. In Proceedings of the Network and Distributed System SecuritySymposium (NDSS), 2004.

[36]Masoud Ghaffarinia and Kevin W. Hamlen.Binary control-flow trimming. In Proceedings of the 26th ACM Conference onComputer and Communications Security

(CCS), 2019.

[37]Seyedhamed Ghavamnia, Tapti Palit, ShacheeMishra, and Michalis Polychronakis. Temporal system call specialization forattack surface reduction. In Proceedings of the 29th USENIX Security Symposium,2020.

[38]Andreas Gustafsson. Egypt. https://www.gson.org/egypt/egypt.html.

[39] Ashish Gehani Hashim Sharif, MuhammadAbubakar and Fareed Zaffar. Trimmer: Application specialization for codedebloating. In Proceedings of the 33rd ACM/IEEE InternationalConference on Automated Software Engineering (ASE), 2018.

[40]Haifeng He, Saumya K Debray, and Gregory RAndrews. The revenge of the overlay: automatic compaction of OS kernel code viaon-demand code loading. In Proceedings

of the 7th ACM & IEEE internationalconference on Embedded software, pages 75–83, 2007.

[41]Kihong Heo, Woosuk Lee, PardisPashakhanloo, and Mayur Naik. Effective program debloating via reinforcementlearning. In Proceedings of the 24th ACM Conference on Computer andCommunications Security (CCS), 2018.

[42]Michael Hind. Pointer analysis: Haven’t wesolved this

problem yet? In Proceedings of the ACMSIGPLANSIGSOFT Workshop on Program Analysis for Software Tools and Engineering(PASTE), pages 54–61, 2001.

[43]Kapil Jain and R Sekar. User-levelinfrastructure for system call interposition: A platform for intrusiondetection and confinement. In Proceedings of the Network and

Distributed System Security Symposium (NDSS),2000.

[44]Yufei Jiang, Can Zhang, Dinghao Wu, andPeng Liu. Feature-based software customization: Preliminary analysis, formalization,and methods. In Proceedings

of the 17th IEEE International Symposium on HighAssurance Systems Engineering (HASE), 2016.

[45]Vasileios P. Kemerlis. Protecting CommodityOperating Systems through Strong Kernel Isolation. PhD thesis, ColumbiaUniversity, 2015.

[46]Vasileios P. Kemerlis, MichalisPolychronakis, and Angelos D. Keromytis. ret2dir: Rethinking kernel isolation.In Proceedings of the 23rd USENIX Security Symposium, pages 957–972, 2014.

[47]Hyungjoon Koo, Seyedhamed Ghavamnia, andMichalis Polychronakis. Configuration-driven software debloating. InProceedings of the 12th European Workshop on Systems Security, 2019.

[48] Alexey Kopytov. Sysbench.https://github.com/akopytov/sysbench.

[49]Christopher Kruegel, Engin Kirda, DarrenMutz,William Robertson, and Giovanni Vigna. Automating mimicry attacks usingstatic binary analysis. In Proceedings of

the USENIX Security Symposium, 2005.

[50]Anil Kurmus, Reinhard Tartler, DanielaDorneanu, Bernhard Heinloth, Valentin Rothberg, Andreas Ruprecht, WolfgangSchroder-Preikschat, Daniel Lohmann, and

Rudiger Kapitza. Attack surface metrics andautomated compile-time OS kernel tailoring. In Proceedings of the Network andDistributed System Security Symposium (NDSS), 2013.

[51]Chi-Tai Lee, Jim-Min Lin, Zeng-Wei Hong,and Wei-Tsong Lee. An application-oriented Linux kernel customization forembedded systems. J. Inf. Sci. Eng.,

20(6):1093–1107, 2004.

[52]Lingguang Lei, Jianhua Sun, Kun Sun, ChrisShenefiel, Rui Ma, Yuewu Wang, and Qi Li. SPEAKER: Split-phase execution ofapplication containers. In Proceedings of the 12th Conference on Detection of Intrusionsand Malware, and Vulnerability Assessment

(DIMVA), pages 230–251, 2017.

[53]Yiwen Li, Brendan Dolan-Gavitt, Sam Weber,and Justin Cappos. Lock-in-pop: Securing privileged operating system kernels bykeeping on the beaten path. In Proceedings of the USENIX Annual Technical Conference(ATC), 2017.

[54]Xin Lin, Lingguang Lei, Yuewu Wang, JiwuJing, Kun Sun, and Quan Zhou. A measurement study on Linux container security:Attacks and countermeasures. In

Proceedings of the 34th Annual Computer SecurityApplications Conference (ACSAC), pages 418–429, 2018.

[55]Steven McCanne and Van Jacobson. The BSDpacket filter: A new architecture for user-level packet capture. In Proceedingsof the USENIX Winter Conference, 1993.

[56]Shachee Mishra and Michalis Polychronakis.Shredder: Breaking Exploits through API Specialization. In Proceedings of the34th Annual Computer Security

Applications Conference (ACSAC), 2018.

[57]Shachee Mishra and Michalis Polychronakis.Saffire: Context-sensitive function specialization against code reuse attacks.In Proceedings of the 5th IEEE European

Symposium on Security and Privacy (EuroS&P),2020.

[58]Collin Mulliner and MatthiasNeugschwandtner. Breaking payloads with runtime code stripping and imagefreezing, 2015. Black Hat USA.

[59]Karen Scarfone Murugiah Souppaya, JohnMorello. Application Container Security Guide, 2017. https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-190.pdf.

[60]Tapti Palit, Yongming Shen, and MichaelFerdman. Demystifying cloud benchmarking. In Proceedings of the InternationalSymposium on Performance Analysis of Systems and Software (ISPASS), pages122–132, 2016.

[61]Chetan Parampalli, R Sekar, and RobJohnson. A practical mimicry attack against powerful system-call monitors. InProceedings of the ACM Symposium on Information, Computer and CommunicationsSecurity (ASIACCS), pages 156–167, 2008.

[62]Nikita Popov. PHP abstract syntax tree. https://github.com/nikic/php-ast.

[63]Chris Porter, Girish Mururu, PrithayanBarua, and Santosh Pande. Blankit library debloating: Getting what you wantinstead of cutting what you don’t. In Proceedings of the 41st ACM SIGPLANConference on Programming Language Design and Implementation

(PLDI), pages 164–180, 2020.

[64]Chenxiong Qian, Hong Hu, Mansour Alharthi,Pak Ho

Chung, Taesoo Kim, and Wenke Lee. RAZOR: Aframework for post-deployment software debloating. In Proceedings of the 28thUSENIX Security Symposium, 2019.

[65]Anh Quach and Aravind Prakash. Bloatfactors and binary specialization. In Proceedings of the 3rd ACM Workshop onForming an Ecosystem Around Software

Transformation (FEAST), pages 31–38, 2019.

[66]Anh Quach, Aravind Prakash, and Lok Yan.Debloating software through piece-wise compilation and loading. In Proceedingsof the 27th USENIX Security Symposium,

pages 869–886, 2018.

[67]Mohan Rajagopalan, Matti Hiltunen, TrevorJim, and Richard Schlichting. Authenticated system calls. In Proceedings of theInternational Conference on Dependable Systems and Networks (DSN), pages358–367, 2005.

[68]Vaibhav Rastogi, Drew Davidson, Lorenzo DeCarli, Somesh Jha, and Patrick D. McDaniel. Cimplifier: automatically debloatingcontainers. In Proceedings of the 11th Joint Meeting on Foundations of SoftwareEngineering (ESEC/FSE), 2017.

[69]Vaibhav Rastogi, Chaitra Niddodi, SibinMohan, and Somesh Jha. New directions for container debloating. In Proceedingsof the 2nd Workshop on Forming an Ecosystem Around Software Transformation(FEAST), pages 51–56, 2017.

[70]Daniel Shapira. Escaping Docker containerusing waitid() – CVE-2017-5123, 2017. https://www.twistlock.com/labs-blog/escaping-doc

ker-container-using-waitid-cve-2017-5123/.

[71]Rui Shu, Xiaohui Gu, and William Enck. Astudy of security vulnerabilities on Docker Hub. In Proceedings of the 7th ACMConference on Data and Application Security and Privacy (CODASPY), pages269–280, 2017.

[72]Linhai Song and Xinyu Xing. Fine-grainedlibrary customization. In Proceedings of the 1st ECOOP International Workshopon Software Debloating and

Delayering (SALAD), 2018.

[73]Kanchi Gopinath Suparna Bhattacharya andMangala Gowri Nanda. Combining concern input with program analysis for bloatdetection. In Proceedings of the ACM SIGPLAN International Conference on ObjectOriented Programming Systems Languages & Applications (OOPSLA), 2013.

[74]David Wagner and Drew Dean. Intrusiondetection via static analysis. In Proceedings of the IEEE Symposium on Security& Privacy, pages 156–168, 2001.

[75]Zhiyuan Wan, David Lo, Xin Xia, Liang Cai,and Shanping Li. Mining Sandboxes for Linux Containers. In Proceedings of the10th IEEE International Conference

on Software Testing, Verification and Validation(ICST), pages 92–102, 2017.

[76]David Williams-King, Hidenori Kobayashi,Kent Williams-King, Graham Patterson, Frank Spano, Yu Jian Wu, Junfeng Yang,and Vasileios P Kemerlis. Egalito: Layout-agnostic binary recompilation. InProceedings of the 25th International Conference on Architectural Support forProgramming Languages and Operating Systems (ASPLOS), pages 133–147, 2020.

[77]Dinghao Wu Yufei Jiang and Peng Liu. Jred:Program customization and bloatware mitigation based on static analysis. InProceedings of the 40th Annual Computer Software and Applications Conference(ACSAC), 2016.

[78]Tong Zhang, Wenbo Shen, Dongyoon Lee,Changhee Jung, Ahmed M. Azab, and Ruowen Wang. PeX: A permission check analysisframework for linux kernel. In Proceedings of the 28th USENIX SecuritySymposium, pages 1205–1220, 2019.

[79]Zhi Zhang, Yueqiang Cheng, Surya Nepal,Dongxi Liu, Qingni Shen, and Fethi Rabhi. KASR: A reliable and practical approachto attack surface reduction of commodity OS kernels. In Proceedings of the InternationalConference on Research in Attacks,

Intrusions, and Defenses (RAID), pages 691–710,2018.

[80]Xiangyu Zhang Zhongshu Gu, BrendanSaltaformaggio and Dongyan Xu. Face-change: Application-driven dynamic kernelview switching in a virtual machine. In

Proceedings of the 44th IEEE/IFIP InternationalConference on Dependable Systems and Networks (DSN), 2014.



原文来源:关键基础设施安全应急响应中心

Confine 拍了拍你,问要不要强化下你的 docker 容器

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: