|
| 1 | +--- |
| 2 | +author_name: Taylor Smith |
| 3 | +title: Apps Script project impersonation / Google Apps Script persistence |
| 4 | +description: Google Workspace Apps Script projects create hidden GCP projects (sys-<...>) that can be impersonated by attackers. This technique enables stealthy persistence (service accounts, hidden compute, cryptomining) and can bypass common console inspections. |
| 5 | +--- |
| 6 | + |
| 7 | +<div class="grid cards" markdown> |
| 8 | +- :material-account:{ .lg .middle } __Original Research__ |
| 9 | + |
| 10 | + --- |
| 11 | + |
| 12 | + [Ghost in the Script: Impersonating Google App Script projects for stealthy persistence](https://www.exaforce.com/blogs/ghost-in-the-script) by [Bleon Proko](https://www.linkedin.com/in/bleon-proko/) and [Jakub Pavlik](https://www.linkedin.com/in/pavlikjakub/). |
| 13 | +</div> |
| 14 | + |
| 15 | + |
| 16 | +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. |
| 17 | + |
| 18 | +## The Vulnerability |
| 19 | + |
| 20 | +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. |
| 21 | + |
| 22 | +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. |
| 23 | + |
| 24 | +## Attack Vector 1: Malicious Resources |
| 25 | + |
| 26 | +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. |
| 27 | + |
| 28 | +1. **Create a hidden project** with a name mimicking a legitimate function: |
| 29 | + ```bash |
| 30 | + gcloud projects create 'sys-22222222222222222222222222' --name "Exaforce Google Sheet Function" --folder-936086877446 |
| 31 | + ``` |
| 32 | +2. **Link the project to a billing account:** |
| 33 | + ```bash |
| 34 | + gcloud beta billing projects link sys-22222222222222222222222222-billing-account xxxxxx |
| 35 | + ``` |
| 36 | +3. **Create a large compute instance** for cryptomining: |
| 37 | + ```bash |
| 38 | + 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 |
| 39 | + ``` |
| 40 | + |
| 41 | +## Attack Vector 2: Stealthy Persistence |
| 42 | + |
| 43 | +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. |
| 44 | + |
| 45 | +1. **Create a service account** inside the hidden project: |
| 46 | + ```bash |
| 47 | + gcloud iam service-accounts create persistence-service-account --display-name "Persistence Service Account" --project sys-22222222222222222222222222 |
| 48 | + ``` |
| 49 | +2. **Bind the service account** to a privileged role at the organization level: |
| 50 | + ```bash |
| 51 | + gcloud organizations add-iam-policy-binding xxxxxxx --member="serviceAccount:persistence-service-account@sys-222222222222.iam.gserviceaccount.com" --role="organizations/xxxxx/roles/AppScriptPolicy" |
| 52 | + ``` |
| 53 | + |
| 54 | +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. |
| 55 | + |
| 56 | +----- |
| 57 | + |
| 58 | +## Detection and Mitigation |
| 59 | + |
| 60 | +### Detection Methods |
| 61 | + |
| 62 | +1. **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>`. |
| 63 | + |
| 64 | +2. **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>`. |
| 65 | +
|
| 66 | +3. **Audit Logs**: Query the `CreateProject` logs for the `apps-script` folder. A legitimate project's `principalEmail` will be `appsdev-apps-dev-script-auth@system.gserviceaccount.com`. An impersonated project will show the attacker's user email as the creator. |
| 67 | +
|
| 68 | +### Mitigation Strategy |
| 69 | +
|
| 70 | +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. |
| 71 | +
|
| 72 | +```yaml |
| 73 | +name: >- |
| 74 | +
|
| 75 | +organizations/012345678912/customConstraints/custom.denyAppsScriptProjectImpersonation |
| 76 | +resource_types: cloudresourcemanager.googleapis.com/Project |
| 77 | +method_types: |
| 78 | + - CREATE |
| 79 | + - UPDATE |
| 80 | +condition: 'resource.projectId.matches(''sys-[0-9]{26}'')' |
| 81 | +action_type: DENY |
| 82 | +display_name: Deny Apps Script Project Impersonation |
| 83 | +description: '' |
| 84 | +``` |
| 85 | +
|
| 86 | +**Note:** This policy is effective but will also block the creation of legitimate Apps Script projects for all users in the organization. |
0 commit comments