Building a Serverless Defense Against Subdomain Takeover with Domain Protect
Hook
A single dangling DNS record pointing to an unclaimed S3 bucket cost Uber researchers a potential foothold into their infrastructure. In 2023, subdomain takeover remained in OWASP's top 10 web vulnerabilities, yet most organizations still rely on quarterly manual audits.
Context
Subdomain takeover happens when your DNS records point to cloud resources that no longer exist—an S3 bucket you deleted, a CloudFront distribution you decommissioned, an Elastic IP you released. Attackers scan for these dangling references, claim the resources themselves, and suddenly control a subdomain under your legitimate domain. They can phish your customers, steal session cookies with valid domain certificates, or bypass CSP policies that trust your domain.
Traditional security tools struggle with this problem because it sits at the intersection of DNS management and cloud resource lifecycle. A CloudFront distribution deleted in AWS doesn't automatically clean up the DNS CNAME pointing to it. Manual audits don't scale when engineering teams spin up and tear down infrastructure daily. Bug bounty programs frequently report subdomain takeovers because the attack surface grows faster than security teams can manually track it. Domain Protect emerged from this gap—providing continuous, automated monitoring that catches dangling DNS before attackers exploit the window of vulnerability.
Technical Insight
Domain Protect's architecture centers on scheduled Lambda functions that perform systematic DNS reconnaissance across your infrastructure. Deployed via Terraform, it establishes an EventBridge rule triggering Lambda executions at configurable intervals (hourly by default). The scanner pulls DNS records from Route53 hosted zones and optionally Cloudflare via API integration, then cross-references each record against AWS services to detect orphaned references.
The detection logic is vulnerability-specific. For S3 buckets, it checks whether a CNAME points to a bucket name that either doesn't exist or exists in a different AWS account. For CloudFront distributions, it validates the distribution ID still exists and serves your content. For Elastic Beanstalk, it verifies the environment is running. Here's a simplified example of how you'd deploy it with Terraform:
module "domain_protect" {
source = "domain-protect/domain-protect/aws"
version = "~> 1.0"
org_primary_account = "123456789012"
project = "security"
# Enable Cloudflare scanning
cloudflare_enabled = true
cloudflare_api_token = var.cloudflare_token
# Configure notifications
slack_webhook_urls = [var.slack_webhook]
# Auto-remediation settings
update_dns_tags = true
remediate_s3 = true # Careful with this
# Scan configuration
scan_schedule = "rate(1 hour)"
}
The remediation strategy differs by resource type. For S3-related takeovers, Domain Protect can create placeholder buckets with access denied policies, effectively claiming the namespace before attackers can. For CloudFront distributions, it adds resource tags marking them for review rather than auto-deleting DNS records that might impact production traffic. This graduated response acknowledges that some findings are genuinely dangerous (unclaimed S3 buckets) while others require human judgment (deprecated API endpoints).
Domain Protect uses DynamoDB for state management, tracking previously identified vulnerabilities to avoid alert fatigue. When it finds a new dangling DNS record, it writes to the findings table with metadata including discovery timestamp, resource type, and risk severity. Subsequent scans check this table—if a finding persists across multiple runs, it escalates notification priority. This temporal analysis helps distinguish transient deployment states from genuine security gaps.
The multi-account strategy leverages AWS Organizations and cross-account IAM roles. Deploy the core scanner in your security account, then create read-only roles in each member account allowing Route53 enumeration and resource validation. The Lambda assumes these roles sequentially, building a comprehensive view across your entire AWS organization without centralizing DNS management:
# Pseudocode of the scanning logic
for account in org_accounts:
credentials = assume_role(account, 'DomainProtectScanner')
hosted_zones = route53.list_hosted_zones(credentials)
for zone in hosted_zones:
records = get_all_records(zone)
for record in records:
if is_s3_cname(record):
bucket = extract_bucket_name(record.value)
if not bucket_exists(bucket, account):
findings.append({
'type': 'S3_TAKEOVER',
'severity': 'HIGH',
'record': record.name,
'target': bucket
})
The Slack integration deserves attention—rather than email spam, it posts rich messages with context buttons. Each alert includes the vulnerable subdomain, resource type, affected account, and action buttons linking to AWS Console locations for immediate remediation. This UX detail matters for security tools that run continuously; actionable alerts with low friction response paths see faster mean-time-to-remediation than generic email notifications.
One architectural highlight is the extensible vulnerability database. Domain Protect maintains an internal registry of takeover-vulnerable services and their fingerprints. As cloud providers introduce new services or change CNAME patterns, contributors update this database. For example, when AWS changed the naming convention for Elastic Beanstalk environments, a quick database update protected all users without requiring code changes in deployed Lambdas.
Gotcha
The most significant limitation is the recent deprecation and repository migration. The original domain-protect/domain-protect repo now redirects users to domain-protect/terraform-aws-domain-protect, requiring existing deployments to update their Terraform source references. While this consolidation makes sense long-term, it creates migration overhead for production deployments and outdated documentation in third-party guides.
Automated remediation requires careful consideration. Setting remediate_s3 = true means Domain Protect will create S3 buckets in response to findings. In an organization with complex DNS inherited from acquisitions or migrations, you might discover legitimate external services referenced in old CNAME records. Auto-creating buckets could interfere with planned migrations or cause namespace conflicts. The recommendation is running in alert-only mode for several weeks, reviewing findings manually, and only enabling auto-remediation after establishing baseline confidence in your DNS hygiene. Additionally, the tool operates within AWS's eventual consistency model—a race condition exists where you delete a CloudFront distribution and an attacker claims it before Domain Protect's next hourly scan. For critical subdomains, immediate manual verification after infrastructure teardown remains necessary.
Verdict
Use Domain Protect if you manage AWS infrastructure at scale with frequent resource churn, run bug bounty programs where subdomain takeover is commonly reported, or face compliance requirements demanding continuous security monitoring. It's particularly valuable for organizations with multiple AWS accounts, distributed teams managing their own Route53 zones, or migration projects where DNS records outlive the underlying infrastructure. The Terraform deployment model makes it trivial to integrate into existing IaC pipelines, and the serverless architecture means operational costs stay negligible even scanning thousands of domains. Skip it if you're primarily on GCP or Azure (AWS-specific deployment), manage fewer than 50 relatively static domains where quarterly manual audits suffice, or lack the AWS expertise to troubleshoot Lambda/IAM issues when they arise. Also skip if you need real-time protection rather than scheduled scanning—the typical hourly cadence leaves windows of vulnerability that might be unacceptable for extremely high-value targets.