Article by Taylor Smith
Apps Script project impersonation / Google Apps Script persistence
-
Original Research
Ghost in the Script: Impersonating Google App Script projects for stealthy persistence by Bleon Proko and Jakub Pavlik.
Google Apps Script is a low-code platform that allows users to automate and integrate Google Workspace applications. When an Apps Script is deployed, it automatically creates a corresponding Google Cloud Platform (GCP) Project within the user's organization. These projects are often hidden from the standard GCP console view, creating an opportunity for attackers to achieve stealthy persistence.
The Vulnerability¶
Apps Script projects are created with a specific ID format: sys-<26 numbers>
. These projects reside in a system-gsuite/apps-script
folder and are not visible in the organization's project list in the GCP console, though they are visible via the gcloud
CLI.
An attacker with the resourcemanager.projects.create
permission can create a standard GCP project that mimics this naming convention. By placing this impersonated project inside the apps-script
folder, it becomes hidden from the console view, just like a legitimate Apps Script project. This allows the attacker to host malicious resources undetected.
Attack Vector 1: Malicious Resources¶
A common abuse is to deploy a high-performance instance for cryptomining within a hidden project. This requires linking the project to a billing account and enabling the necessary APIs.
- Create a hidden project with a name mimicking a legitimate function:
gcloud projects create 'sys-22222222222222222222222222' --name "Exaforce Google Sheet Function" --folder-936086877446
- Link the project to a billing account:
gcloud beta billing projects link sys-22222222222222222222222222-billing-account xxxxxx
- Create a large compute instance for cryptomining:
gcloud compute instances create app-script-instance project=sys-22222222222222222222222222 --zone=us-central1-a --machine-type=n2-standard-16 --image-family=debian-11 --image-project=deblan-cloud --boot-disk-size=200GB
Attack Vector 2: Stealthy Persistence¶
Attackers can use a hidden project to create a service account and grant it highly privileged roles for long-term persistence. This identity is hard to discover because it is associated with a project that appears to be managed by Google.
- Create a service account inside the hidden project:
gcloud iam service-accounts create persistence-service-account --display-name "Persistence Service Account" --project sys-22222222222222222222222222
- Bind the service account to a privileged role at the organization level:
gcloud organizations add-iam-policy-binding xxxxxxx --member="serviceAccount:persistence-service-account@sys-222222222222.iam.gserviceaccount.com" --role="organizations/xxxxx/roles/AppScriptPolicy"
Notably, IAM service accounts are free resources, so this persistence mechanism can be established even without linking the project to a billing account, bypassing some detection methods.
Detection and Mitigation¶
Detection Methods¶
-
Billing Information: A legitimate Apps Script project will have
billingEnabled: false
. An impersonated project used for cryptomining will be linked to a billing account (billingEnabled: true
). You can check this with the command:acloud beta billing projects describe <project-id>
. -
Enabled APIs: A real Apps Script project has no services enabled by default. An attacker's manually created project will have a list of default APIs enabled. Compare a real vs. fake project with:
gcloud services list --enabled-project <project-id>
. -
Audit Logs: Query the
CreateProject
logs for theapps-script
folder. A legitimate project'sprincipalEmail
will be[email protected]
. An impersonated project will show the attacker's user email as the creator.
Mitigation Strategy¶
You can prevent this attack by implementing a custom organization policy that denies the creation of projects with an ID matching the Apps Script format.
name: >-
organizations/012345678912/customConstraints/custom.denyAppsScriptProjectImpersonation
resource_types: cloudresourcemanager.googleapis.com/Project
method_types:
- CREATE
- UPDATE
condition: 'resource.projectId.matches(''sys-[0-9]{26}'')'
action_type: DENY
display_name: Deny Apps Script Project Impersonation
description: ''
Note: This policy is effective but will also block the creation of legitimate Apps Script projects for all users in the organization.