AI & assistant-friendly summary

This section provides structured content for AI assistants and search engines. You can cite or summarize it when referencing this page.

Summary

Attackers do not need to take down your service to hurt you — they can send traffic designed to maximize your AWS bill. DDoS amplification, Lambda invocation bombs, and SQS message flooding are billing attacks, not just availability attacks.

Key Facts

  • Attackers do not need to take down your service to hurt you — they can send traffic designed to maximize your AWS bill
  • DDoS amplification, Lambda invocation bombs, and SQS message flooding are billing attacks, not just availability attacks
  • Attackers do not need to take down your service to hurt you — they can send traffic designed to maximize your AWS bill
  • DDoS amplification, Lambda invocation bombs, and SQS message flooding are billing attacks, not just availability attacks

Entity Definitions

Lambda
Lambda is an AWS service discussed in this article.
SQS
SQS is an AWS service discussed in this article.

How to Protect AWS Infrastructure from Cost-Based Attacks

Security & Compliance Palaniappan P 17 min read

Quick summary: Attackers do not need to take down your service to hurt you — they can send traffic designed to maximize your AWS bill. DDoS amplification, Lambda invocation bombs, and SQS message flooding are billing attacks, not just availability attacks.

Key Takeaways

  • Attackers do not need to take down your service to hurt you — they can send traffic designed to maximize your AWS bill
  • DDoS amplification, Lambda invocation bombs, and SQS message flooding are billing attacks, not just availability attacks
  • Attackers do not need to take down your service to hurt you — they can send traffic designed to maximize your AWS bill
  • DDoS amplification, Lambda invocation bombs, and SQS message flooding are billing attacks, not just availability attacks
How to Protect AWS Infrastructure from Cost-Based Attacks
Table of Contents

Traditional security advice focuses on availability and data integrity: prevent outages, protect customer data, stop unauthorized access. Cost-based attacks flip this model. The attacker’s goal is not to steal your data or take down your service — it is to generate a bill large enough to cause financial harm, operational panic, or forced service shutdown.

These attacks are particularly effective against startups and small teams because: AWS bills in arrears (you pay for what you consumed, not what you anticipated), the default AWS service quotas allow large bursts, and many AWS services have no built-in spending caps. A successful billing attack can generate a four-figure or five-figure AWS bill in hours — before any monitoring alerts fire, before anyone is awake, before you know it is happening.

Understanding the attack surface requires understanding how AWS pricing actually works under adversarial conditions.

The Cost-Based Attack Taxonomy

DDoS → CloudFront Bill Amplification

CloudFront provides significant DDoS protection — volumetric Layer 3/4 attacks are absorbed at edge locations with AWS’s transit capacity. But CloudFront’s protection is not free protection: you pay for the requests and bandwidth that CloudFront processes, including attack traffic.

CloudFront pricing (us-east-1 region, 2026):

  • HTTPS requests: $0.0100 per 10,000 requests
  • Data transfer out: $0.0085/GB after first 1 TB

A DDoS attack sending 50,000 requests/second:

  • Request charges: $0.0100 × (50,000 × 3,600 / 10,000) = $180/hour
  • If CloudFront serves cached responses (200 bytes each): 50,000 × 200 bytes × 3,600s = 360 GB/hour × $0.0085 = $3.06/hour in bandwidth
  • Total: ~$183/hour, $4,392/day

If the attacker uses cache-busting query strings to force origin fetches, your ALB, ECS/EC2 compute, and RDS costs compound on top of the CloudFront costs.

Key insight: CloudFront does not distinguish between legitimate and attack traffic for billing purposes. You are billed for every request it receives, regardless of whether it was a DDoS packet or a real user.

Lambda Invocation Bombs

If you expose a Lambda function URL or API Gateway → Lambda integration publicly, attackers can invoke it directly and repeatedly. Lambda’s free tier is 1 million invocations per month — after that, $0.20 per million invocations plus duration charges.

At 10,000 invocations/second with a 100ms average duration (common for a simple API Lambda):

  • Invocation cost: 10,000 × 3,600 = 36 million invocations/hour × $0.20/million = $7.20/hour
  • Duration cost: 10,000 × 0.1s × 1,024 MB memory = 1,024,000 GB-seconds/hour × $0.0000166667 = $17.07/hour
  • Total: ~$24/hour, $576/day

With a 1,024 MB memory Lambda and no reserved concurrency limit, an attacker can drive Lambda costs into five figures per day before account limits intervene (the default account concurrency limit is 1,000 concurrent executions, which bounds this somewhat).

