深入解析Kubernetes安全配置项allowPrivilegeEscalation

本文详细剖析了Kubernetes中allowPrivilegeEscalation安全选项的真实作用,通过代码示例演示其防御机制,并澄清了关于该配置项的常见误解,帮助开发者正确理解和使用这一安全特性。

停止对’allowPrivilegeEscalation’的过度担忧

Kubernetes安全上下文允许在Pod或容器级别配置安全选项。虽然某些参数已被广泛理解,但其他参数可能更加晦涩难懂。本文将揭晓allowPrivilegeEscalation选项的真相。

TL;DR——allowPrivilegeEscalation是一项安全加固选项,仅此而已。如果您能轻松地将其关闭作为快速安全措施,请务必这样做!否则,它本身并不会导致系统被入侵。如果您没有显式禁用它,通常也不会有什么问题。

什么是’allowPrivilegeEscalation’?

询问任何安全工程师是否应该允许应用程序"提升权限",您可能会得到茫然的目光、困惑的表情,甚至是对您理智的质疑。

好消息!您和安全工程师至少有一个共同点:你们都不清楚allowPrivilegeEscalation标志的真正含义——说实话,这能怪谁呢?

关于’allowPrivilegeEscalation’的常见误解

开门见山:虽然关闭allowPrivilegeEscalation有一定价值,但它只是一个安全加固设置。特别是,如果您将其保持为true(默认值):

  • 它不会神奇地允许容器中的非特权进程将其权限提升至root
  • 它不会允许容器内运行的进程逃逸出容器
  • 它不会允许Pod在集群内执行任何形式的权限提升

‘allowPrivilegeEscalation’实战演示

让我们重现一个场景:漏洞允许非特权进程在容器内将其权限提升至root。这可能会发生在DirtyCow、DirtyPipe或OverlayFS中的CVE-2023-0386等内核级漏洞中。我们也可以测试一个更简单(但同样现实)的场景:滥用设置了setuid位的root所属二进制文件。

我们使用以下程序,它使用setreuid(即"设置真实和有效用户ID")和setregid有效地将权限提升至root。根据设计,这仅在二进制文件由root拥有并设置了setuid位时才有效:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void) {
    // 提升至root权限
    setreuid(0, 0); 
    setregid(0, 0);

    // 生成shell
    char* const argv[] = {"/bin/bash", NULL};
    char* const environ[] = {NULL};
    execve("/bin/bash", argv, environ);
}

编译并设置权限:

1
2
3
gcc escalate.c -Wall -o /tmp/escalate
sudo chown root:root /tmp/escalate
sudo chmod +s /tmp/escalate

以下Dockerfile模拟了一个Alpine容器镜像,其中应用程序以非特权用户身份运行,并包含易受攻击的二进制文件:

 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
30
31
32
33
FROM alpine:3.20 AS builder
WORKDIR /build
RUN cat > escalate.c <<EOF
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>

int main(void) {
    // 提升至root权限
    setreuid(0, 0); 
    setregid(0, 0);

    // 生成shell
    char* const argv[] = {"/bin/bash", NULL};
    char* const environ[] = {"PATH=/bin:/sbin:/usr/bin:/usr/sbin", NULL};
    if (-1 == execve("/bin/bash", argv, environ)) {
        printf("Unable to execve /bin/bash, errno %d\n", errno);
    }
}
EOF
RUN cat /build/escalate.c
RUN apk add --no-cache gcc musl-dev
RUN gcc escalate.c -Wall -o escalate

FROM alpine:3.20 AS runner
WORKDIR /app
COPY --from=builder /build/escalate ./escalate
RUN chown root:root ./escalate && chmod +s ./escalate
RUN adduser app-user --uid 1000 --system --disabled-password --no-create-home
RUN apk add bash
USER app-user
ENTRYPOINT ["sh", "-c", "echo Application running && sleep infinity"]

构建镜像并在Kubernetes集群中运行,显式开启allowPrivilegeEscalation(尽管这是默认值):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 构建镜像
docker build . -t my-app:0.1

# 创建kind集群并运行镜像
kind create cluster
kind load docker-image my-app:0.1

kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
  containers:
  - name: my-app
    image: my-app:0.1
    securityContext:
      allowPrivilegeEscalation: true
EOF

如预期那样,我们能够利用漏洞将权限提升至root。但如果我们将allowPrivilegeEscalation设置为false启动Pod,操作将会失败。

‘allowPrivilegeEscalation’工作原理

根据Kubernetes文档:

AllowPrivilegeEscalation控制进程是否能获得比其父进程更多的权限。此布尔值直接控制是否会在容器进程上设置no_new_privs标志。

no_new_privs标志是内核3.5版本(2012年发布)引入的特性。启用后,它确保没有子进程能获得比其父进程更多的权限。

我们可以通过一个小工具程序手动设置no_new_privs来确认这一行为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/prctl.h>

int main(void) {
    // 设置no_new_privs
    if (-1 == prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        printf("Could not set prctl: %s\n", strerror(errno));
    }

    // 生成shell
    char* const argv[] = {"/bin/sh", NULL};
    char* const environ[] = {"PATH=/bin:/sbin:/usr/bin:/usr/sbin", NULL};
    if (-1 == execve("/bin/sh", argv, environ)) {
        printf("Unable to execve /bin/sh, errno %d\n", errno);
    }
}

结论

安全——就像大多数试图处理系统性故障的学科一样,是关于构建不同层次以确保单个缺陷不会演变成数据泄露。

在这个背景下:是的,显式关闭allowPrivilegeEscalation是一个合理的安全加固实践。关闭它可以大大增加攻击者在入侵非特权应用程序后无法将其权限提升至容器内root的信心,从而降低利用需要root权限的其他漏洞的风险。

如果您没有在工作负载上关闭它,会很糟糕吗?可能不会。将其视为您尚未启用的(又一个)加固机制。这不是导致您被入侵的原因。除非您是一个成熟的安全团队,否则您最好首先关注容器安全路线图中更有价值的项目。

也就是说,这不是一个您应该忽略的设置;确保它成为您容器安全路线图的一部分。

常见问题解答

allowPrivilegeEscalation的默认值是什么? 默认为true。

如果我的工作负载在容器内以root身份运行,关闭allowPrivilegeEscalation有意义吗? 没有意义。如果工作负载以root身份运行,它们在容器内无法实现进一步的权限提升。

allowPrivilegeEscalation能防止容器内的所有权限提升吗? 不能。例如,如果攻击者利用内核漏洞提升权限,它将无济于事。也就是说,它应该能阻止所有通过利用setuid/setgid工作的权限提升。

allowPrivilegeEscalation和privileged之间有关联吗? 没有。关闭allowPrivilegeEscalation是一种安全加固机制。如果保持默认值,容器内的进程仍然不能轻易提升其权限或逃逸容器。

攻击者在容器内提升至root权限是世界末日吗? 另一个误解。在容器内以root身份运行的进程不能轻易逃逸到容器外部。它必须利用另一个漏洞或错误配置。

希望本文提供了关于’allowPrivilegeEscalation’是什么、不是什么以及使用它的明确好处的深入概述。当我第一次发现它时,我自己也很困惑,由于它不幸的命名,这似乎是许多人的困惑之源。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计