gcp-cloudscheduler-uri-retarget-token-theft
Retargeting an existing HTTP job's URI (via jobs.update) to an attacker-controlled endpoint causes the existing SA's OIDC/OAuth2 bearer token to be delivered on the next invocation - no actAs on the SA required since the SA is unchanged.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?job) ==
Scheduler
?job has an HTTP target with oidcToken.serviceAccountEmail or oauthToken.serviceAccountEmail set (a SA-authenticated HTTP job)
?principal does NOT need to hold iam.serviceAccounts.actAs on the existing SA — URI-only update does not require actAs
emit
| source type | Identity |
|---|---|
| target type | Token |
| source | <principal> |
| target | <OIDC/OAuth2 bearer token of the existing SA configured in the job> |
| permissions | cloudscheduler.jobs.update |
| conditions | service_state |
| state logic | ACTIVE when the principal holds cloudscheduler.jobs.update on the job AND the job is ENABLED (or PAUSED but can be run via jobs.run). The bearer token is delivered on the next scheduled invocation (or on manual jobs.run trigger). If the principal also holds cloudscheduler.jobs.run, immediate delivery is possible (ACTIVE). Without jobs.run: ACTIVE but bounded by the schedule cadence (next firing time). |
Narrative
{principal.name} can update {job.name}'s HTTP URI to an attacker-controlled endpoint (cloudscheduler.jobs.update) without needing actAs on the existing SA. On the next invocation, the Cloud Scheduler service agent delivers the existing SA's OIDC/OAuth2 bearer token to the attacker's endpoint - yielding a short-lived token for that SA.
Raw rule rules/derived/gcp/cloudscheduler.yaml
id: gcp-cloudscheduler-uri-retarget-token-theft
emits: CanRetrieveToken
description: "Retargeting an existing HTTP job's URI (via jobs.update) to an attacker-controlled endpoint\
\ causes the existing SA's OIDC/OAuth2 bearer token to be delivered on the next invocation \u2014 no\
\ actAs on the SA required since the SA is unchanged."
match:
- - principal: null
- CanModifyConfiguration
- job: null
where:
- node_type(?job) == Scheduler
- ?job has an HTTP target with oidcToken.serviceAccountEmail or oauthToken.serviceAccountEmail set (a
SA-authenticated HTTP job)
- "?principal does NOT need to hold iam.serviceAccounts.actAs on the existing SA \u2014 URI-only update\
\ does not require actAs"
emit:
source_type: Identity
target_type: Token
source: <principal>
target: <OIDC/OAuth2 bearer token of the existing SA configured in the job>
permissions:
- cloudscheduler.jobs.update
conditions:
- service_state
state_logic: 'ACTIVE when the principal holds cloudscheduler.jobs.update on the job AND the job is ENABLED
(or PAUSED but can be run via jobs.run). The bearer token is delivered on the next scheduled invocation
(or on manual jobs.run trigger). If the principal also holds cloudscheduler.jobs.run, immediate delivery
is possible (ACTIVE). Without jobs.run: ACTIVE but bounded by the schedule cadence (next firing time).'
confidence: min(contributing_confidences) * 0.9
derived_from:
- ?principal CanModifyConfiguration ?job (gcp-cloudscheduler-update-config)
false_positive_note: "This path requires the job to already have an SA-authenticated HTTP target (oidcToken\
\ or oauthToken set). A job with no authentication configured (no oidcToken/oauthToken) does not produce\
\ a bearer token \u2014 do not emit. The token received at the attacker's endpoint is a short-lived\
\ OIDC JWT (1-hour TTL) or OAuth2 access token scoped to https://www.googleapis.com/auth/cloud-platform;\
\ it can be used to call GCP APIs as the SA. The attacker must intercept the HTTP request (the retargeted\
\ endpoint must be under attacker control). This is a weaker path than CanExecuteAs (no ongoing code\
\ execution), but each invocation delivers a fresh token."
narrative: "{principal.name} can update {job.name}'s HTTP URI to an attacker-controlled endpoint (cloudscheduler.jobs.update)\
\ without needing actAs on the existing SA. On the next invocation, the Cloud Scheduler service agent\
\ delivers the existing SA's OIDC/OAuth2 bearer token to the attacker's endpoint \u2014 yielding a\
\ short-lived token for that SA."