Back to Blog
Exaforce
Research
September 9, 2025

Ghost in the Script: Impersonating Google App Script projects for stealthy persistence

Exploring the risks of Google Apps Script abuse, from cryptomining to stealthy service accounts, and ways to detect misuse.

One of the most important steps in an infrastructure attack is persisting in the target’s environment. Persistence often needs to be established multiple times, depending on the level of access they gain. Security systems have become smart enough to detect many forms of persistence mechanisms. This has led attackers to continue to find new and creative ways to persist in a target’s environment.

Google Workspace Apps Script is a feature that allows any user with a Gmail account to automate business applications and enables the applications to interact with each other. Underneath, when an Apps Script app is deployed, a GCP Project is created on the GCP Organization the account is part of. Aside from the format of the Project ID, these projects are not very different from a normal GCP project. This means, an attacker can choose to use one of these projects to host resources and persist on the target. They can also create a GCP project with the same name format as an Apps Script project to impersonate a legitimate Apps Script Project and evade detection.

This blog will go through how Apps Script projects work and how an attacker can utilize the Apps Script projects to persist in a target’s environment. Then, we will look into how these techniques can be detected and prevented, so they will not be able to be maliciously utilized by attackers.

The ins and outs of Apps Script

Google Workspace Apps Script, associated with the endpoint script.google.com, are a low code solution, allowing anyone with a Gmail account to automate business applications that integrate with Google Workspace. It offers a scripting interface using JavaScript to integrate Google services and build lightweight automations.

Example Google Apps Script

Apps Script is highly flexible. With it, you can:

  • Create custom menus, dialogs, and sidebars in Google Docs, Sheets, and Forms
  • Develop custom functions and macros for Google Sheets
  • Publish web apps, either as standalone applications or embedded within Google Sites
  • Connect with other Google services such as AdSense, Analytics, Calendar, Drive, Gmail, and Maps
  • Build lightweight add-ons and share them through the Google Workspace Marketplace
Apps Script types available

When an App Script is created, a project with a prefix sys- is automatically created. These projects are not visible in the organization’s projects list on the console, which makes sense, since they are not considered Organization Projects.

Project list without Apps Script projects

However, the projects are visible through the terminal tool (gcloud) by identities with access to execute resourcemanager.projects.list.

gcloud projects list that does contain Apps Script projects

When an App Script project is created, GCP will create a Resource Manager folder and subfolder by default in the organization, with the names system-gsuite/apps-script. Here again, there seem to be no projects inside these folders when viewed in the console.

Console view of the Apps Script subfolder with no projects visible

Console view of the Apps Script subfolder with no projects visible

Using the CLI, however, we see the App Script projects inside the apps-script subfolder. This is where the App Script projects reside after creation.

CLI output with Apps Script projects in the system-gsuite/apps-script subfolder

Abusing Apps Script impersonation on a GCP Organization

Cryptomining Instance

App Script projects follow an ID format of sys-<26 numbers>. In GCP, we can create a project and store it in any folder or subfolder we have access to and we can set the project name to anything as long as it contains ASCII letters, digits, and hyphens, and is between 6-30 characters. The combination of sys-<26 numbers> is exactly 30 characters long, containing numbers, letters, and a hyphen.

Creating a GCP project that looks like an Apps Script project

One difference we found was how the projects looked based on the location where they were stored. If a project is stored at the organization level, the project, though having an ID format of sys-<26 numbers>, will show in the console (project sys-00000000000000000000000000). However, when created inside the apps-script folder, the app does not show as a project on the console (project sys-11111111111111111111111111).

Console view of projects where sys-00000000000000000000000000 is shown due to being in the organization level, but sys-11111111111111111111111111 in the apps-script folder is not

The projects are still listed when resourcemanager.projects.list is executed on the terminal.

gcloud CLI listing both projects

An attacker with permissions to resourcemanager.projects.create can utilize the fact App Script projects do not show as other projects do to create a project in the target’s organization and store resources there. Each project can also have a name, which can be provided by the creator. An attacker can also look at other projects in the target’s organziation to find a convincing name for the project.

gcloud CLI used to create a hidden project

