Breaking Open the Forgotten Vault

A patched PythonScript policy flaw allowed JavaScript to run outside its usual sandbox.

5.4K

Research details

Patch status
Patched

Introduction

PythonScript is one of the ways to add custom logic to an Apigee proxy when the built-in policies are not enough. Its intended role is straightforward: load a Python resource and execute it as part of the proxy flow. JavaScript has a separate policy, with its own sandbox restrictions.

This finding came from the same Apigee research that led to the earlier sandbox writeups. Attempts to reach Java runtime methods through the JavaScript policy had failed at that point. PythonScript became relevant when a typo in its configuration exposed support for a language that was not expected there.

An unexpected resource type

The first clue came from a typo in a resource URL. PythonScript extends an API proxy with scripts stored in /resources/py, referenced through the py:// scheme. While testing the policy, I accidentally entered a different prefix in ResourceURL.

The example below uses bad:// to show the mistake. The actual typo was closer to _pt_; there is no screenshot of that original input.

xml
<?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>

Saving the policy produced this validation error:

ScreenshotScreenshot

“The resource type 'bad' is not supported for this policy. Must be 'js', 'jsc', or 'py'.”

The unexpected part was the list of supported types. A PythonScript policy was advertising js and jsc alongside py. JavaScript has its own policy in Apigee, with a different execution environment, so accepting it here raised a question about which restrictions would apply.

JavaScript through PythonScript

Apigee’s JavaScript policy uses Mozilla’s Rhino engine and restricts access to Java classes. Earlier attempts to reach the Java runtime through that policy had failed. A separate issue later provided another route, described in Rhino’s blind spot, but that had not been found at this point.

The resource-type error suggested another execution path worth checking. If PythonScript accepted a JavaScript file, would it apply the restrictions used by the JavaScript policy?

The test used a small JavaScript file that requested the Java runtime:

javascript
// java-runtime-access.js
var Runtime = java.lang.Runtime.getRuntime();

The PythonScript policy referenced that file with a js:// resource URL:

xml
<!-- 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>

The script ran successfully. The same access would have been rejected in the JavaScript policy, but this execution path did not enforce those restrictions. What looked like an unexpected supported resource type had exposed access to the Java runtime.

This distinction mattered more than the language of the script. The JavaScript policy’s sandbox could restrict a script correctly while the same script, invoked through PythonScript, reached Java classes outside those restrictions.

Confirming runtime access

A proof of concept then tested whether the runtime access could be used beyond retrieving a Java object. It used a PythonScript policy referencing a JavaScript file in /resources/js, with a reverse-shell payload. After the proxy was deployed, a GET request triggered the policy and established the connection.

ScreenshotScreenshot

The result confirmed code execution in the Apigee runtime. It also made it possible to examine what data and platform components were reachable from that context.

A possible legacy execution path

The behavior appeared consistent with an older design in which the <Script> element supported several scripting languages. PythonScript and JavaScript may later have become separate policies without fully removing JavaScript support from PythonScript.

That is an explanation inferred from the behavior, rather than a confirmed history of the implementation. The validation error listed js and jsc, and JavaScript resources really did execute through the policy. Those observations established the remaining execution path; they did not establish why it had been left there.

Whatever its origin, that path crossed a boundary between policies with different sandbox rules. Removing a language from the intended interface had not removed every way of executing it.

Access to Cassandra data

The impact extended beyond the API proxy that triggered the policy. In Apigee, Cassandra stores OAuth tokens, cached responses, and Key-Value Maps (KVMs). Depending on the organization’s configuration, these resources can hold API data, credentials, keys, and other secrets. KVM values may be plain text or encrypted, but the access obtained in this test made extraction possible.

Direct Cassandra access allowed enumeration of keyspaces, KVMs, and cache resources. The screenshot below shows the proof of concept against cache_to_use, containing data written by an API proxy with PopulateCache.

ScreenshotScreenshot

This was a separate consequence of the same runtime access: data held by the platform was reachable, rather than only the input and output of the affected proxy.

Modifying an Apigee Edge library

Apigee Edge relies on Java libraries for policy execution, request handling, and proxy behavior. Runtime access also made it possible to modify those libraries. To make the effect visible, I changed a core library responsible for handling HTTP methods.

After that change, requests processed by the Edge instance used PWNED as the method, whether the incoming request had been GET, POST, or something else.

ScreenshotScreenshot

The altered method was deliberately conspicuous. It demonstrated that the access could change how the platform processed requests, rather than merely execute a command inside one proxy. Request methods, headers, and payloads were all part of the potential impact of modifying that processing.

Consequences for other APIs

The library experiment changed HTTP methods; it did not demonstrate every possible use of that access. A further consequence would be modification of a proxy execution library to record headers, bodies, or tokens and forward them elsewhere. That would affect APIs running on the compromised instance, not just the proxy used in the test.

For an instance hosting 200 APIs, for example, the potential exposure would extend to traffic handled by all 200. Sensitive transactions, credentials, and responses could be exposed or altered at the platform level. The number is illustrative; it is not a claim that 200 APIs were compromised during this research.

The patch and the underlying boundary

Google patched the vulnerability by removing JavaScript references from PythonScript policies and tightening the sandbox restrictions.

The finding points to a specific review problem: checking the JavaScript sandbox in isolation would not have exposed a second policy that could execute the same language under different rules. Resource types, execution paths, and the restrictions attached to each need to agree, including paths retained from older implementations.

The initial error message was useful because it described behavior that was not expected from the policy. Following up on that discrepancy turned a failed configuration into evidence of a separate execution path.

Select text to leave a comment.