SQS Message Flooding

SQS pricing: $0.40 per million requests. An attacker with access to your SQS queue endpoint (or who can trigger it through a public API) can flood the queue with messages. Your consumers then incur processing costs reading and deleting those messages.

If your SQS consumer is a Lambda function that processes one message at a time:

  • 1,000 garbage messages/second = 86.4 million messages/day
  • SQS cost: 86.4 million × $0.40/million = $34.56/day
  • Lambda processing cost: 86.4 million invocations × $0.20/million = $17.28/day for invocations alone
  • Plus Lambda duration: each invocation reads a garbage message (200ms to detect and discard) = expensive

Total: over $50/day in direct compute costs, plus the engineering cost of diagnosing why your SQS queue has 10 million unprocessed messages.

S3 Storage and Request Flooding

Two S3 attack vectors: request flooding (PUT/GET requests) and storage flooding (uploading large objects if your application accepts S3 uploads).

S3 PUT request cost: $0.005 per 1,000 requests. At 10,000 PUT/second: 10,000 × 3,600 = 36 million requests/hour × $0.005/1,000 = $180/hour in PUT charges alone — before storage costs for uploaded data.

If your application allows unauthenticated or weakly-authenticated S3 uploads (common in file upload features), an attacker who bypasses or abuses your presigned URL generation can upload indefinitely. 1 TB of garbage data costs $23.52/month in storage, but at $0.005 per PUT: 1 TB in 10 MB chunks = 100,000 PUT requests = $0.50 in request costs to upload. The ongoing storage cost is the real weapon.

WAF Architecture: Where to Place It

The fundamental WAF placement question: CloudFront vs ALB? The answer for most architectures is both, with different rule sets, but the CloudFront WAF placement is more cost-effective for blocking attack traffic.

CloudFront WAF (global): Blocks traffic at edge, before it reaches your AWS region. Attack traffic blocked at CloudFront edge does not incur ALB, ECS, or RDS costs. WAF processing cost is the same regardless of placement: $5/month WebACL + $1/million requests inspected.

ALB WAF (regional): Blocks traffic after it has reached your region and been processed by ALB. ALB LCU charges are already incurred by the time WAF blocks the request. For attacks targeting your origin directly (bypassing CloudFront), ALB WAF is your last line of defense.

The correct architecture for cost protection:

Internet → CloudFront (WAF: Bot Control, rate limiting, common rules)
         → ALB (WAF: additional rate limiting, custom rules specific to your app)
         → ECS / EC2 / Lambda

WAF at both layers adds defense-in-depth and ensures that direct-to-ALB attacks (bypassing CloudFront) are still blocked.

WAF WebACL with Rate-Based Rules and Bot Control (Terraform)

# terraform/waf.tf

