Continuing my journey through previously unpublished Apigee vulnerabilities, I’m sharing details of another serious flaw - an issue in the PythonScript policy that allowed attackers to bypass sandboxing and execute arbitrary Java code. This vulnerability has already been patched, but the story behind it illustrates how legacy design decisions can leave unexpected security gaps.
The PythonScript policy in Apigee was designed to provide developers with the flexibility to extend API proxy functionality using Python scripts. It allowed custom scripts to be used from the /resources/py
directory, enabling functionality beyond what out-of-the-box policies could offer. However, I discovered that this policy wasn’t as straightforward as it seemed.
When an Error Tells a Bigger Story
It all started with what seemed like a simple mistake. I was testing Apigee’s PythonScript policy and accidentally pointed it to the wrong resource type. Instead of referencing a Python script in the /resources/py
directory by using py://
scheme, I mistakenly referenced something else - made a typo in the ResourceURL
value.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Script name="Python-1">
<DisplayName>Python-1</DisplayName>
<!-- bad:// instead of py:// -->
<ResourceURL>bad://myscript.py</ResourceURL>
</Script>
Of course, during my research I didn't
use bad
, but something like pt
instead of py
but I didn't take a screenshot!
When I tried to save the policy, Apigee threw an error👇
“The resource type 'bad' is not supported for this policy. Must be 'js', 'jsc', or 'py'.”
Wait. "js
"? What’s JavaScript doing here? I wasn’t using a JavaScript policy - I was working with PythonScript. My curiosity got the better of me, and I decided to investigate. That’s when I realized this wasn’t just a quirky error message - it was the breadcrumb that led to the discovery of a critical Remote Code Execution vulnerability.
Following the Breadcrumbs: PythonScript’s Hidden Behaviour
It’s worth mentioning that prior to investigating this policy I had spent a significant amount of time trying to find ways to bypass the restrictions on executing Java code within a standard JavaScript policy. The JavaScript policy uses Mozilla’s Rhino engine, which imposes strict sandboxing, making it nearly impossible to execute anything beyond the basic Java classes it allows. At that point, my attempts to break out of those constraints had been unsuccessful (though, as I later discovered, there was a way - Rhino’s blind spot).
Still, I wasn’t ready to give up. The error message from the PythonScript policy sparked an idea: if JavaScript could be referenced here, perhaps the sandboxing rules would behave differently. I decided to test my luck. I created a simple JavaScript file with code that would attempt to execute Java runtime methods and pointed the PythonScript policy at it.
// java-runtime-access.js
var Runtime = java.lang.Runtime.getRuntime();
<!-- PythonScript.xml -->
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Script name="Python-1">
<DisplayName>Python-1</DisplayName>
<ResourceURL>js://java-runtime-access.js</ResourceURL>
</Script>
Surprisingly, it worked. The JavaScript executed without any issues! It wasn’t behaving like a typical JavaScript policy, which would definitely throw an error. There were no sandbox constraints. Rhino’s restrictions were nowhere to be found. Instead, the script seemed to have unrestricted access to the entire Java runtime. Suddenly, the barriers that had blocked me in the JavaScript policy were gone.
That’s when it hit me: this wasn’t just a quirk—it was a wide-open door. By executing JavaScript through the PythonScript policy, I had bypassed every security mechanism Apigee had in place for dynamic code execution.
From Oversight to Exploit
After understanding the scope of the issue, I developed a proof-of-concept exploit. Here’s what I did:
- Created a PythonScript Policy: I set up a PythonScript policy that pointed to a malicious JavaScript file in the
/resources/js
directory. - Added a Reverse Shell Payload: The JavaScript file contained a payload that bound a reverse shell to my machine.
- Deployed the Proxy: I uploaded and deployed the crafted proxy on an Apigee environment.
- Triggered the Payload: With a simple GET request, I triggered the PythonScript policy with a JavaScript code.
- Established a Reverse Shell: A reverse shell connection was established, giving me full control over the runtime.
An Error That Exposed the Cracks
This vulnerability was a striking example of how overlooked functionality can have severe implications. The issue stemmed from a design decision that likely originated in the early stages of Apigee's development. The <Script>
tag, which is used in PythonScript policies, appeared to have been designed as a generic mechanism to support multiple scripting languages. Somewhere along the way, the implementation was split into dedicated PythonScript and JavaScript policies, but the ability to reference JavaScript files within the PythonScript policy wasn’t fully removed.
Even more telling was the error message itself, which confirmed the existence of this legacy feature. The fact that it explicitly listed “js
” and “jsc
” as supported resource types for the PythonScript policy hinted at a half-finished or abandoned feature. This legacy oversight created a loophole that allowed JavaScript to bypass all sandboxing mechanisms and execute in an unrestricted context.
Implications in the Real World
By exploiting this vulnerability, an attacker could establish a reverse shell and gain complete control over the Apigee runtime. This access extends far beyond just the API proxy layer. In Apigee's architecture, Cassandra serves as a critical component, storing sensitive backend data. This includes OAuth tokens, which are essential for authentication and authorization flows. It also holds cached responses, which often contain temporarily stored API data that may include user-sensitive information. Additionally, Cassandra is used to manage Key-Value Maps (KVMs), where organizations store configuration details, such as API keys, credentials, or other sensitive data. These values may be stored in either plain text or encrypted formats, depending on implementation, but in either case, they could be extracted directly by the attacker.
The screenshot below perfectly illustrates the impact of compromising an Apigee instance. Using direct access to Cassandra, I was able to enumerate all available keyspaces, KVMs, cache resources, and other critical components. As a PoC, I retrieved details from cache_to_use
, which includes sensitive cached data that had been stored by API proxy using the PopulateCache policy.
In Apigee Edge, the situation is even more alarming. The attacker could go beyond runtime exploitation and alter the platform itself.
Modifying Apigee Edge: The PWNED Experiment
Apigee isn’t just a single piece of software - it’s built on top of dozens of Java libraries, each responsible for a specific piece of functionality. These libraries govern everything from policy execution to request handling and proxy behaviour. With control over the runtime, an attacker could replace or modify these libraries, injecting malicious functionality deep into the core of the platform.
I decided to test the limits of what could be done by tampering with the libraries. Just for fun, I modified one of the core libraries responsible for handling HTTP methods. The result? Every request processed by Apigee Edge, no matter what method was originally used (GET, POST, etc.), would include PWNED
as the method. Yes, every single API request from Apigee was stamped with PWNED
.
This experiment wasn’t just for laughs - it demonstrated the depth of control an attacker could achieve. On a compromised instance, they could rewrite request methods, headers, or payloads with impunity. If I could pull this off with minimal effort, a more malicious actor could implement far more sinister changes.
From Fun to Devastation
While my playful experiment was harmless, the same level of access could be used for serious damage. For instance, an attacker could modify the proxy execution library to silently log every piece of data passing through the instance - headers, body content, tokens - and forward it to a remote server under their control. This wouldn’t just compromise a single API. Every API running on that particular instance would be affected.
If the compromised instance hosted 200 APIs, the attacker would effectively gain full visibility into all 200. They could monitor sensitive transactions, extract credentials, or tamper with API responses in real time. The instance wouldn’t just be compromised - it would become a fully weaponized surveillance tool for the attacker.
Closing the Door
Thankfully, this vulnerability has been patched by Google. The ability to reference JavaScript files in PythonScript policies has been removed, and the sandboxing mechanisms have been tightened to prevent similar exploits.
This discovery serves as a cautionary tale for both developers and security teams. It highlights the importance of:
- Legacy Code Reviews: Features and functionalities that are no longer actively maintained should be thoroughly reviewed for potential security implications.
- Error Message Auditing: Even something as simple as an error message can reveal unintended functionality or expose critical vulnerabilities.
- Continuous Security Assessments: Regular application security audits and code reviews can help identify and mitigate risks before they are exploited.
Lessons Learned
Looking back, this vulnerability is a perfect example of how flexibility in design can sometimes create unintended risks. Apigee’s ability to execute custom scripts was a powerful feature, but it also opened the door to complex security challenges. The oversight in the PythonScript policy’s implementation underscores the importance of striking a balance between extensibility and security.
For me, this was a reminder of why attention to detail matters so much in security research. A simple typo in a resource URL - a mistake anyone could make - ended up uncovering a serious flaw. It’s moments like this that keep me curious, pushing me to explore the “what ifs” that lie beneath the surface of every system.
Breaking Open the Forgotten Vault