推荐公众号:云淡纤尘
主要分享电子数据取证技术,坚持不易,还请大家多多支持前言
这一次取证其实不难,但是因为之前从来没有接触过K8S,所以对其的一些基本组成要件,运行逻辑等都没有概念。这一次取证从拿到题目开始,就是一边取证一边学习,耗时两个月也是终于成功将网站给搭建起来了,也取出数据库了。但其实对我来说最大的成就是搞懂了K8S的一些基本逻辑。同时这也是我第一次接触服务器集群的取证,我心里也大概对服务器集群取证的步骤有了大致的思路,不至于像一开始一点头绪都没有。同时,集群服务器的取证与单服务器取证最大的区别之一就是很难用取证软件一把梭梭出来,所以针对集群服务器的取证联系可以让自己不过度依赖取证软件,而是关注于个人能力的提升。这次的题目涉密,所以具体题目没有办法放出,读者朋友看一下我的取证思路,同时也请各位大佬如果有更好的取证思路欢迎交流,如果有错误也请及时指正。
关于Kubernetes(K8S)
Kubernetes(K8S)是一个开源的容器编排平台,由Google开发,现在由云原生计算基金会(CNCF)进行维护。它旨在自动化部署、扩展和管理容器化应用程序,是云技术的核心部分,也是构建云原生的基石。
1. 主要组件
主节点(Master):
kube-apiserver:集群的前端接口,提供RESTful API接口,用于管理集群状态、配置和操作。 etcd:分布式、一致性的键值存储数据库,用于存储集群的配置信息、状态和元数据。 kube-scheduler:调度组件,负责将新创建的Pod调度到集群中的节点上。 控制器管理器:包含多个控制器,负责监控集群的状态,并根据预设的期望状态来实现集群的自愈和自动化操作。 控制平面的核心部分,负责管理整个集群的状态和配置。
主要组件包括:
子节点(Node):
kubelet:负责管理节点上的Pod和容器,与主Master通信,并根据Master的指令创建、启动、监控和终止Pod。 kube-proxy:负责实现Kubernetes Service的负载均衡和代理转发,以及维护网络规则和iptables规则等。 Container Runtime:负责管理和运行容器,如Docker、containerd等。 Pod网络插件:负责为Pod分配IP地址,并提供Pod之间和Pod与外部网络之间的网络通信。 集群的工作节点,执行Master的操作指令。
主要组件包括:
2. 基本概念
Namespace
定义:Namespace是K8s中用于实现多套环境的资源隔离或多租户的资源隔离的一种机制。通过将集群内部的资源分配到不同的Namespace中,可以形成逻辑上的“组”,以方便不同的组的资源进行隔离使用和管理。
作用:
资源隔离:不同Namespace中的资源是相互隔离的,一个Namespace中的资源无法直接访问另一个Namespace中的资源。 多租户管理:可以将不同的Namespace交给不同的租户进行管理,实现多租户的资源隔离和权限控制。 环境隔离:可以将开发、测试、生产等不同环境的资源划分到不同的Namespace中,以便于管理和维护。
默认Namespace:
K8s在集群启动后会默认创建几个Namespace,如default、kube-node-lease、kube-public、kube-system等。 所有未指定Namespace的对象都会被分配在default命名空间中。
Pod
定义:Pod是K8s集群中进行管理的最小单元,是资源对象模型中由用户创建或部署的最小资源对象模型。程序要运行必须部署在容器中,而容器必须存在于Pod中。因此,Pod可以看作是容器的封装。
组成:
一个Pod中可以存在一个或多个容器。这些容器共享存储、网络、以及生命周期等。 每个Pod都有一个特殊的被称为“根容器”的Pause容器(也称为Infrastructure容器)。Pause容器负责维护整个Pod的网络空间,并对用户不可见。
特点:
共享资源:Pod中的容器可以共享存储卷和网络IP,实现文件共享和通信。 生命周期:Pod是一个相对短暂的组件。当Pod所在节点发生故障时,Pod会被调度到其他节点上重新创建,但这是一个全新的Pod实例,与之前的Pod没有关联。 单一职责:虽然一个Pod中可以包含多个容器,但通常建议每个Pod只运行一个主容器,以便于管理和维护。如果有多个紧密相关的容器需要一起运行,可以将它们放在同一个Pod中。
使用和管理:
Pod的创建和管理通常通过K8s的控制器(如Deployment、ReplicaSet等)来完成,而不是直接创建Pod。 控制器可以确保Pod的数量和状态符合预期,并在Pod出现故障时自动进行重启或重建。
开始取证
取证环境
简要介绍一下本次取证的基本环境:本次题目给了三个镜像,分别是一个master
节点,一个node1
节点,一个node2
节点。操作系统为CentOS Stream release 8
。三台机器均使用静态IP配置。网站框架为WordPress,数据库使用了mysql。
取证工具
火眼证据分析软件
火眼仿真取证软件
Finalshell
VMware Workstation Pro 17.0
Navicat Premium 16
取证过程
仿真
首先先将三个服务器镜像仿真起来,网络模式就选NAT。具体仿真过程中需要注意的问题我在前文有讲过,可翻看我第一篇文章
网络配置
仿真起来第一件事就是查看IP,但是这三台机器是没有办法输出IP的,如下图所示
所以此时我们需要查看网络配置文件,因为集群服务器一般而言都会采用静态IP然后使用同一个NAT出口来进行联网,所以我们要去检查配置文件。这个配置文件的路径需要记住:/etc/sysconfig/network-scripts
此处可以看到,嫌疑人是把服务器重新设置了一个ens160的网卡,并使用静态IP来进行服务器配置
划重点:
此时会有好多人会把IP改成自动分配或者换成自己的C段。但是注意这是一个服务器集群,如果在主机名的配置文件里,嫌疑人没有给IP使用主机名,或者在其他服务的配置文件里,没有使用主机名进行通信,那么我们把服务IP改动了会造成服务器无法正常运行,更不用说重构网站什么的了。所以这里我们需要改动我们自己电脑的虚拟网卡配置
我们进入VMware,在工具栏选中编辑,进入虚拟网络编辑器,可以看到图中这三个服务器的C段为160.所以我们把自己的虚拟网卡的子网IP的C段改成160,同时DHCP里也要同步改动,如图所示
同时,我们把三台服务器的网络配置文件里的两个ens160
改成ens33
,让其作为正常的ens33
网卡使用,如图所示
vim /etc/sysconfig/networl-scripts/ens160 && reboot
三台机器重复相同的操作之后就可以正常显示IP并且连接Finalshell了
重构
通信
首先我们要对K8S的基本结构有所了解
K8S
的运行逻辑与Docker
逻辑很类似,K8S将空间分为一个个命名空间namespaces
,然后命名空间内存在很多Pods
,这个Pods
和Docker
的容器概念是一样的
我们先查看一下这个集群各个节点能否通信,看一看各个节点的在线情况
kubectl get node
OK,各节点状态良好,可以通信
小坑
由于这个命令只能在master节点上使用,所以经常会忽略两个node节点的通信情况,当我们在node节点上运行这个命令的时候,可以看到输出
注意报错信息,http://localhost:8080/api
这说明,这个节点还是在“以自我为中心”,没有摆正自己的位置,他是一个work节点,应该是暴露自己的IP让master节点和其他work节点通信的
从报错可以看出,kubectl 没有使用到证书与k8s api通信,在初始化集群的时候要想使用kubectl需要,申明证书变量 export KUBECONFIG=/etc/kubernetes/admin.conf
,重启之后 KUBECONFIG
变量失效了。所有需要输出如下
echo "export KUBECONFIG=/etc/kubernetes/kubelet.conf" >> /etc/profile
source /etc/profile
查看Namespaces
和Pods
注意,查看Pods的时候要指定Namespaces,不然会默认输出defauts的pods
看到这里我们可以得到,我们需要重构的网站是一个wordpress框架的网站,同时有一个redis和一个不知道是什么数据库的镜像,但其命名为wordpressdb估计就是网站后台的数据库
也可以推测出来名为wordpress-v1-55cb456f68-vgxf2
的Pod就是网站源码的容器
我们可以查看一下这Pod的详细信息
wordpress-v1-55cb456f68-vgxf2
kubectl describe pod wordpress-v1-55cb456f68-27hj -n wordpress
输出如下
[root@master ~]# kubectl describe pod wordpress-v1-55cb456f68-vgxf2 -n wordpress
Name: wordpress-v1-55cb456f68-vgxf2
Namespace: wordpress
Priority: 0
Service Account: default
Node: node2/192.168.160.153
Start Time: Sun, 30 Jun 2024 12:47:23 +0800
Labels: app=wordpress
app.kubernetes.io/name=wordpress
app.kubernetes.io/version=v1
pod-template-hash=55cb456f68
version=v1
Annotations: cni.projectcalico.org/containerID: 0183fd2c4df531208566146323ffc18ae5cae201fc9159accd40b08ea08028cf
cni.projectcalico.org/podIP: 10.213.96.226/32
cni.projectcalico.org/podIPs: 10.213.96.226/32
kubesphere.io/creator: admin
kubesphere.io/imagepullsecrets: {}
kubesphere.io/restartedAt: 2024-06-30T04:39:29.238Z
logging.kubesphere.io/logsidecar-config: {}
sidecar.istio.io/inject: false
Status: Running
IP: 10.213.96.226
IPs:
IP: 10.213.96.226
Controlled By: ReplicaSet/wordpress-v1-55cb456f68
Containers:
container-arwmr6:
Container ID: containerd://949bab5c7fc4c79ee203e3d8d0e80c1f835b3355736d4a04f8d3611bca5201f1
Image: wordpress:4.8-apache
Image ID: docker.io/library/wordpress@sha256:6216f64ab88fc51d311e38c7f69ca3f9aaba621492b4f1fa93ddf63093768845
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Wed, 20 Nov 2024 20:02:21 +0800
Last State: Terminated
Reason: Unknown
Exit Code: 255
Started: Tue, 02 Jul 2024 20:22:58 +0800
Finished: Wed, 20 Nov 2024 19:20:45 +0800
Ready: True
Restart Count: 9
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-f974c (ro)
/var/www/html from volume-onlsh6 (rw)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
volume-onlsh6:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: wordpress-pvc
ReadOnly: false
kube-api-access-f974c:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SandboxChanged 143d kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 143d kubelet Container image "wordpress:4.8-apache" already present on machine
Normal Created 143d kubelet Created container container-arwmr6
Normal Started 143d kubelet Started container container-arwmr6
Normal SandboxChanged 143d kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 143d kubelet Container image "wordpress:4.8-apache" already present on machine
Normal Created 143d kubelet Created container container-arwmr6
Normal Started 143d kubelet Started container container-arwmr6
Normal SandboxChanged 141d (x2 over 141d) kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 141d kubelet Container image "wordpress:4.8-apache" already present on machine
Normal Created 141d kubelet Created container container-arwmr6
Normal Started 141d kubelet Started container container-arwmr6
Normal SandboxChanged 141d kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 141d kubelet Container image "wordpress:4.8-apache" already present on machine
Normal Created 141d kubelet Created container container-arwmr6
Normal Started 141d kubelet Started container container-arwmr6
Normal SandboxChanged 141d (x3 over 141d) kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 141d kubelet Container image "wordpress:4.8-apache" already present on machine
Normal Created 141d kubelet Created container container-arwmr6
Normal Started 141d kubelet Started container container-arwmr6
Normal SandboxChanged 141d kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 141d kubelet Container image "wordpress:4.8-apache" already present on machine
Normal Created 141d kubelet Created container container-arwmr6
Normal Started 141d kubelet Started container container-arwmr6
Warning NodeNotReady 27m node-controller Node is not ready
Normal SandboxChanged 26m (x2 over 26m) kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 26m kubelet Container image "wordpress:4.8-apache" already present on machine
Normal Created 26m kubelet Created container container-arwmr6
Normal Started 26m kubelet Started container container-arwmr6
可以看出来这个镜像的信息如下:
部署在node2(192.168.160.153)节点上
Pod内部通信IP是10.213.96.226
Pod端口是80
镜像sha256值是6216f64ab88fc51d311e38c7f69ca3f9aaba621492b4f1fa93ddf63093768845
wordpressdb-v1-0
kubectl describe pod wordpressdb-v1-0 -n wordpress
输出如下
[root@master ~]# kubectl describe pod wordpressdb-v1-0 -n wordpress
Name: wordpressdb-v1-0
Namespace: wordpress
Priority: 0
Service Account: default
Node: node1/192.168.160.152
Start Time: Sun, 30 Jun 2024 02:29:11 +0800
Labels: app=wordpressdb
app.kubernetes.io/name=wordpress
app.kubernetes.io/version=v1
controller-revision-hash=wordpressdb-v1-5ccd5b447d
statefulset.kubernetes.io/pod-name=wordpressdb-v1-0
version=v1
Annotations: cni.projectcalico.org/containerID: 11cc1ff5e31eda29899f9a2655a3c7364e63f5e1c7ead3c4f5fc57e4a02dbc56
cni.projectcalico.org/podIP: 10.213.90.152/32
cni.projectcalico.org/podIPs: 10.213.90.152/32
kubesphere.io/creator: admin
kubesphere.io/imagepullsecrets: {}
kubesphere.io/restartedAt: 2024-06-29T18:20:30.359Z
logging.kubesphere.io/logsidecar-config: {}
sidecar.istio.io/inject: false
Status: Running
IP: 10.213.90.152
IPs:
IP: 10.213.90.152
Controlled By: StatefulSet/wordpressdb-v1
Containers:
container-rdcw3a:
Container ID: containerd://b8a4887b46ef77e133400d7c6a0a3878633bd88a8c12d1eeb8b5c91d3454906f
Image: mysql:5.6
Image ID: docker.io/library/mysql@sha256:20575ecebe6216036d25dab5903808211f1e9ba63dc7825ac20cb975e34cfcae
Port: 3306/TCP
Host Port: 0/TCP
State: Running
Started: Wed, 20 Nov 2024 20:01:09 +0800
Last State: Terminated
Reason: Unknown
Exit Code: 255
Started: Tue, 02 Jul 2024 20:23:25 +0800
Finished: Wed, 20 Nov 2024 19:19:16 +0800
Ready: True
Restart Count: 10
Environment:
MYSQL_ROOT_PASSWORD: <set to the key 'MYSQL_ROOT_PASSWORD' in secret 'mysql-secret'> Optional: false
Mounts:
/var/lib/mysql from mysql (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-jhvxr (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
mysql:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: mysql-wordpressdb-v1-0
ReadOnly: false
kube-api-access-jhvxr:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SandboxChanged 143d kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 143d kubelet Container image "mysql:5.6" already present on machine
Normal Created 143d kubelet Created container container-rdcw3a
Normal Started 143d kubelet Started container container-rdcw3a
Normal SandboxChanged 143d kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 143d kubelet Container image "mysql:5.6" already present on machine
Normal Created 143d kubelet Created container container-rdcw3a
Normal Started 143d kubelet Started container container-rdcw3a
Normal SandboxChanged 141d (x2 over 141d) kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 141d kubelet Container image "mysql:5.6" already present on machine
Normal Created 141d kubelet Created container container-rdcw3a
Normal Started 141d kubelet Started container container-rdcw3a
Normal SandboxChanged 141d (x2 over 141d) kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 141d kubelet Container image "mysql:5.6" already present on machine
Normal Created 141d kubelet Created container container-rdcw3a
Normal Started 141d kubelet Started container container-rdcw3a
Normal SandboxChanged 141d kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 141d kubelet Container image "mysql:5.6" already present on machine
Normal Created 141d kubelet Created container container-rdcw3a
Normal Started 141d kubelet Started container container-rdcw3a
Normal SandboxChanged 141d kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 141d kubelet Container image "mysql:5.6" already present on machine
Normal Created 141d kubelet Created container container-rdcw3a
Normal Started 141d kubelet Started container container-rdcw3a
Normal SandboxChanged 31m (x2 over 31m) kubelet Pod sandbox changed, it will be killed and re-created.
Normal Pulled 31m kubelet Container image "mysql:5.6" already present on machine
Normal Created 31m kubelet Created container container-rdcw3a
Normal Started 31m kubelet Started container container-rdcw3a
可以看出来这个镜像的信息如下:
数据库类型是mysql
部署在node1(192.168.160.152)节点上
Pod内部通信IP是10.213.90.152
Pod端口是3306
镜像sha256值是20575ecebe6216036d25dab5903808211f1e9ba63dc7825ac20cb975e34cfcae
查看映射
需要注意的是,上面的Pod端口并不是实际端口,我们应该找到Pod映射到主机的端口
而K8S映射端口的方式大部分是Service
kubectl get service -o wide
这里可以发现,只有源码Pod以NodePort的方式映射到了主机31121端口,而MySQL的Pod并没有进行映射,所以我们要给他用Service创建一个映射
删除原来的Service
kubectl delete service wordpressdb -n wordpress
创建一个 wordpressdb-service.yaml
文件:
vim wordpressdb-service.yaml
apiVersion: v1
kind: Service
metadata:
name: wordpressdb
namespace: wordpress
spec:
selector:
app: wordpressdb
ports:
- protocol: TCP
port: 3306 # Service的端口
targetPort: 3306 # Pod的内部端口
type: NodePort # 也可以用 LoadBalancer
应用 YAML 文件:
kubectl apply -f wordpressdb-service.yaml
验证:
kubectl get service -n wordpress
成功,进行下一步
数据库处理
此时我们也成功让数据库暴露在外了,但是还有一点棘手的是没有数据库的用户密码。这里也是本次取证唯一用到了取证软件的地方(仿真除外)。在全部文件中检索包含mysql的yaml配置文件
最终在node2里找到了一个名为mysql.yaml.j2
的文件,记录了MYSQL_ROOT_PASSWORD
可以看到这个密码被保存进了这个容器的环境里
进入这个容器的命令行,输入
env
找到密码为my123654
此时有了数据库root用户的密码,但是因为root一般不会用于外部访问,所以我们需要在root用户下创建一个拥有所有权限的用户
进入数据库
mysql -u root -p
然后输入密码
先看一下都有什么用户(别忘了加分号)
select user,host from mysql.user;
可以看到果然root用户只能用于本地服务器访问
先刷新一下用户权限
flush privileges;
然后创建一个可以在外网登录的用户
create user 'lu'@'%' identified by '123456';
各参数含义
lu:账户(用root作为名字会出问题,最好用别的名字) %:允许内部和外部登录的权限 123456:密码
然后进行授权
grant all privileges on *.* to 'lu'@'%' identified by '123456';
再刷新权限
flush privileges;
用Navicat连接成功
网站登录
这是一个wordpress架构的网站
进去的时候需要重新配置数据库,按照之前获得的信息进行连接
踩坑
这里的数据库主机需要填Pod的通信IP,因为网站源码是一个Pod,MySQL数据库也是一个Pod,所以用外部IP是不好使的,需要Pod的IP
数据库用户密码的加密方式为PHPass,这是一个加盐加密的算法,如果要破解的话会很麻烦
这里讲一个取巧的方法,因为我自己的博客网站也是使用的Wordpress搭建的,而在上一步我们已经可以拿到网站的数据库。所以我可以直接把我自己的数据库里的密码把嫌疑人网站的密码替换为我的密码
Wordpress的用户密码存放在前缀_users
表里
这个是我的数据库
可以直接把user_pass字段的数据复制到嫌疑人网站数据库
这个是嫌疑人的后台数据库
登录
用原本的用户名和我替换之后的密码进行登录
成功
总结
我这一次从零开始学习K8S的取证,算是完成了我在服务器取证领域里面对服务器集群进行取证的入门和起步。虽然说我耗时了半个月,但其实琢磨清楚之后这一套从分析到重构的流程其实并不难。所以我会继续探索。同时感谢我的师傅哥赵哥在这段时间给我的帮助。另外赵哥的独家秘笈我是不会外传的,所以文章并未体现出哈哈哈哈。