For example, a bad actor could use this hidden project to create a large instance and use it as a cryptomining harvester. To do that, we need to:

  • Enable billing for the project
  • Enable the compute API
  • Create an instance
Enabling and then creating a large instance in a hidden project

The attacker now controls a high performance instance they can use as a cryptomining harvester.

Persist on the Organization using a Service Account inside a hidden project

Persistence allows an attacker to return to the target’s infrastructure, ideally as a highly privileged identity. There are different ways to persist in a GCP organization, including user creation service account creation, creating permanent credentials, and creating resources with highly privileged identities assigned to them. If a persistence mechanism can be created inside a project, it can be created into an App Script impersonated project. For example, we can create a service account, create a key for it, assign a highly privileged role on the organization and other projects, and keep it for later use.

Creating a service account in a hidden project

To make matters worse, the identity will only be listed if the project name is known. We can even put a policy on the project that prevents anybody from accessing the service account. This isn’t an “unbreakable” prevention, but it might prevent some attempts to clean up the service account, especially since these projects look like they are created and managed by Google.

name: organizations/ORG_ID/denyPolicies/deny-service-account-all
displayName: "Restrict all SA usage"
rules:
- denyRule:    
    deniedPrincipals:
    - principalSet://goog/public:all    
    deniedPermissions:    
    - iam.serviceAccounts.*

Why even impersonate an Apps Script project?

An Apps Script project underneath is a normal GCP project. What differs from a normal project is that by default, no identity, except for one identity controlled by Google, will have the right access to it. The service account appsdev-apps-dev-script-auth@system.gserviceaccount.com is the universal identity that creates the Apps Script projects and is only managed by Google. It is the only identity that, by default, can manage the Apps Script project and its resources.

Showing how only a Google account can access a true Apps Script project by default

An attacker with the right permissions can modify the project’s IAM policy to allow itself to host any resource on any service it wants on this project.

Updating the project policy to allow an attacker access to modify a real Apps Script project

For example, a bad actor could:

  • Creating a service account and assigning an organization policy to it to persist in the target organization
  • Link the project to a Billing Account and create large resources on it for cryptomining

Detecting the abuse of Apps Script projects

Finding project impersonation by looking at the billing information of the project

GCP has different billing types for resources, with some free without limits. IAM identities are a good example of such resources. Other examples of free resources include IAM resource manager organizations, artifact registry, VPC basic networking, and more.

To be able to use a specific service and its resources, an organization owner needs to link a project to a Billing Account. Linking a project to a Billing Account is a fancy way of saying “put a payment method on the project”. The Billing Account links a payment method with the project, and each month, based on the usage of resources, the owner of the project pays, while the Billing Account provides them with an invoice for the costs.

Being able to create a large compute instance means a Billing Account needs to be linked to the project. One way to detect if an App Script project is created by an attacker and used as a host for large resources is to see if the project is linked to a Billing Account. In the example below, project sys-22222222222222222222222222, which is an attacker created project with a large compute instance, will have a Billing Account linked to it (as seen by the fields billingAccountName and billingEnabled), while a legitimate App Script project (project sys-14600875379148140018929136) does not require a link to a billing account.

Comparison of a hidden project attached to a billing account and a legitimate Apps Script project not attached to a billing account

Blending the project in by modifying billing information

One bypass to this detection is to unlink the project from the Billing Account. The project will need to be part of the billing account if the attacker wants to create an instance (or any paid service). So, any resource or service not offered in the free-tier will need to be deployed into a project with a linked Billing Account.

Unlinking the project from the Billing Account will result in the resource being deleted.

Unlinking a billing account that deletes the instance

IAM service accounts do not fall into the paid resources scope, so even a project not linked to a billing account can contain a persistent service account, breaking the previous detection.

Unlinking a billing account while persisting a service account

Finding occurrences of an App Script project impersonation through enabled API Services

To be able to use a specific service on a project through the API (which gcloud uses), the service API needs to be enabled. By default, a new project will have several services enabled, with more services being able to be enabled or disabled using serviceusage.services.enable or serviceusage.services.disable.

Apps Script projects, on the other hand, by default will have no services enabled, but they can be enabled based on demands. One way to detect if a project is not actually an Apps Script project is to look at the enabled services.

