LoFP LoFP / a principal that legitimately both registers a high-compute public-image task definition and runs ecs workloads in the same window could match (for example, some data-science or batch pipelines). confirm the image and cpu in \"aws.cloudtrail.request_parameters\" of the registertaskdefinition event, the launched workload, and whether the principal in \"aws.cloudtrail.user_identity.arn\" is an expected ecs operator; exclude known principals after validation. the cpu threshold and registry list are tunable in the query.

Techniques

Sample rules

AWS Potential Cryptomining via ECS Task Definition Deployment

Description

Identifies a principal that, within a short window, both registers an Amazon ECS task definition using a public / non-ECR container image at a high CPU allocation (8 or 16 vCPU) AND launches ECS workloads (RunTask, StartTask, or CreateService). Registering a public miner image at maximum compute and then launching it is the ECS/Fargate cryptocurrency-mining deployment pattern seen after credential compromise. Requiring both the mining-signature registration and a launch by the same principal confirms an actual deployment rather than a standalone (possibly benign) task-definition registration, which sharply reduces false positives from high-compute workloads that are merely registered.

Detection logic

FROM logs-aws.cloudtrail-*
| WHERE event.provider == "ecs.amazonaws.com"
    AND event.action IN ("RegisterTaskDefinition", "RunTask", "StartTask", "CreateService")
| EVAL Esql.miner_register = CASE(
        event.action == "RegisterTaskDefinition"
            AND (aws.cloudtrail.request_parameters RLIKE """.*image=(docker.io|index.docker.io|ghcr.io|quay.io|public.ecr.aws)/.*"""
                OR aws.cloudtrail.request_parameters RLIKE """.*image=[-a-zA-Z0-9_]+(/|[,} :]).*""")
            AND aws.cloudtrail.request_parameters RLIKE """.*cpu=(8192|16384)[,} ].*""", 1, 0),
    Esql.task_run = CASE(event.action IN ("RunTask", "StartTask", "CreateService"), 1, 0)
| STATS Esql.miner_register_sum = SUM(Esql.miner_register), Esql.task_run_sum = SUM(Esql.task_run), Esql.event_count = COUNT(*),
        Esql.cloud_region_count_distinct = COUNT_DISTINCT(cloud.region), Esql.cloud_region_values = VALUES(cloud.region),
        Esql.event_action_values = VALUES(event.action), Esql.source_ip_values = VALUES(source.ip),
        Esql.source_as_number_values = VALUES(source.as.number), Esql.user_agent_original_values = VALUES(user_agent.original),
        Esql.cloud_account_id_values = VALUES(cloud.account.id), Esql.aws_cloudtrail_user_identity_type_values = VALUES(aws.cloudtrail.user_identity.type),
        Esql.timestamp_min = MIN(@timestamp), Esql.timestamp_max = MAX(@timestamp)
        BY aws.cloudtrail.user_identity.arn
| WHERE Esql.miner_register_sum >= 1 AND Esql.task_run_sum >= 1
| KEEP aws.*, Esql.aws_cloudtrail_user_identity_type_values, Esql.miner_register_sum, Esql.task_run_sum, Esql.event_count, Esql.cloud_region_count_distinct, Esql.cloud_region_values, Esql.event_action_values, Esql.source_ip_values, Esql.source_as_number_values, Esql.user_agent_original_values, Esql.cloud_account_id_values, Esql.timestamp_min, Esql.timestamp_max