Six Zero-Days in a Single Open-Source Platform: The Anatomy of a Complete Compromise

The activity of pentest teams is both interesting and intense, marked on the one hand by continuous questions about when and how artificial intelligence will change the way they work, but especially by the far less romantic reality of deadlines, regulations, and above all an increasingly dynamic environment (see the article referring to the ENISA Threat Landscape Report for references).

That is why, in our internal discussions, we quite often analyze situations and problems identified as part of the internal cross-departmental training process. I wrote about one such case today, using a combination of technical and non-technical terminology, in an attempt to detail what pentesting work really means and what the benefits of collaborating with experienced teams are.

In a recent audit of an open-source platform used in production by our client, we discovered six zero-day vulnerabilities, none of which had been publicly documented at the time of the analysis. We are not talking about misconfigurations or the client’s security hygiene, but about defects in the platform’s own code: problems inherited by anyone running that software, regardless of how disciplined they are in managing their infrastructure.

The conclusion: chained together, these vulnerabilities allow complete compromise of the server and all data hosted on it. And the most severe ones do not require an administrator account. In some cases, on installations with the default configuration, they do not require any account at all.

What we found

The six issues cover three distinct classes:

  • Two remote code executions (RCE). The first, through the evaluation of a server-side expression as JavaScript, the most severe vector in the entire set. The second, an eval injection present in older versions, subsequently remediated by removing the vulnerable component.
  • An arbitrary file write, which can be escalated to RCE.
  • Three distinct Server-Side Request Forgery (SSRF) vulnerabilities, one of which is not merely a new vector, but a bypass of an existing anti-SSRF control in the application.

The severity ranges from High to Critical, with CVSS 3.1 scores between 7.4 and 9.9. What turns this set from a list of bugs into a disaster scenario is accessibility: the most severe ones are exploitable by authenticated users without administrative privileges: exactly the profile that any multi-tenant platform considers “low trust.”

The 9.9 RCE: when a feature becomes a gateway

The highest-severity issue is an RCE with a CVSS score of 9.9, accessible to any authenticated user.

The root of the problem is a feature that, on paper, seems harmless: the application allows conditional expressions to be attached to the steps of a workflow. In practice, these expressions are evaluated server-side as full JavaScript, inside a vm.runInNewContext() context in Node.js.

This is where a misconception that appears surprisingly often in production code comes in: vm in Node is not a security sandbox. The official Node documentation states this explicitly. It is a context-isolation mechanism, not a barrier against malicious code, and in this case, the difference costs the entire infrastructure.

Because the string stored by the user is never validated, an attacker can escape the context through a classic chain: globalThis.constructor.constructor leads to the Function constructor of the host realm and, from there, to the process object, from which arbitrary commands can be executed at the operating system level. The code runs with the privileges of the service account, within the control-plane process. In practice, this means direct access to secrets in the configuration: signing keys, database credentials, vault keys, and escalation from ordinary user to administrator without any other vulnerability. A single flaw, control of the entire system.

File writing: a subtle Python detail with major consequences

Complementing the RCE, the arbitrary file write exploits a behavior that many Python developers encounter without noticing. The destination filename, taken unsanitized from input, is passed to os.path.join(). What few people know is that os.path.join() completely ignores the base directory when it receives an absolute path. This is documented behavior, but a counterintuitive one.

The consequence: an attacker can overwrite a Python module located on the application’s import path and, implicitly, obtain code execution the next time the module is loaded. A second path to the same destination: complete control.

The SSRF that bypasses its own defensive wall

Of the three SSRF vectors, one deserves special attention because it demonstrates how an existing security control can provide a false sense of security.

The application already had an allowlist-based anti-SSRF validation. The problem: the validation was applied only once, to the entire multi-line input block, while the parser that actually performs the request extracts only the first host. The result is a classic discrepancy between what is validated and what is executed: any URL placed on the second line passes unnoticed by the allowlist and is downloaded without verification.

In cloud environments, this gap translates directly into the extraction of IAM credentials from the metadata endpoint, meaning, once again, going from a simple application request to the keys to the kingdom.

Why it matters beyond this particular situation

Each of these flaws says something broader than “a particular platform has a bug”:

  • Flexible functionality is an attack surface. Any place where the application evaluates user input as code, whether JavaScript, a template, or an expression, must be treated as a potential RCE until proven otherwise.
  • An untested security control is sometimes more dangerous than having none at all, because it reduces vigilance. The SSRF bypass was possible precisely because there was an allowlist that “seemed” sufficient.
  • Zero-day does not simply mean “unknown bug,” but a risk that no prompt patching can address, because the patch does not exist yet. Here, the value does not lie in reacting quickly to public advisories, but in finding them before attackers do.

For any organization running open-source software in production, which practically means all of them, the message is simple: dependencies do not come with a security guarantee, and an offensive audit focused on exploitation chains, rather than isolated bugs, is the only way to see the system as an attacker sees it.

From “what could go wrong” to certainty

The six vulnerabilities above were not found by automatically scanning for known signatures. They were found by thinking like an attacker: following how features connect to one another, where input becomes code, and where an “existing” security control does not actually cover what it should.

This is exactly the type of analysis that the FORT team brings to every engagement.

If your organization runs open-source software or custom applications in production and you want to know what an attacker sees before you find out the hard way, write to us here, we can support you with:

  • Penetration testing: manual, specialist-led testing that goes beyond automated scans and follows real exploitation chains, as an attacker would exploit them
  • DevSecOps: integrating security into the development lifecycle so that defects are caught before they reach production
  • Cybersecurity audit and consulting: assessing the security posture and providing concrete remediation recommendations
  • Incident response and SOC: detection, monitoring, and rapid response when a threat becomes real.

Related articles