Listing servies for real and fake Apps Script projects

Blending in by disabling API endpoints

One problem with this detection method is that an Apps Script project might need some services enabled, based on the task they will execute. An attacker can choose to disable all the services they are not using and make the project seem a bit more legitimate.

Disabling unused services to appear more like a real Apps Script project

For example, to be able to create a compute instance in the project, compute and oslogin services need to be enabled. If disabled while a resource exists, the execution will fail, indicating current project usage. Other than that, the rest of the services can be disabled and then be temporarily re-enabled by the attacker each time a task executes on the target’s environment.

Execution failure when a service is disabled

Another problem is that some service APIs will work in a project without the service API being enabled on the project. So, services like IAM or some basic storage commands will be allowed, as well as the service usage API (so that the service APIs can be enabled or disabled on the project) and Resource Manager.

  • IAM
  • Cloud Resource Manager
  • Service Usage API
  • Cloud Storage (base functionality is always available; enabling billing gives extra control, billing, and metrics).

This means, a project showing no services enabled will still be able to host a service account, which can be used as a persistence mechanism by the attacker.

Finding occurrences of an App Script project impersonation in the Logs

Another detection approach is looking at the logs for the apps-script folder. In the example below, the projects created by the attacker (sys-11111111111111111111111111 and sys-22222222222222222222222222) have the principalEmail field set to the creator’s user email, while the ones created by Google have an email of appsdev-apps-dev-script-auth@system.gserviceaccount.com. This is tricky because the events can only be found under the folder’s events, not the Organization’s.

Logs showing the differences in principalEmail from fake and legitimate Apps Script projects

Limiting Project Impersonation using organization policies

Apps Script projects, when created, will have an ID format of sys-<26 random digits>. One way to prevent project creation of an Apps Script impersonator would be to deny the creation and update of projects with an ID format of sys-<26 random digits> using the following policy.

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: ''

Whenever an attacker tries to create a project with that format, the organization policy will deny the execution.

Organization policy blocking a hidden project creation

The Organization Policy will deny the creation of any project with an ID format of sys-<26 random digits>, including legitimate ones created by Google through the endpoint script.google.com.

Blocked Apps Script project creation

Detecting Apps Script project abuse with Exaforce

Exaforce provides layered coverage to detect and stop malicious usage of Apps Script projects, whether through impersonation or misuse. Our approach leverages advanced anomaly detection to understand the baseline behavior of entities in your environment to spot abnormal behavior. We’ve enabled some new detections to protect our customers as a result of this research.

  • Exaforce detects Google Apps Script projects that use unusual API services, labels, or policy bindings, so they are flagged during onboarding.
  • Exaforce detects project impersonation attempts wherein an attacker attempts to create projects that look like an Apps Script project.

Below is an example scenario of an App Script Project Impersonation attempt.

Example detection of a hidden Apps Script impersonation project

The detection overview provides a summary and conclusion from our Exabot’s (AI agent) automated triage of the alert. It outlines critical information such as the principal in question. 

Session with all related events where impersonation is performed

We map related events for this principal into a session, so it’s easy to understand the context in which this project was created and other activities performed by this principal for comprehensive impact analysis.  

Visual graph of the events during the creation of an impersonating Apps Script project

All related events and resources impacted are mapped visually into a graph for easy investigation. 

Preventative controls

Exaforce detections will ensure you have visibility into this potential issue. We also recommend the enforcement of the organization policy mentioned above that blocks creation of projects with IDs matching sys-[0-9]{26} if Apps Script is not in use at the organization. This will lower your attack surface and greatly improve the security posture of your organization.

Stealthy persistence and defense

Apps Script projects can serve as stealthy persistence mechanisms if left unmonitored. Attackers can impersonate them to hide cryptomining, privileged service accounts, or other malicious resources inside your environment.

Defenders need to understand how Apps Script projects work under the hood and how to detect and block potential abuse. Leveraging organization policies and strong detections like those provided by Exaforce provides comprehensive coverage for this persistence vector.

Share

Table of contents

Share

Recent posts

No items found.
Button Text

Explore how Exaforce can help transform your security operations

See what Exabots + humans can do for you

No items found.
No items found.