# WAF WebACL for CloudFront (must be in us-east-1 for CloudFront)
resource "aws_wafv2_web_acl" "cloudfront" {
  provider    = aws.us_east_1  # CloudFront WAF must be us-east-1
  name        = "${var.project}-cloudfront-waf"
  description = "WAF for CloudFront distribution"
  scope       = "CLOUDFRONT"

  default_action {
    allow {}
  }

  # Rule 1: AWS Managed Rules - Common Rule Set (SQLi, XSS, path traversal)
  rule {
    name     = "AWSManagedRulesCommonRuleSet"
    priority = 10

    override_action {
      none {} # Use managed rule actions (block/count as configured by AWS)
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesCommonRuleSet"
        vendor_name = "AWS"

        # Override specific rules to count-only during initial rollout
        rule_action_override {
          action_to_use {
            count {}
          }
          name = "SizeRestrictions_BODY"  # May need tuning for file upload APIs
        }
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "CommonRuleSet"
      sampled_requests_enabled   = true
    }
  }

  # Rule 2: AWS Managed Rules - Known Bad Inputs
  rule {
    name     = "AWSManagedRulesKnownBadInputsRuleSet"
    priority = 20

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesKnownBadInputsRuleSet"
        vendor_name = "AWS"
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "KnownBadInputs"
      sampled_requests_enabled   = true
    }
  }

  # Rule 3: Bot Control (blocks crawlers, scrapers, credential stuffers)
  rule {
    name     = "AWSManagedRulesBotControlRuleSet"
    priority = 30

    override_action {
      none {}
    }

    statement {
      managed_rule_group_statement {
        name        = "AWSManagedRulesBotControlRuleSet"
        vendor_name = "AWS"

        managed_rule_group_config {
          aws_managed_rules_bot_control_rule_set {
            inspection_level = "COMMON"  # TARGETED adds ~$10/month more
          }
        }

        # Allow legitimate bots (Googlebot, monitoring services)
        rule_action_override {
          action_to_use {
            allow {}
          }
          name = "CategoryVerifiedSearchEngine"
        }
        rule_action_override {
          action_to_use {
            allow {}
          }
          name = "CategoryVerifiedSocialMedia"
        }
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "BotControl"
      sampled_requests_enabled   = true
    }
  }

  # Rule 4: Rate-based rule per IP — 2,000 requests per 5 minutes
  # Adjust the limit based on your legitimate traffic patterns
  rule {
    name     = "RateLimitPerIP"
    priority = 40

    action {
      block {
        custom_response {
          response_code = 429
          response_header {
            name  = "Retry-After"
            value = "300"
          }
        }
      }
    }

    statement {
      rate_based_statement {
        limit              = 2000   # requests per 5-minute window per IP
        aggregate_key_type = "IP"

        # Apply rate limiting only to non-static assets
        scope_down_statement {
          not_statement {
            statement {
              byte_match_statement {
                search_string         = "/assets/"
                field_to_match {
                  uri_path {}
                }
                text_transformations {
                  priority = 0
                  type     = "LOWERCASE"
                }
                positional_constraint = "STARTS_WITH"
              }
            }
          }
        }
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "RateLimitPerIP"
      sampled_requests_enabled   = true
    }
  }

  # Rule 5: Aggressive rate limit on authentication endpoints
  rule {
    name     = "RateLimitAuthEndpoints"
    priority = 35  # Higher priority than general rate limit

    action {
      block {
        custom_response {
          response_code = 429
        }
      }
    }

    statement {
      rate_based_statement {
        limit              = 50    # 50 requests per 5 minutes on /auth/* endpoints
        aggregate_key_type = "IP"

        scope_down_statement {
          byte_match_statement {
            search_string = "/auth/"
            field_to_match {
              uri_path {}
            }
            text_transformations {
              priority = 0
              type     = "LOWERCASE"
            }
            positional_constraint = "STARTS_WITH"
          }
        }
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "RateLimitAuthEndpoints"
      sampled_requests_enabled   = true
    }
  }

  # Rule 6: Block requests with oversized bodies (prevents HTTP body amplification)
  rule {
    name     = "BlockOversizedRequests"
    priority = 5  # Check size before parsing

    action {
      block {}
    }

    statement {
      size_constraint_statement {
        comparison_operator = "GT"
        size                = 8192  # 8 KB — adjust for file upload endpoints
        field_to_match {
          body {
            oversize_handling = "MATCH"  # Block if body exceeds 8 KB
          }
        }
        text_transformations {
          priority = 0
          type     = "NONE"
        }
      }
    }

    visibility_config {
      cloudwatch_metrics_enabled = true
      metric_name                = "OversizedRequestsBlocked"
      sampled_requests_enabled   = true
    }
  }

  visibility_config {
    cloudwatch_metrics_enabled = true
    metric_name                = "CloudFrontWAF"
    sampled_requests_enabled   = true
  }

  tags = {
    Environment = var.environment
    Project     = var.project
  }
}

# Attach WAF to CloudFront distribution
resource "aws_cloudfront_distribution" "main" {
  # ... your CloudFront config ...

  web_acl_id = aws_wafv2_web_acl.cloudfront.arn

  # ... rest of config ...
}

Lambda Reserved Concurrency: Blast-Radius Limiter

Lambda reserved concurrency sets a hard ceiling on how many concurrent executions a function can have. Without a reservation, a single Lambda function can consume your entire account concurrency limit (default 1,000). Setting reserved concurrency limits the blast radius of an invocation attack — and directly caps the maximum possible Lambda compute cost.

# terraform/lambda.tf

resource "aws_lambda_function" "api_handler" {
  function_name = "${var.project}-api-handler"
  role          = aws_iam_role.lambda.arn
  handler       = "index.handler"
  runtime       = "nodejs22.x"
  timeout       = 30
  memory_size   = 512

  # Reserved concurrency: hard cap on concurrent executions
  # 100 concurrent × $0.0000166667/GB-second × 512MB/1024 × 30s max = $0.025 max/burst
  # Total daily max cost at 100% utilization: well-bounded
  reserved_concurrent_executions = 100

  # For internal-only functions with no public exposure:
  # reserved_concurrent_executions = 10

  environment {
    variables = {
      ENVIRONMENT = var.environment
    }
  }

  # ... rest of Lambda config ...
}

# Separate function for high-priority operations with higher concurrency allowance
resource "aws_lambda_function" "payment_processor" {
  function_name = "${var.project}-payment-processor"
  role          = aws_iam_role.lambda.arn
  handler       = "index.handler"
  runtime       = "nodejs22.x"
  timeout       = 30
  memory_size   = 512

  # Payment processing needs higher concurrency but is triggered
  # by SQS (which you control), not public HTTP — still limit blast radius
  reserved_concurrent_executions = 50

  # ... rest of Lambda config ...
}

# Public-facing API Lambda with WAF protection and strict concurrency
resource "aws_lambda_function_url" "api" {
  # Note: Lambda function URLs bypass WAF unless behind CloudFront
  # Prefer API Gateway or ALB with WAF over direct Lambda function URLs
  # for public APIs
  function_name      = aws_lambda_function.api_handler.function_name
  authorization_type = "NONE"  # Only if you have WAF in front via CloudFront

  cors {
    allow_credentials = false
    allow_origins     = ["https://your-domain.com"]
    allow_methods     = ["GET", "POST"]
    max_age           = 86400
  }
}

Critical note on Lambda function URLs: AWS Lambda function URLs (https://XXXXX.lambda-url.us-east-1.on.aws/) bypass WAF and CloudFront unless you explicitly route them through CloudFront. If you use Lambda function URLs for a public API, place CloudFront with WAF in front of them. Without this, your Lambda function is exposed directly to the internet with no rate limiting except the reserved concurrency limit.

Billing Alarm + SNS → Lambda Remediation

The defensive automation chain: CloudWatch Billing Alarm → SNS → Lambda → remediation action. This provides a last-resort circuit breaker when WAF rate limits are insufficient.

# terraform/billing-protection.tf

# Billing metric alarm (must be in us-east-1 — billing metrics are global)
provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"
}

resource "aws_cloudwatch_metric_alarm" "billing_critical" {
  provider            = aws.us_east_1
  alarm_name          = "${var.project}-billing-critical"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 1
  metric_name         = "EstimatedCharges"
  namespace           = "AWS/Billing"
  period              = 21600   # 6-hour period — billing metrics update every 6 hours
  statistic           = "Maximum"
  threshold           = var.daily_budget_usd * 0.5  # Alert at 50% of daily budget

  dimensions = {
    Currency = "USD"
  }

  alarm_description = "AWS estimated charges exceeded 50% of daily budget"
  alarm_actions     = [aws_sns_topic.billing_alerts.arn]
  ok_actions        = [aws_sns_topic.billing_alerts.arn]

  treat_missing_data = "notBreaching"
}

resource "aws_sns_topic" "billing_alerts" {
  provider = aws.us_east_1
  name     = "${var.project}-billing-alerts"
}

resource "aws_sns_topic_subscription" "billing_lambda" {
  provider  = aws.us_east_1
  topic_arn = aws_sns_topic.billing_alerts.arn
  protocol  = "lambda"
  endpoint  = aws_lambda_function.billing_remediation.arn
}

resource "aws_lambda_permission" "billing_sns" {
  statement_id  = "AllowSNSInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.billing_remediation.function_name
  principal     = "sns.amazonaws.com"
  source_arn    = aws_sns_topic.billing_alerts.arn
}

# Remediation Lambda in your primary region
resource "aws_lambda_function" "billing_remediation" {
  function_name = "${var.project}-billing-remediation"
  role          = aws_iam_role.billing_remediation.arn
  handler       = "index.handler"
  runtime       = "nodejs22.x"
  timeout       = 60

  # This Lambda MUST have reserved concurrency — it must run even during an attack
  reserved_concurrent_executions = 5

  environment {
    variables = {
      WAF_WEB_ACL_ARN         = aws_wafv2_web_acl.cloudfront.arn
      LAMBDA_FUNCTION_NAME    = aws_lambda_function.api_handler.function_name
      ALERT_SNS_TOPIC_ARN     = aws_sns_topic.ops_alerts.arn
      EMERGENCY_RATE_LIMIT    = "100"  # Reduce to 100 requests/5 min during attack
    }
  }

  filename         = data.archive_file.billing_remediation.output_path
  source_code_hash = data.archive_file.billing_remediation.output_base64sha256
}

The remediation Lambda handles three actions in sequence: alert the team (high-priority SNS notification to Slack/PagerDuty), tighten WAF rate limits (update the WAF rule to a lower threshold), and — as a last resort — reduce Lambda reserved concurrency to 10 (skeleton-crew mode that keeps the service barely alive while containing cost).

// lambda/billing-remediation/index.mjs
import {
  WAFV2Client,
  GetWebACLCommand,
  UpdateWebACLCommand,
} from '@aws-sdk/client-wafv2';
import {
  LambdaClient,
  PutFunctionConcurrencyCommand,
} from '@aws-sdk/client-lambda';
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns';

const waf = new WAFV2Client({ region: 'us-east-1' });
const lambda = new LambdaClient({});
const sns = new SNSClient({});

export async function handler(event) {
  console.log('Billing alert received:', JSON.stringify(event));

  // Step 1: Send critical alert
  await sns.send(new PublishCommand({
    TopicArn: process.env.ALERT_SNS_TOPIC_ARN,
    Subject: 'CRITICAL: AWS Billing Threshold Exceeded — Possible Cost Attack',
    Message: JSON.stringify({
      alert: 'Billing threshold exceeded',
      timestamp: new Date().toISOString(),
      action: 'Tightening WAF rate limits and Lambda concurrency',
      event,
    }),
  }));

  // Step 2: Reduce Lambda concurrency to limit blast radius
  await lambda.send(new PutFunctionConcurrencyCommand({
    FunctionName: process.env.LAMBDA_FUNCTION_NAME,
    ReservedConcurrentExecutions: 10,  // Skeleton crew — service stays up, cost bounded
  }));

  console.log('Lambda concurrency reduced to 10');

  // Note: Restoring normal concurrency requires manual intervention
  // to prevent the attack from immediately re-escalating
  // Add a Systems Manager Automation document for the recovery procedure
}

Auth Abuse Cost Patterns

Authentication endpoints are high-value attack targets for billing attacks because they trigger expensive operations: database lookups, bcrypt/Argon2 password hashing (CPU-intensive), email sending (OTP codes), and session creation.

Login flooding → RDS connection storms: A credential stuffing attack sending 1,000 login attempts/second forces your application to maintain open RDS connections for each attempt. RDS Aurora Serverless scales with connections; RDS Provisioned instances have a maximum connection limit (typically 1,000 for a db.r6g.large). At connection limit, legitimate users get “too many connections” errors. The fix: rate limit authentication endpoints aggressively at WAF (50 requests/5 minutes per IP as shown in the WAF config above) and implement connection pooling with RDS Proxy ($0.015/hour per endpoint — cheap compared to connection storm mitigation).

OTP spam → SNS cost explosion: If your application sends SMS verification codes (OTP) via Amazon SNS, an attacker who can trigger the “send OTP” endpoint can drain your SNS SMS budget. SNS SMS costs $0.00645 per SMS to US numbers. At 1,000 SMS/minute: 1,000 × $0.00645 × 60 × 24 = $9,288/day. Defenses: WAF rate limit on the OTP endpoint (10 requests/5 minutes per IP), implement a CAPTCHA before OTP sending, set an SNS monthly SMS spending limit in the SNS console (this hard-caps total SMS cost for the month).

Session token flooding: If you issue JWTs or store sessions in ElastiCache/DynamoDB, attackers creating thousands of fake accounts force repeated writes. DynamoDB on-demand pricing: $1.25 per million write request units. At 10,000 writes/second: 10,000 × 3,600 = 36 million writes/hour × $1.25/million = $45/hour. Mitigation: DynamoDB provisioned capacity with Auto Scaling caps your write throughput and causes throttling instead of unbounded cost scaling.

Scraping and API Abuse

Web scraping is a billing attack vector when your pages require compute to render (SSR or API calls) or when your API returns data that costs money to generate (database queries, ML inference).

Progressive response blocking: Serve scrapers static 200 responses (appears successful to the scraper, no wasted compute), but return empty or minimal data. WAF Bot Control identifies most scraper signatures; custom fingerprinting (browser feature detection via Cloudflare or AWS WAF Fraud Control) catches more sophisticated bots.

Expensive operation rate limiting: For API endpoints that trigger ML inference, image processing, or complex database aggregations, implement application-level rate limiting independent of WAF. WAF rate limits by IP; application rate limits by authenticated user. An attacker with many IP addresses (botnet) can bypass IP-based WAF rate limits but cannot bypass per-account rate limiting.

Shield Advanced: ROI Analysis

AWS Shield Advanced pricing: $3,000/month per organization (flat fee, all protected resources) plus data transfer out costs at standard Shield rates.

The ROI calculation breaks into two components:

DDoS cost protection: Shield Advanced includes a DDoS cost protection SLA — if a DDoS event causes your CloudFront, Route 53, ELB, or EC2 costs to spike, AWS credits the excess cost. This is the primary financial justification. The question: how often are you actually DDoS’d, and what would those attacks cost at standard pricing?

For most companies: rarely. WAF + CloudFront absorbs the vast majority of attack traffic, and the cost of typical attack traffic that passes through is well under $3,000/month. The DDoS cost protection is valuable insurance but rarely exercised.

Shield Response Team (SRT) access: 24/7 access to AWS DDoS experts who can implement custom mitigations during active attacks. This is valuable during large, sophisticated DDoS campaigns that bypass standard WAF rules. It is not valuable for commodity attack traffic.

The decision framework:

  • Revenue loss per hour of DDoS downtime > $4,000 ($3,000/month ÷ 720 hours = $4.17/hour break-even on just the fee): Shield Advanced is worth evaluating
  • You handle payments, sensitive data, or have regulatory requirements mandating “best effort” DDoS mitigation: Shield Advanced’s SRT access justifies cost
  • You are under $10M ARR with standard web traffic: WAF + CloudFront + rate limiting + AWS Budgets is sufficient and costs under $100/month

SQS and S3 Flood Defense

SQS message flooding defense: The first line is IAM — SQS queue policies should restrict sqs:SendMessage to specific AWS principals (your application’s IAM role), not allow public access. A publicly accessible SQS queue is an open billing vector. Verify with: aws sqs get-queue-attributes --queue-url YOUR_URL --attribute-names Policy and check for "Principal": "*" in the policy. If found, restrict immediately.

For queues that must accept messages from untrusted sources (webhook processors, third-party integrations), implement a Lambda receiver that validates message structure and drops garbage before enqueuing. The Lambda invocation cost ($0.20/million) is far lower than SQS + downstream processing costs for garbage messages.

S3 PUT flood defense: Bucket policies should restrict s3:PutObject to authenticated principals. Never allow "Principal": "*" for s3:PutObject. For user file uploads, use presigned URLs generated by your backend — each presigned URL is scoped to a specific object key, expires in minutes, and limits upload size. Combined with AWS S3 Object Lambda or a Lambda authorizer, you can enforce per-user upload quotas.

Set S3 bucket lifecycle rules to automatically delete objects in user-upload staging buckets after 24 hours if not processed — this bounds the storage cost of garbage uploads.

Defense-in-Depth Summary

The effective cost-attack defense is layered:

  1. WAF at CloudFront edge: Block bots and rate-limit before traffic reaches your origin
  2. Reserved Lambda concurrency: Hard cap on compute blast radius
  3. RDS Proxy: Connection pooling prevents connection storm cost escalation
  4. Per-endpoint application rate limiting: User-level limits that bypass IP rotation
  5. AWS Budgets + SNS + Lambda remediation: Automated response to anomalous spend
  6. IAM least privilege on SQS/S3: No public write access, ever
  7. SNS SMS spend limits: Hard cap on notification abuse
  8. DynamoDB provisioned capacity for write-heavy tables: Throttling instead of unbounded spend

No single layer is sufficient. WAF stops most bots but not all. Reserved concurrency stops Lambda bombs but not SQS floods. Budgets alarms fire after the damage starts. The combination makes cost-based attacks economically unattractive by capping the damage at each layer.


Related reading: AWS WAF Web Application Firewall Production Guide covers the full WAF rule set for production workloads. AWS GuardDuty Threat Detection Production Guide covers detecting the reconnaissance phase that often precedes cost-based attacks. AWS Cost Control Architecture and Optimization Playbook covers the broader cost governance framework these controls fit into.

PP
Palaniappan P

AWS Cloud Architect & AI Expert

AWS-certified cloud architect and AI expert with deep expertise in cloud migrations, cost optimization, and generative AI on AWS.

AWS ArchitectureCloud MigrationGenAI on AWSCost OptimizationDevOps

Ready to discuss your AWS strategy?

Our certified architects can help you implement these solutions.

Recommended Reading

Explore All Articles »
AWS IAM Best Practices: Least Privilege Access Control

AWS IAM Best Practices: Least Privilege Access Control

A practical guide to AWS IAM — least privilege policies, IAM roles vs users, permission boundaries, SCPs, identity federation, and the access control patterns that secure production workloads without slowing teams down.